Compare commits

..
Author SHA1 Message Date
Rob HoganandFacebook GitHub Bot d53cb0adbf Remove last use of Metro deep imports
Summary:
Remove the last use of a Metro deep import in preparation for making all deep imports semver-private.

Changelog: [Internal]

Reviewed By: huntie

Differential Revision: D77450110
2025-07-06 04:42:01 -07:00
652 changed files with 7532 additions and 9824 deletions
+9
View File
@@ -9,3 +9,12 @@ end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
[*.gradle]
indent_size = 4
[*.kts]
indent_size = 4
[BUCK]
indent_size = 4
@@ -14,23 +14,23 @@ runs:
uses: actions/download-artifact@v4
with:
name: hermes-workspace
path: 'C:\tmp\hermes'
path: 'D:\tmp\hermes'
- name: Set up workspace
shell: powershell
run: |
mkdir -p C:\tmp\hermes\osx-bin
mkdir -p D:\tmp\hermes\osx-bin
mkdir -p .\packages\react-native\sdks\hermes
cp -r -Force C:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
cp -r -Force D:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
cp -r -Force .\packages\react-native\sdks\hermes-engine\utils\* .\packages\react-native\sdks\hermes\.
- name: Windows cache
uses: actions/cache@v4
with:
key: v3-hermes-${{ github.job }}-windows-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}
path: |
C:\tmp\hermes\win64-bin\
C:\tmp\hermes\hermes\icu\
C:\tmp\hermes\hermes\deps\
C:\tmp\hermes\hermes\build_release\
D:\tmp\hermes\win64-bin\
D:\tmp\hermes\hermes\icu\
D:\tmp\hermes\hermes\deps\
D:\tmp\hermes\hermes\build_release\
- name: setup-msbuild
uses: microsoft/setup-msbuild@v1.3.2
- name: Set up workspace
@@ -83,4 +83,4 @@ runs:
uses: actions/upload-artifact@v4.3.4
with:
name: hermes-win64-bin
path: C:\tmp\hermes\win64-bin\
path: D:\tmp\hermes\win64-bin\
@@ -17,7 +17,7 @@ runs:
env:
SCRATCH_DIR: ${{ runner.temp }}/diff-js-api-breaking-changes
run: |
node ./scripts/js-api/diff-api-snapshot \
node ./scripts/diff-api-snapshot \
${{ github.workspace }}/packages/react-native/ReactNativeApi.d.ts \
$SCRATCH_DIR/ReactNativeApi-after.d.ts \
> $SCRATCH_DIR/output.json
+1 -1
View File
@@ -4,7 +4,7 @@ inputs:
node-version:
description: 'The node.js version to use'
required: false
default: '22.14.0'
default: '22'
runs:
using: "composite"
steps:
@@ -48,14 +48,6 @@ runs:
- name: Print third-party folder
shell: bash
run: ls -lR /tmp/third-party
- name: Download React Native Prebuilds
uses: actions/download-artifact@v4
with:
name: ReactCore${{ inputs.flavor }}.xcframework.tar.gz
path: /tmp/ReactCore
- name: Print ReactCore folder
shell: bash
run: ls -lR /tmp/ReactCore
- name: Install iOS dependencies - Configuration ${{ inputs.flavor }};
shell: bash
run: |
@@ -81,7 +73,6 @@ runs:
TARBALL_FILENAME=$(node ../../packages/react-native/scripts/hermes/get-tarball-name.js --buildType "$BUILD_TYPE")
export HERMES_ENGINE_TARBALL_PATH="$HERMES_WS_DIR/hermes-runtime-darwin/$TARBALL_FILENAME"
export RCT_USE_LOCAL_RN_DEP="/tmp/third-party/ReactNativeDependencies${{ inputs.flavor }}.xcframework.tar.gz"
export RCT_TESTONLY_RNCORE_TARBALL_PATH="/tmp/ReactCore/ReactCore${{ inputs.flavor }}.xcframework.tar.gz"
yarn bootstrap ios "${args[@]}" | cat
+1 -10
View File
@@ -94,14 +94,6 @@ runs:
- name: Print third-party folder
shell: bash
run: ls -lR /tmp/third-party
- name: Download React Native Prebuilds
uses: actions/download-artifact@v4
with:
name: ReactCore${{ inputs.flavor }}.xcframework.tar.gz
path: /tmp/ReactCore
- name: Print ReactCore folder
shell: bash
run: ls -lR /tmp/ReactCore
- name: Setup xcode build cache
uses: ./.github/actions/setup-xcode-build-cache
with:
@@ -113,13 +105,12 @@ runs:
shell: bash
run: |
export HERMES_ENGINE_TARBALL_PATH=$HERMES_ENGINE_TARBALL_PATH
export RCT_USE_LOCAL_RN_DEP="/tmp/third-party/ReactNativeDependencies${{ inputs.flavor }}.xcframework.tar.gz"
export RCT_TESTONLY_RNCORE_TARBALL_PATH="/tmp/ReactCore/ReactCore${{ inputs.flavor }}.xcframework.tar.gz"
if [[ ${{ inputs.use-frameworks }} == "DynamicFrameworks" ]]; then
export USE_FRAMEWORKS=dynamic
fi
export RCT_USE_LOCAL_RN_DEP="/tmp/third-party/ReactNativeDependencies${{ inputs.flavor }}.xcframework.tar.gz"
cd packages/rn-tester
bundle install
-2
View File
@@ -2,8 +2,6 @@ name: yarn-install
runs:
using: composite
steps:
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Install dependencies
shell: bash
run: |
@@ -38,7 +38,7 @@ describe('#verifyArtifactsAreOnMaven', () => {
expect(mockSleep).toHaveBeenCalledTimes(1);
expect(mockFetch).toHaveBeenCalledWith(
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1/react-native-artifacts-0.78.1.pom',
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1',
);
});
@@ -55,7 +55,7 @@ describe('#verifyArtifactsAreOnMaven', () => {
expect(mockSleep).toHaveBeenCalledTimes(1);
expect(mockFetch).toHaveBeenCalledWith(
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1/react-native-artifacts-0.78.1.pom',
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1',
);
});
@@ -67,7 +67,7 @@ describe('#verifyArtifactsAreOnMaven', () => {
expect(mockSleep).toHaveBeenCalledTimes(0);
expect(mockFetch).toHaveBeenCalledWith(
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1/react-native-artifacts-0.78.1.pom',
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1',
);
});
@@ -81,7 +81,7 @@ describe('#verifyArtifactsAreOnMaven', () => {
expect(mockSleep).toHaveBeenCalledTimes(90);
expect(mockExit).toHaveBeenCalledWith(1);
expect(mockFetch).toHaveBeenCalledWith(
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1/react-native-artifacts-0.78.1.pom',
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1',
);
});
});
@@ -13,14 +13,13 @@ const SLEEP_S = 60; // 1 minute
const MAX_RETRIES = 90; // 90 attempts. Waiting between attempt: 1 min. Total time: 90 min.
const ARTIFACT_URL =
'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/';
const ARTIFACT_NAME = 'react-native-artifacts-';
async function verifyArtifactsAreOnMaven(version, retries = MAX_RETRIES) {
if (version.startsWith('v')) {
version = version.substring(1);
}
const artifactUrl = `${ARTIFACT_URL}${version}/${ARTIFACT_NAME}${version}.pom`;
const artifactUrl = `${ARTIFACT_URL}${version}`;
for (let currentAttempt = 1; currentAttempt <= retries; currentAttempt++) {
const response = await fetch(artifactUrl);
-4
View File
@@ -18,10 +18,6 @@ jobs:
run: |
git config --local user.email "bot@reactnative.dev"
git config --local user.name "React Native Bot"
- name: Setup xcode
uses: ./.github/actions/setup-xcode
with:
xcode-version: '16.2.0'
- name: Extract branch name
run: |
TAG="${{ github.ref_name }}";
+3 -3
View File
@@ -128,9 +128,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
+5 -5
View File
@@ -23,7 +23,7 @@ jobs:
id: restore-ios-slice
uses: actions/cache/restore@v4
with:
key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/setup.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }}
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'
@@ -83,7 +83,7 @@ jobs:
# Move the XCFramework in the destination directory
mv /tmp/third-party/packages/react-native/third-party/ReactNativeDependencies.xcframework packages/react-native/third-party/ReactNativeDependencies.xcframework
VERSION=$(jq -r '.version' packages/react-native/package.json)
VERSION=$(jq -r '.version' package.json)
echo "$VERSION-${{matrix.flavor}}" > "packages/react-native/third-party/version.txt"
cat "packages/react-native/third-party/version.txt"
# Check destination directory
@@ -117,7 +117,7 @@ jobs:
uses: actions/cache/save@v4
if: ${{ github.ref == 'refs/heads/main' }} # To avoid that the cache explode
with:
key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/setup.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }}
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
@@ -140,7 +140,7 @@ jobs:
uses: actions/cache/restore@v4
with:
path: packages/react-native/.build/output/xcframeworks
key: v2-ios-core-xcframework-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/setup.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }}
key: v2-ios-core-xcframework-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
- name: Setup node.js
if: steps.restore-ios-xcframework.outputs.cache-hit != 'true'
uses: ./.github/actions/setup-node
@@ -209,4 +209,4 @@ jobs:
path: |
packages/react-native/.build/output/xcframeworks/ReactCore${{matrix.flavor}}.xcframework.tar.gz
packages/react-native/.build/output/xcframeworks/ReactCore${{matrix.flavor}}.framework.dSYM.tar.gz
key: v2-ios-core-xcframework-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/setup.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }}
key: v2-ios-core-xcframework-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
+3 -3
View File
@@ -125,9 +125,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
+30 -20
View File
@@ -128,7 +128,7 @@ jobs:
test_ios_rntester_ruby_3_2_0:
runs-on: macos-14
needs:
[build_apple_slices_hermes, prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies, prebuild_react_native_core]
[build_apple_slices_hermes, prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies]
env:
HERMES_WS_DIR: /tmp/hermes
HERMES_TARBALL_ARTIFACTS_DIR: /tmp/hermes/hermes-runtime-darwin
@@ -141,12 +141,31 @@ jobs:
ruby-version: "3.2.0"
hermes-version: ${{ needs.prepare_hermes_workspace.outputs.hermes-version }}
react-native-version: ${{ needs.prepare_hermes_workspace.outputs.react-native-version }}
flavor: Debug
test_ios_rntester_dynamic_frameworks:
runs-on: macos-14
needs:
[build_apple_slices_hermes, prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies]
env:
HERMES_WS_DIR: /tmp/hermes
HERMES_TARBALL_ARTIFACTS_DIR: /tmp/hermes/hermes-runtime-darwin
continue-on-error: true
strategy:
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run it
uses: ./.github/actions/test-ios-rntester
with:
use-frameworks: DynamicFrameworks
hermes-version: ${{ needs.prepare_hermes_workspace.outputs.hermes-version }}
react-native-version: ${{ needs.prepare_hermes_workspace.outputs.react-native-version }}
test_ios_rntester:
runs-on: macos-14-large
needs:
[build_apple_slices_hermes, prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies, prebuild_react_native_core]
[build_apple_slices_hermes, prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies]
env:
HERMES_WS_DIR: /tmp/hermes
HERMES_TARBALL_ARTIFACTS_DIR: /tmp/hermes/hermes-runtime-darwin
@@ -155,14 +174,14 @@ jobs:
fail-fast: false
matrix:
flavor: [Debug, Release]
frameworks: [StaticLibraries, DynamicFrameworks]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Run it
uses: ./.github/actions/test-ios-rntester
with:
use-frameworks: ${{ matrix.frameworks }}
run-unit-tests: "false"
use-frameworks: StaticLibraries
hermes-version: ${{ needs.prepare_hermes_workspace.outputs.hermes-version }}
react-native-version: ${{ needs.prepare_hermes_workspace.outputs.react-native-version }}
flavor: ${{ matrix.flavor }}
@@ -245,14 +264,6 @@ jobs:
- name: Print third-party folder
shell: bash
run: ls -lR /tmp/third-party
- name: Download React Native Prebuilds
uses: actions/download-artifact@v4
with:
name: ReactCore${{ matrix.flavor }}.xcframework.tar.gz
path: /tmp/ReactCore
- name: Print ReactCore folder
shell: bash
run: ls -lR /tmp/ReactCore
- name: Prepare artifacts
run: |
REACT_NATIVE_PKG=$(find /tmp/react-native-tmp -type f -name "*.tgz")
@@ -275,7 +286,6 @@ jobs:
NEW_ARCH_ENABLED=1
export RCT_USE_LOCAL_RN_DEP=/tmp/third-party/ReactNativeDependencies${{ matrix.flavor }}.xcframework.tar.gz
export RCT_TESTONLY_RNCORE_TARBALL_PATH="/tmp/ReactCore/ReactCore${{ matrix.flavor }}.xcframework.tar.gz"
HERMES_ENGINE_TARBALL_PATH=$HERMES_PATH RCT_NEW_ARCH_ENABLED=$NEW_ARCH_ENABLED bundle exec pod install
xcodebuild \
@@ -382,9 +392,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
@@ -538,7 +548,7 @@ jobs:
test_ios_helloworld_with_ruby_3_2_0:
runs-on: macos-14
needs: [prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies, prebuild_react_native_core] # prepare_hermes_workspace must be there because we need its reference to retrieve a couple of outputs
needs: [prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies] # prepare_hermes_workspace must be there because we need its reference to retrieve a couple of outputs
env:
PROJECT_NAME: iOSTemplateProject
HERMES_WS_DIR: /tmp/hermes
@@ -555,7 +565,7 @@ jobs:
test_ios_helloworld:
runs-on: macos-14
needs: [prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies, prebuild_react_native_core] # prepare_hermes_workspace must be there because we need its reference to retrieve a couple of outputs
needs: [prepare_hermes_workspace, build_hermes_macos, prebuild_apple_dependencies] # prepare_hermes_workspace must be there because we need its reference to retrieve a couple of outputs
strategy:
matrix:
flavor: [Debug, Release]
@@ -583,7 +593,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: ["24", "22", "20"]
node-version: ["24", "22"]
steps:
- name: Checkout
uses: actions/checkout@v4
-25
View File
@@ -319,31 +319,6 @@ ChuiHW))
- **JSC:** Clean up RCTBridgeDelegate to remove shouldBridgeUseCustomJSC method ([c8f1506f13](https://github.com/facebook/react-native/commit/c8f1506f13310ffafe370273805684e696d72d50) by [@zhongwuzw](https://github.com/zhongwuzw))
- **Layout:** Remove no longer needed UISceneDelegate ([a033cf9d5e](https://github.com/facebook/react-native/commit/a033cf9d5e8ffbda8b6f86cf3ce152b4ccb73187) by [@okwasniewski](https://github.com/okwasniewski))
## v0.78.3
### Added
#### Android specific
- **Gradle:** RNGP - Add support for `exclusiveEnterpriseRepository` to specify an internal Maven mirror. ([6cb8dc37c7](https://github.com/facebook/react-native/commit/6cb8dc37c74995cba3f9f0a845919f305de53c3d) by [@cortinico](https://github.com/cortinico))
### Fixed
- **DevTools**: Temporarily disable eager evaluation and live expressions in the console tab ([07717b5275](https://github.com/facebook/react-native/commit/07717b5275d80cde7b2b4edbe032ab629127fbf0) by [@huntie](https://github.com/huntie))
- **InteropLayer:** Fixed adding child views to a native view using the interop layer ([d53a60dd23](https://github.com/facebook/react-native/commit/d53a60dd23c5df8afca058a867c50df8b61f62e2) by [@chrfalch](https://github.com/chrfalch))
- **Runtime:** Align timer IDs and timer function argument error handling with web standards. ([480a4642e5](https://github.com/facebook/react-native/commit/480a4642e5a644becf1c477d3d239f9b57efff3a) by [@kitten](https://github.com/kitten))
#### Android specific
- **StyleSheet:** Wrong `borderBottomEndRadius` on RTL ([68d6ada448](https://github.com/facebook/react-native/commit/68d6ada44893701b6006a6b1753131c7e880a30a) by [@riteshshukla04](https://github.com/riteshshukla04))
#### iOS specific
- **Codegen:** Skip codegen for selectively disabled libraries in react-native.config.js ([be8595b18a](https://github.com/facebook/react-native/commit/be8595b18a46635bf679d8e7473f2960c33530fa) by [@ismarbesic](https://github.com/ismarbesic))
- **Layout:** Layout direction changes are now honored on bundle reload ([36f29beac4](https://github.com/facebook/react-native/commit/36f29beac47259768612bf56e5d9acfa4b94ab1a) by [@chrsmys](https://github.com/chrsmys))
- **Runtime:** Re-enable enableFixForViewCommandRace feature flag ([ae59702f8e](https://github.com/facebook/react-native/commit/ae59702f8ee89e7bddec971e0a041744cb91e65c) by [@okwasniewski](https://github.com/okwasniewski))
- **Switch:** Fixed switches correctly reverting to controlled state ([aa8c072870](https://github.com/facebook/react-native/commit/aa8c072870f6f9740e567a0f455c0e500ff1400c) by [@javache](https://github.com/javache))
## v0.78.2
### Changed
-222
View File
@@ -1,223 +1,5 @@
# Changelog
## v0.81.0-rc.1
### Added
#### iOS specific
- Add release/debug switch script for React-Core-prebuilt ([42d1a7934c](https://github.com/facebook/react-native/commit/42d1a7934cad4b2c92653e3fa7781c2af8f44df4) by [@chrfalch](https://github.com/chrfalch))
- Added support for using USE_FRAMEWORKS with prebuilt React Native Core ([40e45f5366](https://github.com/facebook/react-native/commit/40e45f53661ce80c3a6fbbf07f52dc900afcad52) by [@chrfalch](https://github.com/chrfalch))
- Add the `ENTERPRISE_REPOSITORY` env variable to cocopaods infra ([23f3bf9239](https://github.com/facebook/react-native/commit/23f3bf9239a849590f1c72b25732d0090780128c) by [@cipolleschi](https://github.com/cipolleschi))
### Changed
- Bump Metro to 0.83.0 ([6b9f5d622f](https://github.com/facebook/react-native/commit/6b9f5d622ffbe79da8f4e7b7d8094504a480425e) by [@robhogan](https://github.com/robhogan))
#### Android specific
- Gradle to 8.14.3 ([6892dde363](https://github.com/facebook/react-native/commit/6892dde36373bbef2d0afe535ae818b1a7164f08) by [@cortinico](https://github.com/cortinico))
- Expose `react_renderer_bridging` headers via prefab ([d1730ff960](https://github.com/facebook/react-native/commit/d1730ff960fcb9a01ee94b9e46e5a9fbb7d73f4a) by [@tomekzaw](https://github.com/tomekzaw))
- Introduce more deprecation warnings for Legacy Arch classes ([625f69f284](https://github.com/facebook/react-native/commit/625f69f284ddfd9c6beecaa4052a871d092053ef) by [@cortinico](https://github.com/cortinico))
### Fixed
- Fixed nodes with `display: contents` set being cloned with the wrong owner ([d4b36b0300](https://github.com/facebook/react-native/commit/d4b36b03003eb2de9eaf5b57bb639bae8cc12f20) by [@j-piasecki](https://github.com/j-piasecki))
#### iOS specific
- Fixed premature return in header file generation from podspec globs ([f2b064c2d4](https://github.com/facebook/react-native/commit/f2b064c2d40c39017ac2a31bf3caf8acef23038c) by [@chrfalch](https://github.com/chrfalch))
## v0.81.0-rc.0
### Breaking
- **APIs:** All `react-native/Libraries/BugReporting` APIs have been removed ([9d4d8dcb02](https://github.com/facebook/react-native/commit/9d4d8dcb0264273cc1522ed6e9de47cdb05606f4) by [@huntie](https://github.com/huntie))
- **APIs:** Add public JS API breaking change detection under `yarn diff-api-snapshot` script. ([6b40f35032](https://github.com/facebook/react-native/commit/6b40f35032462de8a9bad0e9f186916562475a40) by [@coado](https://github.com/coado))
- **APIs:** Community CLI users: user-defined `resolver.resolveRequest` and `serializer.getModulesRunBeforeMainModule` Metro config now takes precedence over CLI defaults ([fe2bcbf4ba](https://github.com/facebook/react-native/commit/fe2bcbf4ba7ce983fac0cd09727c165517b6337f) by [@robhogan](https://github.com/robhogan))
- **Error Handling:** Improve messaging and add error stack trace for uncaught throws. ([5ba0e1f97a](https://github.com/facebook/react-native/commit/5ba0e1f97ad40f84d83efaa9cfdbaf9ad22a18e8) by [@vzaidman](https://github.com/vzaidman))
- **Flow:** The `react-native` package no longer ships with the `flow` directory ([38acb4c074](https://github.com/facebook/react-native/commit/38acb4c0746e48ebb10729360788e26454736d1b) by [@huntie](https://github.com/huntie))
- **Node:** Minimum Node version is now bumped to Node.js 22.14.0 ([df39eadc03](https://github.com/facebook/react-native/commit/df39eadc03edcd23fab47712d24818d2d0c75d16) by [@huntie](https://github.com/huntie))
- **View:** `View` no longer sets any default accessibility props, which should not result in visible changes in behaviour but may affect snapshot tests. ([039a333df5](https://github.com/facebook/react-native/commit/039a333df57e20133af3ec77e995ec8fe4dc7f5c) by [@javache](https://github.com/javache))
- **View:** Upgrade `View` component to React 19. ([eedd60b9e6](https://github.com/facebook/react-native/commit/eedd60b9e6b595801d05c2fa223124fb8a895c3c) by [@EvanBacon](https://github.com/EvanBacon))
#### Android specific
- **APIs:** Cleanup and internalize `FpsDebugFrameCallback` ([cf6569bc18](https://github.com/facebook/react-native/commit/cf6569bc18082253fa84feecdfaa7a28413bc993) by [@cortinico](https://github.com/cortinico))
- **CMake:** Correctly propagate `RN_SERIALIZABLE_STATE` to 3rd party `CMake` targets. Users with custom `CMake` and C++ code should update to use `target_compile_reactnative_options` inside their `CMakeLists.txt` files.([c059ae1b77](https://github.com/facebook/react-native/commit/c059ae1b77b073e6990dc2a5d81979de679c2b01) by [@cortinico](https://github.com/cortinico))
- **FabricUIManager:** Remove `FabricUIManager.measure` overload which accepts attachment positions ([2ba86caf18](https://github.com/facebook/react-native/commit/2ba86caf18d86f6902f987ec9a0aa94bf67c1b4e) by [@NickGerleman](https://github.com/NickGerleman))
- **Kotlin:** Migrate `ViewManagerInterfaces` to kotlin. Some types in code generated ViewManagerInterfaces might differ. e.g. this will start enforcing nullability in parameters of viewManagerInterface methods (e.g. String commands parameters are not nullable, view params are not nullable in any method, etc) ([76ff1aa5c6](https://github.com/facebook/react-native/commit/76ff1aa5c6d30935ec33708d3a13ac7e5a82f551) by [@mdvacca](https://github.com/mdvacca))
- **Kotlin:** Migrate `com.facebook.react.ReactDelegate` to Kotlin. Some users implementing this class in Kotlin could have breakages. ([50ea5b4380](https://github.com/facebook/react-native/commit/50ea5b43806a9047bace81267c97d5dd73e0e74d) by [@mateoguzmana](https://github.com/mateoguzmana))
- **Kotlin:** Convert to Kotlin and internalize `MountingManager` ([f33fdca876](https://github.com/facebook/react-native/commit/f33fdca87679d5cc628a2e9dccada728cbb0335b) by [@cortinico](https://github.com/cortinico))
- **textAlignVertical:** Move `textAlignVertical` to paragraph attributes instead of text attributes ([55fd8b26f8](https://github.com/facebook/react-native/commit/55fd8b26f8791848dd886bd7fb5110b401038234) by [@joevilches](https://github.com/joevilches))
- **TextLayoutManager:** Make Java Side `TextLayoutManager` Internal ([e82a677c79](https://github.com/facebook/react-native/commit/e82a677c7966209b05fe55209fcb26c067427393) by [@NickGerleman](https://github.com/NickGerleman))
#### iOS specific
- **RCTDisplayLink:** Migrate `RCTDisplayLink`'s API from `RCTModuleData` ([70eeb9f541](https://github.com/facebook/react-native/commit/70eeb9f54194cc807017bec8c71080972c5c4e65) by [@RSNara](https://github.com/RSNara))
- **SynchronouslyUpdateViewOnUIThread:** `SynchronouslyUpdateViewOnUIThread` now accepts `folly::dynamic` instead of `NSDictionary`. Use https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/utils/platform/ios/react/utils/FollyConvert.h#L14 for conversion. ([82279bd981](https://github.com/facebook/react-native/commit/82279bd9811c406d21496d03a1572b98946c50b6) by [@sammy-SC](https://github.com/sammy-SC))
- **Xcode:** Bump min Xcode to 16.1 ([c27a8804a6](https://github.com/facebook/react-native/commit/c27a8804a6fdaea2d4bef4a4c689bfe2c343daaa) by [@NickGerleman](https://github.com/NickGerleman))
### Added
- **APIs:** Expose `unstable_TextAncestorContext` API ([962a7dda44](https://github.com/facebook/react-native/commit/962a7dda440863e7888fd2cc01c065c8762857e6) by [@huntie](https://github.com/huntie))
- **APIs:** Expose additional `*AnimationConfig` types on the `Animated` namespace ([11a1ad7a98](https://github.com/facebook/react-native/commit/11a1ad7a98a71cd1189550a8ae5666e5a2ed8d57) by [@huntie](https://github.com/huntie))
- **APIs:** `InterpolationConfig` is now exposed on the `Animated` namespace ([b01a5f91fe](https://github.com/facebook/react-native/commit/b01a5f91fedc19495e8a9d6ce079feb5898e7b87) by [@huntie](https://github.com/huntie))
- **APIs:** Expose `ScrollViewImperativeMethods` and `ScrollViewScrollToOptions` types to public API ([f184b591cf](https://github.com/facebook/react-native/commit/f184b591cfb49ed372efb0bdd55a145230112f45) by Antonio Pires)
- **APIs:** Add `--validate` flag to `build-types` script for JS API snapshot validation. ([f529fd6ba5](https://github.com/facebook/react-native/commit/f529fd6ba590101a3dfa710a92befb81994ed2dd) by [@coado](https://github.com/coado))
- **Bridging:** Added support for bridging `Class` methods return types ([e403b510d0](https://github.com/facebook/react-native/commit/e403b510d0de74ac7e62defeb1e80eff84b956e2) by [@hoxyq](https://github.com/hoxyq))
- **Error Handling:** Improve error messages when enum members are missing ([12ced22f70](https://github.com/facebook/react-native/commit/12ced22f70438064bf815c2413cbd12a80dbf0a7) by Yannick Loriot)
- **Fantom:** Add `Fantom.getFabricUpdateProps` for reading fabric update props scheduled via `UIManager::updateShadowTree` ([cc442eb8c8](https://github.com/facebook/react-native/commit/cc442eb8c85d516701f840046d73683a7cd51424) by [@zeyap](https://github.com/zeyap))
- **Flow:** Add support for Flow opaque types in codegen for native modules ([a15fc102e6](https://github.com/facebook/react-native/commit/a15fc102e63eb3b37852ca45fe4c65e894ecef7d) by [@rubennorte](https://github.com/rubennorte))
- **HMR:** Process HMR `registerBundle` calls from the same origin only ([a9007ea586](https://github.com/facebook/react-native/commit/a9007ea586f6e87db47c6305be3232d760abfd57) by [@jbroma](https://github.com/jbroma))
- **IntersectionObserver:** `IntersectionObserver` support for `root` with fixes for viewport offsets ([c5b6716311](https://github.com/facebook/react-native/commit/c5b67163117e13c99a9c57816f0ff36efc80ccf5) by [@lunaleaps](https://github.com/lunaleaps))
- **ReactNativeFeatureFlags:** Allow Custom ReactNativeFeatureFlags for Shell 2.0 ([bbc1e121c7](https://github.com/facebook/react-native/commit/bbc1e121c71d14803d29a931f642bf8ea6ee2023) by Maddie Lord)
- **ScrollView:** Added more Pending Decleration for `ScrollView` ([a6908ad1a5](https://github.com/facebook/react-native/commit/a6908ad1a5d998505b2bb6ba3e39910fee17329a) by [@riteshshukla04](https://github.com/riteshshukla04))
- **ShadowNode:** Added `cloneMultiple` to `ShadowNode` class. ([1161fb4fcd](https://github.com/facebook/react-native/commit/1161fb4fcd6a0cac3a691de1f37cc7f9d6a861a5) by [@bartlomiejbloniarz](https://github.com/bartlomiejbloniarz))
- **Typescript:** Add `pressRetentionOffset` prop to be recognised by typescript in `Text.d.ts` ([d94f4d8c9d](https://github.com/facebook/react-native/commit/d94f4d8c9deef78c0345a7fd3de74424f864c080) by [@iamAbhi-916](https://github.com/iamAbhi-916))
- **URLSearchParams:** Added size property to `URLSearchParams` implementation ([9b1a8ffac4](https://github.com/facebook/react-native/commit/9b1a8ffac4368b9304939359917c7cfd0a9501bf) by [@louix](https://github.com/louix))
#### Android specific
- **BaseViewManager:** Adds support for `onFocus` / `onBlur` event dispatching logic to all native views that implement `BaseViewManager` ([e960a28af7](https://github.com/facebook/react-native/commit/e960a28af7f4541dcf67d3c7148b2d32a39e1b04) by [@Abbondanzo](https://github.com/Abbondanzo))
- **Edge To Edge:** Add Android edge-to-edge opt-in support ([09ef774ff6](https://github.com/facebook/react-native/commit/09ef774ff6dac10a00a8b35670f9b3941d810dfb) by [@zoontek](https://github.com/zoontek))
- **RNGP:** `RNGP`- Add support for `exclusiveEnterpriseRepository` to specify an internal Maven mirror. ([6cb8dc37c7](https://github.com/facebook/react-native/commit/6cb8dc37c74995cba3f9f0a845919f305de53c3d) by [@cortinico](https://github.com/cortinico))
- **RNTester:** Added explicit build tool version to `RNTester` `build.gradle` to avoid automatic installation of Android SDK Build Tools. ([35dba09724](https://github.com/facebook/react-native/commit/35dba097243ff2d21466f860ec9831e1ff2a97ac) by [@mojavad](https://github.com/mojavad))
- **ScrollView:** Allow `fadingEdgeLength` to be set independently on the start and end of the `ScrollView` ([a21a4b87c3](https://github.com/facebook/react-native/commit/a21a4b87c337f3f2d998a30841430f587c066580) by Mark Verlingieri)
- **View:** Support for `onFocus` and `onBlur` function calls in `View` components ([af0a76cf5f](https://github.com/facebook/react-native/commit/af0a76cf5fdb8107294dff2c9aa0dbc36c7d5443) by [@Abbondanzo](https://github.com/Abbondanzo))
#### iOS specific
- **borderWidth:** Add support for different `borderWidth`s ([70962ef3ed](https://github.com/facebook/react-native/commit/70962ef3ed06a76a96cb2e72c374dc028628c829) by [@a-klotz-p8](https://github.com/a-klotz-p8))
- **Modal:** Allow to interactively swipe down `Modal`s. ([28986a7599](https://github.com/facebook/react-native/commit/28986a7599952a77b8b8e433f72ca837afde310e) by [@okwasniewski](https://github.com/okwasniewski))
- **Package.swift:** Added missing search path to `Package.swift` ([592b09781b](https://github.com/facebook/react-native/commit/592b09781bb94fe6dc00ba49c7a86649980fed5d) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Add more logging around `computeNightlyTarballURL` in ios pre-build ([1a6887bd70](https://github.com/facebook/react-native/commit/1a6887bd70cdefb8fbc421467de841ece74d5c6b) by [@cortinico](https://github.com/cortinico))
- **Prebuild:** Added backwards compatible use of prebuild through cocoapods ([d8e00f0bb1](https://github.com/facebook/react-native/commit/d8e00f0bb1940fc9cc7e5cbb68b26c2d05824486) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Ship the `React-Core-prebuilt.podspec` in `package.json` ([46b562b9b3](https://github.com/facebook/react-native/commit/46b562b9b3bf4c0b8c5af25ab84a43509979c4a7) by [@cipolleschi](https://github.com/cipolleschi))
- **Prebuild:** Added support for using prebuilt `RNCore` with Cocoapods ([90654e4ba2](https://github.com/facebook/react-native/commit/90654e4ba2f3cc2f6b0d8f08769ce58b4e5d1b51) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Add `React-Core-prebuild.podspec` to integrate React native core prebuilds using Cocoapods ([1a86ee17fb](https://github.com/facebook/react-native/commit/1a86ee17fb80cfa1b8bcff30f4f3d5cdb193900d) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Added building `XCFframework` from the prebuild script ([55534f518a](https://github.com/facebook/react-native/commit/55534f518aab53bcdc3fe12d987ab7ef6e620c77) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Added building swift package from the prebuild script ([3c01b1b6f0](https://github.com/facebook/react-native/commit/3c01b1b6f04d285c97bb182131135903b0c1cdd5) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Added downloading of hermes artifacts when pre-building for iOS. ([41d2b5de0a](https://github.com/facebook/react-native/commit/41d2b5de0af21c72227ef030dcddf208e2eb221a) by [@chrfalch](https://github.com/chrfalch))
- **runtime:** Added `HERMES_ENABLE_DEBUGGER` to debug configuration for the `reactRuntime` target. ([560ac23001](https://github.com/facebook/react-native/commit/560ac23001b02f19d4c6ca4ea493c17060cfaf5f) by [@chrfalch](https://github.com/chrfalch))
### Changed
- **Animated:** Animated now always flattens `props.style`, which fixes an error that results from `props.style` objects in which `AnimatedNode` instances are shadowed (i.e. flattened to not exist in the resulting `props.style` object). ([da520848c9](https://github.com/facebook/react-native/commit/da520848c931f356d013623c412af11dce7ff114) by [@yungsters](https://github.com/yungsters))
- **Animated:** Creates a feature flag that changes `Animated` to no longer produce invalid `props.style` if every `AnimatedNode` instance is shadowed via style flattening. ([5c8c5388fc](https://github.com/facebook/react-native/commit/5c8c5388fc53ef2430b0bb6bbbc628479819e23d) by [@yungsters](https://github.com/yungsters))
- **Animated:** Enabled a feature flag that optimizes `Animated` to reduce memory usage. ([2a13d20085](https://github.com/facebook/react-native/commit/2a13d200850e4f161a29b252a0ccedc158e53937) by [@yungsters](https://github.com/yungsters))
- **Error handling:** Errors will no longer have the "js engine" suffix. ([a293925280](https://github.com/facebook/react-native/commit/a2939252803d5cd4b68340da08820174c30a53e6) by [@yungsters](https://github.com/yungsters))
- **Fibers:** Reduces memory usage, by improving memory management of parent alternate fibers. (Previously, a parent fiber might retain memory associated with shadow nodes from a previous commit.) ([0411c43b3a](https://github.com/facebook/react-native/commit/0411c43b3a239384c778baad22c7b4c501008449) by [@yungsters](https://github.com/yungsters))
- **infoLog:** Removed `infoLog` from `react-native` package ([8a0cfec815](https://github.com/facebook/react-native/commit/8a0cfec81584e966c9e6ea0f5e438022e0129bcd) by [@coado](https://github.com/coado))
- **IntersectionObserver:** Fixed `IntersectionObserver#observe` to avoid retaining memory for unmounted child nodes of observed views. ([d945c5863a](https://github.com/facebook/react-native/commit/d945c5863a5ed7b755e577bc25d681bfcc1c401b) by [@yungsters](https://github.com/yungsters))
- **Jest:** Improved default mocking for Jest unit tests. ([1fd9508ecc](https://github.com/facebook/react-native/commit/1fd9508ecc499df89b086e0c46035f43f6f78ad9) by [@yungsters](https://github.com/yungsters))
- **LegacyArchitecture:** Raise loglevel for assertion of `LegacyArchitecture` classes ([38a4b62211](https://github.com/facebook/react-native/commit/38a4b6221164d36eb4ac95c9f3bc7f7e7235e383) by [@mdvacca](https://github.com/mdvacca))
- **LegacyArchitecture:** Raise logLevel of `LegacyArchitecture` classes when minimizing of legacy architecture is enabled ([0d1cde7f36](https://github.com/facebook/react-native/commit/0d1cde7f36e9de72c997fc812bba023694c2a369) by [@mdvacca](https://github.com/mdvacca))
- **Metro:** Bump Metro to `^0.82.5` ([083644647e](https://github.com/facebook/react-native/commit/083644647eff502f484b3ba24f9d361d5df56546) by [@robhogan](https://github.com/robhogan))
- **React DevTools:** Bumped React DevTools to `6.1.5` ([c302902b1d](https://github.com/facebook/react-native/commit/c302902b1db7e8f8ac5b61472c095dc0755d6d1c) by [@hoxyq](https://github.com/hoxyq))
- **RuntimeExecutor:** `RuntimeExecutor`: Remove noexcept from sync ui thread utils ([7ef278af50](https://github.com/facebook/react-native/commit/7ef278af505deba6b8a47876c6824f9a7fefa427) by [@RSNara](https://github.com/RSNara))
- **Typescript:** Bump `types/react` to `19.1` ([3ae9328571](https://github.com/facebook/react-native/commit/3ae932857174e9c39cd5d9c53922f849aa1401b1) by [@gabrieldonadel](https://github.com/gabrieldonadel))
#### Android specific
- **APIs:** Deprecate `DefaultNewArchitectureEntryPoint.load(Boolean, Boolean, Boolean)` ([efdf73983c](https://github.com/facebook/react-native/commit/efdf73983cef1f371511b6e1efa5e01835ebcabb) by [@cortinico](https://github.com/cortinico))
- **APIs:** Make `com.facebook.react.views.common.ContextUtils` internal ([d1ef8f1fa3](https://github.com/facebook/react-native/commit/d1ef8f1fa36cbfc34d05c409abf693e4e1cac3de) by [@cortinico](https://github.com/cortinico))
- **deps:** Bump `AGP` to `8.11.0` ([04858ecbab](https://github.com/facebook/react-native/commit/04858ecbab808ddca80e20e76f1359619bb5e865) by [@cortinico](https://github.com/cortinico))
- **deps:** Bump `Gradle` to `8.14.2` ([e20bb56f3b](https://github.com/facebook/react-native/commit/e20bb56f3b4db0d3e69154b95b952b1fe8e29959) by [@cortinico](https://github.com/cortinico))
- **JS FPS:** Hide JS FPS on performance overlay as not accurate ([feec8d0148](https://github.com/facebook/react-native/commit/feec8d014877b2177f1c7dded7eb9664f53ee471) by [@cortinico](https://github.com/cortinico))
- Updated targetSdk to 36 in Android. ([477d8df312](https://github.com/facebook/react-native/commit/477d8df3126b325b8cc9b410f1eaeb56b727d4d9) by [@kikoso](https://github.com/kikoso))
- **Kotlin:** Convert `UIManagerModuleConstantsHelper` to Kotlin ([45fd7feb9f](https://github.com/facebook/react-native/commit/45fd7feb9f083e5c8afc916732aed9795d344e09) by [@cortinico](https://github.com/cortinico))
- **Kotlin:** Migrate `ThemedReactContext` to Kotlin ([78c9671c24](https://github.com/facebook/react-native/commit/78c9671c241a86bedb17862e549842b7e36d77ea) by [@cortinico](https://github.com/cortinico))
- **Kotlin:** Convert `ReactViewGroup` to Kotlin ([48395d346b](https://github.com/facebook/react-native/commit/48395d346bc89f63d38889e58508304df0088e4f) by [@cortinico](https://github.com/cortinico))
- **Kotlin:** Migrate `com.facebook.react.LazyReactPackage` to Kotlin. ([b4ae5c1de1](https://github.com/facebook/react-native/commit/b4ae5c1de1003c343d43c3be1b59ee2b800b9258) by [@Xintre](https://github.com/Xintre))
- **Kotlin:** Apply Collections Kotlin DSL helpers in `ReactAndroid` package ([b2ffd34a39](https://github.com/facebook/react-native/commit/b2ffd34a392de2bddba5ee13248796ccc2db6039) by [@l2hyunwoo](https://github.com/l2hyunwoo))
#### iOS specific
- **Accessibility:** Only generate recursive accessibility label for accessible elements ([7e2f17ffe2](https://github.com/facebook/react-native/commit/7e2f17ffe229e09288deba9061221835300ec153) by [@janicduplessis](https://github.com/janicduplessis))
- **GC:** Hermes GC is now triggered in response to iOS memory pressure warning. ([12b2b56102](https://github.com/facebook/react-native/commit/12b2b5610263cb145d1ade8eaf06d8a6e015b10e) by [@yungsters](https://github.com/yungsters))
- **Gradients:** Optimised Radial Gradients. ([f238b74658](https://github.com/facebook/react-native/commit/f238b74658fd155366d4909872ab06781403f31d) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
- **Gradients:** Optimised Linear Gradients. ([2f3b104224](https://github.com/facebook/react-native/commit/2f3b1042249411e84f9a1d5bb1191461cd2dc5ee) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
- **Prebuild:** Simplified logging in prebuild scripts ([1477cc0dbd](https://github.com/facebook/react-native/commit/1477cc0dbdee4b50fee4b1b98346812868148aa5) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Fail fast when pod install if using prebuild and frameworks are not present in the disk. ([60c01b4715](https://github.com/facebook/react-native/commit/60c01b4715053bccbeca2673ff1be1fca60bce9b) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Update `ReactCodegen` to support Core prebuilds ([152cb538f6](https://github.com/facebook/react-native/commit/152cb538f69cd07422526802defe3f3617302098) by [@chrfalch](https://github.com/chrfalch))
### Deprecated
- **hasTVPreferredFocus:** Deprecate `hasTVPreferredFocus` ([cfb6c968dd](https://github.com/facebook/react-native/commit/cfb6c968ddce56138553a9d2e0cc8dfc666eb943) by [@Abbondanzo](https://github.com/Abbondanzo))
- **SafeAreaView:** Deprecate `SafeAreaView` due to its iOS-only support and incompatibility with Android 15 edge-to-edge behavior; recommend using `react-native-safe-area-context` instead. ([73133a31d5](https://github.com/facebook/react-native/commit/73133a31d5a47e71edd4e6b798184df8045e2234) by [@kikoso](https://github.com/kikoso))
- **ShadowNode:** `ShadowNode::Shared` is now deprecated. Use `std::shared_ptr<const ShadowNode>` instead. ([0e175ce5b6](https://github.com/facebook/react-native/commit/0e175ce5b6c80a21237f5cd0f20c9876fa975935) by [@sammy-SC](https://github.com/sammy-SC))
- **ShadowNode:** Deprecate type aliases `ShadowNode::Unshared` and `ShadowNode::Weak` in favour of `std::shared_ptr<ShadowNode>` and `std::weak_ptr<ShadowNode>` ([12fb101e30](https://github.com/facebook/react-native/commit/12fb101e306778b6e5399b23f822cc0874a5c386) by [@sammy-SC](https://github.com/sammy-SC))
#### iOS specific
- **RCTFollyConvert:** `RCTFollyConvert.h` is deprecated please use `/ReactCommon/react/utils/platform/ios/react/utils/FollyConvert.h` instead ([685a60e6b4](https://github.com/facebook/react-native/commit/685a60e6b44018531abf47f98fd38d9c75f6aca6) by [@sammy-SC](https://github.com/sammy-SC))
### Removed
- **Yoga:** Remove `YogaLayoutableShadowNode::cleanLayout()` and Fix `ParagraphShadowNode` Font Size Invalidation Logic ([7979c7ce06](https://github.com/facebook/react-native/commit/7979c7ce0664bf019e17781e95a74baf95ec89f1) by [@NickGerleman](https://github.com/NickGerleman))
#### Android specific
- **APIs:** Internalize `NetworkingModule`'s `UriHandler`, `RequestBodyHandler`, and `ResponseHandler` APIs ([987e3f8c00](https://github.com/facebook/react-native/commit/987e3f8c0031affe89218675061eca3a4620e0cd) by [@huntie](https://github.com/huntie))
- **DeveloperSettings:** Remove deprecated `isStartSamplingProfilerOnInit` from `DeveloperSettings` ([ccb9edc717](https://github.com/facebook/react-native/commit/ccb9edc7179ec1b568038408118971c2ee4c1b27) by [@cortinico](https://github.com/cortinico))
- **JSC:** Remove 1st party JSC support ([8174d02811](https://github.com/facebook/react-native/commit/8174d028116f00b6e89968d368b719ec8b7f6ff6) by [@cortinico](https://github.com/cortinico))
- **JSEngineResolutionAlgorithm:** Remove and cleanup `JSEngineResolutionAlgorithm` ([0954c1db45](https://github.com/facebook/react-native/commit/0954c1db45511d1a640deb8d921d26457bb3777c) by [@cortinico](https://github.com/cortinico))
#### iOS specific
- **JSC:** Remove code from jsc folder from React Native ([331fab0683](https://github.com/facebook/react-native/commit/331fab068355a3ba78776fbdf7e75ff4af3b740f) by [@cipolleschi](https://github.com/cipolleschi))
- **JSC:** Remove the option to use JSC from core ([a6ea626255](https://github.com/facebook/react-native/commit/a6ea6262555e7a25e27934f33099e95cb45c8077) by [@cipolleschi](https://github.com/cipolleschi))
- **Turbo Modules** Disable Turbo Modules fix for #51103 (dictionary stripped out when value is null) until more testing can be done. ([ca5f4d1721](https://github.com/facebook/react-native/commit/ca5f4d1721878ccd81ddcb9267f30160c6d17dd4) by [@javache](https://github.com/javache))
### Fixed
- **APIs:** Renamed argument names in the `onContentSizeChange` callback's type definition ([0386b9bd51](https://github.com/facebook/react-native/commit/0386b9bd5144b785af51c96ea6bada5c55127e98) by [@pchalupa](https://github.com/pchalupa))
- **BindingsInstallerHolder:** Fixed deprecation message for `BindingsInstallerHolder` ([4a8fda83e3](https://github.com/facebook/react-native/commit/4a8fda83e3d0f8aa0e5ac0c427c30fd9170ad375) by [@tomekzaw](https://github.com/tomekzaw))
- **C++:** Add `default:` case to avoid warnings/errors for targets that compile with `-Wswitch-enum` and `-Wswitch-default` enabled ([22b8b53c77](https://github.com/facebook/react-native/commit/22b8b53c77cb2fe87aa54be92d1758a603b7dd35) by [@NSProgrammer](https://github.com/NSProgrammer))
- **C++:** Add `default:` case to avoid warnings/errors for targets that compile with `-Wswitch-enum` and `-Wswitch-default` enabled ([9079b53c6f](https://github.com/facebook/react-native/commit/9079b53c6fa4ec9494f22390a89d1d42b77108a8) by [@NSProgrammer](https://github.com/NSProgrammer))
- **C++:** Fix clang tidy for react-native ([3e49d17f58](https://github.com/facebook/react-native/commit/3e49d17f58b9b7cc051925f43b709b020745312c) by [@RSNara](https://github.com/RSNara))
- **Color APIs:** Fix the serialization of the alpha channel in the `rgba()` color string format. ([1cc12ce7fd](https://github.com/facebook/react-native/commit/1cc12ce7fd5c8c766872906b4175122558d369a0) by [@piaskowyk](https://github.com/piaskowyk))
- **Color APIs:** Fix incorrect flattening / non-rendering of views with `backgroundColor` set to `rgba(255, 255, 255, 127/256)` ([b1e8729f4d](https://github.com/facebook/react-native/commit/b1e8729f4dfcb065978887b94a9e0ca65cdcfa77) by [@rubennorte](https://github.com/rubennorte))
- **Fantom:** Support viewport offsets for Fantom root and fix `getBoundingClientRect` to respect viewport offsets ([b5c62f52d1](https://github.com/facebook/react-native/commit/b5c62f52d185d6427425e25e6f18d0d86acaebf0) by [@lunaleaps](https://github.com/lunaleaps))
- **IntersectionObserver:** Fix potential leak inside `IntersectionObserver` ([a55f430daa](https://github.com/facebook/react-native/commit/a55f430daa4c9168272482125998da849840f9dd) by [@RSNara](https://github.com/RSNara))
- **LogBox:** Remove LogBox patch, de-duplicating errors ([e0797d0e03](https://github.com/facebook/react-native/commit/e0797d0e03bde6cd3321ff76556c5e2f0454ec63) by [@rickhanlonii](https://github.com/rickhanlonii))
- **ScrollView:** Expose `ScrollView.getNativeScrollRef` on the type definition to allow accessing the underlying `HostInstance`. ([4b91b63094](https://github.com/facebook/react-native/commit/4b91b630945b3d0f82656791d089451066aad538) by [@zbauman3](https://github.com/zbauman3))
- **Typescript:** Add `ImageSource` type to TypeScript ([42ca46b95c](https://github.com/facebook/react-native/commit/42ca46b95cf9938de00b76dc61948a4ae7116e2b) by [@okwasniewski](https://github.com/okwasniewski))
- **Typescript:** Devtools TS Types ([8f189fce03](https://github.com/facebook/react-native/commit/8f189fce03db367abdceca6ad57ae28b613fdd7d) by [@krystofwoldrich](https://github.com/krystofwoldrich))
- **Yoga:** Fix possible invalid measurements with width or height is zero pixels ([5cc4d0a086](https://github.com/facebook/react-native/commit/5cc4d0a086d450e0f9d8ab6194013348f9de1f58) by [@NickGerleman](https://github.com/NickGerleman))
#### Android specific
- **BaseViewManager:** Remove focus change listener when dropping/recycling view instances ([94cbf206d6](https://github.com/facebook/react-native/commit/94cbf206d607477257c65039d97565a79e94c7dd) by [@Abbondanzo](https://github.com/Abbondanzo))
- **BoringLayout:** Include fallback line spacing in `BoringLayout` ([2fe6c1a947](https://github.com/facebook/react-native/commit/2fe6c1a94758223a5342fdfa90163971eb588e6a) by [@NickGerleman](https://github.com/NickGerleman))
- **Bridgeless:** Adding `shouldForwardToReactInstance` check in `ReactDelegate` for Bridgeless ([0f7bf66bba](https://github.com/facebook/react-native/commit/0f7bf66bba8498c89384e96ad9219cdad0107b0c) by [@arushikesarwani94](https://github.com/arushikesarwani94))
- **Codegen:** Fix combining schema in Codegen process to exclude platforms correctly ([6104ccdc6e](https://github.com/facebook/react-native/commit/6104ccdc6ef89c2d4da25e60dcc55d73038e023f) by [@arushikesarwani94](https://github.com/arushikesarwani94))
- **Edge To Edge:** Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled ([85d10ed904](https://github.com/facebook/react-native/commit/85d10ed90401a13de1f74aeddd773736195da285) by [@zoontek](https://github.com/zoontek))
- **FBReactNativeSpec:** Extract out `FBReactNativeSpec`'s core components including Unimplemented from auto-generated registry ([b417b0c2d5](https://github.com/facebook/react-native/commit/b417b0c2d56dc37f824c0e77e98d1014d21cd8f8) by [@arushikesarwani94](https://github.com/arushikesarwani94))
- **Gradle:** Fix Gradle v8.0 builds by using .set() for Property ([777397667c](https://github.com/facebook/react-native/commit/777397667c2625aab3fc907b9ef4bd564963d8bb) by [@meghancampbel9](https://github.com/meghancampbel9))
- **ImageFetcher:** Change `free` to `delete` to call destructor of `ImageFetcher` and release `contextContainer`. ([90da666691](https://github.com/facebook/react-native/commit/90da666691745ab9bf3930dc3347d8e51683099f) by [@WoLewicki](https://github.com/WoLewicki))
- **Modal:** Fix `Modal` first frame being rendered on top-left corner ([b950fa2afb](https://github.com/facebook/react-native/commit/b950fa2afb20e2213ff6c733cb1c2465b90406ef) by [@cortinico](https://github.com/cortinico))
- **onTextLayout:** Fix `onTextLayout` metrics not incorporating `ReactTextViewManagerCallback` ([a6a2884d63](https://github.com/facebook/react-native/commit/a6a2884d63717a42ac2bafd2054991ce8b32a2e9) by [@NickGerleman](https://github.com/NickGerleman))
- **Text:** Fix more text rounding bugs ([1fe3ff86c3](https://github.com/facebook/react-native/commit/1fe3ff86c364fad023ad1e426f26608699314339) by [@NickGerleman](https://github.com/NickGerleman))
- **Text:** Fix `TextLayoutManager` `MeasureMode` Regression ([99119a2104](https://github.com/facebook/react-native/commit/99119a210487af18983145fd374ff7ebc88931f3) by [@NickGerleman](https://github.com/NickGerleman))
- **TextInput:** Fix bug where focus would jump to top text input upon clearing a separate text input. ([79c47987b7](https://github.com/facebook/react-native/commit/79c47987b74ab044574fc542fd4b13a9f11aa491) by [@joevilches](https://github.com/joevilches))
#### iOS specific
- **Gradient**: Gradient interpolation for transparent colors ([097d482446](https://github.com/facebook/react-native/commit/097d482446b7a03ca0f8c7e0254f4d770e05c79c) by [@intergalacticspacehighway](https://github.com/intergalacticspacehighway))
- **Prebuild:** Fixed wrong path in prebuild hermes check ([be11f2ee77](https://github.com/facebook/react-native/commit/be11f2ee77fd793efe0a1aa225897a1924163925) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Fixed resolving build type when downloading hermes artifacts ([9371e20192](https://github.com/facebook/react-native/commit/9371e201927fd105e797bf06e43943dd21e04381) by [@chrfalch](https://github.com/chrfalch))
- **Package.swift:** Add missing `React-RCTSettings` to `Package.swift` ([e40c1d265a](https://github.com/facebook/react-native/commit/e40c1d265a2045730dcf751eed4ebf32e099f0c7) by [@chrfalch](https://github.com/chrfalch))
- **Package.swift:** Fixed defines in `Package.swift` ([e2f6ce4ddf](https://github.com/facebook/react-native/commit/e2f6ce4ddfbea5814fc5d8632df14daeae3636d1) by [@chrfalch](https://github.com/chrfalch))
- **RCTImage:** Allow for consuming `RCTImage` in Swift codebase by enabling "Defines Module" option ([1d80586730](https://github.com/facebook/react-native/commit/1d8058673085580f402ec3a320fce810db7ad2ef) by [@kkafar](https://github.com/kkafar))
- **RCTImageComponentView:** Fix `RCTImageComponentView` image loading after source props change with no layout invalidation ([cd5d74518b](https://github.com/facebook/react-native/commit/cd5d74518becb3355519373211d2f54ff7dbd208) by Nick Lefever)
- **RCTScreenSize:** Make `RCTScreenSize` take horizontal orientation into account ([50ce8c77a7](https://github.com/facebook/react-native/commit/50ce8c77a74f2f2574030db04dc88c6092e68ba8) by [@okwasniewski](https://github.com/okwasniewski))
- **TextInput:** Fixed blank space at the bottom of multiline `TextInput` on iOS ([2da4a6059a](https://github.com/facebook/react-native/commit/2da4a6059a82430fa7c1c078f0dcd38f0d3fe3cb) by [@tomekzaw](https://github.com/tomekzaw))
- **Turbo Modules:** Turbo Modules- Fixes dictionary stripped out when value is `null` ([4a4fd1cb8b](https://github.com/facebook/react-native/commit/4a4fd1cb8bb06eee185a3b2463caec4d2b7e9235) by [@zhongwuzw](https://github.com/zhongwuzw))
## v0.80.1
### Added
@@ -576,10 +358,6 @@ See [CHANGELOG-0.7x](./CHANGELOG-0.7x#v0791)
See [CHANGELOG-0.7x](./CHANGELOG-0.7x#v0790)
## v0.78.3
See [CHANGELOG-0.7x](./CHANGELOG-0.7x#v0783)
## v0.78.2
See [CHANGELOG-0.7x](./CHANGELOG-0.7x#v0782)
-28
View File
@@ -13,7 +13,6 @@ plugins {
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.binary.compatibility.validator) apply true
alias(libs.plugins.android.test) apply false
alias(libs.plugins.ktfmt) apply true
}
val reactAndroidProperties = java.util.Properties()
@@ -131,30 +130,3 @@ if (project.findProperty("react.internal.useHermesNightly")?.toString()?.toBoole
}
}
}
ktfmt {
blockIndent.set(2)
continuationIndent.set(4)
maxWidth.set(100)
removeUnusedImports.set(false)
manageTrailingCommas.set(false)
}
// Configure ktfmt tasks to include gradle-plugin
listOf("ktfmtCheck", "ktfmtFormat").forEach { taskName ->
tasks.named(taskName) { dependsOn(gradle.includedBuild("gradle-plugin").task(":$taskName")) }
}
allprojects {
// Apply exclusions for specific files that should not be formatted
val excludePatterns =
listOf(
"**/build/**",
"**/hermes-engine/**",
"**/internal/featureflags/**",
"**/systeminfo/ReactNativeVersion.kt")
listOf(
com.ncorti.ktfmt.gradle.tasks.KtfmtCheckTask::class,
com.ncorti.ktfmt.gradle.tasks.KtfmtFormatTask::class)
.forEach { tasks.withType(it) { exclude(excludePatterns) } }
}
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+1 -1
View File
@@ -32,7 +32,7 @@ if (process.env.FBSOURCE_ENV === '1') {
require('@fb-tools/babel-register');
} else {
// Register Babel to allow local packages to be loaded from source
require('../scripts/shared/babelRegister').registerForMonorepo();
require('../scripts/babel-register').registerForMonorepo();
}
const transformer = require('@react-native/metro-babel-transformer');
+7 -12
View File
@@ -8,19 +8,15 @@
"android": "yarn --cwd packages/rn-tester android",
"build-android": "./gradlew :packages:react-native:ReactAndroid:build",
"build": "node ./scripts/build/build.js",
"build-types": "node ./scripts/js-api/build-types",
"build-types": "node ./scripts/build-types",
"clang-format": "clang-format -i --glob=*/**/*.{h,cpp,m,mm}",
"clean": "node ./scripts/build/clean.js",
"cxx-api-build": "node ./scripts/cxx-api/public-api.js",
"flow-check": "flow check",
"flow": "flow",
"format-check": "prettier --list-different \"./**/*.{js,md,yml,ts,tsx}\"",
"format": "npm run prettier && npm run clang-format",
"featureflags": "yarn --cwd packages/react-native featureflags",
"js-api-diff": "node ./scripts/js-api/diff-api-snapshot",
"lint-ci": "./.github/workflow-scripts/analyze_code.sh && yarn shellcheck",
"lint-kotlin-check": "./gradlew ktfmtCheck",
"lint-kotlin": "./gradlew ktfmtFormat",
"lint-markdown": "markdownlint-cli2 2>&1",
"lint": "eslint --max-warnings 0 .",
"prettier": "prettier --write \"./**/*.{js,md,yml,ts,tsx}\"",
@@ -30,8 +26,8 @@
"set-version": "node ./scripts/releases/set-version.js",
"test-android": "./gradlew :packages:react-native:ReactAndroid:test",
"test-ci": "jest --maxWorkers=2 --ci --reporters=\"default\" --reporters=\"jest-junit\"",
"test-release-local-clean": "node ./scripts/release-testing/test-release-local-clean.js",
"test-release-local": "node ./scripts/release-testing/test-release-local.js",
"test-e2e-local-clean": "node ./scripts/release-testing/test-e2e-local-clean.js",
"test-e2e-local": "node ./scripts/release-testing/test-e2e-local.js",
"test-ios": "./scripts/objc-test.sh test",
"test-typescript": "tsc -p packages/react-native/types/tsconfig.json",
"test-generated-typescript": "tsc -p packages/react-native/types_generated/tsconfig.test.json",
@@ -60,7 +56,7 @@
"@react-native/metro-config": "0.81.0-main",
"@tsconfig/node22": "22.0.2",
"@types/react": "^19.1.0",
"@typescript-eslint/parser": "^8.36.0",
"@typescript-eslint/parser": "^7.1.1",
"ansi-styles": "^4.2.1",
"babel-plugin-minify-dead-code-elimination": "^0.5.2",
"babel-plugin-syntax-hermes-parser": "0.29.1",
@@ -86,7 +82,6 @@
"glob": "^7.1.1",
"hermes-eslint": "0.29.1",
"hermes-transform": "0.29.1",
"ini": "^5.0.0",
"inquirer": "^7.1.0",
"jest": "^29.7.0",
"jest-config": "^29.7.0",
@@ -95,9 +90,9 @@
"jest-snapshot": "^29.7.0",
"markdownlint-cli2": "^0.17.2",
"markdownlint-rule-relative-links": "^3.0.0",
"memfs": "^4.7.7",
"metro-babel-register": "^0.83.0",
"metro-transform-plugins": "^0.83.0",
"metro-babel-register": "^0.82.5",
"metro-memory-fs": "^0.82.5",
"metro-transform-plugins": "^0.82.5",
"micromatch": "^4.0.4",
"node-fetch": "^2.2.0",
"nullthrows": "^1.1.1",
+1 -1
View File
@@ -17,7 +17,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"path-support.js",
+1 -1
View File
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"index.js"
+5 -5
View File
@@ -25,13 +25,13 @@
"@react-native/dev-middleware": "0.81.0-main",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"metro": "^0.83.0",
"metro-config": "^0.83.0",
"metro-core": "^0.83.0",
"metro": "^0.82.5",
"metro-config": "^0.82.5",
"metro-core": "^0.82.5",
"semver": "^7.1.3"
},
"devDependencies": {
"metro-resolver": "^0.83.0"
"metro-resolver": "^0.82.5"
},
"peerDependencies": {
"@react-native-community/cli": "*",
@@ -43,6 +43,6 @@
}
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
@@ -147,7 +147,7 @@ async function runServer(
// $FlowIgnore[cannot-write] Assigning to readonly property
metroConfig.reporter = reporter;
await Metro.runServer(metroConfig, {
const serverInstance = await Metro.runServer(metroConfig, {
host: args.host,
secure: args.https,
secureCert: args.cert,
@@ -161,6 +161,18 @@ async function runServer(
reportEvent = eventsSocketEndpoint.reportEvent;
// In Node 8, the default keep-alive for an HTTP connection is 5 seconds. In
// early versions of Node 8, this was implemented in a buggy way which caused
// some HTTP responses (like those containing large JS bundles) to be
// terminated early.
//
// As a workaround, arbitrarily increase the keep-alive from 5 to 30 seconds,
// which should be enough to send even the largest of JS bundles.
//
// For more info: https://github.com/nodejs/node/issues/13391
//
serverInstance.keepAliveTimeout = 30000;
await version.logIfUpdateAvailable(cliConfig, terminalReporter);
}
+1 -1
View File
@@ -13,7 +13,7 @@ export type * from './index.flow';
*/
if (!process.env.BUILD_EXCLUDE_BABEL_REGISTER) {
require('../../../scripts/shared/babelRegister').registerForMonorepo();
require('../../../scripts/babel-register').registerForMonorepo();
}
module.exports = require('./index.flow');
+1 -1
View File
@@ -21,7 +21,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"dist"
+1 -1
View File
@@ -13,7 +13,7 @@ export type * from './index.flow';
*/
if (process.env.BUILD_EXCLUDE_BABEL_REGISTER == null) {
require('../../../scripts/shared/babelRegister').registerForMonorepo();
require('../../../scripts/babel-register').registerForMonorepo();
}
module.exports = require('./index.flow');
+3 -2
View File
@@ -76,8 +76,9 @@ function getNodePackagePath(packageName: string): string {
}
function metro(...args: $ReadOnlyArray<string>): ExecaPromise {
log(`🚇 metro ${args.join(' ')} `);
return execa('npx', ['--offline', 'metro', ...args]);
const metroPath = getNodePackagePath(path.join('metro', 'src', 'cli.js'));
log(`🚇 ${metroPath} ${args.join(' ')} `);
return execa('node', [metroPath, ...args]);
}
export const tasks = {
@@ -13,7 +13,7 @@ export type * from './version.flow';
*/
if (process.env.BUILD_EXCLUDE_BABEL_REGISTER == null) {
require('../../../../scripts/shared/babelRegister').registerForMonorepo();
require('../../../../scripts/babel-register').registerForMonorepo();
}
module.exports = require('./version.flow');
+2 -2
View File
@@ -1,5 +1,5 @@
@generated SignedSource<<e2d97b04634bf3b566f33d455f38658f>>
Git revision: 8dc0d5b365e6b4600b78e5a7381aa861d8c1c81e
@generated SignedSource<<8c4db6a5e1ba269169ac93cc53f95538>>
Git revision: 51a91a2ad62e7f585912ed314a350a72de84d6ed
Built with --nohooks: false
Is local checkout: false
Remote URL: https://github.com/facebook/react-native-devtools-frontend
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -20,6 +20,6 @@
"BUILD_INFO"
],
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -26,7 +26,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 20.19.4",
"node": ">= 22.14.0",
"electron": ">=36.3.0"
},
"dependencies": {
@@ -10,25 +10,9 @@
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
const {app} = require('electron') as any;
const util = require('util');
// Handle global command line arguments which don't require a window
// or the single instance lock to be held.
const {
values: {version = false},
} = util.parseArgs({
options: {version: {type: 'boolean'}},
args: process.argv.slice(app.isPackaged ? 1 : 2),
strict: false,
});
if (version) {
console.log(`${app.getName()} v${app.getVersion()}`);
// Not app.quit() - we want to exit immediately without initialising the graphical subsystem.
app.exit(0);
}
const gotTheLock = app.requestSingleInstanceLock({
argv: process.argv.slice(app.isPackaged ? 1 : 2),
argv: process.argv.slice(2),
});
if (!gotTheLock) {
@@ -13,7 +13,7 @@ export type * from './index.flow';
*/
if (!Boolean(process.env.BUILD_EXCLUDE_BABEL_REGISTER)) {
require('../../../../scripts/shared/babelRegister').registerForMonorepo();
require('../../../../scripts/babel-register').registerForMonorepo();
}
module.exports = require('./index.flow');
+1 -1
View File
@@ -13,7 +13,7 @@ export type * from './index.flow';
*/
if (!process.env.BUILD_EXCLUDE_BABEL_REGISTER) {
require('../../../../scripts/shared/babelRegister').registerForMonorepo();
require('../../../../scripts/babel-register').registerForMonorepo();
}
export * from './index.flow';
+1 -1
View File
@@ -35,7 +35,7 @@
"ws": "^6.2.3"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"devDependencies": {
"selfsigned": "^2.4.1",
+1 -1
View File
@@ -13,7 +13,7 @@ export type * from './index.flow';
*/
if (!process.env.BUILD_EXCLUDE_BABEL_REGISTER) {
require('../../../scripts/shared/babelRegister').registerForMonorepo();
require('../../../scripts/babel-register').registerForMonorepo();
}
export * from './index.flow';
@@ -16,15 +16,15 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "index.js",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/eslint-parser": "^7.25.1",
"@react-native/eslint-plugin": "0.81.0-main",
"@typescript-eslint/eslint-plugin": "^8.36.0",
"@typescript-eslint/parser": "^8.36.0",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-ft-flow": "^2.0.1",
@@ -22,6 +22,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -36,6 +36,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -22
View File
@@ -5,10 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
plugins {
alias(libs.plugins.kotlin.jvm).apply(false)
alias(libs.plugins.ktfmt).apply(true)
}
plugins { alias(libs.plugins.kotlin.jvm).apply(false) }
tasks.register("build") {
dependsOn(
@@ -27,21 +24,3 @@ tasks.register("clean") {
":shared:clean",
)
}
tasks.named("ktfmtCheck") {
dependsOn(
":react-native-gradle-plugin:ktfmtCheck",
":settings-plugin:ktfmtCheck",
":shared-testutil:ktfmtCheck",
":shared:ktfmtCheck",
)
}
tasks.named("ktfmtFormat") {
dependsOn(
":react-native-gradle-plugin:ktfmtFormat",
":settings-plugin:ktfmtFormat",
":shared-testutil:ktfmtFormat",
":shared:ktfmtFormat",
)
}
@@ -6,7 +6,6 @@ javapoet = "1.13.0"
junit = "4.13.2"
kotlin = "2.1.20"
assertj = "3.25.1"
ktfmt = "0.22.0"
[libraries]
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
@@ -19,4 +18,3 @@ assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"scripts": {
"build": "./gradlew build",
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
id("java-gradle-plugin")
}
@@ -18,7 +18,6 @@ import com.facebook.react.tasks.GenerateEntryPointTask
import com.facebook.react.tasks.GeneratePackageListTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildTypesForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevServerLocation
import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
@@ -85,7 +84,6 @@ class ReactPlugin : Plugin<Project> {
configureAutolinking(project, extension)
configureCodegen(project, extension, rootExtension, isLibrary = false)
configureResources(project, extension)
configureBuildTypesForApp(project)
}
// Library Only Configuration
@@ -148,7 +148,6 @@ abstract class GeneratePackageListTask : DefaultTask() {
{{ packageImports }}
@SuppressWarnings("deprecation")
public class PackageList {
private Application application;
private ReactNativeHost reactNativeHost;
@@ -19,7 +19,6 @@ import java.net.Inet4Address
import java.net.NetworkInterface
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.plus
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
@@ -28,26 +27,6 @@ import org.w3c.dom.Element
@Suppress("UnstableApiUsage")
internal object AgpConfiguratorUtils {
fun configureBuildTypesForApp(project: Project) {
val action =
Action<AppliedPlugin> {
project.extensions
.getByType(ApplicationAndroidComponentsExtension::class.java)
.finalizeDsl { ext ->
ext.buildTypes {
val debug =
getByName("debug").apply {
manifestPlaceholders["usesCleartextTraffic"] = "true"
}
getByName("release").apply {
manifestPlaceholders["usesCleartextTraffic"] = "false"
}
}
}
}
project.pluginManager.withPlugin("com.android.application", action)
}
fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
val action =
Action<AppliedPlugin> {
@@ -233,7 +233,6 @@ class GeneratePackageListTaskTest {
@SuppressWarnings("deprecation")
public class PackageList {
private Application application;
private ReactNativeHost reactNativeHost;
@@ -312,8 +311,7 @@ class GeneratePackageListTaskTest {
import com.facebook.react.aPackage;
// @react-native/another-package
import com.facebook.react.anotherPackage;
@SuppressWarnings("deprecation")
public class PackageList {
private Application application;
private ReactNativeHost reactNativeHost;
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
id("java-gradle-plugin")
}
@@ -10,10 +10,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
}
plugins { alias(libs.plugins.kotlin.jvm) }
repositories { mavenCentral() }
@@ -10,10 +10,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
}
plugins { alias(libs.plugins.kotlin.jvm) }
repositories { mavenCentral() }
+3 -3
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"exports": {
".": "./src/index.js",
@@ -28,7 +28,7 @@
"dependencies": {
"@react-native/js-polyfills": "0.81.0-main",
"@react-native/metro-babel-transformer": "0.81.0-main",
"metro-config": "^0.83.0",
"metro-runtime": "^0.83.0"
"metro-config": "^0.82.5",
"metro-runtime": "^0.82.5"
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ export type * from './index.flow';
*/
if (!process.env.BUILD_EXCLUDE_BABEL_REGISTER) {
require('../../../scripts/shared/babelRegister').registerForMonorepo();
require('../../../scripts/babel-register').registerForMonorepo();
}
module.exports = require('./index.flow');
+1 -1
View File
@@ -30,6 +30,6 @@
}
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -65,7 +65,7 @@ const Links: Array<{
},
{
title: 'Community',
description: 'Explore & get help',
description: 'Expore & get help',
url: 'https://reactnative.dev/community/overview',
},
{
+12 -17
View File
@@ -13,7 +13,10 @@ import {ThemedText, useTheme} from './Theme';
import * as React from 'react';
import {
Image,
Platform,
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
TouchableHighlight,
@@ -26,32 +29,24 @@ import {version as ReactNativeVersion} from 'react-native/Libraries/Core/ReactNa
export type NewAppScreenProps = $ReadOnly<{
templateFileName?: string,
safeAreaInsets?: $ReadOnly<{
top: number,
bottom: number,
left: number,
right: number,
}>,
}>;
const statusBarHeightOffset = Platform.select({
android: StatusBar.currentHeight || 0,
default: 0,
});
export default function NewAppScreen({
templateFileName = 'App.tsx',
safeAreaInsets = {top: 0, bottom: 0, left: 0, right: 0},
}: NewAppScreenProps): React.Node {
const {colors} = useTheme();
const isDarkMode = useColorScheme() === 'dark';
const isLargeScreen = useWindowDimensions().width > 600;
return (
<View
style={{
backgroundColor: colors.background,
paddingTop: safeAreaInsets.top,
paddingLeft: safeAreaInsets.left,
paddingRight: safeAreaInsets.right,
}}>
<ScrollView style={{paddingBottom: safeAreaInsets.bottom}}>
<View style={styles.container}>
<SafeAreaView style={{backgroundColor: colors.background}}>
<ScrollView>
<View style={[styles.container, {paddingTop: statusBarHeightOffset}]}>
<View style={styles.header}>
<Image
style={styles.logo}
@@ -104,7 +99,7 @@ export default function NewAppScreen({
</View>
</View>
</ScrollView>
</View>
</SafeAreaView>
);
}
-8
View File
@@ -11,14 +11,6 @@ import type * as React from 'react';
export type NewAppScreenProps = Readonly<{
templateFileName?: string | undefined;
safeAreaInsets?:
| Readonly<{
top: number;
bottom: number;
left: number;
right: number;
}>
| undefined;
}>;
export function NewAppScreen(props: NewAppScreenProps): React.ReactNode;
@@ -16,7 +16,6 @@ describe('console.timeStamp()', () => {
});
it("doesn't throw when label is not specified", () => {
// $FlowExpectedError[incompatible-call] not passing label intentionally
expect(() => console.timeStamp()).not.toThrow();
});
@@ -26,6 +25,7 @@ describe('console.timeStamp()', () => {
it("doesn't throw when additional arguments are specified", () => {
expect(() =>
// $FlowExpectedError[extra-arg]
console.timeStamp('label', 100, 500, 'Track', 'Group', 'error'),
).not.toThrow();
});
@@ -34,7 +34,7 @@ describe('console.timeStamp()', () => {
// $FlowExpectedError[incompatible-call]
expect(() => console.timeStamp({})).not.toThrow();
expect(() =>
// $FlowExpectedError[incompatible-call]
// $FlowExpectedError[extra-arg]
console.timeStamp('label', true, null, {}, [], () => {}),
).not.toThrow();
});
+6 -10
View File
@@ -569,7 +569,10 @@ function consoleAssertPolyfill(expression, label) {
}
}
function stub() {}
// https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp_static.
// Non-standard API for recording markers on a timeline of the Performance instrumentation.
// The actual logging is not provided by definition.
function consoleTimeStampPolyfill() {}
if (global.nativeLoggingHook) {
const originalConsole = global.console;
@@ -582,11 +585,7 @@ if (global.nativeLoggingHook) {
}
global.console = {
time: stub,
timeEnd: stub,
timeStamp: stub,
count: stub,
countReset: stub,
timeStamp: consoleTimeStampPolyfill,
...(originalConsole ?? {}),
error: getNativeLogFunction(LOG_LEVELS.error),
info: getNativeLogFunction(LOG_LEVELS.info),
@@ -677,6 +676,7 @@ if (global.nativeLoggingHook) {
});
}
} else if (!global.console) {
function stub() {}
const log = global.print || stub;
global.console = {
@@ -692,8 +692,6 @@ if (global.nativeLoggingHook) {
}
},
clear: stub,
count: stub,
countReset: stub,
dir: stub,
dirxml: stub,
group: stub,
@@ -702,8 +700,6 @@ if (global.nativeLoggingHook) {
profile: stub,
profileEnd: stub,
table: stub,
time: stub,
timeEnd: stub,
timeStamp: stub,
};
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"console.js",
@@ -13,7 +13,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "src/index.js",
"files": [
+14 -22
View File
@@ -38,6 +38,19 @@ function isFirstParty(fileName) {
// use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
const loose = true;
const defaultPlugins = [
[require('babel-plugin-syntax-hermes-parser'), {parseLangTypes: 'flow'}],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-transform-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[require('@babel/plugin-transform-private-property-in-object'), {loose}],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
];
// For Static Hermes testing (experimental), the hermes-canary transformProfile
// is used to enable regenerator (and some related lowering passes) because SH
// requires more Babel lowering than Hermes temporarily.
@@ -221,28 +234,7 @@ const getPreset = (src, options) => {
plugins: [require('@babel/plugin-transform-flow-strip-types')],
},
{
plugins: [
[
require('babel-plugin-syntax-hermes-parser'),
{
parseLangTypes: 'flow',
reactRuntimeTarget: '19',
...options.hermesParserOptions,
},
],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-transform-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[
require('@babel/plugin-transform-private-property-in-object'),
{loose},
],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
],
plugins: defaultPlugins,
},
{
test: isTypeScriptSource,
@@ -14,7 +14,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "src/index.js",
"files": [
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"scripts": {
"build": "yarn clean && node scripts/build.js --verbose",
@@ -51,13 +51,12 @@ if (!schemaQuery.startsWith('@')) {
const schemaQueryOutputFile = schemaQuery.replace(/^@/, '');
const schemaQueryOutput = fs.readFileSync(schemaQueryOutputFile, 'utf8');
const schemaFiles = schemaQueryOutput.split(' ');
const modules: {
[hasteModuleName: string]: NativeModuleSchema | ComponentSchema,
} = {};
const specNameToFile: {[hasteModuleName: string]: string} = {};
const schemaFiles =
schemaQueryOutput.length > 0 ? schemaQueryOutput.split(' ') : [];
for (const file of schemaFiles) {
const schema: SchemaType = JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -65,15 +65,15 @@ const TestTemplate = ({
propValue: string,
}) => `
TEST(${componentName}_${testName}, etc) {
RawPropsParser propParser{};
auto propParser = RawPropsParser();
propParser.prepare<${componentName}>();
${componentName} sourceProps{};
RawProps rawProps(folly::dynamic::object("${propName}", ${propValue}));
auto const &sourceProps = ${componentName}();
auto const &rawProps = RawProps(folly::dynamic::object("${propName}", ${propValue}));
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
rawProps.parse(propParser);
rawProps.parse(propParser, parserContext);
${componentName}(parserContext, sourceProps, rawProps);
}
`;
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"exports": {
".": "./src/index.js",
@@ -12,9 +12,7 @@ import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
@Suppress("DEPRECATION")
public class OSSLibraryExamplePackage : ReactPackage {
@Deprecated("Migrate to [BaseReactPackage] and implement [getModule] instead.")
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
listOf(NativeSampleModule(reactContext))
@@ -537,160 +537,3 @@ test('animate layout props', () => {
<rn-view height="100.000000" />,
);
});
test('AnimatedValue.interpolate', () => {
let _valueX;
let _interpolatedValueX;
const viewRef = createRef<HostInstance>();
function MyApp({outputRangeX}: $ReadOnly<{outputRangeX: number}>) {
const valueX = useAnimatedValue(0.5, {useNativeDriver: true});
_valueX = valueX;
const offset = outputRangeX - 1;
const interpolatedValueX = valueX.interpolate({
inputRange: [0, 1],
outputRange: [offset, 100],
});
_interpolatedValueX = interpolatedValueX;
return (
<Animated.View
ref={viewRef}
style={[
{
width: 100,
height: 100,
},
{transform: [{translateX: valueX}, {translateY: interpolatedValueX}]},
]}
/>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<MyApp outputRangeX={1} />);
});
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
expect(_valueX?.__getValue()).toBe(0.5);
expect(_interpolatedValueX?.__getValue()).toBe(50);
expect(
JSON.stringify(
Fantom.unstable_getDirectManipulationProps(viewElement).transform,
),
).toBe('[{"translateX":0.5},{"translateY":50}]');
expect(viewElement.getBoundingClientRect().x).toBe(0.5);
expect(viewElement.getBoundingClientRect().y).toBe(50);
Fantom.runTask(() => {
root.render(<MyApp outputRangeX={51} />);
});
expect(_valueX?.__getValue()).toBe(0.5);
expect(_interpolatedValueX?.__getValue()).toBe(75);
expect(
JSON.stringify(
Fantom.unstable_getDirectManipulationProps(viewElement).transform,
),
).toBe('[{"translateX":0.5},{"translateY":75}]');
expect(viewElement.getBoundingClientRect().x).toBe(0.5);
expect(viewElement.getBoundingClientRect().y).toBe(75);
});
test('Animated.sequence', () => {
let _translateY;
let _isSequenceFinished = false;
const elementRef = createRef<HostInstance>();
function MyApp() {
const translateY = useAnimatedValue(0, {useNativeDriver: true});
_translateY = translateY;
return (
<View>
<Animated.View
ref={elementRef}
style={[
{
position: 'absolute',
width: 100,
height: 100,
},
{
transform: [
{
translateY: translateY.interpolate({
inputRange: [0, 1],
outputRange: [0, -16],
}),
},
],
},
]}
/>
</View>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<MyApp />);
});
const element = ensureInstance(elementRef.current, ReactNativeElement);
expect(element.getBoundingClientRect().y).toBe(0);
Fantom.runTask(() => {
Animated.sequence([
Animated.timing(_translateY, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
Animated.timing(_translateY, {
toValue: 0,
duration: 0,
useNativeDriver: true,
}),
]).start(({finished}) => {
if (finished) {
_isSequenceFinished = true;
}
});
});
Fantom.unstable_produceFramesForDuration(500);
expect(
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(element).transform[0].translateY,
).toBeCloseTo(-16, 0.001);
if (!ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
Fantom.runWorkLoop(); // React update to sync end state of 1st timing animation in sequence
}
expect(_isSequenceFinished).toBe(false);
expect(element.getBoundingClientRect().y).toBe(-16);
Fantom.runWorkLoop(); // React render
expect(
// $FlowFixMe[incompatible-use]
Fantom.unstable_getDirectManipulationProps(element).transform[0].translateY,
).toBeCloseTo(0, 0.001);
if (!ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
Fantom.runWorkLoop(); // React update to sync end state of 2nd timing animation in sequence
}
expect(element.getBoundingClientRect().y).toBe(0);
if (ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
expect(_isSequenceFinished).toBe(false);
Fantom.runWorkLoop();
}
expect(_isSequenceFinished).toBe(true);
});
@@ -31,8 +31,12 @@ function mockQueueMicrotask() {
}
describe('Animated', () => {
let ReactNativeFeatureFlags;
beforeEach(() => {
jest.resetModules();
ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
});
mockQueueMicrotask();
@@ -109,7 +113,50 @@ describe('Animated', () => {
expect(callback.mock.calls.length).toBe(1);
});
it('does not detach on updates', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => false,
});
const opacity = new Animated.Value(0);
jest.spyOn(opacity, '__detach');
const root = await create(<Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
await update(root, <Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
await unmount(root);
expect(opacity.__detach).toBeCalled();
});
it('stops animation when detached', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => false,
});
const opacity = new Animated.Value(0);
const callback = jest.fn();
const root = await create(<Animated.View style={{opacity}} />);
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: false,
}).start(callback);
await unmount(root);
expect(callback).toBeCalledWith({finished: false});
});
it('detaches only on unmount (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const opacity = new Animated.Value(0);
jest.spyOn(opacity, '__detach');
@@ -128,6 +175,10 @@ describe('Animated', () => {
});
it('restores default values only on update (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const __restoreDefaultValues = jest.spyOn(
AnimatedProps.prototype,
'__restoreDefaultValues',
@@ -162,6 +213,10 @@ describe('Animated', () => {
});
it('stops animation when detached (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const opacity = new Animated.Value(0);
const callback = jest.fn();
@@ -84,5 +84,4 @@ Pod::Spec.new do |s|
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end
@@ -55,5 +55,4 @@ Pod::Spec.new do |s|
end
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end
@@ -0,0 +1,140 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter';
import NativeRedBox from '../NativeModules/specs/NativeRedBox';
import {type EventSubscription} from '../vendor/emitter/EventEmitter';
import NativeBugReporting from './NativeBugReporting';
type ExtraData = {[key: string]: string, ...};
type SourceCallback = () => string;
type DebugData = {
extras: ExtraData,
files: ExtraData,
...
};
function defaultExtras() {
BugReporting.addFileSource('react_hierarchy.txt', () =>
require('./dumpReactTree').default(),
);
}
/**
* A simple class for collecting bug report data. Components can add sources that will be queried when a bug report
* is created via `collectExtraData`. For example, a list component might add a source that provides the list of rows
* that are currently visible on screen. Components should also remember to call `remove()` on the object that is
* returned by `addSource` when they are unmounted.
*/
class BugReporting {
static _extraSources: Map<string, SourceCallback> = new Map();
static _fileSources: Map<string, SourceCallback> = new Map();
static _subscription: ?EventSubscription = null;
static _redboxSubscription: ?EventSubscription = null;
static _maybeInit() {
if (!BugReporting._subscription) {
BugReporting._subscription = RCTDeviceEventEmitter.addListener(
'collectBugExtraData',
// $FlowFixMe[method-unbinding]
BugReporting.collectExtraData,
null,
);
defaultExtras();
}
if (!BugReporting._redboxSubscription) {
BugReporting._redboxSubscription = RCTDeviceEventEmitter.addListener(
'collectRedBoxExtraData',
// $FlowFixMe[method-unbinding]
BugReporting.collectExtraData,
null,
);
}
}
/**
* Maps a string key to a simple callback that should return a string payload to be attached
* to a bug report. Source callbacks are called when `collectExtraData` is called.
*
* Returns an object to remove the source when the component unmounts.
*
* Conflicts trample with a warning.
*/
static addSource(
key: string,
callback: SourceCallback,
): {remove: () => void, ...} {
return this._addSource(key, callback, BugReporting._extraSources);
}
/**
* Maps a string key to a simple callback that should return a string payload to be attached
* to a bug report. Source callbacks are called when `collectExtraData` is called.
*
* Returns an object to remove the source when the component unmounts.
*
* Conflicts trample with a warning.
*/
static addFileSource(
key: string,
callback: SourceCallback,
): {remove: () => void, ...} {
return this._addSource(key, callback, BugReporting._fileSources);
}
static _addSource(
key: string,
callback: SourceCallback,
source: Map<string, SourceCallback>,
): {remove: () => void, ...} {
BugReporting._maybeInit();
if (source.has(key)) {
console.warn(
`BugReporting.add* called multiple times for same key '${key}'`,
);
}
source.set(key, callback);
return {
remove: () => {
source.delete(key);
},
};
}
/**
* This can be called from a native bug reporting flow, or from JS code.
*
* If available, this will call `NativeModules.BugReporting.setExtraData(extraData)`
* after collecting `extraData`.
*/
static collectExtraData(): DebugData {
const extraData: ExtraData = {};
for (const [key, callback] of BugReporting._extraSources) {
extraData[key] = callback();
}
const fileData: ExtraData = {};
for (const [key, callback] of BugReporting._fileSources) {
fileData[key] = callback();
}
if (NativeBugReporting != null && NativeBugReporting.setExtraData != null) {
NativeBugReporting.setExtraData(extraData, fileData);
}
if (NativeRedBox != null && NativeRedBox.setExtraData != null) {
NativeRedBox.setExtraData(extraData, 'From BugReporting.js');
}
return {extras: extraData, files: fileData};
}
}
export default BugReporting;
@@ -0,0 +1,14 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
export * from '../../src/private/specs_DEPRECATED/modules/NativeBugReporting';
import NativeBugReporting from '../../src/private/specs_DEPRECATED/modules/NativeBugReporting';
export default NativeBugReporting;
@@ -0,0 +1,151 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
/*
const getReactData = require('getReactData').default;
const INDENTATION_SIZE = 2;
const MAX_DEPTH = 2;
const MAX_STRING_LENGTH = 50;
*/
/**
* Dump all React Native root views and their content. This function tries
* it best to get the content but ultimately relies on implementation details
* of React and will fail in future versions.
*/
function dumpReactTree(): string {
try {
return getReactTree();
} catch (e) {
return 'Failed to dump react tree: ' + e;
}
}
function getReactTree() {
// TODO(sema): Reenable tree dumps using the Fiber tree structure. #15945684
return (
'React tree dumps have been temporarily disabled while React is ' +
'upgraded to Fiber.'
);
/*
let output = '';
const rootIds = Object.getOwnPropertyNames(ReactNativeMount._instancesByContainerID);
for (const rootId of rootIds) {
const instance = ReactNativeMount._instancesByContainerID[rootId];
output += `============ Root ID: ${rootId} ============\n`;
output += dumpNode(instance, 0);
output += `============ End root ID: ${rootId} ============\n`;
}
return output;
*/
}
/*
function dumpNode(node: Object, indentation: number) {
const data = getReactData(node);
if (data.nodeType === 'Text') {
return indent(indentation) + data.text + '\n';
} else if (data.nodeType === 'Empty') {
return '';
}
let output = indent(indentation) + `<${data.name}`;
if (data.nodeType === 'Composite') {
for (const propName of Object.getOwnPropertyNames(data.props || {})) {
if (isNormalProp(propName)) {
try {
const value = convertValue(data.props[propName]);
if (value) {
output += ` ${propName}=${value}`;
}
} catch (e) {
const message = `[Failed to get property: ${e}]`;
output += ` ${propName}=${message}`;
}
}
}
}
let childOutput = '';
for (const child of data.children || []) {
childOutput += dumpNode(child, indentation + 1);
}
if (childOutput) {
output += '>\n' + childOutput + indent(indentation) + `</${data.name}>\n`;
} else {
output += ' />\n';
}
return output;
}
function isNormalProp(name: string): boolean {
switch (name) {
case 'children':
case 'key':
case 'ref':
return false;
default:
return true;
}
}
function convertObject(object: Object, depth: number) {
if (depth >= MAX_DEPTH) {
return '[...omitted]';
}
let output = '{';
let first = true;
for (const key of Object.getOwnPropertyNames(object)) {
if (!first) {
output += ', ';
}
output += `${key}: ${convertValue(object[key], depth + 1)}`;
first = false;
}
return output + '}';
}
function convertValue(value, depth = 0): ?string {
if (!value) {
return null;
}
switch (typeof value) {
case 'string':
return JSON.stringify(possiblyEllipsis(value).replace('\n', '\\n'));
case 'boolean':
case 'number':
return JSON.stringify(value);
case 'function':
return '[function]';
case 'object':
return convertObject(value, depth);
default:
return null;
}
}
function possiblyEllipsis(value: string) {
if (value.length > MAX_STRING_LENGTH) {
return value.slice(0, MAX_STRING_LENGTH) + '...';
} else {
return value;
}
}
function indent(size: number) {
return ' '.repeat(size * INDENTATION_SIZE);
}
*/
export default dumpReactTree;
@@ -0,0 +1,187 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
/**
* Convert a react internal instance to a sanitized data object.
*
* This is shamelessly stolen from react-devtools:
* https://github.com/facebook/react-devtools/blob/HEAD/backend/getData.js
*/
function getData(element: Object): Object {
let children = null;
let props = null;
let state = null;
let context = null;
let updater = null;
let name = null;
let type = null;
let text = null;
let publicInstance = null;
let nodeType = 'Native';
// If the parent is a native node without rendered children, but with
// multiple string children, then the `element` that gets passed in here is
// a plain value -- a string or number.
if (typeof element !== 'object') {
nodeType = 'Text';
text = element + '';
} else if (
element._currentElement === null ||
element._currentElement === false
) {
nodeType = 'Empty';
} else if (element._renderedComponent) {
nodeType = 'NativeWrapper';
children = [element._renderedComponent];
props = element._instance.props;
state = element._instance.state;
context = element._instance.context;
if (context && Object.keys(context).length === 0) {
context = null;
}
} else if (element._renderedChildren) {
children = childrenList(element._renderedChildren);
} else if (element._currentElement && element._currentElement.props) {
// This is a native node without rendered children -- meaning the children
// prop is just a string or (in the case of the <option>) a list of
// strings & numbers.
children = element._currentElement.props.children;
}
if (!props && element._currentElement && element._currentElement.props) {
props = element._currentElement.props;
}
// != used deliberately here to catch undefined and null
if (element._currentElement != null) {
type = element._currentElement.type;
if (typeof type === 'string') {
name = type;
} else if (element.getName) {
nodeType = 'Composite';
name = element.getName();
// 0.14 top-level wrapper
// TODO(jared): The backend should just act as if these don't exist.
if (
element._renderedComponent &&
element._currentElement.props ===
element._renderedComponent._currentElement
) {
nodeType = 'Wrapper';
}
if (name === null) {
name = 'No display name';
}
} else if (element._stringText) {
nodeType = 'Text';
text = element._stringText;
} else {
name = type.displayName || type.name || 'Unknown';
}
}
if (element._instance) {
const inst = element._instance;
updater = {
setState: inst.setState && inst.setState.bind(inst),
forceUpdate: inst.forceUpdate && inst.forceUpdate.bind(inst),
setInProps: inst.forceUpdate && setInProps.bind(null, element),
setInState: inst.forceUpdate && setInState.bind(null, inst),
setInContext: inst.forceUpdate && setInContext.bind(null, inst),
};
publicInstance = inst;
// TODO: React ART currently falls in this bucket, but this doesn't
// actually make sense and we should clean this up after stabilizing our
// API for backends
if (inst._renderedChildren) {
children = childrenList(inst._renderedChildren);
}
}
return {
nodeType,
type,
name,
props,
state,
context,
children,
text,
updater,
publicInstance,
};
}
function setInProps(
internalInst: any,
path: Array<string | number>,
value: any,
) {
const element = internalInst._currentElement;
internalInst._currentElement = {
...element,
props: copyWithSet(element.props, path, value),
};
internalInst._instance.forceUpdate();
}
function setInState(inst: any, path: Array<string | number>, value: any) {
setIn(inst.state, path, value);
inst.forceUpdate();
}
function setInContext(inst: any, path: Array<string | number>, value: any) {
setIn(inst.context, path, value);
inst.forceUpdate();
}
function setIn(obj: Object, path: Array<string | number>, value: any) {
const last = path.pop();
const parent = path.reduce((obj_, attr) => (obj_ ? obj_[attr] : null), obj);
if (parent) {
parent[last] = value;
}
}
function childrenList(children: any) {
const res = [];
for (const name in children) {
res.push(children[name]);
}
return res;
}
function copyWithSetImpl(
obj: any | Array<any>,
path: Array<string | number>,
idx: number,
value: any,
): any {
if (idx >= path.length) {
return value;
}
const key = path[idx];
const updated = Array.isArray(obj) ? obj.slice() : {...obj};
// $FlowFixMe[incompatible-use] number or string is fine here
updated[key] = copyWithSetImpl(obj[key], path, idx + 1, value);
return updated;
}
function copyWithSet(
obj: Object | Array<any>,
path: Array<string | number>,
value: any,
): Object | Array<any> {
return copyWithSetImpl(obj, path, 0, value);
}
export default getData;
@@ -673,7 +673,7 @@ type ScrollViewBaseProps = $ReadOnly<{
}>;
export type ScrollViewProps = $ReadOnly<{
...ViewProps,
...Omit<ViewProps, 'experimental_accessibilityOrder'>,
...ScrollViewPropsIOS,
...ScrollViewPropsAndroid,
...ScrollViewBaseProps,
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @fantom_flags enableFixForParentTagDuringReparenting:true
* @fantom_flags enableSynchronousStateUpdates:true
* @fantom_flags enableViewCulling:true
* @flow strict-local
* @format
@@ -2555,56 +2556,3 @@ describe('culling inside ScrollView with overflow visible', () => {
);
});
});
describe('horizontal ScrollView in RTL script', () => {
it('renders item 1', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
style={{direction: 'rtl', height: 100, width: 100}}
horizontal={true}>
<View nativeID={'item1'} style={{height: 90, width: 90, margin: 5}} />
<View nativeID={'item2'} style={{height: 90, width: 90, margin: 5}} />
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "AndroidHorizontalScrollContentView", nativeID: (N/A)}',
'Create {type: "View", nativeID: "item1"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "item1"}',
'Insert {type: "AndroidHorizontalScrollContentView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
});
it('takes contentOffset into account', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
style={{direction: 'rtl', height: 100, width: 100}}
horizontal={true}
contentOffset={{x: 100, y: 0}}>
<View nativeID={'item1'} style={{height: 90, width: 90, margin: 5}} />
<View nativeID={'item2'} style={{height: 90, width: 90, margin: 5}} />
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "AndroidHorizontalScrollContentView", nativeID: (N/A)}',
'Create {type: "View", nativeID: "item2"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "item2"}',
'Insert {type: "AndroidHorizontalScrollContentView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
});
});
@@ -1031,7 +1031,7 @@ type TextInputBaseProps = $ReadOnly<{
}>;
export type TextInputProps = $ReadOnly<{
...Omit<ViewProps, 'style'>,
...Omit<ViewProps, 'style' | 'experimental_accessibilityOrder'>,
...TextInputIOSProps,
...TextInputAndroidProps,
...TextInputBaseProps,
@@ -10,14 +10,9 @@
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
import ensureInstance from '../../../../src/private/__tests__/utilities/ensureInstance';
import * as Fantom from '@react-native/fantom';
import * as React from 'react';
import {createRef} from 'react';
import {View} from 'react-native';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
describe('<View>', () => {
describe('width and height style', () => {
@@ -186,40 +181,6 @@ describe('<View>', () => {
<rn-view transform='[{"translateX": 10.000000}]' />,
);
});
[
[undefined, {x: -5, y: 0, width: 20, height: 10}],
['50% 50%', {x: -5, y: 0, width: 20, height: 10}],
['top left', {x: 0, y: 0, width: 20, height: 10}],
['right bottom', {x: -10, y: 0, width: 20, height: 10}],
].forEach(([transformOrigin, expectedBounds]) => {
it(`applies transformOrigin correctly for ${String(transformOrigin)}`, () => {
const root = Fantom.createRoot();
const viewRef = createRef<HostInstance>();
Fantom.runTask(() => {
root.render(
<View
ref={viewRef}
style={{
width: 10,
height: 10,
transform: [{scaleX: 2}],
transformOrigin,
}}
/>,
);
});
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
const viewBounds = viewElement.getBoundingClientRect();
expect(viewBounds.x).toBe(expectedBounds.x);
expect(viewBounds.y).toBe(expectedBounds.y);
expect(viewBounds.width).toBe(expectedBounds.width);
expect(viewBounds.height).toBe(expectedBounds.height);
});
});
});
describe('props', () => {
@@ -242,8 +242,6 @@ function reactConsoleErrorHandler(...args) {
if (__DEV__) {
// If we're not reporting to the console in reportException,
// we need to report it as a console.error here.
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
if (!reportToConsole) {
require('../LogBox/LogBox').default.addConsoleLog('error', ...args);
}
@@ -49,7 +49,6 @@ function _defineCheckVersionTests() {
// $FlowFixMe[cannot-write]
console.error = jest.fn();
// $FlowFixMe[cannot-write]
// $FlowFixMe[unsafe-addition]
global.console = {error: jest.fn(error => (consoleOutput += error))};
spyOnConsoleError = jest.spyOn(global.console, 'error');
});
+1 -2
View File
@@ -8,13 +8,12 @@
* @format
*/
import Performance from '../../src/private/webapis/performance/Performance';
import NativePerformance from '../../src/private/webapis/performance/specs/NativePerformance';
// In case if the native implementation of the Performance API is available, use it,
// otherwise fall back to the legacy/default one, which only defines 'Performance.now()'
if (NativePerformance) {
const Performance =
require('../../src/private/webapis/performance/Performance').default;
// $FlowExpectedError[cannot-write]
global.performance = new Performance();
} else {
@@ -53,5 +53,4 @@ Pod::Spec.new do |s|
add_dependency(s, "React-NativeModulesApple")
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end
@@ -4,32 +4,176 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @flow strict-local
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import type {Task} from './TaskQueue';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import EventEmitter from '../vendor/emitter/EventEmitter';
const BatchedBridge = require('../BatchedBridge/BatchedBridge').default;
const TaskQueue = require('./TaskQueue').default;
const invariant = require('invariant');
export type SimpleTask = {
name: string,
run: () => void,
};
export type PromiseTask = {
name: string,
gen: () => Promise<void>,
};
export type Task = SimpleTask | PromiseTask | (() => void);
export type {Task, SimpleTask, PromiseTask} from './TaskQueue';
export type Handle = number;
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
const _emitter = new EventEmitter<{
interactionComplete: [],
interactionStart: [],
}>();
const DEBUG_DELAY: 0 = 0;
const DEBUG: false = false;
const InteractionManagerImpl = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
const tasks: Array<Task> = [];
const promise = new Promise((resolve: () => void) => {
_scheduleUpdate();
if (task) {
tasks.push(task);
}
tasks.push({
run: resolve,
name: 'resolve ' + ((task && task.name) || '?'),
});
_taskQueue.enqueueTasks(tasks);
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel: function () {
_taskQueue.cancelTasks(tasks);
},
};
},
/**
* Notify manager that an interaction has started.
*/
createInteractionHandle(): Handle {
DEBUG && console.log('InteractionManager: create interaction handle');
_scheduleUpdate();
const handle = ++_inc;
_addInteractionSet.add(handle);
return handle;
},
/**
* Notify manager that an interaction has completed.
*/
clearInteractionHandle(handle: Handle) {
DEBUG && console.log('InteractionManager: clear interaction handle');
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
_scheduleUpdate();
_addInteractionSet.delete(handle);
_deleteInteractionSet.add(handle);
},
// $FlowFixMe[unclear-type] unclear type of _emitter
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
addListener: _emitter.addListener.bind(_emitter) as (
eventType: string,
// $FlowFixMe[unclear-type] unclear type of arguments
listener: (...args: any) => mixed,
context: mixed,
) => EventSubscription,
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*/
setDeadline(deadline: number) {
_deadline = deadline;
},
};
const _interactionSet = new Set<number | Handle>();
const _addInteractionSet = new Set<number | Handle>();
const _deleteInteractionSet = new Set<Handle>();
const _taskQueue = new TaskQueue({onMoreTasks: _scheduleUpdate});
let _nextUpdateHandle: $FlowFixMe | TimeoutID = 0;
let _inc = 0;
let _deadline = -1;
/**
* Schedule an asynchronous update to the interaction state.
*/
function _scheduleUpdate() {
if (!_nextUpdateHandle) {
if (_deadline > 0) {
_nextUpdateHandle = setTimeout(_processUpdate, 0 + DEBUG_DELAY);
} else {
_nextUpdateHandle = setImmediate(_processUpdate);
}
}
}
/**
* Notify listeners, process queue, etc
*/
function _processUpdate() {
_nextUpdateHandle = 0;
const interactionCount = _interactionSet.size;
_addInteractionSet.forEach(handle => _interactionSet.add(handle));
_deleteInteractionSet.forEach(handle => _interactionSet.delete(handle));
const nextInteractionCount = _interactionSet.size;
if (interactionCount !== 0 && nextInteractionCount === 0) {
// transition from 1+ --> 0 interactions
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
_emitter.emit(InteractionManager.Events.interactionComplete);
} else if (interactionCount === 0 && nextInteractionCount !== 0) {
// transition from 0 --> 1+ interactions
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
_emitter.emit(InteractionManager.Events.interactionStart);
}
// process the queue regardless of a transition
if (nextInteractionCount === 0) {
while (_taskQueue.hasTasksToProcess()) {
_taskQueue.processNext();
if (
_deadline > 0 &&
BatchedBridge.getEventLoopRunningTime() >= _deadline
) {
// Hit deadline before processing all tasks, so process more later.
_scheduleUpdate();
break;
}
}
}
_addInteractionSet.clear();
_deleteInteractionSet.clear();
}
/**
@@ -83,106 +227,11 @@ function reject(error: Error): void {
*
* @deprecated
*/
const InteractionManagerStub = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
const InteractionManager = (
ReactNativeFeatureFlags.disableInteractionManager()
? // $FlowFixMe[incompatible-variance]
require('./InteractionManagerStub').default
: InteractionManagerImpl
) as typeof InteractionManagerImpl;
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*
* @deprecated
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
task.gen().then(resolve, reject);
} else if (typeof task.run === 'function') {
try {
task.run();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
}
} else if (typeof task === 'function') {
try {
task();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError('Invalid task of type: ' + typeof task));
}
});
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel() {
clearImmediate(immediateID);
},
};
},
/**
* Notify manager that an interaction has started.
*
* @deprecated
*/
createInteractionHandle(): Handle {
return -1;
},
/**
* Notify manager that an interaction has completed.
*
* @deprecated
*/
clearInteractionHandle(handle: Handle) {
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
},
/**
* @deprecated
*/
addListener(
eventType: string,
// $FlowIgnore[unclear-type]
listener: (...args: any) => mixed,
context: mixed,
): EventSubscription {
return {
remove() {},
};
},
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*
* @deprecated
*/
setDeadline(deadline: number) {
// Do nothing.
},
};
export default InteractionManagerStub;
export default InteractionManager;
@@ -0,0 +1,184 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
const invariant = require('invariant');
export type Handle = number;
type Task =
| {
name: string,
run: () => void,
}
| {
name: string,
gen: () => Promise<void>,
}
| (() => void);
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
}
/**
* InteractionManager allows long-running work to be scheduled after any
* interactions/animations have completed. In particular, this allows JavaScript
* animations to run smoothly.
*
* Applications can schedule tasks to run after interactions with the following:
*
* ```
* InteractionManager.runAfterInteractions(() => {
* // ...long-running synchronous task...
* });
* ```
*
* Compare this to other scheduling alternatives:
*
* - requestAnimationFrame(): for code that animates a view over time.
* - setImmediate/setTimeout(): run code later, note this may delay animations.
* - runAfterInteractions(): run code later, without delaying active animations.
*
* The touch handling system considers one or more active touches to be an
* 'interaction' and will delay `runAfterInteractions()` callbacks until all
* touches have ended or been cancelled.
*
* InteractionManager also allows applications to register animations by
* creating an interaction 'handle' on animation start, and clearing it upon
* completion:
*
* ```
* var handle = InteractionManager.createInteractionHandle();
* // run animation... (`runAfterInteractions` tasks are queued)
* // later, on animation completion:
* InteractionManager.clearInteractionHandle(handle);
* // queued tasks run if all handles were cleared
* ```
*
* `runAfterInteractions` takes either a plain callback function, or a
* `PromiseTask` object with a `gen` method that returns a `Promise`. If a
* `PromiseTask` is supplied, then it is fully resolved (including asynchronous
* dependencies that also schedule more tasks via `runAfterInteractions`) before
* starting on the next task that might have been queued up synchronously
* earlier.
*
* By default, queued tasks are executed together in a loop in one
* `setImmediate` batch. If `setDeadline` is called with a positive number, then
* tasks will only be executed until the deadline (in terms of js event loop run
* time) approaches, at which point execution will yield via setTimeout,
* allowing events such as touches to start interactions and block queued tasks
* from executing, making apps more responsive.
*
* @deprecated
*/
const InteractionManagerStub = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*
* @deprecated
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
task.gen().then(resolve, reject);
} else if (typeof task.run === 'function') {
try {
task.run();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
}
} else if (typeof task === 'function') {
try {
task();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError('Invalid task of type: ' + typeof task));
}
});
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel() {
clearImmediate(immediateID);
},
};
},
/**
* Notify manager that an interaction has started.
*
* @deprecated
*/
createInteractionHandle(): Handle {
return -1;
},
/**
* Notify manager that an interaction has completed.
*
* @deprecated
*/
clearInteractionHandle(handle: Handle) {
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
},
/**
* @deprecated
*/
addListener(): EventSubscription {
return {
remove() {},
};
},
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*
* @deprecated
*/
setDeadline(deadline: number) {
// Do nothing.
},
};
export default InteractionManagerStub;
+183
View File
@@ -0,0 +1,183 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
const invariant = require('invariant');
export type SimpleTask = {
name: string,
run: () => void,
};
export type PromiseTask = {
name: string,
gen: () => Promise<void>,
};
export type Task = SimpleTask | PromiseTask | (() => void);
const DEBUG: false = false;
/**
* TaskQueue - A system for queueing and executing a mix of simple callbacks and
* trees of dependent tasks based on Promises. No tasks are executed unless
* `processNext` is called.
*
* `enqueue` takes a Task object with either a simple `run` callback, or a
* `gen` function that returns a `Promise` and puts it in the queue. If a gen
* function is supplied, then the promise it returns will block execution of
* tasks already in the queue until it resolves. This can be used to make sure
* the first task is fully resolved (including asynchronous dependencies that
* also schedule more tasks via `enqueue`) before starting on the next task.
* The `onMoreTasks` constructor argument is used to inform the owner that an
* async task has resolved and that the queue should be processed again.
*
* Note: Tasks are only actually executed with explicit calls to `processNext`.
*/
class TaskQueue {
/**
* TaskQueue instances are self contained and independent, so multiple tasks
* of varying semantics and priority can operate together.
*
* `onMoreTasks` is invoked when `PromiseTask`s resolve if there are more
* tasks to process.
*/
constructor({onMoreTasks}: {onMoreTasks: () => void, ...}) {
this._onMoreTasks = onMoreTasks;
this._queueStack = [{tasks: [], popable: false}];
}
/**
* Add a task to the queue. It is recommended to name your tasks for easier
* async debugging. Tasks will not be executed until `processNext` is called
* explicitly.
*/
enqueue(task: Task): void {
this._getCurrentQueue().push(task);
}
enqueueTasks(tasks: Array<Task>): void {
tasks.forEach(task => this.enqueue(task));
}
cancelTasks(tasksToCancel: Array<Task>): void {
// search through all tasks and remove them.
this._queueStack = this._queueStack
.map(queue => ({
...queue,
tasks: queue.tasks.filter(task => tasksToCancel.indexOf(task) === -1),
}))
.filter((queue, idx) => queue.tasks.length > 0 || idx === 0);
}
/**
* Check to see if `processNext` should be called.
*
* @returns {boolean} Returns true if there are tasks that are ready to be
* processed with `processNext`, or returns false if there are no more tasks
* to be processed right now, although there may be tasks in the queue that
* are blocked by earlier `PromiseTask`s that haven't resolved yet.
* `onMoreTasks` will be called after each `PromiseTask` resolves if there are
* tasks ready to run at that point.
*/
hasTasksToProcess(): boolean {
return this._getCurrentQueue().length > 0;
}
/**
* Executes the next task in the queue.
*/
processNext(): void {
const queue = this._getCurrentQueue();
if (queue.length) {
const task = queue.shift();
try {
if (typeof task === 'object' && task.gen) {
DEBUG && console.log('TaskQueue: genPromise for task ' + task.name);
this._genPromise(task);
} else if (typeof task === 'object' && task.run) {
DEBUG && console.log('TaskQueue: run task ' + task.name);
task.run();
} else {
invariant(
typeof task === 'function',
'Expected Function, SimpleTask, or PromiseTask, but got:\n' +
JSON.stringify(task, null, 2),
);
DEBUG && console.log('TaskQueue: run anonymous task');
task();
}
} catch (e) {
e.message =
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
'TaskQueue: Error with task ' + (task.name || '') + ': ' + e.message;
throw e;
}
}
}
_queueStack: Array<{
tasks: Array<Task>,
popable: boolean,
...
}>;
_onMoreTasks: () => void;
_getCurrentQueue(): Array<Task> {
const stackIdx = this._queueStack.length - 1;
const queue = this._queueStack[stackIdx];
if (
queue.popable &&
queue.tasks.length === 0 &&
this._queueStack.length > 1
) {
this._queueStack.pop();
DEBUG &&
console.log('TaskQueue: popped queue: ', {
stackIdx,
queueStackSize: this._queueStack.length,
});
return this._getCurrentQueue();
} else {
return queue.tasks;
}
}
_genPromise(task: PromiseTask) {
// Each async task pushes it's own queue onto the queue stack. This
// effectively defers execution of previously queued tasks until the promise
// resolves, at which point we allow the new queue to be popped, which
// happens once it is fully processed.
this._queueStack.push({tasks: [], popable: false});
const stackIdx = this._queueStack.length - 1;
const stackItem = this._queueStack[stackIdx];
DEBUG && console.log('TaskQueue: push new queue: ', {stackIdx});
DEBUG && console.log('TaskQueue: exec gen task ' + task.name);
task
.gen()
.then(() => {
DEBUG &&
console.log('TaskQueue: onThen for gen task ' + task.name, {
stackIdx,
queueStackSize: this._queueStack.length,
});
stackItem.popable = true;
this.hasTasksToProcess() && this._onMoreTasks();
})
.catch(ex => {
setTimeout(() => {
ex.message = `TaskQueue: Error resolving Promise in task ${task.name}: ${ex.message}`;
throw ex;
}, 0);
});
}
}
export default TaskQueue;
@@ -0,0 +1,342 @@
/**
* 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.
*
* @noflow
* @format
*/
'use strict';
import type {ReactNativeFeatureFlagsJsOnlyOverrides} from '../../../src/private/featureflags/ReactNativeFeatureFlags';
function importModules(overrides: ReactNativeFeatureFlagsJsOnlyOverrides) {
const ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
// Make sure to setup overrides before importing any modules.
ReactNativeFeatureFlags.override(overrides);
const BatchedBridge = require('../../BatchedBridge/BatchedBridge').default;
const InteractionManager = require('../InteractionManager').default;
jest.mock('../../vendor/core/ErrorUtils');
jest.mock('../../BatchedBridge/BatchedBridge');
return {
BatchedBridge,
InteractionManager,
};
}
const isWindows = process.platform === 'win32';
const itif = (condition: boolean) => (condition ? it : it.skip);
function expectToBeCalledOnce(
fn: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
): void {
expect(fn.mock.calls.length).toBe(1);
}
describe('InteractionManager', () => {
let InteractionManager;
let interactionStart;
let interactionComplete;
beforeEach(() => {
jest.resetModules();
({InteractionManager} = importModules({
disableInteractionManager: () => false,
}));
interactionStart = jest.fn();
interactionComplete = jest.fn();
InteractionManager.addListener(
InteractionManager.Events.interactionStart,
interactionStart,
);
InteractionManager.addListener(
InteractionManager.Events.interactionComplete,
interactionComplete,
);
});
it('throws when clearing an undefined handle', () => {
// $FlowExpectedError[incompatible-call]
expect(() => InteractionManager.clearInteractionHandle()).toThrow();
});
it('notifies asynchronously when interaction starts', () => {
InteractionManager.createInteractionHandle();
expect(interactionStart).not.toBeCalled();
jest.runAllTimers();
expect(interactionStart).toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('notifies asynchronously when interaction stops', () => {
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
interactionStart.mockClear();
InteractionManager.clearInteractionHandle(handle);
expect(interactionComplete).not.toBeCalled();
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).toBeCalled();
});
it('does not notify when started & stopped in same event loop', () => {
const handle = InteractionManager.createInteractionHandle();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('does not notify when going from two -> one active interactions', () => {
InteractionManager.createInteractionHandle();
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
interactionStart.mockClear();
interactionComplete.mockClear();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('runs tasks asynchronously when there are interactions', () => {
const task = jest.fn();
InteractionManager.runAfterInteractions(task);
expect(task).not.toBeCalled();
jest.runAllTimers();
expect(task).toBeCalled();
});
it('runs tasks when interactions complete', () => {
const task = jest.fn();
const handle = InteractionManager.createInteractionHandle();
InteractionManager.runAfterInteractions(task);
jest.runAllTimers();
InteractionManager.clearInteractionHandle(handle);
expect(task).not.toBeCalled();
jest.runAllTimers();
expect(task).toBeCalled();
});
it('does not run tasks twice', () => {
const task1 = jest.fn();
const task2 = jest.fn();
InteractionManager.runAfterInteractions(task1);
jest.runAllTimers();
InteractionManager.runAfterInteractions(task2);
jest.runAllTimers();
expectToBeCalledOnce(task1);
});
it('runs tasks added while processing previous tasks', () => {
const task1 = jest.fn(() => {
InteractionManager.runAfterInteractions(task2);
});
const task2 = jest.fn();
InteractionManager.runAfterInteractions(task1);
expect(task2).not.toBeCalled();
jest.runAllTimers();
expect(task1).toBeCalled();
expect(task2).toBeCalled();
});
it('allows tasks to be cancelled', () => {
const task1 = jest.fn();
const task2 = jest.fn();
const promise1 = InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
expect(task1).not.toBeCalled();
expect(task2).not.toBeCalled();
promise1.cancel();
jest.runAllTimers();
expect(task1).not.toBeCalled();
expect(task2).toBeCalled();
});
});
describe('promise tasks', () => {
let BatchedBridge;
let InteractionManager;
let sequenceId;
function createSequenceTask(expectedSequenceId: number) {
return jest.fn(() => {
expect(++sequenceId).toBe(expectedSequenceId);
});
}
beforeEach(() => {
jest.resetModules();
({BatchedBridge, InteractionManager} = importModules({
disableInteractionManager: () => false,
}));
sequenceId = 0;
});
it('should run a basic promise task', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
return new Promise(resolve => resolve());
});
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
jest.runAllTimers();
expectToBeCalledOnce(task1);
});
it('should handle nested promises', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
return new Promise(resolve => {
InteractionManager.runAfterInteractions({
gen: task2,
name: 'gen2',
}).then(resolve);
});
});
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => resolve());
});
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
jest.runAllTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
});
it('should pause promise tasks during interactions then resume', () => {
const task1 = createSequenceTask(1);
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => {
setTimeout(() => {
InteractionManager.runAfterInteractions(task3).then(resolve);
}, 1);
});
});
const task3 = createSequenceTask(3);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
jest.runAllTimers(); // Just to be sure...
expect(task3).not.toBeCalled();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expectToBeCalledOnce(task3);
});
it('should execute tasks in loop within deadline', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(10);
const task1 = createSequenceTask(1);
const task2 = createSequenceTask(2);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
});
it('should execute tasks one at a time if deadline exceeded', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
const task1 = createSequenceTask(1);
const task2 = createSequenceTask(2);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expect(task2).not.toBeCalled();
jest.runOnlyPendingTimers(); // resolve1
jest.runOnlyPendingTimers(); // task2
expectToBeCalledOnce(task2);
});
const bigAsyncTest = resolveTest => {
jest.useRealTimers();
const task1 = createSequenceTask(1);
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => {
InteractionManager.runAfterInteractions(task3);
setTimeout(() => {
InteractionManager.runAfterInteractions({
gen: task4,
name: 'gen4',
}).then(resolve);
}, 1);
});
});
const task3 = createSequenceTask(3);
const task4 = jest.fn(() => {
expect(++sequenceId).toBe(4);
return new Promise(resolve => {
InteractionManager.runAfterInteractions(task5).then(resolve);
});
});
const task5 = createSequenceTask(5);
const task6 = createSequenceTask(6);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
InteractionManager.runAfterInteractions(task6);
setTimeout(() => {
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
expectToBeCalledOnce(task4);
expectToBeCalledOnce(task5);
expectToBeCalledOnce(task6);
resolveTest();
}, 100);
};
itif(!isWindows)(
'resolves async tasks recursively before other queued tasks',
() => {
return new Promise(bigAsyncTest);
},
);
itif(!isWindows)('should also work with a deadline', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
return new Promise(bigAsyncTest);
});
});
@@ -0,0 +1,169 @@
/**
* 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.
*
* @noflow
* @format
*/
'use strict';
const Promise = require('promise');
function expectToBeCalledOnce(fn) {
expect(fn.mock.calls.length).toBe(1);
}
function clearTaskQueue(taskQueue) {
do {
jest.runAllTimers();
taskQueue.processNext();
jest.runAllTimers();
} while (taskQueue.hasTasksToProcess());
}
describe('TaskQueue', () => {
let taskQueue;
let onMoreTasks;
let sequenceId;
function createSequenceTask(expectedSequenceId) {
return jest.fn(() => {
expect(++sequenceId).toBe(expectedSequenceId);
});
}
beforeEach(() => {
jest.resetModules();
onMoreTasks = jest.fn();
const TaskQueue = require('../TaskQueue').default;
taskQueue = new TaskQueue({onMoreTasks});
sequenceId = 0;
});
it('should run a basic task', () => {
const task1 = createSequenceTask(1);
taskQueue.enqueue({run: task1, name: 'run1'});
expect(taskQueue.hasTasksToProcess()).toBe(true);
taskQueue.processNext();
expectToBeCalledOnce(task1);
});
it('should handle blocking promise task', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(1);
resolve();
}, 1);
});
});
const task2 = createSequenceTask(2);
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.enqueue({run: task2, name: 'run2'});
taskQueue.processNext();
expectToBeCalledOnce(task1);
expect(task2).not.toBeCalled();
expect(onMoreTasks).not.toBeCalled();
expect(taskQueue.hasTasksToProcess()).toBe(false);
clearTaskQueue(taskQueue);
expectToBeCalledOnce(onMoreTasks);
expectToBeCalledOnce(task2);
});
it('should handle nested simple tasks', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
taskQueue.enqueue({run: task3, name: 'run3'});
});
const task2 = createSequenceTask(2);
const task3 = createSequenceTask(3);
taskQueue.enqueue({run: task1, name: 'run1'});
taskQueue.enqueue({run: task2, name: 'run2'}); // not blocked by task 1
clearTaskQueue(taskQueue);
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
});
it('should handle nested promises', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(1);
taskQueue.enqueue({gen: task2, name: 'gen2'});
taskQueue.enqueue({run: resolve, name: 'resolve1'});
}, 1);
});
});
const task2 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(2);
taskQueue.enqueue({run: task3, name: 'run3'});
taskQueue.enqueue({run: resolve, name: 'resolve2'});
}, 1);
});
});
const task3 = createSequenceTask(3);
const task4 = createSequenceTask(4);
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.enqueue({run: task4, name: 'run4'}); // blocked by task 1 promise
clearTaskQueue(taskQueue);
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
expectToBeCalledOnce(task4);
});
it('should be able to cancel tasks', () => {
const task1 = jest.fn();
const task2 = createSequenceTask(1);
const task3 = jest.fn();
const task4 = createSequenceTask(2);
taskQueue.enqueue(task1);
taskQueue.enqueue(task2);
taskQueue.enqueue(task3);
taskQueue.enqueue(task4);
taskQueue.cancelTasks([task1, task3]);
clearTaskQueue(taskQueue);
expect(task1).not.toBeCalled();
expect(task3).not.toBeCalled();
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task4);
expect(taskQueue.hasTasksToProcess()).toBe(false);
});
it('should not crash when last task is cancelled', () => {
const task1 = jest.fn();
taskQueue.enqueue(task1);
taskQueue.cancelTasks([task1]);
clearTaskQueue(taskQueue);
expect(task1).not.toBeCalled();
expect(taskQueue.hasTasksToProcess()).toBe(false);
});
it('should not crash when task is cancelled between being started and resolved', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1);
});
});
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.processNext();
taskQueue.cancelTasks([task1]);
jest.runAllTimers();
});
});
@@ -802,7 +802,7 @@ describe('LogBox', () => {
expect(logBox.getNotificationUI()).toEqual({
count: '!',
message:
'Invalid prop `invalid` supplied to `React.Fragment`. React.Fragment can only have `key`, `ref`, and `children` props.',
'Invalid prop `invalid` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.',
});
// Open LogBox.
@@ -817,7 +817,7 @@ describe('LogBox', () => {
// This seems like a bug, should be "Render Error".
title: 'Console Error',
message:
'Invalid prop `invalid` supplied to `React.Fragment`. React.Fragment can only have `key`, `ref`, and `children` props.',
'Invalid prop `invalid` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.',
componentStackFrames: ['<TestComponent />'],
isDismissable: true,
});
@@ -51,7 +51,9 @@ RCT_EXPORT_MODULE();
{
// _surfacePresenter set in setSurfacePresenter:
_nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithBridge:nil surfacePresenter:_surfacePresenter];
[_surfacePresenter addObserver:self];
if (!facebook::react::ReactNativeFeatureFlags::animatedShouldSignalBatch()) {
[_surfacePresenter addObserver:self];
}
[[self.moduleRegistry moduleForName:"EventDispatcher"] addDispatchObserver:self];
}
@@ -60,7 +62,9 @@ RCT_EXPORT_MODULE();
[super invalidate];
[_nodesManager stopAnimationLoop];
[[self.moduleRegistry moduleForName:"EventDispatcher"] removeDispatchObserver:self];
[_surfacePresenter removeObserver:self];
if (!facebook::react::ReactNativeFeatureFlags::animatedShouldSignalBatch()) {
[_surfacePresenter removeObserver:self];
}
}
/*
@@ -76,7 +80,11 @@ RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(startOperationBatch) {}
RCT_EXPORT_METHOD(finishOperationBatch) {}
RCT_EXPORT_METHOD(finishOperationBatch)
{
// This method is only called from JS when animatedShouldSignalBatch is enabled.
[self flushOperationQueues];
}
RCT_EXPORT_METHOD(createAnimatedNode : (double)tag config : (NSDictionary<NSString *, id> *)config)
{
@@ -251,27 +259,39 @@ RCT_EXPORT_METHOD(queueAndExecuteBatchedOperations : (NSArray *)operationsAndArg
- (void)queueFlushedOperationBlock:(AnimatedOperation)operation
{
dispatch_async(RCTGetUIManagerQueue(), ^{
if (facebook::react::ReactNativeFeatureFlags::animatedShouldSignalBatch()) {
[self addOperationBlock:operation];
// In Bridge, flushing of native animations is done from RCTCxxBridge batchDidComplete().
// Since RCTCxxBridge doesn't exist in Bridgeless, and components are not remounted in Fabric for native
// animations, flush here for changes in Animated.Value for Animated.event.
[self flushOperationQueues];
});
} else {
dispatch_async(RCTGetUIManagerQueue(), ^{
[self addOperationBlock:operation];
// In Bridge, flushing of native animations is done from RCTCxxBridge batchDidComplete().
// Since RCTCxxBridge doesn't exist in Bridgeless, and components are not remounted in Fabric for native
// animations, flush here for changes in Animated.Value for Animated.event.
[self flushOperationQueues];
});
}
}
- (void)queueOperationBlock:(AnimatedOperation)operation
{
dispatch_async(RCTGetUIManagerQueue(), ^{
if (facebook::react::ReactNativeFeatureFlags::animatedShouldSignalBatch()) {
[self addOperationBlock:operation];
});
} else {
dispatch_async(RCTGetUIManagerQueue(), ^{
[self addOperationBlock:operation];
});
}
}
- (void)queuePreOperationBlock:(AnimatedOperation)operation
{
dispatch_async(RCTGetUIManagerQueue(), ^{
if (facebook::react::ReactNativeFeatureFlags::animatedShouldSignalBatch()) {
[self addPreOperationBlock:operation];
});
} else {
dispatch_async(RCTGetUIManagerQueue(), ^{
[self addPreOperationBlock:operation];
});
}
}
- (void)addOperationBlock:(AnimatedOperation)operation
@@ -50,5 +50,4 @@ Pod::Spec.new do |s|
add_dependency(s, "React-featureflags")
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end

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