diff --git a/.github/actions/build-hermes-macos/action.yml b/.github/actions/build-hermes-macos/action.yml index 41c7e6e869c8e2..d2c3eeed4e368e 100644 --- a/.github/actions/build-hermes-macos/action.yml +++ b/.github/actions/build-hermes-macos/action.yml @@ -15,8 +15,6 @@ runs: steps: - name: Setup xcode uses: ./.github/actions/setup-xcode - - name: Setup node.js - uses: ./.github/actions/setup-node - name: Restore Hermes workspace uses: ./.github/actions/restore-hermes-workspace - name: Restore Cached Artifacts @@ -45,6 +43,8 @@ runs: echo "ARTIFACTS_EXIST=true" >> $GITHUB_ENV echo "ARTIFACTS_EXIST=true" >> $GITHUB_OUTPUT fi + - name: Setup node.js + uses: ./.github/actions/setup-node - name: Yarn- Install Dependencies if: ${{ steps.check_if_apple_artifacts_are_there.outputs.ARTIFACTS_EXIST != 'true' }} uses: ./.github/actions/yarn-install diff --git a/.github/actions/build-hermesc-windows/action.yml b/.github/actions/build-hermesc-windows/action.yml index 7b80fbc9929615..1dcaebb2fb930c 100644 --- a/.github/actions/build-hermesc-windows/action.yml +++ b/.github/actions/build-hermesc-windows/action.yml @@ -14,23 +14,23 @@ runs: uses: actions/download-artifact@v4 with: name: hermes-workspace - path: 'D:\tmp\hermes' + path: 'C:\tmp\hermes' - name: Set up workspace shell: powershell run: | - mkdir -p D:\tmp\hermes\osx-bin + mkdir -p C:\tmp\hermes\osx-bin mkdir -p .\packages\react-native\sdks\hermes - cp -r -Force D:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\. + cp -r -Force C:\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: | - D:\tmp\hermes\win64-bin\ - D:\tmp\hermes\hermes\icu\ - D:\tmp\hermes\hermes\deps\ - D:\tmp\hermes\hermes\build_release\ + C:\tmp\hermes\win64-bin\ + C:\tmp\hermes\hermes\icu\ + C:\tmp\hermes\hermes\deps\ + C:\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: D:\tmp\hermes\win64-bin\ + path: C:\tmp\hermes\win64-bin\ diff --git a/.github/actions/build-npm-package/action.yml b/.github/actions/build-npm-package/action.yml index 807e03a742cfae..4e4e0d6d6c68b9 100644 --- a/.github/actions/build-npm-package/action.yml +++ b/.github/actions/build-npm-package/action.yml @@ -116,12 +116,12 @@ runs: - name: Print Artifacts Directory shell: bash run: ls -lR ./packages/react-native/ReactAndroid/external-artifacts/artifacts/ - - name: Setup node.js - uses: ./.github/actions/setup-node - name: Setup gradle uses: ./.github/actions/setup-gradle with: cache-encryption-key: ${{ inputs.gradle-cache-encryption-key }} + - name: Setup node.js + uses: ./.github/actions/setup-node - name: Install dependencies uses: ./.github/actions/yarn-install - name: Build packages diff --git a/.github/actions/create-release/action.yml b/.github/actions/create-release/action.yml index ff22a0cb3a0906..1d26e17e8fcc24 100644 --- a/.github/actions/create-release/action.yml +++ b/.github/actions/create-release/action.yml @@ -14,6 +14,8 @@ inputs: runs: using: composite steps: + - name: Setup node.js + uses: ./.github/actions/setup-node - name: Yarn install uses: ./.github/actions/yarn-install - name: Configure Git diff --git a/.github/actions/maestro-ios/action.yml b/.github/actions/maestro-ios/action.yml index 5ba4f8dc2aaa66..50b21597c9ed9f 100644 --- a/.github/actions/maestro-ios/action.yml +++ b/.github/actions/maestro-ios/action.yml @@ -35,6 +35,8 @@ runs: with: java-version: '17' distribution: 'zulu' + - name: Setup node.js + uses: ./.github/actions/setup-node - name: Run yarn install uses: ./.github/actions/yarn-install - name: Start Metro in Debug diff --git a/.github/actions/prepare-hermes-workspace/action.yml b/.github/actions/prepare-hermes-workspace/action.yml index 22dbe356ee581d..47953b6760074d 100644 --- a/.github/actions/prepare-hermes-workspace/action.yml +++ b/.github/actions/prepare-hermes-workspace/action.yml @@ -17,9 +17,6 @@ outputs: runs: using: composite steps: - - name: Setup node.js - uses: ./.github/actions/setup-node - - name: Setup hermes version shell: bash id: hermes-version @@ -67,6 +64,8 @@ runs: echo "HERMES_CACHED=true" >> "$GITHUB_OUTPUT" fi + - name: Setup node.js + uses: ./.github/actions/setup-node - name: Yarn- Install Dependencies if: ${{ steps.meaningful-cache.outputs.HERMES_CACHED != 'true' }} uses: ./.github/actions/yarn-install diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index b22fb3e822dbca..d076bf4acb8bc5 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -4,7 +4,7 @@ inputs: node-version: description: 'The node.js version to use' required: false - default: '22' + default: '22.14.0' runs: using: "composite" steps: diff --git a/.github/actions/test-ios-helloworld/action.yml b/.github/actions/test-ios-helloworld/action.yml index 137efb0a08b25b..69f9f05e40875b 100644 --- a/.github/actions/test-ios-helloworld/action.yml +++ b/.github/actions/test-ios-helloworld/action.yml @@ -23,6 +23,8 @@ runs: uses: ./.github/actions/setup-xcode - name: Setup node.js uses: ./.github/actions/setup-node + - name: Run yarn install + uses: ./.github/actions/yarn-install - name: Create Hermes folder shell: bash run: mkdir -p "$HERMES_WS_DIR" @@ -34,8 +36,6 @@ runs: - name: Print Downloaded hermes shell: bash run: ls -lR "$HERMES_WS_DIR" - - name: Run yarn - uses: ./.github/actions/yarn-install - name: Setup ruby uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflow-scripts/__tests__/verifyArtifactsAreOnMaven-test.js b/.github/workflow-scripts/__tests__/verifyArtifactsAreOnMaven-test.js index df1a332ac22feb..e77e7c4e2e4973 100644 --- a/.github/workflow-scripts/__tests__/verifyArtifactsAreOnMaven-test.js +++ b/.github/workflow-scripts/__tests__/verifyArtifactsAreOnMaven-test.js @@ -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', + 'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1/react-native-artifacts-0.78.1.pom', ); }); @@ -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', + 'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1/react-native-artifacts-0.78.1.pom', ); }); @@ -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', + 'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1/react-native-artifacts-0.78.1.pom', ); }); @@ -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', + 'https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.78.1/react-native-artifacts-0.78.1.pom', ); }); }); diff --git a/.github/workflow-scripts/verifyArtifactsAreOnMaven.js b/.github/workflow-scripts/verifyArtifactsAreOnMaven.js index d2091b00cde5bc..1bb46163e0a2b9 100644 --- a/.github/workflow-scripts/verifyArtifactsAreOnMaven.js +++ b/.github/workflow-scripts/verifyArtifactsAreOnMaven.js @@ -13,13 +13,14 @@ 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}`; + const artifactUrl = `${ARTIFACT_URL}${version}/${ARTIFACT_NAME}${version}.pom`; for (let currentAttempt = 1; currentAttempt <= retries; currentAttempt++) { const response = await fetch(artifactUrl); diff --git a/.github/workflows/bump-podfile-lock.yml b/.github/workflows/bump-podfile-lock.yml index cdaf01e2a790ab..80b726edfd5324 100644 --- a/.github/workflows/bump-podfile-lock.yml +++ b/.github/workflows/bump-podfile-lock.yml @@ -18,6 +18,10 @@ 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 }}"; diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2b494041a009c8..24177338f9558f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -128,9 +128,9 @@ jobs: runs-on: windows-2025 needs: prepare_hermes_workspace env: - 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' + 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' 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' diff --git a/.github/workflows/prebuild-ios-core.yml b/.github/workflows/prebuild-ios-core.yml index e4983229f644dc..b2d05c5e88fe13 100644 --- a/.github/workflows/prebuild-ios-core.yml +++ b/.github/workflows/prebuild-ios-core.yml @@ -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') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }} + key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/*.js', 'packages/react-native/scripts/ios-prebuild.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }} 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' package.json) + VERSION=$(jq -r '.version' packages/react-native/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') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }} + key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/*.js', 'packages/react-native/scripts/ios-prebuild.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }} 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') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }} + key: v2-ios-core-xcframework-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/*.js', 'packages/react-native/scripts/ios-prebuild.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }} - 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') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }} + key: v2-ios-core-xcframework-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift', 'packages/react-native/scripts/ios-prebuild/*.js', 'packages/react-native/scripts/ios-prebuild.js', 'packages/react-native/React/**/*', 'packages/react-native/ReactCommon/**/*', 'packages/react-native/Libraries/**/*') }} diff --git a/.github/workflows/prebuild-ios-dependencies.yml b/.github/workflows/prebuild-ios-dependencies.yml index 3ae438892382ae..70ca3fe2a5c70c 100644 --- a/.github/workflows/prebuild-ios-dependencies.yml +++ b/.github/workflows/prebuild-ios-dependencies.yml @@ -179,8 +179,9 @@ jobs: - name: Compress and Rename dSYM if: steps.restore-xcframework.outputs.cache-hit != 'true' run: | - tar -cz -f packages/react-native/third-party/Symbols/ReactNativeDependencies${{ matrix.flavor }}.framework.dSYM.tar.gz \ - packages/react-native/third-party/Symbols/ReactNativeDependencies.framework.dSYM + cd packages/react-native/third-party/Symbols/ + tar -cz -f ../ReactNativeDependencies${{ matrix.flavor }}.framework.dSYM.tar.gz . + mv ../ReactNativeDependencies${{ matrix.flavor }}.framework.dSYM.tar.gz ./ReactNativeDependencies${{ matrix.flavor }}.framework.dSYM.tar.gz - name: Upload XCFramework Artifact uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 2410cb49a3251e..8f356720f0b955 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -125,9 +125,9 @@ jobs: runs-on: windows-2025 needs: prepare_hermes_workspace env: - 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' + 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' 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' diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index e4da3cec69cfdc..7996d461d64f5e 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -392,9 +392,9 @@ jobs: runs-on: windows-2025 needs: prepare_hermes_workspace env: - 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' + 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' 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' @@ -593,7 +593,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: ["24", "22"] + node-version: ["24.4.1", "22", "20.19.4"] steps: - name: Checkout uses: actions/checkout@v4 diff --git a/build.gradle.kts b/build.gradle.kts index 4ed9ea7dc9a2c8..ec7226319214f8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { alias(libs.plugins.nexus.publish) - alias(libs.plugins.android.library) apply false + id(libs.plugins.android.library.get().pluginId) version libs.plugins.android.library.get().version.requiredVersion apply false alias(libs.plugins.android.application) apply false alias(libs.plugins.download) apply false alias(libs.plugins.kotlin.android) apply false diff --git a/discord/bump_version.py b/discord/bump_version.py new file mode 100755 index 00000000000000..fb41c7bc277276 --- /dev/null +++ b/discord/bump_version.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +from pathlib import Path +from typing import List +import re +import subprocess +import sys + + +def check_output(args: List[str]) -> str: + return subprocess.check_output(args).decode('utf-8').strip() + + +VERSION_MATCHER = re.compile(r'^(.*)-discord-(\d*)$') + +status = check_output(['git', 'status', '--porcelain']) +if status != '': + print('Detected changed files, please remove or commit them first.\n') + print(status) + sys.exit(1) + + +root = check_output(['git', 'rev-parse', '--show-toplevel']) +android_path = Path(root) / "ReactAndroid" +props_path = android_path / "gradle.properties" + +version = None +property_lines = [line.strip() for line in props_path.read_text().splitlines()] +for line in property_lines: + if line.startswith("VERSION_NAME="): + version = line.split('=')[1] + +assert version, "unable to find current version" + +matches = VERSION_MATCHER.match(version) +assert matches, f'{version} did not match expected format, X.Y.Z-discord-N' + +upstream = matches[1] +local = int(matches[2]) + +new_version = f'{upstream}-discord-{local + 1}' + +with open(props_path, 'w') as f: + for line in property_lines: + if line.startswith("VERSION_NAME="): + f.write(f'VERSION_NAME={new_version}\n') + else: + f.write(f'{line}\n') + + +branch_name = check_output(['git', 'symbolic-ref', '--short', 'HEAD']) + +subprocess.check_call( + ['../gradlew', 'publishReleasePublicationToDiscordRepository'], + cwd=android_path.absolute() + ) + +subprocess.check_call(['git', 'add', props_path.absolute()]) +subprocess.check_call(['git', 'commit', '-m', f'version bump: {new_version}']) +subprocess.check_call(['git', 'push', 'origin', branch_name]) + +new_commit = check_output(['git', 'rev-parse', 'HEAD']) + + +print(f'NEW TAGGED VERSION: {new_version}') +print(f'NEW COMMIT: {new_commit}') diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 002b867c48b328..d4081da476bb3e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/package.json b/package.json index 84b36a75d70823..1916f9d02ff803 100644 --- a/package.json +++ b/package.json @@ -26,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-e2e-local-clean": "node ./scripts/release-testing/test-e2e-local-clean.js", - "test-e2e-local": "node ./scripts/release-testing/test-e2e-local.js", + "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-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", @@ -52,8 +52,8 @@ "@electron/packager": "^18.3.6", "@jest/create-cache-key-function": "^29.7.0", "@microsoft/api-extractor": "^7.52.2", - "@react-native/metro-babel-transformer": "0.81.0-main", - "@react-native/metro-config": "0.81.0-main", + "@react-native/metro-babel-transformer": "0.81.4", + "@react-native/metro-config": "0.81.4", "@tsconfig/node22": "22.0.2", "@types/react": "^19.1.0", "@typescript-eslint/parser": "^7.1.1", @@ -90,9 +90,9 @@ "jest-snapshot": "^29.7.0", "markdownlint-cli2": "^0.17.2", "markdownlint-rule-relative-links": "^3.0.0", - "metro-babel-register": "^0.82.5", + "metro-babel-register": "^0.83.1", "metro-memory-fs": "^0.82.5", - "metro-transform-plugins": "^0.82.5", + "metro-transform-plugins": "^0.83.1", "micromatch": "^4.0.4", "node-fetch": "^2.2.0", "nullthrows": "^1.1.1", diff --git a/packages/assets/package.json b/packages/assets/package.json index 1b15cd15041f6b..3f5cb83bd195af 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/assets-registry", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Asset support code for React Native.", "license": "MIT", "repository": { @@ -17,7 +17,7 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "files": [ "path-support.js", diff --git a/packages/babel-plugin-codegen/package.json b/packages/babel-plugin-codegen/package.json index 0be39ceb9224e1..10c9c131d6b742 100644 --- a/packages/babel-plugin-codegen/package.json +++ b/packages/babel-plugin-codegen/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/babel-plugin-codegen", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Babel plugin to generate native module and view manager code for React Native.", "license": "MIT", "repository": { @@ -19,14 +19,14 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "files": [ "index.js" ], "dependencies": { "@babel/traverse": "^7.25.3", - "@react-native/codegen": "0.81.0-main" + "@react-native/codegen": "0.81.4" }, "devDependencies": { "@babel/core": "^7.25.2" diff --git a/packages/community-cli-plugin/package.json b/packages/community-cli-plugin/package.json index 92dba5dace2e73..79cc3e04ff4d1d 100644 --- a/packages/community-cli-plugin/package.json +++ b/packages/community-cli-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/community-cli-plugin", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Core CLI commands for React Native", "keywords": [ "react-native", @@ -22,16 +22,16 @@ "dist" ], "dependencies": { - "@react-native/dev-middleware": "0.81.0-main", + "@react-native/dev-middleware": "0.81.4", "debug": "^4.4.0", "invariant": "^2.2.4", - "metro": "^0.82.5", - "metro-config": "^0.82.5", - "metro-core": "^0.82.5", + "metro": "^0.83.1", + "metro-config": "^0.83.1", + "metro-core": "^0.83.1", "semver": "^7.1.3" }, "devDependencies": { - "metro-resolver": "^0.82.5" + "metro-resolver": "^0.83.1" }, "peerDependencies": { "@react-native-community/cli": "*", @@ -40,9 +40,12 @@ "peerDependenciesMeta": { "@react-native-community/cli": { "optional": true + }, + "@react-native/metro-config": { + "optional": true } }, "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" } } diff --git a/packages/community-cli-plugin/src/commands/start/runServer.js b/packages/community-cli-plugin/src/commands/start/runServer.js index 6bb60084cb0bfb..7a56389e0f3f75 100644 --- a/packages/community-cli-plugin/src/commands/start/runServer.js +++ b/packages/community-cli-plugin/src/commands/start/runServer.js @@ -147,7 +147,7 @@ async function runServer( // $FlowIgnore[cannot-write] Assigning to readonly property metroConfig.reporter = reporter; - const serverInstance = await Metro.runServer(metroConfig, { + await Metro.runServer(metroConfig, { host: args.host, secure: args.https, secureCert: args.cert, @@ -161,18 +161,6 @@ 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); } diff --git a/packages/core-cli-utils/package.json b/packages/core-cli-utils/package.json index 24f6810e3aa72c..17396b103e29f5 100644 --- a/packages/core-cli-utils/package.json +++ b/packages/core-cli-utils/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/core-cli-utils", - "version": "0.81.0-main", + "version": "0.81.4", "description": "React Native CLI library for Frameworks to build on", "license": "MIT", "main": "./src/index.flow.js", @@ -21,7 +21,7 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "files": [ "dist" diff --git a/packages/core-cli-utils/src/private/app.js b/packages/core-cli-utils/src/private/app.js index cfc98552a62701..725cf30226a1b8 100644 --- a/packages/core-cli-utils/src/private/app.js +++ b/packages/core-cli-utils/src/private/app.js @@ -76,9 +76,8 @@ function getNodePackagePath(packageName: string): string { } function metro(...args: $ReadOnlyArray): ExecaPromise { - const metroPath = getNodePackagePath(path.join('metro', 'src', 'cli.js')); - log(`🚇 ${metroPath} ${args.join(' ')} `); - return execa('node', [metroPath, ...args]); + log(`🚇 metro ${args.join(' ')} `); + return execa('npx', ['--offline', 'metro', ...args]); } export const tasks = { diff --git a/packages/debugger-frontend/package.json b/packages/debugger-frontend/package.json index 8ca79a5c39d00f..075550a6b09171 100644 --- a/packages/debugger-frontend/package.json +++ b/packages/debugger-frontend/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/debugger-frontend", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Debugger frontend for React Native based on Chrome DevTools", "keywords": [ "react-native", @@ -20,6 +20,6 @@ "BUILD_INFO" ], "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" } } diff --git a/packages/debugger-shell/package.json b/packages/debugger-shell/package.json index cbafe6bf36b162..9a70b1cb298fb9 100644 --- a/packages/debugger-shell/package.json +++ b/packages/debugger-shell/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/debugger-shell", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Experimental debugger shell for React Native for use with @react-native/debugger-frontend", "keywords": [ "react-native", @@ -26,7 +26,7 @@ }, "license": "MIT", "engines": { - "node": ">= 22.14.0", + "node": ">= 20.19.4", "electron": ">=36.3.0" }, "dependencies": { diff --git a/packages/dev-middleware/package.json b/packages/dev-middleware/package.json index ece94b94e65866..f9cc2260ab3855 100644 --- a/packages/dev-middleware/package.json +++ b/packages/dev-middleware/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/dev-middleware", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Dev server middleware for React Native", "keywords": [ "react-native", @@ -23,7 +23,7 @@ ], "dependencies": { "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.81.0-main", + "@react-native/debugger-frontend": "0.81.4", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", @@ -35,7 +35,7 @@ "ws": "^6.2.3" }, "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "devDependencies": { "selfsigned": "^2.4.1", diff --git a/packages/eslint-config-react-native/package.json b/packages/eslint-config-react-native/package.json index 10ead7344c5495..86eba38e857043 100644 --- a/packages/eslint-config-react-native/package.json +++ b/packages/eslint-config-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/eslint-config", - "version": "0.81.0-main", + "version": "0.81.4", "description": "ESLint config for React Native", "license": "MIT", "repository": { @@ -16,13 +16,13 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "main": "index.js", "dependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.25.1", - "@react-native/eslint-plugin": "0.81.0-main", + "@react-native/eslint-plugin": "0.81.4", "@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/parser": "^7.1.1", "eslint-config-prettier": "^8.5.0", diff --git a/packages/eslint-plugin-react-native/package.json b/packages/eslint-plugin-react-native/package.json index 48263c87d2c006..ba8930e3ee2e31 100644 --- a/packages/eslint-plugin-react-native/package.json +++ b/packages/eslint-plugin-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/eslint-plugin", - "version": "0.81.0-main", + "version": "0.81.4", "description": "ESLint rules for @react-native/eslint-config", "license": "MIT", "repository": { @@ -22,6 +22,6 @@ "hermes-eslint": "0.29.1" }, "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" } } diff --git a/packages/eslint-plugin-specs/package.json b/packages/eslint-plugin-specs/package.json index 299f145e4e6b74..6b85964fbe53a9 100644 --- a/packages/eslint-plugin-specs/package.json +++ b/packages/eslint-plugin-specs/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/eslint-plugin-specs", - "version": "0.81.0-main", + "version": "0.81.4", "description": "ESLint rules to validate NativeModule and Component Specs", "license": "MIT", "repository": { @@ -26,7 +26,7 @@ "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@react-native/codegen": "0.81.0-main", + "@react-native/codegen": "0.81.4", "make-dir": "^2.1.0", "pirates": "^4.0.1", "source-map-support": "0.5.0" @@ -36,6 +36,6 @@ "hermes-eslint": "0.29.1" }, "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" } } diff --git a/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties index ff23a68d70f3cd..d4081da476bb3e 100644 --- a/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/packages/gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/packages/gradle-plugin/package.json b/packages/gradle-plugin/package.json index 5897ef53b24449..1e59de2c8a3998 100644 --- a/packages/gradle-plugin/package.json +++ b/packages/gradle-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/gradle-plugin", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Gradle Plugin for React Native", "license": "MIT", "repository": { @@ -16,7 +16,7 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "scripts": { "build": "./gradlew build", diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt index fa9c937361156b..2e89b8c281dd9c 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt @@ -100,10 +100,10 @@ abstract class ReactExtension @Inject constructor(val project: Project) { * Allows to specify the debuggable variants (by default just 'debug'). Variants in this list will * not be bundled (the bundle file will not be created and won't be copied over). * - * Default: ['debug'] + * Default: ['debug', 'debugOptimized'] */ val debuggableVariants: ListProperty = - objects.listProperty(String::class.java).convention(listOf("debug")) + objects.listProperty(String::class.java).convention(listOf("debug", "debugOptimized")) /** Hermes Config */ diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt index 85f8ca32aaf142..3d40455a43a366 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -18,6 +18,7 @@ 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 @@ -84,6 +85,7 @@ class ReactPlugin : Plugin { configureAutolinking(project, extension) configureCodegen(project, extension, rootExtension, isLibrary = false) configureResources(project, extension) + configureBuildTypesForApp(project) } // Library Only Configuration diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactRootProjectPlugin.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactRootProjectPlugin.kt index 5f232cd0e581a5..f8aee12b8228fa 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactRootProjectPlugin.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactRootProjectPlugin.kt @@ -26,5 +26,21 @@ class ReactRootProjectPlugin : Plugin { it.evaluationDependsOn(":app") } } + // We need to make sure that `:app:preBuild` task depends on all other subprojects' preBuild + // tasks. This is necessary in order to have all the codegen generated code before the CMake + // configuration build kicks in. + project.gradle.projectsEvaluated { + val appProject = project.rootProject.subprojects.find { it.name == "app" } + val appPreBuild = appProject?.tasks?.findByName("preBuild") + if (appPreBuild != null) { + // Find all other subprojects' preBuild tasks + val otherPreBuildTasks = + project.rootProject.subprojects + .filter { it != appProject } + .mapNotNull { it.tasks.findByName("preBuild") } + // Make :app:preBuild depend on all others + appPreBuild.dependsOn(otherPreBuildTasks) + } + } } } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt index 36cc66caf88831..100db62882486b 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt @@ -148,6 +148,7 @@ abstract class GeneratePackageListTask : DefaultTask() { {{ packageImports }} + @SuppressWarnings("deprecation") public class PackageList { private Application application; private ReactNativeHost reactNativeHost; diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/PrepareBoostTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/PrepareBoostTask.kt index c13a0d90ea245b..19510ccd5b22bc 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/PrepareBoostTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/PrepareBoostTask.kt @@ -38,7 +38,9 @@ abstract class PrepareBoostTask : DefaultTask() { it.from(boostThirdPartyJniPath) it.include( "CMakeLists.txt", + "boost_${boostVersion.get()}/boost/**/*.h", "boost_${boostVersion.get()}/boost/**/*.hpp", + "boost/boost/**/*.h", "boost/boost/**/*.hpp", "asm/**/*.S") it.includeEmptyDirs = false diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/PreparePrefabHeadersTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/PreparePrefabHeadersTask.kt index f30392dd3ebc1c..ff16016108149d 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/PreparePrefabHeadersTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/internal/PreparePrefabHeadersTask.kt @@ -70,6 +70,22 @@ abstract class PreparePrefabHeadersTask : DefaultTask() { it.include("boost/cstdint.hpp") it.include("boost/utility.hpp") it.include("boost/version.hpp") + // Extra files for kv-storage + it.include("boost/*.hpp") + it.include("boost/align/**/*.hpp") + it.include("boost/container/**/*.hpp") + it.include("boost/intrusive/**/*.hpp") + it.include("boost/iterator/**/*.hpp") + it.include("boost/lockfree/**/*.hpp") + it.include("boost/move/**/*.hpp") + it.include("boost/mp11/**/*.hpp") + it.include("boost/mpl/**/*.hpp") + it.include("boost/parameter/**/*.hpp") + it.include("boost/predef.h") + it.include("boost/predef/**/*.h") + it.include("boost/predef/**/*.hpp") + it.include("boost/type_traits/**/*.hpp") + it.include("boost/utility/**/*.hpp") it.into(File(outputFolder.asFile, headerPrefix)) } } diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt index f8bee6923f901c..c11d0ccf163224 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/AgpConfiguratorUtils.kt @@ -19,6 +19,7 @@ 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 @@ -27,6 +28,36 @@ import org.w3c.dom.Element @Suppress("UnstableApiUsage") internal object AgpConfiguratorUtils { + fun configureBuildTypesForApp(project: Project) { + val action = + Action { + project.extensions + .getByType(ApplicationAndroidComponentsExtension::class.java) + .finalizeDsl { ext -> + ext.buildTypes { + val debug = + getByName("debug").apply { + manifestPlaceholders["usesCleartextTraffic"] = "true" + } + getByName("release").apply { + manifestPlaceholders["usesCleartextTraffic"] = "false" + } + maybeCreate("debugOptimized").apply { + manifestPlaceholders["usesCleartextTraffic"] = "true" + initWith(debug) + externalNativeBuild { + cmake { + arguments("-DCMAKE_BUILD_TYPE=Release") + matchingFallbacks += listOf("release") + } + } + } + } + } + } + project.pluginManager.withPlugin("com.android.application", action) + } + fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) { val action = Action { diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt index fcd48e2cd9c660..ec37b060105775 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/NdkConfiguratorUtils.kt @@ -11,7 +11,6 @@ import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.api.variant.Variant import com.facebook.react.ReactExtension import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures -import com.facebook.react.utils.ProjectUtils.isNewArchEnabled import java.io.File import org.gradle.api.Project @@ -19,12 +18,7 @@ internal object NdkConfiguratorUtils { @Suppress("UnstableApiUsage") fun configureReactNativeNdk(project: Project, extension: ReactExtension) { project.pluginManager.withPlugin("com.android.application") { - project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl { - ext -> - if (!project.isNewArchEnabled(extension)) { - // For Old Arch, we don't need to setup the NDK - return@finalizeDsl - } + project.extensions.getByType(ApplicationAndroidComponentsExtension::class.java).finalizeDsl { ext -> // We enable prefab so users can consume .so/headers from ReactAndroid and hermes-engine // .aar ext.buildFeatures.prefab = true @@ -73,20 +67,12 @@ internal object NdkConfiguratorUtils { * sure we specify the correct .pickFirsts for all the .so files we are producing or that we're * aware of as some of our dependencies are pulling them in. */ + @Suppress("UNUSED_PARAMETER") fun configureNewArchPackagingOptions( project: Project, extension: ReactExtension, variant: Variant ) { - if (!project.isNewArchEnabled(extension)) { - // For Old Arch, we set a pickFirst only on libraries that we know are - // clashing with our direct dependencies (mainly FBJNI and Hermes). - variant.packaging.jniLibs.pickFirsts.addAll( - listOf( - "**/libfbjni.so", - "**/libc++_shared.so", - )) - } else { // We set some packagingOptions { pickFirst ... } for our users for libraries we own. variant.packaging.jniLibs.pickFirsts.addAll( listOf( @@ -100,7 +86,6 @@ internal object NdkConfiguratorUtils { // AGP will give priority of libc++_shared coming from App modules. "**/libc++_shared.so", )) - } } /** diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GeneratePackageListTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GeneratePackageListTaskTest.kt index 0214403d67c8f1..fc85587d15e39f 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GeneratePackageListTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GeneratePackageListTaskTest.kt @@ -233,6 +233,7 @@ class GeneratePackageListTaskTest { + @SuppressWarnings("deprecation") public class PackageList { private Application application; private ReactNativeHost reactNativeHost; @@ -311,7 +312,8 @@ 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; diff --git a/packages/metro-config/package.json b/packages/metro-config/package.json index fb0d4556cfb7a4..6fa770cb1a0e19 100644 --- a/packages/metro-config/package.json +++ b/packages/metro-config/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/metro-config", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Metro configuration for React Native.", "license": "MIT", "repository": { @@ -16,7 +16,7 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "exports": { ".": "./src/index.js", @@ -26,9 +26,9 @@ "dist" ], "dependencies": { - "@react-native/js-polyfills": "0.81.0-main", - "@react-native/metro-babel-transformer": "0.81.0-main", - "metro-config": "^0.82.5", - "metro-runtime": "^0.82.5" + "@react-native/js-polyfills": "0.81.4", + "@react-native/metro-babel-transformer": "0.81.4", + "metro-config": "^0.83.1", + "metro-runtime": "^0.83.1" } } diff --git a/packages/new-app-screen/package.json b/packages/new-app-screen/package.json index ccfe78e7afa480..fa59d004ab4580 100644 --- a/packages/new-app-screen/package.json +++ b/packages/new-app-screen/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/new-app-screen", - "version": "0.81.0-main", + "version": "0.81.4", "description": "NewAppScreen component for React Native", "keywords": [ "react-native" @@ -30,6 +30,6 @@ } }, "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" } } diff --git a/packages/new-app-screen/src/NewAppScreen.js b/packages/new-app-screen/src/NewAppScreen.js index d49ba6beac384f..4518adb6a5b1da 100644 --- a/packages/new-app-screen/src/NewAppScreen.js +++ b/packages/new-app-screen/src/NewAppScreen.js @@ -13,10 +13,7 @@ import {ThemedText, useTheme} from './Theme'; import * as React from 'react'; import { Image, - Platform, - SafeAreaView, ScrollView, - StatusBar, StyleSheet, Text, TouchableHighlight, @@ -29,24 +26,32 @@ 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 ( - - - + + + - + ); } diff --git a/packages/new-app-screen/src/index.d.ts b/packages/new-app-screen/src/index.d.ts index 2568a07fcca4fe..acbe8b24c8505a 100644 --- a/packages/new-app-screen/src/index.d.ts +++ b/packages/new-app-screen/src/index.d.ts @@ -11,6 +11,14 @@ 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; diff --git a/packages/normalize-color/package.json b/packages/normalize-color/package.json index 48b016fcb2b8c6..9c03691a494187 100644 --- a/packages/normalize-color/package.json +++ b/packages/normalize-color/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/normalize-colors", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Color normalization for React Native.", "license": "MIT", "repository": { diff --git a/packages/polyfills/package.json b/packages/polyfills/package.json index baf6f178868d2d..5f0093f10d9935 100644 --- a/packages/polyfills/package.json +++ b/packages/polyfills/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/js-polyfills", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Polyfills for React Native.", "license": "MIT", "repository": { @@ -18,7 +18,7 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "files": [ "console.js", diff --git a/packages/react-native-babel-preset/package.json b/packages/react-native-babel-preset/package.json index 17aaae7809ac0d..dc6957fbe7a93a 100644 --- a/packages/react-native-babel-preset/package.json +++ b/packages/react-native-babel-preset/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/babel-preset", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Babel preset for React Native applications", "repository": { "type": "git", @@ -13,7 +13,7 @@ ], "license": "MIT", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "main": "src/index.js", "files": [ @@ -66,7 +66,7 @@ "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", - "@react-native/babel-plugin-codegen": "0.81.0-main", + "@react-native/babel-plugin-codegen": "0.81.4", "babel-plugin-syntax-hermes-parser": "0.29.1", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" diff --git a/packages/react-native-babel-preset/src/configs/main.js b/packages/react-native-babel-preset/src/configs/main.js index 5f18e42408204d..2cf830c40b8c51 100644 --- a/packages/react-native-babel-preset/src/configs/main.js +++ b/packages/react-native-babel-preset/src/configs/main.js @@ -38,19 +38,6 @@ 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. @@ -234,7 +221,28 @@ const getPreset = (src, options) => { plugins: [require('@babel/plugin-transform-flow-strip-types')], }, { - plugins: defaultPlugins, + 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')], + ], }, { test: isTypeScriptSource, diff --git a/packages/react-native-babel-transformer/package.json b/packages/react-native-babel-transformer/package.json index bf4a934ebcc43b..92290bd89e1e31 100644 --- a/packages/react-native-babel-transformer/package.json +++ b/packages/react-native-babel-transformer/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/metro-babel-transformer", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Babel transformer for React Native applications.", "repository": { "type": "git", @@ -14,7 +14,7 @@ ], "license": "MIT", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "main": "src/index.js", "files": [ @@ -27,7 +27,7 @@ ], "dependencies": { "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.81.0-main", + "@react-native/babel-preset": "0.81.4", "hermes-parser": "0.29.1", "nullthrows": "^1.1.1" }, diff --git a/packages/react-native-codegen/package.json b/packages/react-native-codegen/package.json index 16068f79d45b8c..3b18e02d94eebb 100644 --- a/packages/react-native-codegen/package.json +++ b/packages/react-native-codegen/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/codegen", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Code generation tools for React Native", "license": "MIT", "repository": { @@ -18,7 +18,7 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "scripts": { "build": "yarn clean && node scripts/build.js --verbose", @@ -29,6 +29,8 @@ "lib" ], "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.29.1", "invariant": "^2.2.4", diff --git a/packages/react-native-compatibility-check/package.json b/packages/react-native-compatibility-check/package.json index abe99392696dff..800141036f563b 100644 --- a/packages/react-native-compatibility-check/package.json +++ b/packages/react-native-compatibility-check/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/compatibility-check", - "version": "0.81.0-main", + "version": "0.81.4", "description": "Check a React Native app's boundary between JS and Native for incompatibilities", "license": "MIT", "repository": { @@ -19,7 +19,7 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "exports": { ".": "./src/index.js", @@ -29,7 +29,7 @@ "dist" ], "dependencies": { - "@react-native/codegen": "0.81.0-main" + "@react-native/codegen": "0.81.4" }, "devDependencies": { "flow-remove-types": "^2.237.2", diff --git a/packages/react-native-popup-menu-android/package.json b/packages/react-native-popup-menu-android/package.json index 38344be5bf0d8e..a56538384555bd 100644 --- a/packages/react-native-popup-menu-android/package.json +++ b/packages/react-native-popup-menu-android/package.json @@ -1,6 +1,6 @@ { "name": "@react-native/popup-menu-android", - "version": "0.81.0-main", + "version": "0.81.4", "description": "PopupMenu for the Android platform", "main": "index.js", "files": [ @@ -21,7 +21,7 @@ }, "license": "MIT", "devDependencies": { - "@react-native/codegen": "0.81.0-main" + "@react-native/codegen": "0.81.4" }, "peerDependencies": { "@types/react": "^19.1.0", diff --git a/packages/react-native-test-library/android/src/main/java/com/facebook/react/osslibraryexample/OSSLibraryExamplePackage.kt b/packages/react-native-test-library/android/src/main/java/com/facebook/react/osslibraryexample/OSSLibraryExamplePackage.kt index 00508e089a3e2c..428cd61ed9e621 100644 --- a/packages/react-native-test-library/android/src/main/java/com/facebook/react/osslibraryexample/OSSLibraryExamplePackage.kt +++ b/packages/react-native-test-library/android/src/main/java/com/facebook/react/osslibraryexample/OSSLibraryExamplePackage.kt @@ -12,7 +12,9 @@ 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 = listOf(NativeSampleModule(reactContext)) diff --git a/packages/react-native-test-library/package.json b/packages/react-native-test-library/package.json index 96311c75092f8d..5f7b7168890d3d 100644 --- a/packages/react-native-test-library/package.json +++ b/packages/react-native-test-library/package.json @@ -26,8 +26,8 @@ ], "devDependencies": { "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.81.0-main", - "react-native": "1000.0.0" + "@react-native/babel-preset": "0.81.4", + "react-native": "0.81.4" }, "peerDependencies": { "react": "*", diff --git a/packages/react-native/Libraries/Animated/createAnimatedComponent.js b/packages/react-native/Libraries/Animated/createAnimatedComponent.js index 345c1ac29ab3ea..6472fe0fbba633 100644 --- a/packages/react-native/Libraries/Animated/createAnimatedComponent.js +++ b/packages/react-native/Libraries/Animated/createAnimatedComponent.js @@ -29,6 +29,10 @@ import {useMemo} from 'react'; type Nullable = void | null; type Primitive = string | number | boolean | symbol | void; type Builtin = (...$ReadOnlyArray) => mixed | Date | Error | RegExp; +export type AnimatedProps = { + // eslint-disable-next-line no-unused-vars + +[_K]: any, +}; export type WithAnimatedValue<+T> = T extends Builtin | Nullable ? T diff --git a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec index 2e2bd60415ef75..437a80be92ccb4 100644 --- a/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec +++ b/packages/react-native/Libraries/AppDelegate/React-RCTAppDelegate.podspec @@ -84,4 +84,5 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/Libraries/Blob/React-RCTBlob.podspec b/packages/react-native/Libraries/Blob/React-RCTBlob.podspec index 427a7471909561..b0cab45212894c 100644 --- a/packages/react-native/Libraries/Blob/React-RCTBlob.podspec +++ b/packages/react-native/Libraries/Blob/React-RCTBlob.podspec @@ -55,4 +55,5 @@ Pod::Spec.new do |s| end add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/Libraries/Components/Pressable/useAndroidRippleForView.js b/packages/react-native/Libraries/Components/Pressable/useAndroidRippleForView.js index a786da2db649ee..dcb03b10715385 100644 --- a/packages/react-native/Libraries/Components/Pressable/useAndroidRippleForView.js +++ b/packages/react-native/Libraries/Components/Pressable/useAndroidRippleForView.js @@ -24,12 +24,14 @@ type NativeBackgroundProp = $ReadOnly<{ color: ?number, borderless: boolean, rippleRadius: ?number, + rippleCornerRadius: ?number, }>; export type PressableAndroidRippleConfig = { color?: ColorValue, borderless?: boolean, radius?: number, + cornerRadius?: number, foreground?: boolean, }; @@ -48,7 +50,7 @@ export default function useAndroidRippleForView( | $ReadOnly<{nativeBackgroundAndroid: NativeBackgroundProp}> | $ReadOnly<{nativeForegroundAndroid: NativeBackgroundProp}>, }> { - const {color, borderless, radius, foreground} = rippleConfig ?? {}; + const {color, borderless, radius, cornerRadius, foreground} = rippleConfig ?? {}; return useMemo(() => { if ( @@ -66,6 +68,7 @@ export default function useAndroidRippleForView( color: processedColor, borderless: borderless === true, rippleRadius: radius, + rippleCornerRadius: cornerRadius, }; return { diff --git a/packages/react-native/Libraries/Components/Switch/Switch.js b/packages/react-native/Libraries/Components/Switch/Switch.js index 24db0a95029992..d3b88afad7805d 100644 --- a/packages/react-native/Libraries/Components/Switch/Switch.js +++ b/packages/react-native/Libraries/Components/Switch/Switch.js @@ -264,7 +264,7 @@ const Switch: component( disabled, onTintColor: trackColorForTrue, style: StyleSheet.compose( - {height: 31, width: 51}, + {alignSelf: 'flex-start' as const}, StyleSheet.compose( style, ios_backgroundColor == null diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js index 8bd86ccc2f08b2..1e95eb6d44a692 100644 --- a/packages/react-native/Libraries/Components/TextInput/TextInput.js +++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js @@ -618,6 +618,9 @@ function InternalTextInput(props: TextInputProps): React.Node { // so omitting onBlur and onFocus pressability handlers here. const {onBlur, onFocus, ...eventHandlers} = usePressability(config); + const _accessibilityLabel = + props?.['aria-label'] ?? props?.accessibilityLabel; + let _accessibilityState; if ( accessibilityState != null || @@ -681,6 +684,7 @@ function InternalTextInput(props: TextInputProps): React.Node { {...otherProps} {...eventHandlers} acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes} + accessibilityLabel={_accessibilityLabel} accessibilityState={_accessibilityState} accessible={accessible} submitBehavior={submitBehavior} @@ -744,8 +748,9 @@ function InternalTextInput(props: TextInputProps): React.Node { {...otherProps} {...colorProps} {...eventHandlers} - accessibilityState={_accessibilityState} + accessibilityLabel={_accessibilityLabel} accessibilityLabelledBy={_accessibilityLabelledBy} + accessibilityState={_accessibilityState} accessible={accessible} acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes} autoCapitalize={autoCapitalize} diff --git a/packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-test.js b/packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-test.js index bf63d695721558..a33757e1e15a46 100644 --- a/packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-test.js +++ b/packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-test.js @@ -432,6 +432,7 @@ jest.unmock('../TextInput'); expect(instance.toJSON()).toMatchInlineSnapshot(` >, ...props: ViewProps ) { @@ -213,3 +213,7 @@ export default component View( } return actualView; } + +View.displayName = 'View'; + +export default View; diff --git a/packages/react-native/Libraries/Components/View/ViewPropTypes.js b/packages/react-native/Libraries/Components/View/ViewPropTypes.js index ac7e88db84ff6c..36cb09adf1a5a6 100644 --- a/packages/react-native/Libraries/Components/View/ViewPropTypes.js +++ b/packages/react-native/Libraries/Components/View/ViewPropTypes.js @@ -258,6 +258,7 @@ type AndroidDrawableRipple = $ReadOnly<{ color?: ?number, borderless?: ?boolean, rippleRadius?: ?number, + rippleCornerRadius?: ?number, }>; type AndroidDrawable = AndroidDrawableThemeAttr | AndroidDrawableRipple; diff --git a/packages/react-native/Libraries/Core/ReactNativeVersion.js b/packages/react-native/Libraries/Core/ReactNativeVersion.js index cfee63f4daefcf..e06cc24c94efb4 100644 --- a/packages/react-native/Libraries/Core/ReactNativeVersion.js +++ b/packages/react-native/Libraries/Core/ReactNativeVersion.js @@ -15,8 +15,8 @@ export const version: $ReadOnly<{ patch: number, prerelease: string | null, }> = { - major: 1000, - minor: 0, - patch: 0, + major: 0, + minor: 81, + patch: 4, prerelease: null, }; diff --git a/packages/react-native/Libraries/Core/Timers/JSTimers.js b/packages/react-native/Libraries/Core/Timers/JSTimers.js index 6969b0e700521d..4cb787882561fb 100644 --- a/packages/react-native/Libraries/Core/Timers/JSTimers.js +++ b/packages/react-native/Libraries/Core/Timers/JSTimers.js @@ -486,4 +486,12 @@ BatchedBridge.setReactNativeMicrotasksCallback( JSTimers.callReactNativeMicrotasks, ); +/** + * This is a hack to allow us to flush react native microtasks + * from the native side when bridgeless mode is disabled. + */ +global._flushReactNativeMicrotasks = () => { + return JSTimers.callReactNativeMicrotasks(); +} + export default ExportedJSTimers; diff --git a/packages/react-native/Libraries/Image/Image.js b/packages/react-native/Libraries/Image/Image.js deleted file mode 100644 index 71a280b4b5c9af..00000000000000 --- a/packages/react-native/Libraries/Image/Image.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - */ - -// NOTE: This file supports backwards compatibility of subpath (deep) imports -// from 'react-native' with platform-specific extensions. It can be deleted -// once we remove the "./*" mapping from package.json "exports". - -import Image from './Image'; - -export default Image; diff --git a/packages/react-native/Libraries/Image/React-RCTImage.podspec b/packages/react-native/Libraries/Image/React-RCTImage.podspec index 82e27b2c3ef2da..f2692e24c24db7 100644 --- a/packages/react-native/Libraries/Image/React-RCTImage.podspec +++ b/packages/react-native/Libraries/Image/React-RCTImage.podspec @@ -53,4 +53,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-NativeModulesApple") add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/Libraries/NativeAnimation/React-RCTAnimation.podspec b/packages/react-native/Libraries/NativeAnimation/React-RCTAnimation.podspec index fbe0b69e37f1b6..cc2f5c3d7e6360 100644 --- a/packages/react-native/Libraries/NativeAnimation/React-RCTAnimation.podspec +++ b/packages/react-native/Libraries/NativeAnimation/React-RCTAnimation.podspec @@ -50,4 +50,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-featureflags") add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js index 8dcad92e61de09..6a1b7fb3be4d98 100644 --- a/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js +++ b/packages/react-native/Libraries/NativeComponent/BaseViewConfig.android.js @@ -412,6 +412,9 @@ const validAttributesForEventProps = { onPointerOutCapture: true, onPointerOver: true, onPointerOverCapture: true, + + // Custom discord props: + preventClipping: true, } as const; /** diff --git a/packages/react-native/Libraries/Network/React-RCTNetwork.podspec b/packages/react-native/Libraries/Network/React-RCTNetwork.podspec index 8ffd5c2923dd91..e0eaca0aefc407 100644 --- a/packages/react-native/Libraries/Network/React-RCTNetwork.podspec +++ b/packages/react-native/Libraries/Network/React-RCTNetwork.podspec @@ -52,4 +52,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"]) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/Libraries/Pressability/Pressability.js b/packages/react-native/Libraries/Pressability/Pressability.js index ff70d67148fb2f..d96f80fe23ccaa 100644 --- a/packages/react-native/Libraries/Pressability/Pressability.js +++ b/packages/react-native/Libraries/Pressability/Pressability.js @@ -805,7 +805,7 @@ export default class Pressability { if (typeof this._responderID === 'number') { UIManager.measure(this._responderID, this._measureCallback); } else { - this._responderID.measure(this._measureCallback); + this._responderID.measure(this._measureCallback, true /* measureOnUI - will measure native view hierarchy */); } } diff --git a/packages/react-native/Libraries/PushNotificationIOS/React-RCTPushNotification.podspec b/packages/react-native/Libraries/PushNotificationIOS/React-RCTPushNotification.podspec index 487566103e2439..ffdf21320f6667 100644 --- a/packages/react-native/Libraries/PushNotificationIOS/React-RCTPushNotification.podspec +++ b/packages/react-native/Libraries/PushNotificationIOS/React-RCTPushNotification.podspec @@ -48,4 +48,6 @@ Pod::Spec.new do |s| add_dependency(s, "React-RCTFBReactNativeSpec") add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"]) add_dependency(s, "React-NativeModulesApple") + + add_rncore_dependency(s) end diff --git a/packages/react-native/Libraries/ReactNative/FabricUIManager.js b/packages/react-native/Libraries/ReactNative/FabricUIManager.js index 13b203f0abd953..99c42f31e943c2 100644 --- a/packages/react-native/Libraries/ReactNative/FabricUIManager.js +++ b/packages/react-native/Libraries/ReactNative/FabricUIManager.js @@ -43,10 +43,7 @@ export interface Spec { +appendChild: (parentNode: Node, child: Node) => Node; +appendChildToSet: (childSet: NodeSet, child: Node) => void; +completeRoot: (rootTag: RootTag, childSet: NodeSet) => void; - +measure: ( - node: Node | NativeElementReference, - callback: MeasureOnSuccessCallback, - ) => void; + +measure: (node: Node | NativeElementReference, callback: MeasureOnSuccessCallback, measureOnUI: Boolean) => void; +measureInWindow: ( node: Node | NativeElementReference, callback: MeasureInWindowOnSuccessCallback, diff --git a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js index aef54dee6681da..eb0f888792a19d 100644 --- a/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js +++ b/packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricHostComponent.js @@ -65,12 +65,12 @@ export default class ReactFabricHostComponent implements NativeMethods { TextInputState.focusTextInput(this); } - measure(callback: MeasureOnSuccessCallback) { + measure(callback: MeasureOnSuccessCallback, measureOnUI = false) { const node = getNodeFromInternalInstanceHandle( this.__internalInstanceHandle, ); if (node != null) { - fabricMeasure(node, callback); + fabricMeasure(node, callback, measureOnUI); } } diff --git a/packages/react-native/Libraries/Settings/React-RCTSettings.podspec b/packages/react-native/Libraries/Settings/React-RCTSettings.podspec index 1016a81ad6c665..8bd2c214f6015f 100644 --- a/packages/react-native/Libraries/Settings/React-RCTSettings.podspec +++ b/packages/react-native/Libraries/Settings/React-RCTSettings.podspec @@ -49,4 +49,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"]) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm b/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm index e9ce272b5f55fb..90988b8d60d656 100644 --- a/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm +++ b/packages/react-native/Libraries/Text/BaseText/RCTBaseTextViewManager.mm @@ -28,6 +28,10 @@ - (RCTShadowView *)shadowView // Color RCT_REMAP_SHADOW_PROPERTY(color, textAttributes.foregroundColor, UIColor) RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textAttributes.backgroundColor, UIColor) +RCT_REMAP_SHADOW_PROPERTY(gradientColors, textAttributes.gradientColors, NSArray) +RCT_REMAP_SHADOW_PROPERTY(gradientAngle, textAttributes.gradientAngle, CGFloat) +RCT_REMAP_SHADOW_PROPERTY(gradientWidth, textAttributes.gradientWidth, CGFloat) +RCT_REMAP_SHADOW_PROPERTY(gradientMode, textAttributes.gradientMode, NSString) RCT_REMAP_SHADOW_PROPERTY(opacity, textAttributes.opacity, CGFloat) // Font RCT_REMAP_SHADOW_PROPERTY(fontFamily, textAttributes.fontFamily, NSString) @@ -53,6 +57,9 @@ - (RCTShadowView *)shadowView RCT_REMAP_SHADOW_PROPERTY(textShadowOffset, textAttributes.textShadowOffset, CGSize) RCT_REMAP_SHADOW_PROPERTY(textShadowRadius, textAttributes.textShadowRadius, CGFloat) RCT_REMAP_SHADOW_PROPERTY(textShadowColor, textAttributes.textShadowColor, UIColor) +// Stroke +RCT_REMAP_SHADOW_PROPERTY(textStrokeWidth, textAttributes.textStrokeWidth, CGFloat) +RCT_REMAP_SHADOW_PROPERTY(textStrokeColor, textAttributes.textStrokeColor, UIColor) // Special RCT_REMAP_SHADOW_PROPERTY(isHighlighted, textAttributes.isHighlighted, BOOL) RCT_REMAP_SHADOW_PROPERTY(textTransform, textAttributes.textTransform, RCTTextTransform) diff --git a/packages/react-native/Libraries/Text/RCTTextAttributes.h b/packages/react-native/Libraries/Text/RCTTextAttributes.h index 1b72abe51df44e..4cf2a2d1ee0ddc 100644 --- a/packages/react-native/Libraries/Text/RCTTextAttributes.h +++ b/packages/react-native/Libraries/Text/RCTTextAttributes.h @@ -26,6 +26,10 @@ extern NSString *const RCTTextAttributesTagAttributeName; // Color @property (nonatomic, strong, nullable) UIColor *foregroundColor; @property (nonatomic, strong, nullable) UIColor *backgroundColor; +@property (nonatomic, copy, nullable) NSArray *gradientColors; +@property (nonatomic, assign) CGFloat gradientAngle; +@property (nonatomic, assign) CGFloat gradientWidth; // Width of gradient pattern in pixels; NAN = use default (100px) +@property (nonatomic, copy, nullable) NSString *gradientMode; // "mirror" (default) or "clamp" @property (nonatomic, assign) CGFloat opacity; // Font @property (nonatomic, copy, nullable) NSString *fontFamily; @@ -52,6 +56,9 @@ extern NSString *const RCTTextAttributesTagAttributeName; @property (nonatomic, assign) CGSize textShadowOffset; @property (nonatomic, assign) CGFloat textShadowRadius; @property (nonatomic, strong, nullable) UIColor *textShadowColor; +// Stroke +@property (nonatomic, assign) CGFloat textStrokeWidth; +@property (nonatomic, strong, nullable) UIColor *textStrokeColor; // Special @property (nonatomic, assign) BOOL isHighlighted; @property (nonatomic, strong, nullable) NSNumber *tag; diff --git a/packages/react-native/Libraries/Text/RCTTextAttributes.mm b/packages/react-native/Libraries/Text/RCTTextAttributes.mm index 43dd1f7c18ee0e..5d191f000fab3f 100644 --- a/packages/react-native/Libraries/Text/RCTTextAttributes.mm +++ b/packages/react-native/Libraries/Text/RCTTextAttributes.mm @@ -32,6 +32,8 @@ - (instancetype)init _textShadowRadius = NAN; _opacity = NAN; _textTransform = RCTTextTransformUndefined; + _textStrokeWidth = NAN; + _gradientAngle = NAN; } return self; @@ -46,6 +48,10 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes // Color _foregroundColor = textAttributes->_foregroundColor ?: _foregroundColor; _backgroundColor = textAttributes->_backgroundColor ?: _backgroundColor; + _gradientColors = textAttributes->_gradientColors ?: _gradientColors; + _gradientAngle = !isnan(textAttributes->_gradientAngle) ? textAttributes->_gradientAngle : _gradientAngle; + _gradientWidth = !isnan(textAttributes->_gradientWidth) ? textAttributes->_gradientWidth : _gradientWidth; + _gradientMode = textAttributes->_gradientMode ?: _gradientMode; _opacity = !isnan(textAttributes->_opacity) ? (isnan(_opacity) ? 1.0 : _opacity) * textAttributes->_opacity : _opacity; @@ -89,6 +95,10 @@ - (void)applyTextAttributes:(RCTTextAttributes *)textAttributes _textShadowRadius = !isnan(textAttributes->_textShadowRadius) ? textAttributes->_textShadowRadius : _textShadowRadius; _textShadowColor = textAttributes->_textShadowColor ?: _textShadowColor; + // Stroke + _textStrokeWidth = !isnan(textAttributes->_textStrokeWidth) ? textAttributes->_textStrokeWidth : _textStrokeWidth; + _textStrokeColor = textAttributes->_textStrokeColor ?: _textStrokeColor; + // Special _isHighlighted = textAttributes->_isHighlighted || _isHighlighted; // * _tag = textAttributes->_tag ?: _tag; @@ -209,6 +219,15 @@ - (NSParagraphStyle *)effectiveParagraphStyle attributes[NSShadowAttributeName] = shadow; } + // We don't use NSStrokeWidthAttributeName because it centers the stroke on the text path + // Instead, we do custom two-pass rendering to get true outer stroke + if (!isnan(_textStrokeWidth) && _textStrokeWidth > 0) { + UIColor *strokeColorToUse = _textStrokeColor ?: effectiveForegroundColor; + attributes[@"RCTTextStrokeWidth"] = @(_textStrokeWidth); + attributes[@"RCTTextStrokeColor"] = strokeColorToUse; + } + + // Special if (_isHighlighted) { attributes[RCTTextAttributesIsHighlightedAttributeName] = @YES; @@ -223,14 +242,52 @@ - (NSParagraphStyle *)effectiveParagraphStyle - (UIFont *)effectiveFont { - // FIXME: RCTFont has thread-safety issues and must be rewritten. - return [RCTFont updateFont:nil - withFamily:_fontFamily - size:@(isnan(_fontSize) ? 0 : _fontSize) - weight:_fontWeight - style:_fontStyle - variant:_fontVariant - scaleMultiplier:self.effectiveFontSizeMultiplier]; + NSArray *rawFontFamilies = [_fontFamily componentsSeparatedByString:@","]; + + // If _fontFamily is nil or has a single fontFamily, then use the original RN logic. + if (rawFontFamilies.count <= 1) { + // FIXME: RCTFont has thread-safety issues and must be rewritten. + return [RCTFont updateFont:nil + withFamily:_fontFamily + size:@(isnan(_fontSize) ? 0 : _fontSize) + weight:_fontWeight + style:_fontStyle + variant:_fontVariant + scaleMultiplier:self.effectiveFontSizeMultiplier]; + } + + NSMutableArray *fonts = [NSMutableArray new]; + for (NSString *rawFontFamily in rawFontFamilies) { + NSString *fontFamily = [rawFontFamily stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if (fontFamily.length == 0) { + continue; + } + + UIFont *font = [RCTFont updateFont:nil + withFamily:fontFamily + size:@(isnan(_fontSize) ? 0 : _fontSize) + weight:_fontWeight + style:_fontStyle + variant:_fontVariant + scaleMultiplier:self.effectiveFontSizeMultiplier]; + + if (font) { + [fonts addObject:font]; + } + } + + UIFont *primaryFont = fonts[0]; + + NSMutableArray *fontDescriptors = [NSMutableArray new]; + for (NSUInteger i = 1; i < fonts.count; i++) { + UIFont *font = fonts[i]; + [fontDescriptors addObject:font.fontDescriptor]; + } + + UIFontDescriptor *fontDescriptor = [primaryFont.fontDescriptor fontDescriptorByAddingAttributes: + @{UIFontDescriptorCascadeListAttribute: fontDescriptors}]; + + return [UIFont fontWithDescriptor:fontDescriptor size:primaryFont.pointSize]; } - (CGFloat)effectiveFontSizeMultiplier @@ -256,6 +313,48 @@ - (UIColor *)effectiveForegroundColor { UIColor *effectiveForegroundColor = _foregroundColor ?: [UIColor blackColor]; + if (_gradientColors != nil) { + NSMutableArray *cgColors = [NSMutableArray array]; + for (NSNumber *rawColor in _gradientColors) { + if (rawColor != nil) { + UIColor *color = [RCTConvert UIColor:@((0xFF << 24) | [rawColor integerValue])]; + [cgColors addObject:(id)color.CGColor]; + } + } + + if([cgColors count] > 0) { + // Only duplicate first color at end for mirror mode (default) + BOOL isClampMode = [_gradientMode isEqualToString:@"clamp"]; + if (!isClampMode) { + [cgColors addObject:cgColors[0]]; + } + CAGradientLayer *gradient = [CAGradientLayer layer]; + // Use gradientWidth if specified, otherwise default to 100 + CGFloat patternWidth = (!isnan(_gradientWidth) && _gradientWidth > 0) ? _gradientWidth : 100; + CGFloat height = _lineHeight * self.effectiveFontSizeMultiplier; + gradient.frame = CGRectMake(0, 0, patternWidth, height); + gradient.colors = cgColors; + + CGFloat angle = !isnan(_gradientAngle) ? _gradientAngle : 0.0; + CGFloat radians = angle * M_PI / 180.0; + + CGFloat startX = 0.5 - 0.5 * cos(radians); + CGFloat startY = 0.5 - 0.5 * sin(radians); + CGFloat endX = 0.5 + 0.5 * cos(radians); + CGFloat endY = 0.5 + 0.5 * sin(radians); + + gradient.startPoint = CGPointMake(startX, startY); + gradient.endPoint = CGPointMake(endX, endY); + + UIGraphicsBeginImageContextWithOptions(gradient.frame.size, NO, 0.0); + [gradient renderInContext:UIGraphicsGetCurrentContext()]; + UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + effectiveForegroundColor = [UIColor colorWithPatternImage:gradientImage]; + } + } + if (!isnan(_opacity)) { effectiveForegroundColor = [effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * _opacity]; @@ -329,6 +428,8 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes #define RCTTextAttributesCompareOthers(a) (a == textAttributes->a) return RCTTextAttributesCompareObjects(_foregroundColor) && RCTTextAttributesCompareObjects(_backgroundColor) && + RCTTextAttributesCompareObjects(_gradientColors) && RCTTextAttributesCompareFloats(_gradientAngle) && + RCTTextAttributesCompareFloats(_gradientWidth) && RCTTextAttributesCompareStrings(_gradientMode) && RCTTextAttributesCompareFloats(_opacity) && // Font RCTTextAttributesCompareObjects(_fontFamily) && RCTTextAttributesCompareFloats(_fontSize) && @@ -346,6 +447,8 @@ - (BOOL)isEqual:(RCTTextAttributes *)textAttributes // Shadow RCTTextAttributesCompareSize(_textShadowOffset) && RCTTextAttributesCompareFloats(_textShadowRadius) && RCTTextAttributesCompareObjects(_textShadowColor) && + // Stroke + RCTTextAttributesCompareFloats(_textStrokeWidth) && RCTTextAttributesCompareObjects(_textStrokeColor) && // Special RCTTextAttributesCompareOthers(_isHighlighted) && RCTTextAttributesCompareObjects(_tag) && RCTTextAttributesCompareOthers(_layoutDirection) && RCTTextAttributesCompareOthers(_textTransform); diff --git a/packages/react-native/Libraries/Text/Text.d.ts b/packages/react-native/Libraries/Text/Text.d.ts index 9133cecaf3d88b..0879f07785f226 100644 --- a/packages/react-native/Libraries/Text/Text.d.ts +++ b/packages/react-native/Libraries/Text/Text.d.ts @@ -218,6 +218,38 @@ export interface TextProps pressRetentionOffset?: | {top: number; left: number; bottom: number; right: number} | undefined; + + /** + * Adds a horizontal gradient using the int based color values. + */ + gradientColors?: number[] | undefined; + + /** + * Gradient angle in degrees. Default is 0 (horizontal). + */ + gradientAngle?: number | undefined; + + /** + * Width of the gradient pattern in pixels. Default is 100. + */ + gradientWidth?: number | undefined; + + /** + * Gradient tiling mode. "mirror" (default) tiles the gradient back and forth. + * "clamp" renders the gradient once from start to end colors. + * When using "clamp", set gradientWidth to match your text width. + */ + gradientMode?: 'mirror' | 'clamp' | undefined; + + /** + * Width of the text stroke (outline). Creates an outer stroke effect. + */ + textStrokeWidth?: number | undefined; + + /** + * Color of the text stroke (outline). + */ + textStrokeColor?: ColorValue | undefined; } /** diff --git a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm index 6c815d267eefcd..0b34a8629bd257 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextShadowView.mm @@ -405,13 +405,28 @@ - (CGFloat)lastBaselineForSize:(CGSize)size [attributedText enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attributedText.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired - usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { - if (maximumDescender > font.descender) { - maximumDescender = font.descender; - } - }]; + usingBlock:^(UIFont *font, NSRange range, __unused BOOL *stop) { + if (maximumDescender > font.descender) { + maximumDescender = font.descender; + } + }]; + + // Account for stroke width in baseline calculation + __block CGFloat strokeWidth = 0; + [attributedText enumerateAttribute:@"RCTTextStrokeWidth" + inRange:NSMakeRange(0, attributedText.length) + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (value && [value isKindOfClass:[NSNumber class]]) { + CGFloat width = [value floatValue]; + if (width > 0) { + strokeWidth = MAX(strokeWidth, width); + *stop = YES; + } + } + }]; - return size.height + maximumDescender; + return size.height + maximumDescender + strokeWidth; } static YGSize RCTTextShadowViewMeasure( @@ -441,6 +456,27 @@ static YGSize RCTTextShadowViewMeasure( size.width -= letterSpacing; } + // Account for text stroke width (similar to Android implementation) + // Check if text has custom stroke attribute and add extra space + __block CGFloat strokeWidth = 0; + [textStorage enumerateAttribute:@"RCTTextStrokeWidth" + inRange:NSMakeRange(0, textStorage.length) + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (value && [value isKindOfClass:[NSNumber class]]) { + CGFloat width = [value floatValue]; + if (width > 0) { + strokeWidth = MAX(strokeWidth, width); + *stop = YES; + } + } + }]; + + if (strokeWidth > 0) { + size.width += strokeWidth; + size.height += strokeWidth; + } + size = (CGSize){ MIN(RCTCeilPixelValue(size.width), maximumSize.width), MIN(RCTCeilPixelValue(size.height), maximumSize.height)}; diff --git a/packages/react-native/Libraries/Text/Text/RCTTextView.mm b/packages/react-native/Libraries/Text/Text/RCTTextView.mm index 47632aa885cdb3..0d92ec3dee0ff1 100644 --- a/packages/react-native/Libraries/Text/Text/RCTTextView.mm +++ b/packages/react-native/Libraries/Text/Text/RCTTextView.mm @@ -7,6 +7,7 @@ #import +#import #import #import @@ -119,10 +120,88 @@ - (void)drawRect:(CGRect)rect NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer]; [layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:_contentFrame.origin]; - [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin]; - __block UIBezierPath *highlightPath = nil; + // Check if text has custom stroke attribute NSRange characterRange = [layoutManager characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL]; + __block BOOL hasStroke = NO; + __block CGFloat strokeWidth = 0; + __block UIColor *strokeColor = nil; + + [_textStorage enumerateAttribute:@"RCTTextStrokeWidth" + inRange:characterRange + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (value && [value isKindOfClass:[NSNumber class]]) { + CGFloat width = [value floatValue]; + if (width > 0) { + hasStroke = YES; + strokeWidth = width; + strokeColor = [_textStorage attribute:@"RCTTextStrokeColor" atIndex:range.location effectiveRange:NULL]; + + if (strokeColor) { + CGFloat r, g, b, a; + [strokeColor getRed:&r green:&g blue:&b alpha:&a]; + } + *stop = YES; + } + } + }]; + + if (hasStroke && strokeColor) { + CGContextRef context = UIGraphicsGetCurrentContext(); + + CGContextSetLineWidth(context, strokeWidth); + CGContextSetLineJoin(context, kCGLineJoinRound); + CGContextSetLineCap(context, kCGLineCapRound); + + CGFloat strokeInset = strokeWidth / 2; + + // PASS 1: Draw stroke outline + CGContextSaveGState(context); + CGContextSetTextDrawingMode(context, kCGTextStroke); + + NSMutableAttributedString *strokeText = [_textStorage mutableCopy]; + [strokeText addAttribute:NSForegroundColorAttributeName + value:strokeColor + range:characterRange]; + + CGContextSetTextMatrix(context, CGAffineTransformIdentity); + CGContextTranslateCTM(context, _contentFrame.origin.x + strokeInset, self.bounds.size.height - _contentFrame.origin.y + strokeInset); + CGContextScaleCTM(context, 1.0, -1.0); + + CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)strokeText); + CGMutablePathRef path = CGPathCreateMutable(); + CGPathAddRect(path, NULL, CGRectMake(0, 0, _contentFrame.size.width, _contentFrame.size.height)); + CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); + CTFrameDraw(frame, context); + CFRelease(frame); + CFRelease(path); + CFRelease(framesetter); + CGContextRestoreGState(context); + + // PASS 2: Draw fill on top + CGContextSaveGState(context); + CGContextSetTextDrawingMode(context, kCGTextFill); + + CGContextSetTextMatrix(context, CGAffineTransformIdentity); + CGContextTranslateCTM(context, _contentFrame.origin.x + strokeInset, self.bounds.size.height - _contentFrame.origin.y + strokeInset); + CGContextScaleCTM(context, 1.0, -1.0); + + framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)_textStorage); + path = CGPathCreateMutable(); + CGPathAddRect(path, NULL, CGRectMake(0, 0, _contentFrame.size.width, _contentFrame.size.height)); + frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); + CTFrameDraw(frame, context); + CFRelease(frame); + CFRelease(path); + CFRelease(framesetter); + CGContextRestoreGState(context); + + } else { + [layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:_contentFrame.origin]; + } + + __block UIBezierPath *highlightPath = nil; [_textStorage enumerateAttribute:RCTTextAttributesIsHighlightedAttributeName inRange:characterRange diff --git a/packages/react-native/Libraries/Text/TextNativeComponent.js b/packages/react-native/Libraries/Text/TextNativeComponent.js index 709dd5613ede4d..c7f3091f5eb336 100644 --- a/packages/react-native/Libraries/Text/TextNativeComponent.js +++ b/packages/react-native/Libraries/Text/TextNativeComponent.js @@ -47,6 +47,12 @@ const textViewConfig = { dataDetectorType: true, android_hyphenationFrequency: true, lineBreakStrategyIOS: true, + gradientColors: true, + gradientAngle: true, + gradientWidth: true, + gradientMode: true, + textStrokeWidth: true, + textStrokeColor: true, }, directEventTypes: { topTextLayout: { @@ -61,6 +67,12 @@ const virtualTextViewConfig = { isHighlighted: true, isPressable: true, maxFontSizeMultiplier: true, + gradientColors: true, + gradientAngle: true, + gradientWidth: true, + gradientMode: true, + textStrokeWidth: true, + textStrokeColor: true, }, uiViewClassName: 'RCTVirtualText', }; diff --git a/packages/react-native/Libraries/Vibration/React-RCTVibration.podspec b/packages/react-native/Libraries/Vibration/React-RCTVibration.podspec index 2b8212346cbf7d..9768c13d4b3818 100644 --- a/packages/react-native/Libraries/Vibration/React-RCTVibration.podspec +++ b/packages/react-native/Libraries/Vibration/React-RCTVibration.podspec @@ -49,4 +49,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-NativeModulesApple") add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/Libraries/WebSocket/WebSocket.js b/packages/react-native/Libraries/WebSocket/WebSocket.js index 36aafbbbab176c..67e4cfea6b6921 100644 --- a/packages/react-native/Libraries/WebSocket/WebSocket.js +++ b/packages/react-native/Libraries/WebSocket/WebSocket.js @@ -245,7 +245,8 @@ class WebSocket extends EventTarget { data = BlobManager.createFromOptions(ev.data); break; } - this.dispatchEvent(new MessageEvent('message', {data})); + const raw_length = ev.raw_length; + this.dispatchEvent(new MessageEvent('message', {data, raw_length})); }), this._eventEmitter.addListener('websocketOpen', ev => { if (ev.id !== this._socketId) { diff --git a/packages/react-native/Package.swift b/packages/react-native/Package.swift index c387085ea2368f..c748b549463b23 100644 --- a/packages/react-native/Package.swift +++ b/packages/react-native/Package.swift @@ -84,7 +84,10 @@ let reactDebug = RNTarget( let jsi = RNTarget( name: .jsi, path: "ReactCommon/jsi", - excludedPaths: ["jsi/test", "CMakeLists.txt", "jsi/CMakeLists.txt"], + // JSI is a part of hermes-engine. Including them also in react-native will violate the One Definition Rule. + // Precompiled binaries are only supported with hermes - so we can safely exclude the jsi.cpp file. + // https://github.com/facebook/react-native/issues/53257 + excludedPaths: ["jsi/test", "jsi/jsi.cpp", "CMakeLists.txt", "jsi/CMakeLists.txt"], dependencies: [.reactNativeDependencies] ) @@ -421,6 +424,7 @@ let reactFabricComponents = RNTarget( "components/view/platform/android", "components/view/platform/windows", "components/view/platform/macos", + "components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm", "components/textinput/platform/android", "components/text/platform/android", "components/textinput/platform/macos", @@ -433,7 +437,7 @@ let reactFabricComponents = RNTarget( "conponents/rncore", // this was the old folder where RN Core Components were generated. If you ran codegen in the past, you might have some files in it that might make the build fail. ], dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging], - sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "textlayoutmanager", "textlayoutmanager/platform/ios"] + sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "textlayoutmanager", "textlayoutmanager/platform/ios", "components/switch/iosswitch"] ) /// React-FabricImage.podspec diff --git a/packages/react-native/React-Core-prebuilt.podspec b/packages/react-native/React-Core-prebuilt.podspec index 65a4a448345968..4892ce8ab2785d 100644 --- a/packages/react-native/React-Core-prebuilt.podspec +++ b/packages/react-native/React-Core-prebuilt.podspec @@ -62,8 +62,7 @@ Pod::Spec.new do |s| CONFIG="Debug" fi - # TODO(T228219721): Add this for React Native Core as well - ##### "$NODE_BINARY" "$REACT_NATIVE_PATH/third-party-podspecs/replace_dependencies_version.js" -c "$CONFIG" -r "#{version}" -p "$PODS_ROOT" + "$NODE_BINARY" "$REACT_NATIVE_PATH/scripts/replace-rncore-version.js" -c "$CONFIG" -r "#{version}" -p "$PODS_ROOT" EOS } @@ -73,7 +72,7 @@ Pod::Spec.new do |s| # always run the script without warning script_phase[:always_out_of_date] = "1" end - + s.script_phase = script_phase end end diff --git a/packages/react-native/React-Core.podspec b/packages/react-native/React-Core.podspec index 795801379b9101..63eb78aeb59acb 100644 --- a/packages/react-native/React-Core.podspec +++ b/packages/react-native/React-Core.podspec @@ -83,10 +83,6 @@ Pod::Spec.new do |s| ss.exclude_files = exclude_files ss.private_header_files = "React/Cxx*/*.h" - # Include prebuilt if we're not building from source - if !ReactNativeCoreUtils.build_rncore_from_source() - ss.dependency "React-Core-prebuilt", version - end end s.subspec "DevSupport" do |ss| @@ -137,4 +133,5 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/React/Base/RCTDisplayLink.m b/packages/react-native/React/Base/RCTDisplayLink.m index de7c8700f3e5c6..a33799d9f9cdcc 100644 --- a/packages/react-native/React/Base/RCTDisplayLink.m +++ b/packages/react-native/React/Base/RCTDisplayLink.m @@ -19,7 +19,8 @@ #define RCTAssertRunLoop() \ RCTAssert(_runLoop == [NSRunLoop currentRunLoop], @"This method must be called on the CADisplayLink run loop") -@implementation RCTDisplayLink { +@implementation RCTDisplayLink +{ CADisplayLink *_jsDisplayLink; NSMutableSet> *_frameUpdateObservers; NSRunLoop *_runLoop; diff --git a/packages/react-native/React/Base/RCTUtils.h b/packages/react-native/React/Base/RCTUtils.h index 1f3c29b00629ba..9df03fb29d8470 100644 --- a/packages/react-native/React/Base/RCTUtils.h +++ b/packages/react-native/React/Base/RCTUtils.h @@ -54,6 +54,7 @@ RCT_EXTERN CGFloat RCTScreenScale(void); RCT_EXTERN CGFloat RCTFontSizeMultiplier(void); RCT_EXTERN CGSize RCTScreenSize(void); RCT_EXTERN CGSize RCTViewportSize(void); +RCT_EXTERN CGSize RCTSwitchSize(void); // Round float coordinates to nearest whole screen pixel (not point) RCT_EXTERN CGFloat RCTRoundPixelValue(CGFloat value); diff --git a/packages/react-native/React/Base/RCTUtils.mm b/packages/react-native/React/Base/RCTUtils.mm index dffe4dc800ee97..6d89ba623039e3 100644 --- a/packages/react-native/React/Base/RCTUtils.mm +++ b/packages/react-native/React/Base/RCTUtils.mm @@ -426,6 +426,18 @@ CGSize RCTViewportSize(void) return window ? window.bounds.size : RCTScreenSize(); } +CGSize RCTSwitchSize(void) +{ + static CGSize rctSwitchSize; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + RCTUnsafeExecuteOnMainQueueSync(^{ + rctSwitchSize = [UISwitch new].intrinsicContentSize; + }); + }); + return rctSwitchSize; +} + CGFloat RCTRoundPixelValue(CGFloat value) { CGFloat scale = RCTScreenScale(); diff --git a/packages/react-native/React/Base/RCTVersion.m b/packages/react-native/React/Base/RCTVersion.m index 69ce4f75320e63..5265b86b2feb0e 100644 --- a/packages/react-native/React/Base/RCTVersion.m +++ b/packages/react-native/React/Base/RCTVersion.m @@ -21,9 +21,9 @@ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^(void){ __rnVersion = @{ - RCTVersionMajor: @(1000), - RCTVersionMinor: @(0), - RCTVersionPatch: @(0), + RCTVersionMajor: @(0), + RCTVersionMinor: @(81), + RCTVersionPatch: @(4), RCTVersionPrerelease: [NSNull null], }; }); diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index e1efd2f73425ff..e32a5f9134c21d 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -99,7 +99,7 @@ - (void)initialize _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(interfaceFrameDidChange) + selector:@selector(interfaceFrameDidChangeAsync) name:UIDeviceOrientationDidChangeNotification object:nil]; #endif @@ -276,6 +276,23 @@ - (void)interfaceFrameDidChange RCTExecuteOnMainQueue(^{ [weakSelf _interfaceFrameDidChange]; }); + +} + +// Because this RCTDeviceInfo uses dispatch_get_main_queue, RCTExecuteOnMainQueue +// as specified in the interfaceFrameDidChange method will run without delay. +// +// The call to get window measurements may not be accurate as the window +// may not have updated yet. To ensure we get the correct window measurements +// use `dispatch_async` to delay a tick and wait for the window to update. +// +// This bug was observed and reproduced on an iPad but may exist for iPhone as well. +- (void)interfaceFrameDidChangeAsync +{ + __weak __typeof(self) weakSelf = self; + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf _interfaceFrameDidChange]; + }); } - (void)_interfaceFrameDidChange diff --git a/packages/react-native/React/CoreModules/RCTWebSocketModule.h b/packages/react-native/React/CoreModules/RCTWebSocketModule.h index f92bfc42e3aea3..cf9da4ed2a8f18 100644 --- a/packages/react-native/React/CoreModules/RCTWebSocketModule.h +++ b/packages/react-native/React/CoreModules/RCTWebSocketModule.h @@ -25,6 +25,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)sendData:(NSData *)data forSocketID:(nonnull NSNumber *)socketID; +// Blocking call that waits until there are no more remaining actions on the queue +- (void)flush; + @end @interface RCTBridge (RCTWebSocketModule) diff --git a/packages/react-native/React/CoreModules/RCTWebSocketModule.mm b/packages/react-native/React/CoreModules/RCTWebSocketModule.mm index 3fd7238911f559..eb44017e2d50b2 100644 --- a/packages/react-native/React/CoreModules/RCTWebSocketModule.mm +++ b/packages/react-native/React/CoreModules/RCTWebSocketModule.mm @@ -51,6 +51,14 @@ - (NSArray *)supportedEvents return @[ @"websocketMessage", @"websocketOpen", @"websocketFailed", @"websocketClosed" ]; } + +- (void)flush +{ + for (SRWebSocket *socket in _sockets.allValues) { + [socket close]; + } +} + - (void)invalidate { [super invalidate]; @@ -167,7 +175,6 @@ - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { NSNumber *socketID = [webSocket reactTag]; _contentHandlers[socketID] = nil; - _sockets[socketID] = nil; NSDictionary *body = @{@"message" : error.localizedDescription ?: @"Undefined, error is nil", @"id" : socketID ?: @(-1)}; [self sendEventWithName:@"websocketFailed" body:body]; @@ -180,7 +187,6 @@ - (void)webSocket:(SRWebSocket *)webSocket { NSNumber *socketID = [webSocket reactTag]; _contentHandlers[socketID] = nil; - _sockets[socketID] = nil; [self sendEventWithName:@"websocketClosed" body:@{ @"code" : @(code), diff --git a/packages/react-native/React/CoreModules/React-CoreModules.podspec b/packages/react-native/React/CoreModules/React-CoreModules.podspec index be613510df54a4..6049b7d6b9f8f4 100644 --- a/packages/react-native/React/CoreModules/React-CoreModules.podspec +++ b/packages/react-native/React/CoreModules/React-CoreModules.podspec @@ -63,4 +63,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-NativeModulesApple") add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm index af486993a3f5a5..afb2016fe9703e 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm @@ -96,6 +96,8 @@ static UIModalPresentationStyle presentationConfiguration(const ModalHostViewPro @interface RCTModalHostViewComponentView () +@property (nonatomic, weak) UIView *accessibilityFocusedView; + @end @implementation RCTModalHostViewComponentView { @@ -148,6 +150,7 @@ - (void)ensurePresentedOnlyIfNeeded { BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window; if (shouldBePresented) { + [self saveAccessibilityFocusedView]; self.viewController.presentationController.delegate = self; _isPresented = YES; @@ -179,6 +182,8 @@ - (void)ensurePresentedOnlyIfNeeded if (eventEmitter) { eventEmitter->onDismiss(ModalHostViewEventEmitter::OnDismiss{}); } + + [self restoreAccessibilityFocusedView]; }]; } } @@ -207,6 +212,23 @@ - (void)didMoveToSuperview [self ensurePresentedOnlyIfNeeded]; } +- (void)saveAccessibilityFocusedView +{ + id focusedElement = UIAccessibilityFocusedElement(nil); + if (focusedElement && [focusedElement isKindOfClass:[UIView class]]) { + self.accessibilityFocusedView = (UIView *)focusedElement; + } +} + +- (void)restoreAccessibilityFocusedView +{ + id viewToFocus = self.accessibilityFocusedView; + if (viewToFocus) { + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, viewToFocus); + self.accessibilityFocusedView = nil; + } +} + #pragma mark - RCTFabricModalHostViewControllerDelegate - (void)boundsDidChange:(CGRect)newBounds diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/Switch/RCTSwitchComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/Switch/RCTSwitchComponentView.mm index 552dd5def91afa..ba836bf5fd441f 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/Switch/RCTSwitchComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/Switch/RCTSwitchComponentView.mm @@ -9,10 +9,10 @@ #import -#import #import #import #import +#import #import "RCTFabricComponentsPlugins.h" diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index b5d724226fbe93..577bebe144b23e 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -22,6 +22,7 @@ #import "RCTTextInputNativeCommands.h" #import "RCTTextInputUtils.h" +#import #import "RCTFabricComponentsPlugins.h" /** Native iOS text field bottom keyboard offset amount */ @@ -447,7 +448,7 @@ - (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range } } - if (props.maxLength) { + if (props.maxLength < std::numeric_limits::max()) { NSInteger allowedLength = props.maxLength - _backedTextInputView.attributedText.string.length + range.length; if (allowedLength > 0 && text.length > allowedLength) { diff --git a/packages/react-native/React/Fabric/RCTScheduler.h b/packages/react-native/React/Fabric/RCTScheduler.h index ed585390890b1d..a5e310f8bc2d70 100644 --- a/packages/react-native/React/Fabric/RCTScheduler.h +++ b/packages/react-native/React/Fabric/RCTScheduler.h @@ -43,6 +43,10 @@ NS_ASSUME_NONNULL_BEGIN forShadowView:(const facebook::react::ShadowView &)shadowView; - (void)schedulerDidSynchronouslyUpdateViewOnUIThread:(facebook::react::Tag)reactTag props:(folly::dynamic)props; + +- (void)schedulerMeasure:(const facebook::react::ShadowView &)shadowView + jsCallback:(std::function)jsCallback; + @end /** diff --git a/packages/react-native/React/Fabric/RCTScheduler.mm b/packages/react-native/React/Fabric/RCTScheduler.mm index 6cc4e5b6feb730..58a0ca09305fac 100644 --- a/packages/react-native/React/Fabric/RCTScheduler.mm +++ b/packages/react-native/React/Fabric/RCTScheduler.mm @@ -65,6 +65,12 @@ void schedulerDidSendAccessibilityEvent(const ShadowView &shadowView, const std: RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; [scheduler.delegate schedulerDidSendAccessibilityEvent:shadowView eventType:eventType]; } + + + void schedulerMeasure(const ShadowView& shadowView, std::function jsCallback) override { + RCTScheduler *scheduler = (__bridge RCTScheduler *)scheduler_; + [scheduler.delegate schedulerMeasure:shadowView jsCallback:jsCallback]; + } void schedulerShouldSynchronouslyUpdateViewOnUIThread(facebook::react::Tag tag, const folly::dynamic &props) override { diff --git a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm index 1e675096b3121d..5434977848013e 100644 --- a/packages/react-native/React/Fabric/RCTSurfacePresenter.mm +++ b/packages/react-native/React/Fabric/RCTSurfacePresenter.mm @@ -348,6 +348,15 @@ - (void)removeObserver:(id)observer } } +- (void)schedulerMeasure:(const facebook::react::ShadowView &)shadowView jsCallback:(std::function)jsCallback { + // TODO: do we need to implement this on iOS? It seems to _just work_ +// dispatch_async(dispatch_get_main_queue(), ^{ +// ReactTag tag = shadowView.tag; +// UIView *componentView = +// [self->_mountingManager.componentViewRegistry findComponentViewWithTag:tag]; +// }); +} + #pragma mark - RCTMountingManagerDelegate - (void)mountingManager:(RCTMountingManager *)mountingManager willMountComponentsWithRootTag:(ReactTag)rootTag diff --git a/packages/react-native/React/React-RCTFBReactNativeSpec.podspec b/packages/react-native/React/React-RCTFBReactNativeSpec.podspec index 0920534b444a4b..1abb84e7e47327 100644 --- a/packages/react-native/React/React-RCTFBReactNativeSpec.podspec +++ b/packages/react-native/React/React-RCTFBReactNativeSpec.podspec @@ -45,10 +45,7 @@ Pod::Spec.new do |s| "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = 'FBReactNativeSpec' - s.module_name = 'React_RCTFBReactNativeSpec' - end + resolve_use_frameworks(s, header_mappings_dir: 'FBReactNativeSpec', module_name: "React_RCTFBReactNativeSpec") s.dependency "React-jsi" s.dependency "RCTRequired" @@ -61,6 +58,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.subspec "components" do |ss| ss.source_files = podspec_sources("FBReactNativeSpec/react/renderer/components/FBReactNativeSpec/**/*.{m,mm,cpp,h}", "FBReactNativeSpec/react/renderer/components/FBReactNativeSpec/**/*.{h}") diff --git a/packages/react-native/React/React-RCTFabric.podspec b/packages/react-native/React/React-RCTFabric.podspec index 5dd782ed308a5d..5a45368601174c 100644 --- a/packages/react-native/React/React-RCTFabric.podspec +++ b/packages/react-native/React/React-RCTFabric.podspec @@ -75,6 +75,7 @@ Pod::Spec.new do |s| "react/renderer/components/scrollview/platform/cxx", "react/renderer/components/text/platform/cxx", "react/renderer/components/textinput/platform/ios", + "react/renderer/components/switch/iosswitch", ]); add_dependency(s, "React-graphics", :additional_framework_paths => ["react/renderer/graphics/platform/ios"]) @@ -97,6 +98,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.test_spec 'Tests' do |test_spec| test_spec.source_files = podspec_sources("Tests/**/*.{mm}", "") diff --git a/packages/react-native/React/Runtime/React-RCTRuntime.podspec b/packages/react-native/React/Runtime/React-RCTRuntime.podspec index f0984dc0601a9f..cd5f7f8bd3be2f 100644 --- a/packages/react-native/React/Runtime/React-RCTRuntime.podspec +++ b/packages/react-native/React/Runtime/React-RCTRuntime.podspec @@ -35,9 +35,7 @@ Pod::Spec.new do |s| s.header_dir = header_dir s.module_name = module_name - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = "./" - end + resolve_use_frameworks(s, header_mappings_dir: "./") s.pod_target_xcconfig = { "OTHER_CFLAGS" => "$(inherited) " + new_arch_flags, @@ -68,4 +66,5 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/React/Views/RCTSwitchManager.m b/packages/react-native/React/Views/RCTSwitchManager.m index b04b51f844e11a..9f47d5eb7f0eba 100644 --- a/packages/react-native/React/Views/RCTSwitchManager.m +++ b/packages/react-native/React/Views/RCTSwitchManager.m @@ -49,7 +49,17 @@ - (void)onChange:(RCTSwitch *)sender RCT_EXPORT_VIEW_PROPERTY(onTintColor, UIColor); RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor); RCT_EXPORT_VIEW_PROPERTY(thumbTintColor, UIColor); -RCT_REMAP_VIEW_PROPERTY(value, on, BOOL); +//RCT_REMAP_VIEW_PROPERTY(value, on, BOOL); +RCT_CUSTOM_VIEW_PROPERTY(value, BOOL, RCTSwitch) +{ + if (json) { + BOOL on = [RCTConvert BOOL:json]; + if (view.wasOn != on) { + [(UISwitch *)view setOn:on animated:YES]; + view.wasOn = on; + } + } +} RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock); RCT_CUSTOM_VIEW_PROPERTY(disabled, BOOL, RCTSwitch) { diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index fc70f58c19792f..c2f928e276c000 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -31,8 +31,8 @@ public abstract class com/facebook/react/HeadlessJsTaskService : android/app/Ser public fun ()V public static final fun acquireWakeLockNow (Landroid/content/Context;)V protected final fun getReactContext ()Lcom/facebook/react/bridge/ReactContext; - protected final fun getReactHost ()Lcom/facebook/react/ReactHost; - protected final fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost; + protected fun getReactHost ()Lcom/facebook/react/ReactHost; + protected fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost; protected fun getTaskConfig (Landroid/content/Intent;)Lcom/facebook/react/jstasks/HeadlessJsTaskConfig; public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder; public fun onDestroy ()V @@ -353,7 +353,7 @@ public abstract class com/facebook/react/ReactNativeHost { } public abstract interface class com/facebook/react/ReactPackage { - public abstract fun createNativeModules (Lcom/facebook/react/bridge/ReactApplicationContext;)Ljava/util/List; + public fun createNativeModules (Lcom/facebook/react/bridge/ReactApplicationContext;)Ljava/util/List; public abstract fun createViewManagers (Lcom/facebook/react/bridge/ReactApplicationContext;)Ljava/util/List; public fun getModule (Ljava/lang/String;Lcom/facebook/react/bridge/ReactApplicationContext;)Lcom/facebook/react/bridge/NativeModule; } @@ -1738,36 +1738,20 @@ public final class com/facebook/react/common/SystemClock { public static final fun uptimeMillis ()J } -public final class com/facebook/react/common/assets/ReactFontManager { - public static final field Companion Lcom/facebook/react/common/assets/ReactFontManager$Companion; - public fun ()V - public final fun addCustomFont (Landroid/content/Context;Ljava/lang/String;I)V - public final fun addCustomFont (Ljava/lang/String;Landroid/graphics/Typeface;)V - public static final fun getInstance ()Lcom/facebook/react/common/assets/ReactFontManager; - public final fun getTypeface (Ljava/lang/String;IILandroid/content/res/AssetManager;)Landroid/graphics/Typeface; - public final fun getTypeface (Ljava/lang/String;ILandroid/content/res/AssetManager;)Landroid/graphics/Typeface; - public final fun getTypeface (Ljava/lang/String;IZLandroid/content/res/AssetManager;)Landroid/graphics/Typeface; - public final fun getTypeface (Ljava/lang/String;Lcom/facebook/react/common/assets/ReactFontManager$TypefaceStyle;Landroid/content/res/AssetManager;)Landroid/graphics/Typeface; - public final fun setTypeface (Ljava/lang/String;ILandroid/graphics/Typeface;)V +public abstract interface annotation class com/facebook/react/common/annotations/DeprecatedInNewArchitecture : java/lang/annotation/Annotation { + public abstract fun message ()Ljava/lang/String; } -public final class com/facebook/react/common/assets/ReactFontManager$Companion { - public final fun getInstance ()Lcom/facebook/react/common/assets/ReactFontManager; +public abstract interface annotation class com/facebook/react/common/annotations/FrameworkAPI : java/lang/annotation/Annotation { } -public final class com/facebook/react/common/assets/ReactFontManager$TypefaceStyle { - public static final field BOLD I - public static final field Companion Lcom/facebook/react/common/assets/ReactFontManager$TypefaceStyle$Companion; - public static final field NORMAL I - public fun (I)V - public fun (II)V - public synthetic fun (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun (IZ)V - public final fun apply (Landroid/graphics/Typeface;)Landroid/graphics/Typeface; - public final fun getNearestStyle ()I +public abstract interface annotation class com/facebook/react/common/annotations/StableReactNativeAPI : java/lang/annotation/Annotation { +} + +public abstract interface annotation class com/facebook/react/common/annotations/UnstableReactNativeAPI : java/lang/annotation/Annotation { } -public final class com/facebook/react/common/assets/ReactFontManager$TypefaceStyle$Companion { +public abstract interface annotation class com/facebook/react/common/annotations/VisibleForTesting : java/lang/annotation/Annotation { } public final class com/facebook/react/common/build/ReactBuildConfig { diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 1b3991d467933c..52daf1a2892b8e 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -16,9 +16,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("maven-publish") id("com.facebook.react") - alias(libs.plugins.android.library) - alias(libs.plugins.download) - alias(libs.plugins.kotlin.android) + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.download.get().pluginId) + id(libs.plugins.kotlin.android.get().pluginId) } version = project.findProperty("VERSION_NAME")?.toString()!! @@ -91,6 +91,8 @@ val preparePrefab by Pair("src/main/jni/react/fabric", "react/fabric/"), // glog Pair(File(buildDir, "third-party-ndk/glog/exported/").absolutePath, ""), + // fbgloginit (exports fb/glog_init.h) + Pair("src/main/jni/first-party/fbgloginit", ""), // jsiinpsector Pair("../ReactCommon/jsinspector-modern/", "jsinspector-modern/"), // mapbufferjni @@ -112,6 +114,8 @@ val preparePrefab by Pair( "../ReactCommon/react/renderer/animations/", "react/renderer/animations/"), + // react_renderer_bridging + Pair("../ReactCommon/react/renderer/bridging/", "react/renderer/bridging/"), // react_renderer_componentregistry Pair( "../ReactCommon/react/renderer/componentregistry/", @@ -459,6 +463,65 @@ fun enableWarningsAsErrors(): Boolean { return value?.toString()?.toBoolean() ?: false } +val packageReactNdkLibsForBuck by + tasks.registering(Copy::class) { + dependsOn("mergeDebugNativeLibs") + // Shared libraries (.so) are copied from the merged_native_libs folder instead + from("$buildDir/intermediates/merged_native_libs/debug/out/lib/") + exclude("**/libjsc.so") + exclude("**/libhermes.so") + into("src/main/jni/prebuilt/lib") + } + +// Derivative of the packageReactNdkDebugLibsForBuck task, appends "debug" to the "into" dir +val packageReactNdkDebugLibsForDiscord by + tasks.registering(Copy::class) { + dependsOn("mergeDebugNativeLibs") + // Shared libraries (.so) are copied from the merged_native_libs folder instead + from("$buildDir/intermediates/merged_native_libs/debug/out/lib/") + exclude("**/libjsc.so") + exclude("**/libhermes.so") + into("src/main/jni/prebuilt/lib/debug") + } + +// Derivative of the packageReactNdkReleaseLibsForBuck task, appends "release" to the "into" dir +val packageReactNdkReleaseLibsForDiscord by + tasks.registering(Copy::class) { + dependsOn("mergeReleaseNativeLibs") + // Shared libraries (.so) are copied from the merged_native_libs folder instead + from("$buildDir/intermediates/merged_native_libs/release/out/lib/") + exclude("**/libjsc.so") + exclude("**/libhermes.so") + into("src/main/jni/prebuilt/lib/release") + } + +val createReactNdkLibraryZipArchiveForDiscord by + tasks.registering(Zip::class) { + // This dependsOn tasks gets all our *.so files into the src/main/jni/prebuilt/lib directory + dependsOn("packageReactNdkDebugLibsForDiscord") + dependsOn("packageReactNdkReleaseLibsForDiscord") + + // A searchable self-documenting name for the build process, but its final packaged name will end up being react-native-{version}.zip + archiveFileName.set("ReactNativeLibrariesForDiscord.zip") + from(layout.projectDirectory.dir("src/main/jni/prebuilt")) { + // Get all *.so files in the prebuilt directory + include("**/*.so") + val fileCopyAction = object: Action { + override fun execute(fcd: FileCopyDetails) { + // Trim down each file's directory to just include the "lib/{debug/release}/{architecture}" part + val relativeFileName = RelativePath(true, *fcd.relativePath.segments.takeLast(4).toTypedArray()) + fcd.relativePath = relativeFileName + } + } + eachFile(fileCopyAction) + // Removes empty dirs resulting from the eachFile directory remapping above + includeEmptyDirs = false + } + + // Place this .zip right into our ReactAndroid directory + destinationDirectory.set(layout.projectDirectory) + } + repositories { // Normally RNGP will set repositories for all modules, // but when consumed from source, we need to re-declare @@ -522,7 +585,10 @@ android { "-DANDROID_STL=c++_shared", "-DANDROID_TOOLCHAIN=clang", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", - "-DCMAKE_POLICY_DEFAULT_CMP0069=NEW") + "-DCMAKE_POLICY_DEFAULT_CMP0069=NEW", + "-DRN_SERIALIZABLE_STATE=ON") + cppFlags += "-flto" + cFlags += "-flto" targets( "reactnative", @@ -596,7 +662,7 @@ android { publishing { multipleVariants { withSourcesJar() - includeBuildTypeValues("debug", "release") + includeBuildTypeValues("debug", "release", "debugOptimized") } } @@ -604,6 +670,15 @@ android { unitTests { isIncludeAndroidResources = true } targetSdk = libs.versions.targetSdk.get().toInt() } + + buildTypes { + create("debugOptimized") { + initWith(getByName("debug")) + externalNativeBuild { + cmake { arguments("-DCMAKE_BUILD_TYPE=Release", "-DREACT_NATIVE_DEBUG_OPTIMIZED=True") } + } + } + } } tasks.withType().configureEach { @@ -613,6 +688,7 @@ tasks.withType().configureEach { } dependencies { + implementation("com.tencent:mmkv-static:1.3.14") api(libs.androidx.appcompat) api(libs.androidx.appcompat.resources) api(libs.androidx.autofill) @@ -685,5 +761,21 @@ apply(from = "./publish.gradle") // Please note that the original coordinates, `react-native`, have been voided // as they caused https://github.com/facebook/react-native/issues/35210 publishing { - publications { getByName("release", MavenPublication::class) { artifactId = "react-android" } } + publications { + getByName("release", MavenPublication::class) { + artifactId = "react-android" + } + } +} + +subprojects { + afterEvaluate { + if (project.extensions.findByName("android") != null) { + val androidExt = project.extensions.getByName("android") as com.android.build.gradle.BaseExtension + androidExt.defaultConfig.externalNativeBuild?.cmake?.apply { + cppFlags.add("-flto") + cFlags.add("-flto") + } + } + } } diff --git a/packages/react-native/ReactAndroid/gradle.properties b/packages/react-native/ReactAndroid/gradle.properties index 7c0c467db7a633..644af44766b01f 100644 --- a/packages/react-native/ReactAndroid/gradle.properties +++ b/packages/react-native/ReactAndroid/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=1000.0.0 +VERSION_NAME=0.81.4-discord-30 react.internal.publishingGroup=com.facebook.react android.useAndroidX=true @@ -12,28 +12,28 @@ react.internal.disableJavaVersionAlignment=true # We ignore: # - BuildConfig classes because they are generated and not part of the public API binaryCompatibilityValidator.ignoredClasses=com.facebook.react.BuildConfig,\ - com.facebook.react.views.progressbar.ReactProgressBarViewManager$$PropsSetter,\ - com.facebook.react.views.progressbar.ProgressBarShadowNode$$PropsSetter +com.facebook.react.views.progressbar.ReactProgressBarViewManager$$PropsSetter,\ +com.facebook.react.views.progressbar.ProgressBarShadowNode$$PropsSetter binaryCompatibilityValidator.ignoredPackages=com.facebook.debug,\ - com.facebook.fbreact,\ - com.facebook.hermes,\ - com.facebook.perftest,\ - com.facebook.proguard,\ - com.facebook.react.bridgeless.internal,\ - com.facebook.react.common.annotations,\ - com.facebook.react.fabric.internal.interop,\ - com.facebook.react.flipper,\ - com.facebook.react.internal,\ - com.facebook.react.module.processing,\ - com.facebook.react.processing,\ - com.facebook.react.runtime.internal,\ - com.facebook.react.uimanager.internal,\ - com.facebook.react.views.text.internal,\ - com.facebook.systrace,\ - com.facebook.yoga +com.facebook.fbreact,\ +com.facebook.hermes,\ +com.facebook.perftest,\ +com.facebook.proguard,\ +com.facebook.react.bridgeless.internal,\ +com.facebook.react.common.annotations,\ +com.facebook.react.fabric.internal.interop,\ +com.facebook.react.flipper,\ +com.facebook.react.internal,\ +com.facebook.react.module.processing,\ +com.facebook.react.processing,\ +com.facebook.react.runtime.internal,\ +com.facebook.react.uimanager.internal,\ +com.facebook.react.views.text.internal,\ +com.facebook.systrace,\ +com.facebook.yoga binaryCompatibilityValidator.nonPublicMarkers=com.facebook.react.common.annotations.VisibleForTesting,\ - com.facebook.react.common.annotations.UnstableReactNativeAPI,\ - com.facebook.react.common.annotations.FrameworkAPI +com.facebook.react.common.annotations.UnstableReactNativeAPI,\ +com.facebook.react.common.annotations.FrameworkAPI binaryCompatibilityValidator.validationDisabled=true binaryCompatibilityValidator.outputApiFileName=ReactAndroid diff --git a/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts b/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts index d0228e0d4d910c..92f7a7eca9d68d 100644 --- a/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts +++ b/packages/react-native/ReactAndroid/hermes-engine/build.gradle.kts @@ -12,8 +12,8 @@ import org.apache.tools.ant.taskdefs.condition.Os plugins { id("maven-publish") id("signing") - alias(libs.plugins.android.library) - alias(libs.plugins.download) + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.download.get().pluginId) } group = "com.facebook.react" @@ -149,6 +149,7 @@ val configureBuildForHermes by "-B", hermesBuildDir.toString(), "-DJSI_DIR=" + jsiDir.absolutePath, + "-DICU_FOUND=1", ) if (Os.isFamily(Os.FAMILY_WINDOWS)) { cmakeCommandLine = cmakeCommandLine + "-GNMake Makefiles" @@ -245,6 +246,11 @@ android { ndkVersion = libs.versions.ndkVersion.get() } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + defaultConfig { minSdk = libs.versions.minSdk.get().toInt() @@ -306,6 +312,12 @@ android { } } } + buildTypes { + create("debugOptimized") { + initWith(getByName("debug")) + externalNativeBuild { cmake { arguments("-DCMAKE_BUILD_TYPE=Release") } } + } + } } sourceSets.getByName("main") { diff --git a/packages/react-native/ReactAndroid/publish.gradle b/packages/react-native/ReactAndroid/publish.gradle index 32287a7c81392c..2e44520d6620cb 100644 --- a/packages/react-native/ReactAndroid/publish.gradle +++ b/packages/react-native/ReactAndroid/publish.gradle @@ -68,6 +68,10 @@ publishing { name = "mavenTempLocal" url = mavenTempLocalUrl } + maven { + name = "discord" + url = uri("gcs://discord-maven") + } } if (signingKey && signingPwd) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/BaseReactPackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/BaseReactPackage.kt index c6a3c87d94901a..9ad6912d215f05 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/BaseReactPackage.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/BaseReactPackage.kt @@ -22,6 +22,7 @@ import javax.inject.Provider /** Abstract class that supports lazy loading of NativeModules by default. */ public abstract class BaseReactPackage : ReactPackage { + @Deprecated("Migrate to [BaseReactPackage] and implement [getModule] instead.") override fun createNativeModules(reactContext: ReactApplicationContext): List { throw UnsupportedOperationException( "createNativeModules method is not supported. Use getModule() method instead.") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/HeadlessJsTaskService.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/HeadlessJsTaskService.kt index eff3c4288287e3..09c33f45810446 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/HeadlessJsTaskService.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/HeadlessJsTaskService.kt @@ -112,7 +112,8 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList * simply have a different mechanism for storing a `ReactNativeHost`, e.g. as a static field * somewhere. */ - protected val reactNativeHost: ReactNativeHost + @Suppress("DEPRECATION") + protected open val reactNativeHost: ReactNativeHost get() = (application as ReactApplication).reactNativeHost /** @@ -120,7 +121,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList * [ReactApplication] and calls [ReactApplication.reactHost]. This method assumes it is called in * new architecture and returns null if not. */ - protected val reactHost: ReactHost? + protected open val reactHost: ReactHost? get() = (application as ReactApplication).reactHost protected val reactContext: ReactContext? diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.kt index acf881246c4ac1..66b767066e3f0b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/LazyReactPackage.kt @@ -92,6 +92,8 @@ public abstract class LazyReactPackage : ReactPackage { * @param reactContext react application context that can be used to create modules * @return A [List]<[NativeModule]> to register */ + @Suppress("DEPRECATION") + @Deprecated("Migrate to [BaseReactPackage] and implement [getModule] instead.") override fun createNativeModules(reactContext: ReactApplicationContext): List = buildList { for (holder in getNativeModules(reactContext)) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index 031f534afec3d3..612b252c8898ee 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -19,6 +19,7 @@ import com.facebook.react.modules.core.PermissionListener; import com.facebook.react.util.AndroidVersion; import org.jetbrains.annotations.NotNull; +import com.facebook.react.uimanager.UIManagerConstantsCache; /** Base Activity for React Native applications. */ public abstract class ReactActivity extends AppCompatActivity @@ -58,6 +59,7 @@ protected ReactActivityDelegate createReactActivityDelegate() { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + UIManagerConstantsCache.getInstance().init(this); mDelegate.onCreate(savedInstanceState); if (AndroidVersion.isAtLeastTargetSdk36(this)) { getOnBackPressedDispatcher().addCallback(this, mBackPressedCallback); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index 47945767493c3f..edd1cd53366e6e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -18,20 +18,22 @@ import android.view.Window; import androidx.annotation.Nullable; import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactContext; -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture; import com.facebook.react.interfaces.fabric.ReactSurface; import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags; import com.facebook.react.modules.core.PermissionListener; import com.facebook.react.views.view.WindowUtilKt; import com.facebook.systrace.Systrace; +import java.util.Objects; /** * Delegate class for {@link ReactActivity}. You can subclass this to provide custom implementations * for e.g. {@link #getReactNativeHost()}, if your Application class doesn't implement {@link * ReactApplication}. */ +@Nullsafe(Nullsafe.Mode.LOCAL) public class ReactActivityDelegate { private final @Nullable Activity mActivity; @@ -86,8 +88,11 @@ public ReactActivityDelegate( * ReactApplication#getReactNativeHost()}. Override this method if your application class does not * implement {@code ReactApplication} or you simply have a different mechanism for storing a * {@code ReactNativeHost}, e.g. as a static field somewhere. + * + * @deprecated "Do not access {@link ReactNativeHost} directly. This class is going away in the + * New Architecture. You should access {@link ReactHost} instead." */ - @DeprecatedInNewArchitecture(message = "Use getReactHost()") + @Deprecated protected ReactNativeHost getReactNativeHost() { return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost(); } @@ -107,16 +112,22 @@ protected ReactNativeHost getReactNativeHost() { return mReactDelegate; } - @DeprecatedInNewArchitecture(message = "Use getReactHost()") + /** + * @deprecated @deprecated "Do not access {@link ReactInstanceManager} directly. This class is + * going away in the New Architecture. You should access {@link ReactHost} instead." + * @noinspection deprecation + */ + @Deprecated public ReactInstanceManager getReactInstanceManager() { - return mReactDelegate.getReactInstanceManager(); + return Objects.requireNonNull(mReactDelegate).getReactInstanceManager(); } + @Nullable public String getMainComponentName() { return mMainComponentName; } - public void onCreate(Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { Systrace.traceSection( Systrace.TRACE_TAG_REACT, "ReactActivityDelegate.onCreate::init", @@ -147,6 +158,7 @@ public void onCreate(Bundle savedInstanceState) { launchOptions, isFabricEnabled()) { @Override + @Nullable protected ReactRootView createRootView() { ReactRootView rootView = ReactActivityDelegate.this.createRootView(); if (rootView == null) { @@ -162,31 +174,29 @@ protected ReactRootView createRootView() { }); } - protected void loadApp(String appKey) { - mReactDelegate.loadApp(appKey); + protected void loadApp(@Nullable String appKey) { + Objects.requireNonNull(mReactDelegate).loadApp(Objects.requireNonNull(appKey)); getPlainActivity().setContentView(mReactDelegate.getReactRootView()); } public void setReactSurface(ReactSurface reactSurface) { - mReactDelegate.setReactSurface(reactSurface); + Objects.requireNonNull(mReactDelegate).setReactSurface(reactSurface); } public void setReactRootView(ReactRootView reactRootView) { - mReactDelegate.setReactRootView(reactRootView); + Objects.requireNonNull(mReactDelegate).setReactRootView(reactRootView); } public void onUserLeaveHint() { - if (mReactDelegate != null) { - mReactDelegate.onUserLeaveHint(); - } + Objects.requireNonNull(mReactDelegate).onUserLeaveHint(); } public void onPause() { - mReactDelegate.onHostPause(); + Objects.requireNonNull(mReactDelegate).onHostPause(); } public void onResume() { - mReactDelegate.onHostResume(); + Objects.requireNonNull(mReactDelegate).onHostResume(); if (mPermissionsCallback != null) { mPermissionsCallback.invoke(); @@ -195,43 +205,43 @@ public void onResume() { } public void onDestroy() { - mReactDelegate.onHostDestroy(); + Objects.requireNonNull(mReactDelegate).onHostDestroy(); } - public void onActivityResult(int requestCode, int resultCode, Intent data) { - mReactDelegate.onActivityResult(requestCode, resultCode, data, true); + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + Objects.requireNonNull(mReactDelegate).onActivityResult(requestCode, resultCode, data, true); } public boolean onKeyDown(int keyCode, KeyEvent event) { - return mReactDelegate.onKeyDown(keyCode, event); + return Objects.requireNonNull(mReactDelegate).onKeyDown(keyCode, event); } public boolean onKeyUp(int keyCode, KeyEvent event) { - return mReactDelegate.shouldShowDevMenuOrReload(keyCode, event); + return Objects.requireNonNull(mReactDelegate).shouldShowDevMenuOrReload(keyCode, event); } public boolean onKeyLongPress(int keyCode, KeyEvent event) { - return mReactDelegate.onKeyLongPress(keyCode); + return Objects.requireNonNull(mReactDelegate).onKeyLongPress(keyCode); } public boolean onBackPressed() { - return mReactDelegate.onBackPressed(); + return Objects.requireNonNull(mReactDelegate).onBackPressed(); } - public boolean onNewIntent(Intent intent) { - return mReactDelegate.onNewIntent(intent); + public boolean onNewIntent(@Nullable Intent intent) { + return Objects.requireNonNull(mReactDelegate).onNewIntent(Objects.requireNonNull(intent)); } public void onWindowFocusChanged(boolean hasFocus) { - mReactDelegate.onWindowFocusChanged(hasFocus); + Objects.requireNonNull(mReactDelegate).onWindowFocusChanged(hasFocus); } public void onConfigurationChanged(Configuration newConfig) { - mReactDelegate.onConfigurationChanged(newConfig); + Objects.requireNonNull(mReactDelegate).onConfigurationChanged(newConfig); } public void requestPermissions( - String[] permissions, int requestCode, PermissionListener listener) { + String[] permissions, int requestCode, @Nullable PermissionListener listener) { mPermissionListener = listener; getPlainActivity().requestPermissions(permissions, requestCode); } @@ -267,7 +277,7 @@ protected ReactActivity getReactActivity() { * context will no longer be valid. */ public @Nullable ReactContext getCurrentReactContext() { - return mReactDelegate.getCurrentReactContext(); + return Objects.requireNonNull(mReactDelegate).getCurrentReactContext(); } /** diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactApplication.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactApplication.kt index 9360b51f734237..306eb49aa3cb33 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactApplication.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactApplication.kt @@ -10,6 +10,10 @@ package com.facebook.react /** Interface that represents an instance of a React Native application */ public interface ReactApplication { /** Get the default [ReactNativeHost] for this app. */ + @Suppress("DEPRECATION") + @Deprecated( + "You should not use ReactNativeHost directly in the New Architecture. Use ReactHost instead.", + ReplaceWith("reactHost")) public val reactNativeHost: ReactNativeHost /** diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.kt index 258bb6bcf472f3..4d81bc58c1165c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.kt @@ -14,7 +14,6 @@ import android.os.Bundle import android.view.KeyEvent import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.UiThreadUtil.runOnUiThread -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture import com.facebook.react.devsupport.DoubleTapReloadRecognizer import com.facebook.react.devsupport.ReleaseDevSupportManager import com.facebook.react.devsupport.interfaces.DevSupportManager @@ -26,12 +25,17 @@ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler * A delegate for handling React Application support. This delegate is unaware whether it is used in * an [Activity] or a [android.app.Fragment]. */ +@Suppress("DEPRECATION") public open class ReactDelegate { private val activity: Activity private var internalReactRootView: ReactRootView? = null private val mainComponentName: String? private var launchOptions: Bundle? private var doubleTapReloadRecognizer: DoubleTapReloadRecognizer? + + @Deprecated( + "You should not use ReactNativeHost directly in the New Architecture. Use ReactHost instead.", + ReplaceWith("reactHost")) private var reactNativeHost: ReactNativeHost? = null public var reactHost: ReactHost? = null private set @@ -380,7 +384,8 @@ public open class ReactDelegate { return false } - @DeprecatedInNewArchitecture(message = "Use reactHost") + @Deprecated( + "Do not access [ReactInstanceManager] directly. This class is going away in the New Architecture. You should use [ReactHost] instead.") public fun getReactInstanceManager(): ReactInstanceManager { val nonNullReactNativeHost = checkNotNull(reactNativeHost) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactFragment.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactFragment.kt index a8b45e5745fa17..304a0d003b43e8 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactFragment.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactFragment.kt @@ -58,6 +58,10 @@ public open class ReactFragment : Fragment(), PermissionAwareActivity { * method if your application class does not implement `ReactApplication` or you simply have a * different mechanism for storing a `ReactNativeHost`, e.g. as a static field somewhere. */ + @Suppress("DEPRECATION") + @Deprecated( + "You should not use ReactNativeHost directly in the New Architecture. Use ReactHost instead.", + ReplaceWith("reactHost")) protected open val reactNativeHost: ReactNativeHost? get() = (activity?.application as ReactApplication?)?.reactNativeHost diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 7b0300bc35c9a3..b13b7e9c5c1d1c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -29,7 +29,6 @@ import static com.facebook.react.bridge.ReactMarkerConstants.VM_INIT; import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT; - import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -1481,6 +1480,8 @@ private ReactApplicationContext createReactContext( // architecture so it will always be there. catalystInstance.getRuntimeScheduler(); + TurboModuleManager turboModuleManager = null; + if (ReactNativeNewArchitectureFeatureFlags.useTurboModules() && mTMMDelegateBuilder != null) { TurboModuleManagerDelegate tmmDelegate = mTMMDelegateBuilder @@ -1488,7 +1489,7 @@ private ReactApplicationContext createReactContext( .setReactApplicationContext(reactContext) .build(); - TurboModuleManager turboModuleManager = + turboModuleManager = new TurboModuleManager( catalystInstance.getRuntimeExecutor(), tmmDelegate, @@ -1497,9 +1498,11 @@ private ReactApplicationContext createReactContext( catalystInstance.setTurboModuleRegistry(turboModuleManager); - // Eagerly initialize TurboModules - for (String moduleName : turboModuleManager.getEagerInitModuleNames()) { - turboModuleManager.getModule(moduleName); + if (!reactContext.isBridgeless()) { + // Eagerly initialize TurboModules + for (String moduleName : turboModuleManager.getEagerInitModuleNames()) { + turboModuleManager.getModule(moduleName); + } } } @@ -1525,6 +1528,13 @@ private ReactApplicationContext createReactContext( catalystInstance.runJSBundle(); Systrace.endSection(TRACE_TAG_REACT); + if (reactContext.isBridgeless() && ReactNativeNewArchitectureFeatureFlags.useTurboModules() && mTMMDelegateBuilder != null) { + // Eagerly initialize TurboModules + for (String moduleName : turboModuleManager.getEagerInitModuleNames()) { + turboModuleManager.getModule(moduleName); + } + } + return reactContext; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java index 2745a672cadebb..084dc44b276dfc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java @@ -8,8 +8,11 @@ package com.facebook.react; import android.app.Application; +import android.content.Context; + import androidx.annotation.Nullable; import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.bridge.JSExceptionHandler; import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.ReactMarker; @@ -18,7 +21,6 @@ import com.facebook.react.common.LifecycleState; import com.facebook.react.common.SurfaceDelegate; import com.facebook.react.common.SurfaceDelegateFactory; -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture; import com.facebook.react.common.annotations.internal.LegacyArchitecture; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; @@ -27,17 +29,22 @@ import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager; import com.facebook.react.devsupport.interfaces.RedBoxHandler; import com.facebook.react.internal.ChoreographerProvider; + +import com.facebook.react.runtime.ReactSurfaceView; +import com.facebook.react.runtime.ReactSurfaceImpl; + import java.util.List; + /** * Simple class that holds an instance of {@link ReactInstanceManager}. This can be used in your * {@link Application class} (see {@link ReactApplication}), or as a static field. + * + * @deprecated This class will be replaced by com.facebook.react.ReactHost in the New Architecture. */ -@DeprecatedInNewArchitecture( - message = - "This class will be replaced by com.facebook.react.ReactHost in the new architecture of" - + " React Native.") +@Deprecated @LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR) +@Nullsafe(Nullsafe.Mode.LOCAL) public abstract class ReactNativeHost { static { @@ -247,4 +254,8 @@ protected String getJSMainModuleName() { protected @Nullable ChoreographerProvider getChoreographerProvider() { return null; } + + public ReactSurfaceView createReactSurfaceView(Context context, ReactSurfaceImpl surfaceImpl) { + return new ReactSurfaceView(context, surfaceImpl); + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackage.kt index 2ccc7a0edd43e6..732fe6deedd240 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackage.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackage.kt @@ -10,7 +10,6 @@ package com.facebook.react import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.UIManager -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture import com.facebook.react.common.annotations.StableReactNativeAPI import com.facebook.react.uimanager.ViewManager @@ -34,8 +33,9 @@ public interface ReactPackage { * @return list of native modules to register with the newly created catalyst instance This method * is deprecated in the new Architecture of React Native. */ - @DeprecatedInNewArchitecture(message = "Migrate to BaseReactPackage and implement getModule") - public fun createNativeModules(reactContext: ReactApplicationContext): List + @Deprecated(message = "Migrate to [BaseReactPackage] and implement [getModule] instead.") + public fun createNativeModules(reactContext: ReactApplicationContext): List = + emptyList() /** @return a list of view managers that should be registered with [UIManager] */ public fun createViewManagers( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageHelper.kt index 1ade7b5d98aa11..28a3c0acca3416 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageHelper.kt @@ -28,6 +28,7 @@ internal object ReactPackageHelper { FLog.d( ReactConstants.TAG, "${reactPackage.javaClass.simpleName} is not a LazyReactPackage, falling back to old version.") + @Suppress("DEPRECATION") val nativeModules = reactPackage.createNativeModules(reactApplicationContext) return Iterable { object : Iterator { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageTurboModuleManagerDelegate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageTurboModuleManagerDelegate.kt index 28e3bc0772b562..a80c011b748361 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageTurboModuleManagerDelegate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactPackageTurboModuleManagerDelegate.kt @@ -18,6 +18,7 @@ import com.facebook.react.module.annotations.ReactModule import com.facebook.react.module.model.ReactModuleInfo import com.facebook.react.turbomodule.core.interfaces.TurboModule import javax.inject.Provider +import java.util.concurrent.CountDownLatch public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManagerDelegate { internal fun interface ModuleProvider { @@ -29,6 +30,7 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage private val shouldEnableLegacyModuleInterop = ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() && ReactNativeNewArchitectureFeatureFlags.useTurboModuleInterop() + private val initTasksLeft = CountDownLatch(1) protected constructor( reactApplicationContext: ReactApplicationContext, @@ -48,6 +50,23 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage private fun initialize( reactApplicationContext: ReactApplicationContext, packages: List + ) { + if (!reactApplicationContext.isBridgeless) { + initializeModules(reactApplicationContext, packages) + initTasksLeft.countDown() + return + } + + val worker = Thread { + initializeModules(reactApplicationContext, packages) + initTasksLeft.countDown() + } + worker.start() + } + + private fun initializeModules( + reactApplicationContext: ReactApplicationContext, + packages: List ) { val applicationContext: ReactApplicationContext = reactApplicationContext for (reactPackage in packages) { @@ -83,6 +102,7 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage if (shouldSupportLegacyPackages()) { // TODO(T145105887): Output warnings that ReactPackage was used + @Suppress("DEPRECATION") val nativeModules = reactPackage.createNativeModules(reactApplicationContext) val moduleMap: MutableMap = mutableMapOf() @@ -94,6 +114,7 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage val moduleName = reactModule?.name ?: module.name + @Suppress("DEPRECATION") val moduleInfo: ReactModuleInfo = if (reactModule != null) ReactModuleInfo( @@ -127,6 +148,11 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage override fun unstable_shouldEnableLegacyModuleInterop(): Boolean = shouldEnableLegacyModuleInterop override fun getModule(moduleName: String): TurboModule? { + try { + initTasksLeft.await() + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } var resolvedModule: NativeModule? = null for (moduleProvider in moduleProviders) { @@ -150,6 +176,11 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage } override fun unstable_isModuleRegistered(moduleName: String): Boolean { + try { + initTasksLeft.await() + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } for (moduleProvider in moduleProviders) { val moduleInfo: ReactModuleInfo? = packageModuleInfos[moduleProvider]?.get(moduleName) if (moduleInfo?.isTurboModule == true) { @@ -160,6 +191,11 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage } override fun unstable_isLegacyModuleRegistered(moduleName: String): Boolean { + try { + initTasksLeft.await() + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } for (moduleProvider in moduleProviders) { val moduleInfo: ReactModuleInfo? = packageModuleInfos[moduleProvider]?.get(moduleName) if (moduleInfo?.isTurboModule == false) { @@ -170,6 +206,11 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage } override fun getLegacyModule(moduleName: String): NativeModule? { + try { + initTasksLeft.await() + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } if (!unstable_shouldEnableLegacyModuleInterop()) { return null } @@ -196,11 +237,18 @@ public abstract class ReactPackageTurboModuleManagerDelegate : TurboModuleManage return resolvedModule } - override fun getEagerInitModuleNames(): List = buildList { - for (moduleProvider in moduleProviders) { - for (moduleInfo in packageModuleInfos[moduleProvider]?.values ?: emptyList()) { - if (moduleInfo.isTurboModule && moduleInfo.needsEagerInit) { - add(moduleInfo.name) + override fun getEagerInitModuleNames(): List { + try { + initTasksLeft.await() + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } + return buildList { + for (moduleProvider in moduleProviders) { + for (moduleInfo in packageModuleInfos[moduleProvider]?.values ?: emptyList()) { + if (moduleInfo.isTurboModule && moduleInfo.needsEagerInit) { + add(moduleInfo.name) + } } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 6c625367010a9a..c3bc1d96b117ce 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -135,6 +135,8 @@ public ReactRootView(Context context, AttributeSet attrs, int defStyle) { private void init() { setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag()); setClipChildren(false); + + DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext()); } @Override diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt index 3bc9143d7ed792..791df54613bf64 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt @@ -7,6 +7,7 @@ package com.facebook.react.animated +import android.os.Build import androidx.annotation.AnyThread import androidx.annotation.UiThread import com.facebook.common.logging.FLog @@ -34,6 +35,7 @@ import com.facebook.react.uimanager.common.ViewUtil import java.util.ArrayList import java.util.Queue import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.atomic.AtomicReference import kotlin.concurrent.Volatile @@ -130,7 +132,13 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) : } private inner class ConcurrentOperationQueue { - private val queue: Queue = ConcurrentLinkedQueue() + private val queue: Queue = + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.S) { + // See https://issuetracker.google.com/issues/261481042 + LinkedBlockingQueue() + } else { + ConcurrentLinkedQueue() + } private var peekedOperation: UIThreadOperation? = null @get:AnyThread diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java index 3fec048a7705dd..ca7baa18081300 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java @@ -16,7 +16,6 @@ import com.facebook.infer.annotation.ThreadConfined; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture; import com.facebook.react.common.annotations.StableReactNativeAPI; import com.facebook.react.common.build.ReactBuildConfig; import java.util.Map; @@ -72,7 +71,6 @@ public BaseJavaModule(@Nullable ReactApplicationContext reactContext) { /** * @return a map of constants this module exports to JS. Supports JSON types. */ - @DeprecatedInNewArchitecture() public @Nullable Map getConstants() { return null; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java index 88ea2110858de0..a8b3e7ceae60fa 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java @@ -14,11 +14,11 @@ import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.Nullsafe; import com.facebook.infer.annotation.ThreadConfined; import com.facebook.proguard.annotations.DoNotStrip; import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture; import com.facebook.react.common.annotations.FrameworkAPI; import com.facebook.react.common.annotations.UnstableReactNativeAPI; import com.facebook.react.common.annotations.VisibleForTesting; @@ -27,16 +27,21 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger; import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; import java.util.Collection; +import java.util.Objects; /** * This is the bridge-specific concrete subclass of ReactContext. ReactContext has many methods that * delegate to the react instance. This subclass implements those methods, by delegating to the * CatalystInstance. If you need to create a ReactContext within an "bridge context", please create * BridgeReactContext. + * + * @deprecated This class is deprecated in the New Architecture and will be replaced by {@link + * com.facebook.react.runtime.BridgelessReactContext} */ -@DeprecatedInNewArchitecture @VisibleForTesting @LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR) +@Deprecated +@Nullsafe(Nullsafe.Mode.LOCAL) public class BridgeReactContext extends ReactApplicationContext { static { LegacyArchitectureLogger.assertLegacyArchitecture( @@ -119,6 +124,7 @@ public boolean hasNativeModule(Class nativeModuleInt if (mCatalystInstance == null) { raiseCatalystInstanceMissingException(); } + Assertions.assertNotNull(mCatalystInstance); return mCatalystInstance.hasNativeModule(nativeModuleInterface); } @@ -127,6 +133,7 @@ public Collection getNativeModules() { if (mCatalystInstance == null) { raiseCatalystInstanceMissingException(); } + Assertions.assertNotNull(mCatalystInstance); return mCatalystInstance.getNativeModules(); } @@ -139,6 +146,7 @@ public T getNativeModule(Class nativeModuleInterface if (mCatalystInstance == null) { raiseCatalystInstanceMissingException(); } + Assertions.assertNotNull(mCatalystInstance); return mCatalystInstance.getNativeModule(nativeModuleInterface); } @@ -147,6 +155,7 @@ public T getNativeModule(Class nativeModuleInterface if (mCatalystInstance == null) { raiseCatalystInstanceMissingException(); } + Assertions.assertNotNull(mCatalystInstance); return mCatalystInstance.getNativeModule(moduleName); } @@ -210,8 +219,7 @@ public void destroy() { @Override public void handleException(Exception e) { boolean catalystInstanceVariableExists = mCatalystInstance != null; - boolean isCatalystInstanceAlive = - catalystInstanceVariableExists && !mCatalystInstance.isDestroyed(); + boolean isCatalystInstanceAlive = mCatalystInstance != null && !mCatalystInstance.isDestroyed(); boolean hasExceptionHandler = getJSExceptionHandler() != null; if (isCatalystInstanceAlive && hasExceptionHandler) { @@ -268,18 +276,19 @@ public CallInvokerHolder getJSCallInvokerHolder() { return null; } - @DeprecatedInNewArchitecture( - message = - "This method will be deprecated later as part of Stable APIs with bridge removal and not" - + " encouraged usage.") /** * Get the UIManager for Fabric from the CatalystInstance. * * @return The UIManager when CatalystInstance is active. + * @deprecated Do not use this method. Instead use {@link + * com.facebook.react.uimanager.UIManagerHelper} method {@code getUIManager} to get the + * UIManager instance from the current ReactContext. */ @Override + @Deprecated public @Nullable UIManager getFabricUIManager() { - return mCatalystInstance.getFabricUIManager(); + //noinspection deprecation + return Objects.requireNonNull(mCatalystInstance).getFabricUIManager(); } /** diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxModuleWrapperBase.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxModuleWrapperBase.kt index 944d56892ad9c7..b0ee31db64c65a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxModuleWrapperBase.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CxxModuleWrapperBase.kt @@ -32,6 +32,8 @@ protected constructor( // do nothing } + @Deprecated( + "The method canOverrideExistingModule is not used in the New Architecture and will be removed in a future release.") override fun canOverrideExistingModule(): Boolean = false override fun invalidate() { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt index 8f230012dc6af5..fd3ac8b7bfdc6d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt @@ -11,6 +11,7 @@ import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger.assertLegacyArchitecture +import com.facebook.react.uimanager.UIManagerConstantsCache import com.facebook.react.turbomodule.core.interfaces.TurboModule import com.facebook.systrace.Systrace import com.facebook.systrace.Systrace.TRACE_TAG_REACT @@ -106,7 +107,9 @@ internal class JavaModuleWrapper( .flush() ReactMarker.logMarker(ReactMarkerConstants.GET_CONSTANTS_START, moduleName) + Systrace.beginSection(TRACE_TAG_REACT, "module.getModule") val baseJavaModule = module + Systrace.endSection(TRACE_TAG_REACT) Systrace.beginSection(TRACE_TAG_REACT, "module.getConstants") val map = baseJavaModule.constants @@ -115,6 +118,12 @@ internal class JavaModuleWrapper( Systrace.beginSection(TRACE_TAG_REACT, "create WritableNativeMap") ReactMarker.logMarker(ReactMarkerConstants.CONVERT_CONSTANTS_START, moduleName) try { + if (moduleName == "UIManager") { + val res = UIManagerConstantsCache.getInstance().getUIManagerConstantsAsWritableMap() + if (res != null) { + return res + } + } return Arguments.makeNativeMap(map) } finally { ReactMarker.logMarker(ReactMarkerConstants.CONVERT_CONSTANTS_END, moduleName) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ModuleHolder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ModuleHolder.kt index d6e12cc369f74d..0e35427ebcf03c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ModuleHolder.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ModuleHolder.kt @@ -53,6 +53,7 @@ public class ModuleHolder { public constructor(nativeModule: NativeModule) { name = nativeModule.name + @Suppress("DEPRECATION") reactModuleInfo = ReactModuleInfo( nativeModule.name, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java index 65d1a01a8f0983..6b87da715383fe 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/NativeModule.java @@ -9,7 +9,6 @@ import com.facebook.infer.annotation.Nullsafe; import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture; import com.facebook.react.common.annotations.StableReactNativeAPI; import javax.annotation.Nonnull; @@ -50,8 +49,11 @@ public interface NativeModule { * of a different package (such as the core one). Trying to override without returning true from * this method is considered an error and will throw an exception during initialization. By * default all modules return false. + * + * @deprecated The method canOverrideExistingModule is not used in the New Architecture and will + * be removed in a future release. */ - @DeprecatedInNewArchitecture() + @Deprecated default boolean canOverrideExistingModule() { return false; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactSoftExceptionLogger.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactSoftExceptionLogger.kt index fc7275b9ec40e7..6a0d992c734808 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactSoftExceptionLogger.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactSoftExceptionLogger.kt @@ -18,7 +18,7 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger.Categories.SURFACE_MOU import java.util.concurrent.CopyOnWriteArrayList @DoNotStrip -internal object ReactSoftExceptionLogger { +public object ReactSoftExceptionLogger { @Retention(AnnotationRetention.SOURCE) @StringDef( RVG_IS_VIEW_CLIPPED, @@ -26,15 +26,15 @@ internal object ReactSoftExceptionLogger { CLIPPING_PROHIBITED_VIEW, SOFT_ASSERTIONS, SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE) - annotation class CategoryMode + public annotation class CategoryMode /** Constants that listeners can utilize for custom category-based behavior. */ - object Categories { - const val RVG_IS_VIEW_CLIPPED: String = "ReactViewGroup.isViewClipped" - const val RVG_ON_VIEW_REMOVED: String = "ReactViewGroup.onViewRemoved" - const val CLIPPING_PROHIBITED_VIEW: String = "ReactClippingProhibitedView" - const val SOFT_ASSERTIONS: String = "SoftAssertions" - const val SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE: String = + public object Categories { + public const val RVG_IS_VIEW_CLIPPED: String = "ReactViewGroup.isViewClipped" + public const val RVG_ON_VIEW_REMOVED: String = "ReactViewGroup.onViewRemoved" + public const val CLIPPING_PROHIBITED_VIEW: String = "ReactClippingProhibitedView" + public const val SOFT_ASSERTIONS: String = "SoftAssertions" + public const val SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE: String = "SurfaceMountingManager:MissingViewState" } @@ -44,24 +44,24 @@ internal object ReactSoftExceptionLogger { private val listeners: MutableList = CopyOnWriteArrayList() @JvmStatic - fun addListener(listener: ReactSoftExceptionListener): Unit { + public fun addListener(listener: ReactSoftExceptionListener): Unit { if (!listeners.contains(listener)) { listeners.add(listener) } } @JvmStatic - fun removeListener(listener: ReactSoftExceptionListener): Unit { + public fun removeListener(listener: ReactSoftExceptionListener): Unit { listeners.remove(listener) } @JvmStatic - fun logSoftExceptionVerbose(@CategoryMode category: String, cause: Throwable): Unit { + public fun logSoftExceptionVerbose(@CategoryMode category: String, cause: Throwable): Unit { logSoftException("${category}|${cause.javaClass.simpleName}:${cause.message}", cause) } @JvmStatic - fun logSoftException(@CategoryMode category: String, cause: Throwable): Unit { + public fun logSoftException(@CategoryMode category: String, cause: Throwable): Unit { if (listeners.isNotEmpty()) { for (listener in listeners) { listener.logSoftException(category, cause) @@ -77,7 +77,7 @@ internal object ReactSoftExceptionLogger { logSoftException(category, ReactNoCrashSoftException(message)) } - fun interface ReactSoftExceptionListener { - fun logSoftException(category: String, cause: Throwable) + public fun interface ReactSoftExceptionListener { + public fun logSoftException(category: String, cause: Throwable): Unit } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.kt index 22be6e092cd243..764dec3d0fb4b1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.kt @@ -8,6 +8,7 @@ package com.facebook.react.bridge import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import java.util.ArrayList import java.util.Arrays import kotlin.jvm.JvmStatic @@ -65,9 +66,16 @@ public open class ReadableNativeArray protected constructor() : NativeArray(), R if (other !is ReadableNativeArray) { return false } - return localArray.contentDeepEquals(other.localArray) + + return if (ReactNativeFeatureFlags.useNativeEqualsInNativeReadableArrayAndroid()) { + nativeEquals(other) + } else { + localArray.contentDeepEquals(other.localArray) + } } + private external fun nativeEquals(other: ReadableNativeArray): Boolean + override fun toArrayList(): ArrayList { val arrayList = ArrayList() repeat(size()) { i -> diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt index 21cf94272ec866..b69fdd505f68ec 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UIManager.kt @@ -177,4 +177,18 @@ public interface UIManager : PerformanceCounter { * @param reactTag The react tag for the specific view */ public fun sweepActiveTouchForTag(surfaceId: Int, reactTag: Int) + + /** + * Mark a view as being in or out of an Android view transition. + * + * When a ViewManager calls ViewGroup.startViewTransition/endViewTransition, or uses + * LayoutTransition it should call this method to notify the UI manager. This allows + * the mounting system to queue operations that would otherwise fail because the view + * is temporarily in a transitioning state. + * + * @param surfaceId The surface ID where the view is rendered + * @param reactTag The react tag for the specific view + * @param isTransitioning True if the view is entering a transition, false if exiting + */ + public fun markViewAsInTransition(surfaceId: Int, reactTag: Int, isTransitioning: Boolean) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UiThreadUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UiThreadUtil.kt index 421091a14de573..3b8133bd6c4633 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UiThreadUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/UiThreadUtil.kt @@ -9,7 +9,9 @@ package com.facebook.react.bridge import android.os.Handler import android.os.Looper +import com.facebook.infer.annotation.Assertions import com.facebook.react.common.build.ReactBuildConfig +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags /** Utility for interacting with the UI thread. */ public object UiThreadUtil { @@ -35,6 +37,10 @@ public object UiThreadUtil { */ @JvmStatic public fun assertOnUiThread() { + if (ReactNativeFeatureFlags.runtimeCrashUiThreadUtils()) { + Assertions.assertCondition(isOnUiThread(), "Expected to run on UI thread!") + return + } if (ReactBuildConfig.DEBUG) { SoftAssertions.assertCondition(isOnUiThread(), "Expected to run on UI thread!") } @@ -47,6 +53,10 @@ public object UiThreadUtil { */ @JvmStatic public fun assertNotOnUiThread() { + if (ReactNativeFeatureFlags.runtimeCrashUiThreadUtils()) { + Assertions.assertCondition(!isOnUiThread(), "Expected not to run on UI thread!") + return + } if (ReactBuildConfig.DEBUG) { SoftAssertions.assertCondition(!isOnUiThread(), "Expected not to run on UI thread!") } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/annotations/DeprecatedInNewArchitecture.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/annotations/DeprecatedInNewArchitecture.kt deleted file mode 100644 index 393ac8ade4291b..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/annotations/DeprecatedInNewArchitecture.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.common.annotations - -/** - * Annotates a method or class that will be deprecated once the NewArchitecture is fully released in - * OSS. - */ -@Retention(AnnotationRetention.SOURCE) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) -internal annotation class DeprecatedInNewArchitecture(val message: String = "") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.kt deleted file mode 100644 index 8850773c4e9c1b..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.kt +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.common.assets - -import android.content.Context -import android.content.res.AssetManager -import android.graphics.Typeface -import android.os.Build -import android.util.SparseArray -import androidx.core.content.res.ResourcesCompat -import com.facebook.react.common.ReactConstants - -/** - * Responsible for loading and caching Typeface objects. - * - * This will first try to load a typeface from the assets/fonts folder. If one is not found in that - * folder, this will fallback to the best matching system typeface. - * - * Custom fonts support the extensions `.ttf` and `.otf` and the variants `bold`, `italic`, and - * `bold_italic`. For example, given a font named "ExampleFontFamily", the following are supported: - * * ExampleFontFamily.ttf (or .otf) - * * ExampleFontFamily_bold.ttf (or .otf) - * * ExampleFontFamily_italic.ttf (or .otf) - * * ExampleFontFamily_bold_italic.ttf (or .otf) - */ -public class ReactFontManager { - - private val fontCache: MutableMap = mutableMapOf() - private val customTypefaceCache: MutableMap = mutableMapOf() - - public fun getTypeface( - fontFamilyName: String, - style: Int, - assetManager: AssetManager?, - ): Typeface = getTypeface(fontFamilyName, TypefaceStyle(style), assetManager) - - public fun getTypeface( - fontFamilyName: String, - weight: Int, - italic: Boolean, - assetManager: AssetManager?, - ): Typeface = getTypeface(fontFamilyName, TypefaceStyle(weight, italic), assetManager) - - public fun getTypeface( - fontFamilyName: String, - style: Int, - weight: Int, - assetManager: AssetManager?, - ): Typeface = getTypeface(fontFamilyName, TypefaceStyle(style, weight), assetManager) - - public fun getTypeface( - fontFamilyName: String, - typefaceStyle: TypefaceStyle, - assetManager: AssetManager?, - ): Typeface { - if (customTypefaceCache.containsKey(fontFamilyName)) { - // Apply `typefaceStyle` because custom fonts configure variants using `app:fontStyle` and - // `app:fontWeight` in their resource XML configuration file. - return typefaceStyle.apply(customTypefaceCache[fontFamilyName]) - } - - val assetFontFamily = fontCache.getOrPut(fontFamilyName) { AssetFontFamily() } - val style = typefaceStyle.nearestStyle - return assetFontFamily.getTypefaceForStyle(style) - ?: createAssetTypeface(fontFamilyName, style, assetManager).also { - assetFontFamily.setTypefaceForStyle(style, it) - } - } - - /* - * This method allows you to load custom fonts from res/font folder as provided font family name. - * Fonts may be one of .ttf, .otf or XML (https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml). - * To support multiple font styles or weights, you must provide a font in XML format. - * - * ReactFontManager.getInstance().addCustomFont(this, "Srisakdi", R.font.srisakdi); - */ - public fun addCustomFont(context: Context, fontFamily: String, fontId: Int): Unit { - addCustomFont(fontFamily, ResourcesCompat.getFont(context, fontId)) - } - - /** - * Equivalent method to {@see addCustomFont(Context, String, int)} which accepts a Typeface - * object. - */ - public fun addCustomFont(fontFamily: String, font: Typeface?): Unit { - if (font != null) { - customTypefaceCache[fontFamily] = font - } - } - - /** - * Add additional font family, or replace the exist one in the font memory cache. - * - * @see [Typeface.DEFAULT] - * @see [Typeface.BOLD] - * @see [Typeface.ITALIC] - * @see [Typeface.BOLD_ITALIC] - */ - public fun setTypeface(fontFamilyName: String, style: Int, typeface: Typeface?): Unit { - if (typeface != null) { - fontCache.getOrPut(fontFamilyName) { AssetFontFamily() }.setTypefaceForStyle(style, typeface) - } - } - - /** Responsible for normalizing style and numeric weight for backward compatibility. */ - public class TypefaceStyle { - private val italic: Boolean - private val weight: Int - - public constructor(weight: Int, italic: Boolean) { - this.italic = italic - this.weight = if (weight == ReactConstants.UNSET) NORMAL else weight - } - - /** - * If `weight` is supplied, it will be combined with the italic bit from `style`. Otherwise, any - * existing weight bit in `style` will be used. - */ - @JvmOverloads - public constructor(style: Int, weight: Int = ReactConstants.UNSET) { - val fixedStyle = if (style == ReactConstants.UNSET) Typeface.NORMAL else style - italic = (fixedStyle and Typeface.ITALIC) != 0 - this.weight = - if (weight == ReactConstants.UNSET) - (if ((fixedStyle and Typeface.BOLD) != 0) BOLD else NORMAL) - else weight - } - - public val nearestStyle: Int - get() = - if (weight < BOLD) { - if (italic) Typeface.ITALIC else Typeface.NORMAL - } else { - if (italic) Typeface.BOLD_ITALIC else Typeface.BOLD - } - - public fun apply(typeface: Typeface?): Typeface = - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - Typeface.create(typeface, nearestStyle) - } else { - Typeface.create(typeface, weight, italic) - } - - public companion object { - public const val BOLD: Int = 700 - public const val NORMAL: Int = 400 - } - } - - public companion object { - - // NOTE: Indices in `EXTENSIONS` correspond to the `TypeFace` style constants. - private val EXTENSIONS = arrayOf("", "_bold", "_italic", "_bold_italic") - private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf") - private const val FONTS_ASSET_PATH = "fonts/" - - private val _instance = ReactFontManager() - - @JvmStatic public fun getInstance(): ReactFontManager = _instance - - private fun createAssetTypeface( - fontFamilyName: String, - style: Int, - assetManager: AssetManager?, - ): Typeface { - if (assetManager != null) { - val extension = EXTENSIONS[style] - for (fileExtension in FILE_EXTENSIONS) { - val fileName = "$FONTS_ASSET_PATH$fontFamilyName$extension$fileExtension" - try { - return Typeface.createFromAsset(assetManager, fileName) - } catch (e: RuntimeException) { - // If the typeface asset does not exist, try another extension. - continue - } - } - } - return Typeface.create(fontFamilyName, style) - } - } - - /** Responsible for caching typefaces for each custom font family. */ - private class AssetFontFamily { - private val typefaceSparseArray: SparseArray = SparseArray(4) - - fun getTypefaceForStyle(style: Int): Typeface? = typefaceSparseArray[style] - - fun setTypefaceForStyle(style: Int, typeface: Typeface?) { - typefaceSparseArray.put(style, typeface) - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt index 11b2814c48bb55..6c380f98ada913 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHost.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.defaults import android.content.Context @@ -19,6 +21,8 @@ import com.facebook.react.fabric.ComponentFactory import com.facebook.react.runtime.BindingsInstaller import com.facebook.react.runtime.JSRuntimeFactory import com.facebook.react.runtime.ReactHostImpl +import com.facebook.react.runtime.ReactSurfaceImpl +import com.facebook.react.runtime.ReactSurfaceView import com.facebook.react.runtime.cxxreactpackage.CxxReactPackage import com.facebook.react.runtime.hermes.HermesInstance import java.lang.Exception @@ -57,6 +61,7 @@ public object DefaultReactHost { public fun getDefaultReactHost( context: Context, packageList: List, + createReactSurfaceView: (Context, ReactSurfaceImpl) -> ReactSurfaceView, jsMainModulePath: String = "index", jsBundleAssetPath: String = "index", jsBundleFilePath: String? = null, @@ -67,6 +72,7 @@ public object DefaultReactHost { getDefaultReactHost( context, packageList, + createReactSurfaceView, jsMainModulePath, jsBundleAssetPath, jsBundleFilePath, @@ -103,6 +109,7 @@ public object DefaultReactHost { public fun getDefaultReactHost( context: Context, packageList: List, + createReactSurfaceView: (Context, ReactSurfaceImpl) -> ReactSurfaceView, jsMainModulePath: String = "index", jsBundleAssetPath: String = "index", jsBundleFilePath: String? = null, @@ -134,7 +141,8 @@ public object DefaultReactHost { jsRuntimeFactory = jsRuntimeFactory ?: HermesInstance(), bindingsInstaller = bindingsInstaller, turboModuleManagerDelegateBuilder = defaultTmmDelegateBuilder, - exceptionHandler = exceptionHandler) + exceptionHandler = exceptionHandler, + createReactSurfaceViewCallback = createReactSurfaceView) val componentFactory = ComponentFactory() DefaultComponentsRegistry.register(componentFactory) // TODO: T164788699 find alternative of accessing ReactHostImpl for initialising reactHost @@ -204,6 +212,7 @@ public object DefaultReactHost { getDefaultReactHost( context, packageList, + { ctx, surface -> ReactSurfaceView(ctx, surface) }, jsMainModulePath, jsBundleAssetPath, jsBundleFilePath, @@ -260,6 +269,7 @@ public object DefaultReactHost { getDefaultReactHost( context, packageList, + { ctx, surface -> ReactSurfaceView(ctx, surface) }, jsMainModulePath, jsBundleAssetPath, jsBundleFilePath, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHostDelegate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHostDelegate.kt index 6f37120a46a2d0..780e7700571e94 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHostDelegate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactHostDelegate.kt @@ -7,6 +7,7 @@ package com.facebook.react.defaults +import android.content.Context import com.facebook.jni.annotations.DoNotStrip import com.facebook.react.ReactPackage import com.facebook.react.ReactPackageTurboModuleManagerDelegate @@ -16,6 +17,8 @@ import com.facebook.react.runtime.BindingsInstaller import com.facebook.react.runtime.JSRuntimeFactory import com.facebook.react.runtime.ReactHostDelegate import com.facebook.react.runtime.hermes.HermesInstance +import com.facebook.react.runtime.ReactSurfaceImpl +import com.facebook.react.runtime.ReactSurfaceView /** * A utility class that allows you to simplify the initialization of React Native by setting up a @@ -42,7 +45,9 @@ public class DefaultReactHostDelegate( override val jsRuntimeFactory: JSRuntimeFactory = HermesInstance(), override val bindingsInstaller: BindingsInstaller? = null, private val exceptionHandler: (Exception) -> Unit = { throw it }, - override val turboModuleManagerDelegateBuilder: ReactPackageTurboModuleManagerDelegate.Builder + override val turboModuleManagerDelegateBuilder: ReactPackageTurboModuleManagerDelegate.Builder, + private val createReactSurfaceViewCallback: (Context, ReactSurfaceImpl) -> ReactSurfaceView ) : ReactHostDelegate { override fun handleInstanceException(error: Exception): Unit = exceptionHandler(error) + override fun createReactSurfaceView(context: Context, surfaceImpl: ReactSurfaceImpl): ReactSurfaceView = createReactSurfaceViewCallback(context, surfaceImpl) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactNativeHost.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactNativeHost.kt index 8a98a43c91fcea..36995518f57d56 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactNativeHost.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultReactNativeHost.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.defaults import android.app.Application @@ -19,6 +21,8 @@ import com.facebook.react.fabric.ComponentFactory import com.facebook.react.fabric.FabricUIManagerProviderImpl import com.facebook.react.runtime.JSRuntimeFactory import com.facebook.react.runtime.hermes.HermesInstance +import com.facebook.react.runtime.ReactSurfaceImpl +import com.facebook.react.runtime.ReactSurfaceView import com.facebook.react.uimanager.ViewManagerRegistry import com.facebook.react.uimanager.ViewManagerResolver @@ -36,12 +40,7 @@ protected constructor( ) : ReactNativeHost(application) { override fun getReactPackageTurboModuleManagerDelegateBuilder(): - ReactPackageTurboModuleManagerDelegate.Builder? = - if (isNewArchEnabled) { - DefaultTurboModuleManagerDelegate.Builder() - } else { - null - } + ReactPackageTurboModuleManagerDelegate.Builder? = DefaultTurboModuleManagerDelegate.Builder() override fun getUIManagerProvider(): UIManagerProvider? = if (isNewArchEnabled) { @@ -109,9 +108,12 @@ protected constructor( jsRuntimeFactory: JSRuntimeFactory? = null ): ReactHost { val concreteJSRuntimeFactory = jsRuntimeFactory ?: HermesInstance() + val createReactSurfaceViewCallback: (Context, ReactSurfaceImpl) -> ReactSurfaceView = + { ctx, surfaceImpl -> createReactSurfaceView(ctx, surfaceImpl) } return DefaultReactHost.getDefaultReactHost( context, packages, + createReactSurfaceViewCallback, jsMainModuleName, bundleAssetName ?: "index", jsBundleFile, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultSoLoader.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultSoLoader.kt index c3243f47f9abbf..5ef7e04b27e739 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultSoLoader.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultSoLoader.kt @@ -9,10 +9,10 @@ package com.facebook.react.defaults import com.facebook.soloader.SoLoader -internal object DefaultSoLoader { +public object DefaultSoLoader { @Synchronized @JvmStatic - fun maybeLoadSoLibrary() { + public fun maybeLoadSoLibrary() { SoLoader.loadLibrary("react_newarchdefaults") try { SoLoader.loadLibrary("appmodules") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index 6199a2ca654586..9115873cd962cb 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -25,6 +25,7 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; import androidx.annotation.AnyThread; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.core.util.Preconditions; @@ -34,6 +35,7 @@ import com.facebook.infer.annotation.Nullsafe; import com.facebook.infer.annotation.ThreadConfined; import com.facebook.proguard.annotations.DoNotStripAny; +import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ColorPropConverter; import com.facebook.react.bridge.GuardedRunnable; import com.facebook.react.bridge.LifecycleEventListener; @@ -494,6 +496,14 @@ public void sweepActiveTouchForTag(int surfaceId, int reactTag) { } } + @Override + public void markViewAsInTransition(int surfaceId, int reactTag, boolean isTransitioning) { + SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId); + if (surfaceMountingManager != null) { + surfaceMountingManager.markViewInTransition(reactTag, isTransitioning); + } + } + /** * Method added to Fabric for backward compatibility reasons, as users on Paper could call * [addUiBlock] and [prependUiBlock] on UIManagerModule. @@ -1016,8 +1026,6 @@ public void updateRootLayoutSpecs( @Override public @Nullable View resolveView(int reactTag) { - UiThreadUtil.assertOnUiThread(); - SurfaceMountingManager surfaceManager = mMountingManager.getSurfaceManagerForView(reactTag); return surfaceManager == null ? null : surfaceManager.getView(reactTag); } @@ -1275,6 +1283,27 @@ public String toString() { }); } + public void measure(int surfaceId, int reactTag, final Callback callback) { + mMountItemDispatcher.addMountItem( + new MountItem() { + @Override + public void execute(@NonNull MountingManager mountingManager) { + mMountingManager.measure(surfaceId, reactTag, callback); + } + + @Override + public int getSurfaceId() { + return surfaceId; + } + + @NonNull + @Override + public String toString() { + return "MEASURE_VIEW"; + } + }); + } + @Override public void profileNextBatch() { // TODO T31905686: Remove this method and add support for multi-threading performance counters diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt index 4b52ade0b58eed..0eaed724d0def2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/MountingManager.kt @@ -7,11 +7,14 @@ package com.facebook.react.fabric.mounting +import android.graphics.Matrix +import android.graphics.RectF import android.view.View import androidx.annotation.AnyThread import androidx.annotation.UiThread import com.facebook.common.logging.FLog import com.facebook.infer.annotation.ThreadConfined +import com.facebook.react.bridge.Callback import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReactSoftExceptionLogger.logSoftException import com.facebook.react.bridge.ReadableArray @@ -25,6 +28,8 @@ import com.facebook.react.fabric.events.EventEmitterWrapper import com.facebook.react.fabric.mounting.mountitems.MountItem import com.facebook.react.touch.JSResponderHandler import com.facebook.react.uimanager.RootViewManager +import com.facebook.react.uimanager.PixelUtil + import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewManagerRegistry import com.facebook.react.uimanager.common.ViewUtil @@ -362,6 +367,87 @@ internal class MountingManager( smm.enqueuePendingEvent(reactTag, eventName, canCoalesceEvent, params, eventCategory) } + /** + * Measure a mounted view and return its bounds relative to the root in DIP units. + * The callback signature matches legacy UIManager: (x0, y0, width, height, pageX, pageY). + */ + @UiThread + @Synchronized + fun measure(surfaceId: Int, reactTag: Int, callback: Callback) { + assertOnUiThread() + val smm = getSurfaceMountingManager(surfaceId, reactTag) + if (smm == null) { + callback.invoke(0, 0, 0, 0, 0, 0) + return + } + val view: View = try { + smm.getView(reactTag) + } catch (e: Exception) { + FLog.e(TAG, "Failed to get view for reactTag: %d, surfaceId: %d", reactTag, surfaceId, e) + return + } + val measureBuffer = IntArray(4) + val rootView = smm.getRootViewIfAttached() + if (rootView == null) { + FLog.e(TAG, "Failed to get root view for surfaceId: %d", surfaceId) + return + } + + measure(rootView, view, measureBuffer) + + val x = PixelUtil.toDIPFromPixel(measureBuffer[0].toFloat()) + val y = PixelUtil.toDIPFromPixel(measureBuffer[1].toFloat()) + val width = PixelUtil.toDIPFromPixel(measureBuffer[2].toFloat()) + val height = PixelUtil.toDIPFromPixel(measureBuffer[3].toFloat()) + callback.invoke(0, 0, width, height, x, y) + } + + @Synchronized + fun measure(rootView: View, v: View, outputBuffer: IntArray) { + computeBoundingBox(rootView, outputBuffer) + val rootX = outputBuffer[0] + val rootY = outputBuffer[1] + computeBoundingBox(v, outputBuffer) + outputBuffer[0] -= rootX + outputBuffer[1] -= rootY + } + + private fun computeBoundingBox(view: View, outputBuffer: IntArray) { + val boundingBox = RectF() + boundingBox.set(0f, 0f, view.width.toFloat(), view.height.toFloat()) + mapRectFromViewToWindowCoords(view, boundingBox) + + outputBuffer[0] = Math.round(boundingBox.left) + outputBuffer[1] = Math.round(boundingBox.top) + outputBuffer[2] = Math.round(boundingBox.right - boundingBox.left) + outputBuffer[3] = Math.round(boundingBox.bottom - boundingBox.top) + } + + private fun mapRectFromViewToWindowCoords(view: View, rect: RectF) { + var matrix: Matrix = view.matrix + if (!matrix.isIdentity) { + matrix.mapRect(rect) + } + + rect.offset(view.left.toFloat(), view.top.toFloat()) + + var parent = view.parent + while (parent is View) { + val parentView = parent as View + + rect.offset(-parentView.scrollX.toFloat(), -parentView.scrollY.toFloat()) + + matrix = parentView.matrix + if (!matrix.isIdentity) { + matrix.mapRect(rect) + } + + rect.offset(parentView.left.toFloat(), parentView.top.toFloat()) + + parent = parentView.parent + } + } + private fun getSurfaceMountingManager(surfaceId: Int, reactTag: Int): SurfaceMountingManager? = if (surfaceId == ViewUtil.NO_SURFACE_ID) getSurfaceManagerForView(reactTag) else getSurfaceManager(surfaceId) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java index dee20e3f89b04d..c1181096566508 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java @@ -9,6 +9,7 @@ import static com.facebook.infer.annotation.ThreadConfined.ANY; import static com.facebook.infer.annotation.ThreadConfined.UI; +import static com.facebook.react.fabric.mounting.ViewOperationsKt.DELETE_VIEW_PARENT_TAG; import android.view.View; import android.view.ViewGroup; @@ -76,9 +77,6 @@ public class SurfaceMountingManager { private RootViewManager mRootViewManager; private MountItemExecutor mMountItemExecutor; - @ThreadConfined(UI) - private final Set mErroneouslyReaddedReactTags = new HashSet<>(); - // This set is used to keep track of views that are currently being interacted with (i.e. // views that saw a ACTION_DOWN but not a ACTION_UP event yet). This is used to prevent // views from being removed while they are being interacted with as their event emitter will @@ -95,6 +93,9 @@ public class SurfaceMountingManager { private final int mSurfaceId; + // Coordinates view operations that need to wait for Android view transitions + private final ViewTransitionCoordinator mViewTransitionCoordinator; + public SurfaceMountingManager( int surfaceId, @NonNull JSResponderHandler jsResponderHandler, @@ -108,6 +109,7 @@ public SurfaceMountingManager( mRootViewManager = rootViewManager; mMountItemExecutor = mountItemExecutor; mThemedReactContext = reactContext; + mViewTransitionCoordinator = new ViewTransitionCoordinator(); } public boolean isStopped() { @@ -123,6 +125,17 @@ public int getSurfaceId() { return mSurfaceId; } + public @Nullable View getRootViewIfAttached() { + ViewState viewState = getNullableViewState(mSurfaceId); + if (viewState == null || viewState.mView == null) { + return null; + } + if (!viewState.mIsRoot) { + return null; + } + return viewState.mView; + } + public boolean isRootViewAttached() { return mRootViewAttached; } @@ -283,6 +296,9 @@ public void stopSurface() { // causes further operations to noop. mIsStopped = true; + // Clear all pending operations + mViewTransitionCoordinator.clearAllPending(); + // Reset all StateWrapper objects // Since this can happen on any thread, is it possible to race between StateWrapper destruction // and some accesses from View classes in the UI thread? @@ -331,6 +347,32 @@ public void stopSurface() { } } + /** + * Mark a view as being in or out of an Android view transition. + * This is called by ViewManagers when they call ViewGroup.startViewTransition/endViewTransition. + * + * @param tag The React tag of the view + * @param isTransitioning True if the view is entering a transition, false if exiting + */ + @UiThread + public void markViewInTransition(final int tag, boolean isTransitioning) { + UiThreadUtil.assertOnUiThread(); + + ViewState viewState = getNullableViewState(tag); + if (viewState == null) { + return; + } + + View view = viewState.mView; + + // Delegate to coordinator with a callback to drain queues when transition completes + mViewTransitionCoordinator.markViewInTransition( + tag, + isTransitioning, + view, + () -> mViewTransitionCoordinator.drainOperationsForChild(tag, this)); + } + @UiThread public void addViewAt(final int parentTag, final int tag, final int index) { UiThreadUtil.assertOnUiThread(); @@ -358,62 +400,80 @@ public void addViewAt(final int parentTag, final int tag, final int index) { "Unable to find view for viewState " + viewState + " and tag " + tag); } - // Display children before inserting - if (SHOW_CHANGED_VIEW_HIERARCHIES) { - FLog.e(TAG, "addViewAt: [" + tag + "] -> [" + parentTag + "] idx: " + index + " BEFORE"); - logViewHierarchy(parentView, false); + ViewParent viewParent = view.getParent(); + boolean shouldEnqueueOperation = mViewTransitionCoordinator.shouldEnqueueOperation(tag, parentTag); + // Fast path: no queue, no parent, we can just add the view immediately + if (!shouldEnqueueOperation && viewParent == null) { + addViewAtInternal(parentView, view, index); + return; } - ViewParent viewParent = view.getParent(); - if (viewParent != null) { - int actualParentId = - viewParent instanceof ViewGroup ? ((ViewGroup) viewParent).getId() : View.NO_ID; - ReactSoftExceptionLogger.logSoftException( + + // Either we explicitly need to enqueue, or … + if (!shouldEnqueueOperation) { + // … the view has a parent, which we treat as signal to enqueue (as the view must be in transition) + mViewTransitionCoordinator.markViewInTransition( + tag, + true, + view, + () -> mViewTransitionCoordinator.drainOperationsForChild(tag, this) + ); + FLog.w(TAG, + "addViewAt: View with tag [" + + tag + + "] already has a parent [" + + ((ViewGroup) viewParent).getId() + + "], enqueuing add operation into ViewTransitionCoordinator"); + } + + AddViewOperation operation = + new AddViewOperation(tag, parentTag, index, parentView, view); + mViewTransitionCoordinator.enqueueOperation(operation); + } + + /** + * Internal method to add a view to its parent. This is called either directly from addViewAt + * (for immediate execution) or from the coordinator (for delayed execution). + */ + @UiThread + private void addViewAtInternal(ViewGroup parentView, View child, int atIndex) { + UiThreadUtil.assertOnUiThread(); + if (isStopped()) { + return; + } + + if (child.getParent() != null) { + throw new IllegalViewOperationException( + "addViewAtInternal: cannot insert view [" + + child.getId() + + "] into parent [" + + parentView.getId() + + "]: View already has a parent: [" + + ((ViewGroup) child.getParent()).getId() + + "]"); + } + + // Display children before inserting + if (SHOW_CHANGED_VIEW_HIERARCHIES) { + FLog.e( TAG, - new IllegalStateException( - "addViewAt: cannot insert view [" - + tag - + "] into parent [" - + parentTag - + "]: View already has a parent: [" - + actualParentId - + "] " - + " Parent: " - + viewParent.getClass().getSimpleName() - + " View: " - + view.getClass().getSimpleName())); - - // We've hit an error case, and `addView` will crash below - // if we don't take evasive action (it is an error to add a View - // to the hierarchy if it already has a parent). - // We don't know /why/ this happens yet, but it does happen - // very infrequently in production. - // Thus, we do three things here: - // (1) We logged a SoftException above, so if there's a crash later - // on, we might have some hints about what caused it. - // (2) We remove the View from its parent. - // (3) In case the View was removed from the hierarchy with the - // RemoveDeleteTree instruction, and is now being readded - which - // should be impossible - we mark this as a "readded" View and - // thus prevent the RemoveDeleteTree worker from deleting this - // View in the future. - if (viewParent instanceof ViewGroup) { - ((ViewGroup) viewParent).removeView(view); - } - mErroneouslyReaddedReactTags.add(tag); + "addViewAt: [" + child.getId() + "] -> [" + parentView.getId() + "] idx: " + atIndex + + " BEFORE"); + logViewHierarchy(parentView, false); } + ViewState parentViewState = getViewState(parentView.getId()); try { - getViewGroupManager(parentViewState).addView(parentView, view, index); + getViewGroupManager(parentViewState).addView(parentView, child, atIndex); } catch (IllegalStateException | IndexOutOfBoundsException e) { // Wrap error with more context for debugging throw new IllegalStateException( "addViewAt: failed to insert view [" - + tag + + child.getId() + "] into parent [" - + parentTag + + parentView.getId() + "] at index " - + index, + + atIndex, e); } @@ -430,31 +490,34 @@ public void addViewAt(final int parentTag, final int tag, final int index) { @Override public void run() { FLog.e( - TAG, "addViewAt: [" + tag + "] -> [" + parentTag + "] idx: " + index + " AFTER"); + TAG, + "addViewAt: [" + + child.getId() + + "] -> [" + + parentView.getId() + + "] idx: " + + atIndex + + " AFTER"); logViewHierarchy(parentView, false); } }); } } + /** + * Execute an AddViewOperation from the coordinator. + * This is called by ViewOperation.execute() after notifying the coordinator. + */ + public void executeAddViewOperation(AddViewOperation operation) { + addViewAtInternal(operation.getParent(), operation.getChild(), operation.getIndex()); + } + @UiThread public void removeViewAt(final int tag, final int parentTag, int index) { if (isStopped()) { return; } - // This is "impossible". See comments above. - if (mErroneouslyReaddedReactTags.contains(tag)) { - ReactSoftExceptionLogger.logSoftException( - TAG, - new IllegalViewOperationException( - "removeViewAt tried to remove a React View that was actually reused. This indicates a" - + " bug in the Differ (specifically instruction ordering). [" - + tag - + "]")); - return; - } - UiThreadUtil.assertOnUiThread(); ViewState parentViewState = getNullableViewState(parentTag); @@ -485,12 +548,33 @@ public void removeViewAt(final int tag, final int parentTag, int index) { throw new IllegalStateException("Unable to find view for tag [" + parentTag + "]"); } + if (mViewTransitionCoordinator.shouldEnqueueOperation(tag, parentTag, /* checkTransitionStatus */ false)) { + // ^ checkTransitionStatus = false means we don't check if the view is marked as in transition. + // When a view is in transition we want to call removeViewAt immediately, so it gets marked for removal + // and the onDetach listener will actually fire at some point. + // Only queue if there is already a queue (ie. remove ("immediate"), add (queued), remove (queued)) + RemoveViewOperation operation = + new RemoveViewOperation(tag, parentTag, index, parentView); + mViewTransitionCoordinator.enqueueOperation(operation); + return; + } + + removeViewAtInternal(parentTag, parentView, tag, index); + } + + /** + * Internal method to remove a view from its parent. This is called either directly from + * removeViewAt (for immediate execution) or from the coordinator (for delayed execution). + */ + @UiThread + private void removeViewAtInternal(int parentTag, final ViewGroup parentView, final int tag, int index) { if (SHOW_CHANGED_VIEW_HIERARCHIES) { // Display children before deleting any FLog.e(TAG, "removeViewAt: [" + tag + "] -> [" + parentTag + "] idx: " + index + " BEFORE"); logViewHierarchy(parentView, false); } + ViewState parentViewState = getViewState(parentTag); IViewGroupManager viewGroupManager = getViewGroupManager(parentViewState); // Verify that the view we're about to remove has the same tag we expect @@ -608,6 +692,14 @@ public void run() { } } + public void executeRemoveViewOperation(RemoveViewOperation operation) { + removeViewAtInternal( + operation.getParentTag(), + operation.getParentView(), + operation.getChildTag(), + operation.getIndex()); + } + @UiThread public void createView( @NonNull String componentName, @@ -619,6 +711,7 @@ public void createView( if (isStopped()) { return; } + mViewTransitionCoordinator.notifyViewCreated(reactTag); // We treat this as a perf problem and not a logical error. View Preallocation or unexpected // changes to Differ or C++ Binding could cause some redundant Create instructions. // There are cases where preallocation happens and a node is recreated: if a node is @@ -1021,6 +1114,8 @@ private void onViewStateDeleted(ViewState viewState) { viewState.mEventEmitter = null; } + // TODO: i think we should also clear out pending operations? + // For non-root views we notify viewmanager with {@link ViewManager#onDropInstance} ViewManager viewManager = viewState.mViewManager; if (!viewState.mIsRoot && viewManager != null) { @@ -1035,13 +1130,24 @@ public void deleteView(int reactTag) { return; } + if (mViewTransitionCoordinator.shouldEnqueueOperation(reactTag, DELETE_VIEW_PARENT_TAG)) { + DeleteViewOperation operation = new DeleteViewOperation(reactTag); + mViewTransitionCoordinator.enqueueOperation(operation); + return; + } + + deleteViewInternal(reactTag); + } + + @UiThread + private void deleteViewInternal(int reactTag) { ViewState viewState = getNullableViewState(reactTag); if (viewState == null) { ReactSoftExceptionLogger.logSoftException( - ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE, - new ReactNoCrashSoftException( - "Unable to find viewState for tag: " + reactTag + " for deleteView")); + ReactSoftExceptionLogger.Categories.SURFACE_MOUNTING_MANAGER_MISSING_VIEWSTATE, + new ReactNoCrashSoftException( + "Unable to find viewState for tag: " + reactTag + " for deleteView")); return; } @@ -1062,6 +1168,11 @@ public void deleteView(int reactTag) { } } + @UiThread + protected void executeDeleteViewOperation(DeleteViewOperation operation) { + deleteViewInternal(operation.getChildTag()); + } + @UiThread public void preallocateView( @NonNull String componentName, @@ -1128,6 +1239,11 @@ public View getView(int reactTag) { } public void printSurfaceState() { + if (!ReactBuildConfig.DEBUG) { + // this is too noisy in prod and is overloading our breadcrumbs + return; + } + FLog.e(TAG, "Views created for surface {%d}:", getSurfaceId()); for (ViewState viewState : mTagToViewState.values()) { String viewManagerName = @@ -1210,8 +1326,7 @@ private static class ViewState { @Nullable EventEmitterWrapper mEventEmitter = null; @ThreadConfined(UI) - @Nullable - Queue mPendingEventQueue = null; + @Nullable Queue mPendingEventQueue = null; private ViewState(int reactTag) { this(reactTag, null, null, false); @@ -1269,4 +1384,5 @@ public void dispatch(EventEmitterWrapper eventEmitter) { } } } + } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewOperations.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewOperations.kt new file mode 100644 index 00000000000000..4f2d4f66156c74 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewOperations.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.fabric.mounting + +import android.view.View +import android.view.ViewGroup +import com.facebook.infer.annotation.ThreadConfined + +/** + * These ViewOperations are used by [ViewTransitionCoordinator] when a view is marked + * as in transition: https://developer.android.com/reference/android/view/ViewGroup#startViewTransition(android.view.View) + */ +internal interface ViewOperation { + val childTag: Int + val parentTag: Int + val index: Int? + + /** + * Check if this operation is ready to execute. + * An operation is ready when: + * 1. The child view is not attached to any parent (for add operations) + * 2. This operation is first in line for the child (determined by coordinator) + */ + fun isReadyToExecute(coordinator: ViewTransitionCoordinator): Boolean + + /** + * Execute the operation. This is called by the coordinator when the operation + * becomes ready to execute. + */ + fun execute(manager: SurfaceMountingManager) +} + +@ThreadConfined(ThreadConfined.UI) +internal data class AddViewOperation( + override val childTag: Int, + override val parentTag: Int, + override val index: Int, + val parent: ViewGroup, + val child: View +) : ViewOperation { + + override fun isReadyToExecute(coordinator: ViewTransitionCoordinator): Boolean { + if (child.parent != null) { + // The child is still attached to a parent, so we can't add it yet + return false + } + + return coordinator.isFirstInLineForChild(childTag, parentTag) + } + + override fun execute(manager: SurfaceMountingManager) { + manager.executeAddViewOperation(this) + } + + override fun toString(): String { + return "AddViewOperation(parent=$parentTag, child=$childTag, index=$index)" + } +} + + +@ThreadConfined(ThreadConfined.UI) +internal data class RemoveViewOperation( + override val childTag: Int, + override val parentTag: Int, + override val index: Int, + val parentView: ViewGroup +) : ViewOperation { + + override fun isReadyToExecute(coordinator: ViewTransitionCoordinator): Boolean { + // Remove operations are always ready - we just need to maintain order + // within the queue for the parent + return coordinator.isFirstInLineForChild(childTag, parentTag) + } + + override fun execute(manager: SurfaceMountingManager) { + manager.executeRemoveViewOperation(this) + } + + override fun toString(): String { + return "RemoveViewOperation(parent=$parentTag, child=$childTag, index=$index)" + } +} + +/** Delete operations don't have a parent view, so we use a hardcoded value */ +public const val DELETE_VIEW_PARENT_TAG: Int = -1337 + +@ThreadConfined(ThreadConfined.UI) +internal data class DeleteViewOperation( + override val childTag: Int, + override val parentTag: Int, + override val index: Int? +) : ViewOperation { + constructor(reactTag: Int) : this(reactTag, DELETE_VIEW_PARENT_TAG, null) + + override fun isReadyToExecute(coordinator: ViewTransitionCoordinator): Boolean { + // Remove operations are always ready - we just need to maintain order + // within the queue for the parent + return coordinator.isFirstInLineForChild(childTag, parentTag) + } + + override fun execute(manager: SurfaceMountingManager) { + manager.executeDeleteViewOperation(this) + } + + override fun toString(): String { + return "DeleteViewOperation(child=$childTag)" + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewTransitionCoordinator.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewTransitionCoordinator.kt new file mode 100644 index 00000000000000..198622e94299e1 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/ViewTransitionCoordinator.kt @@ -0,0 +1,276 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.fabric.mounting + +import android.view.View +import androidx.annotation.VisibleForTesting +import com.facebook.common.logging.FLog +import com.facebook.infer.annotation.ThreadConfined +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.common.build.ReactBuildConfig +import java.util.LinkedList + +/** + * Coordinates pending view operations across Android view transitions. + * + * When Android starts a view transition (via ViewGroup.startViewTransition), views cannot be + * immediately added or removed from the view hierarchy until the transition completes and the + * view detaches. This coordinator manages a queue of pending operations and ensures they execute + * in the correct order once transitions complete. + * + * Key responsibilities: + * - Track which views are currently in transitions + * - Queue operations that can't execute immediately + * - Coordinate cross-parent dependencies (when a view needs to be added to multiple parents) + * - Drain queues when views become ready + */ +@ThreadConfined(ThreadConfined.UI) +internal class ViewTransitionCoordinator { + + companion object { + private const val TAG = "ViewTransitionCoordinator" + } + + // Views currently in Android transitions (via ViewGroup.startViewTransition) + private val viewsInTransition = mutableSetOf() + + // Per-parent queues of pending operations + // Key: parent tag, Value: list of operations waiting to execute + private val parentQueues = mutableMapOf>() + + // Key: child tag, Value: ordered list of parent tags waiting for this child + private val childToParentOrder = mutableMapOf>() + + /** + * Mark a view as being in or out of an Android transition. + */ + fun markViewInTransition( + tag: Int, + transitioning: Boolean, + view: View?, + onDetach: Runnable + ) { + UiThreadUtil.assertOnUiThread() + + if (transitioning) { + if (viewsInTransition.contains(tag)) { + return + } + + viewsInTransition.add(tag) + + if (view != null) { + // TODO: we re-create the listener every time for the same view; consider caching it by view + val listener = object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) {} + + override fun onViewDetachedFromWindow(v: View) { + view.removeOnAttachStateChangeListener(this) + // Looking at how endViewTransition is implemented, dispatchDetachedFromWindow + // gets called _before_ the parent relation is removed, so we need to post this to the end of the frame: + UiThreadUtil.runOnUiThread { + viewsInTransition.remove(tag) + onDetach.run() + } + } + } + view.addOnAttachStateChangeListener(listener) + } + } else { + viewsInTransition.remove(tag) + // NOTE: we don't remove the listener here, as "endViewTransition" may be called + // before the view actually detaches. The listener will remove itself when the view detaches at some point. + } + } + + fun notifyViewCreated(tag: Int) { + // Check for queued delete operations for that tag. + // If a create operation for a tag happens while we want to delete it, we can drop the delete. + val deleteQueue = parentQueues[DELETE_VIEW_PARENT_TAG] ?: return + + val index = deleteQueue.indexOfFirst { it.childTag == tag } + if (index == -1) { + return + } + + deleteQueue.removeAt(index) + if (deleteQueue.isEmpty()) { + parentQueues.remove(DELETE_VIEW_PARENT_TAG) + } + + val orderList = childToParentOrder[tag] + orderList?.remove(DELETE_VIEW_PARENT_TAG) + if (orderList != null && orderList.isEmpty()) { + childToParentOrder.remove(tag) + } + + if (ReactBuildConfig.DEBUG) { + FLog.d( + TAG, + "Dropping queued delete operation for tag $tag as the view was re-created" + ) + } + } + + @JvmOverloads + fun shouldEnqueueOperation(childTag: Int, parentTag: Int, checkTransitionStatus: Boolean = true): Boolean { + if (childToParentOrder.containsKey(childTag)) { + // If the child is queued on some parents we can be sure that the operation needs to be queued + return true + } + + // If parent has a queue, everything goes to the queue to maintain order. + // Exception is the delete queue, as that's global. We don't want to block deletes + // for views whose hierarchy isn't marked as transitioning. + if (parentQueues.containsKey(parentTag) && parentTag != DELETE_VIEW_PARENT_TAG) { + return true + } + + // If child is transitioning, we need to queue + if (checkTransitionStatus && viewsInTransition.contains(childTag)) { + return true + } + + return false + } + + fun enqueueOperation(operation: ViewOperation) { + UiThreadUtil.assertOnUiThread() + + val parentTag = operation.parentTag + val childTag = operation.childTag + + val queue = parentQueues.getOrPut(parentTag) { mutableListOf() } + queue.add(operation) + + // Track cross-queue ordering for add operations + val orderList = childToParentOrder.getOrPut(childTag) { LinkedList() } + val lastItem = orderList.lastOrNull() + if (lastItem != parentTag) { + orderList.add(parentTag) + } + + if (ReactBuildConfig.DEBUG) { + FLog.d( + TAG, + "Enqueued operation: $operation" + ) + } + } + + private var drainingParentTag: Int? = null + /** + * Drain all pending operations for a specific child tag. + * This is called when a child view becomes ready (e.g., transitions complete, view detaches). + */ + fun drainOperationsForChild(childTag: Int, manager: SurfaceMountingManager) { + UiThreadUtil.assertOnUiThread() + + var madeProgress: Boolean + do { + val parentOrderForChild = childToParentOrder[childTag] + if (parentOrderForChild.isNullOrEmpty()) { + break + } + + val parentTag = parentOrderForChild.first + val queue = parentQueues[parentTag] + + if (queue == null) { + error("No queue for parentTag=$parentTag. This should not happen as childToParentOrder indicates there are pending operations. childToParentOrder=$childToParentOrder") + } + if (drainingParentTag == parentTag) { + // we are already draining this parent (re-entrancy), avoid infinite loop + break + } + drainingParentTag = parentTag + + madeProgress = drainQueue(parentTag, queue, manager) + + if (queue.isEmpty()) { + parentQueues.remove(parentTag) + } + + } while (madeProgress) + + drainingParentTag = null + } + + private val executedChildIdsForParent = mutableMapOf>() + /** + * Drain a single parent's queue, executing all ready operations. + * + * @return true if any progress was made (operations executed) + */ + private fun drainQueue( + parentTag: Int, + queue: MutableList, + manager: SurfaceMountingManager + ): Boolean { + var madeProgress = false + + val iterator = queue.iterator() + while (iterator.hasNext()) { + val operation = iterator.next() + + if (!operation.isReadyToExecute(this) || viewsInTransition.contains(operation.childTag)) { + break + } + + if (ReactBuildConfig.DEBUG) { + FLog.d( + TAG, + "Executing $operation" + ) + } + + iterator.remove() // remove before executing, as execution may re-enter draining + + val executedChildIds = executedChildIdsForParent.getOrPut(parentTag) { mutableSetOf() } + executedChildIds.add(operation.childTag) + + operation.execute(manager) + madeProgress = true + } + + val executedChildIds = executedChildIdsForParent[parentTag] + if (queue.isEmpty() && executedChildIds != null) { + // we drained the whole queue for this parent, now we need to remove this parent from all executed child's order lists + for (childId in executedChildIds) { + val parentOrderForChild = childToParentOrder[childId] + if (parentOrderForChild?.first != parentTag) { + error("Internal error: operation parentTag $parentTag is not first in childToParentOrder for childTag $childId: $childToParentOrder") + } + parentOrderForChild.removeFirst() + if (parentOrderForChild.isEmpty()) { + childToParentOrder.remove(childId) + } + } + executedChildIdsForParent.remove(parentTag) + } + + return madeProgress + } + + + fun isFirstInLineForChild(childTag: Int, parentTag: Int): Boolean { + val orderList = childToParentOrder[childTag] + return orderList.isNullOrEmpty() || orderList.first == parentTag + } + + fun clearAllPending() { + parentQueues.clear() + childToParentOrder.clear() + viewsInTransition.clear() + } + + @VisibleForTesting + fun isEmpty(): Boolean { + return parentQueues.isEmpty() && childToParentOrder.isEmpty() && viewsInTransition.isEmpty() + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index da0fefc85cc951..0303f9e53c6b71 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<21b64dde474f052173af1442e222e62c>> */ /** @@ -312,6 +312,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun preparedTextCacheSize(): Double = accessor.preparedTextCacheSize() + /** + * Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again. + */ + @JvmStatic + public fun preventShadowTreeCommitExhaustion(): Boolean = accessor.preventShadowTreeCommitExhaustion() + /** * Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause. */ @@ -336,6 +342,18 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun useFabricInterop(): Boolean = accessor.useFabricInterop() + /** + * Use a native implementation of equals in NativeReadableArray. + */ + @JvmStatic + public fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = accessor.useNativeEqualsInNativeReadableArrayAndroid() + + /** + * Use a native implementation of TransformHelper + */ + @JvmStatic + public fun useNativeTransformHelperAndroid(): Boolean = accessor.useNativeTransformHelperAndroid() + /** * When enabled, the native view configs are used in bridgeless mode. */ @@ -378,6 +396,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun virtualViewPrerenderRatio(): Double = accessor.virtualViewPrerenderRatio() + /** + * Instead of logging a soft exception crash the app in UiThreadUtils. + */ + @JvmStatic + public fun runtimeCrashUiThreadUtils(): Boolean = accessor.runtimeCrashUiThreadUtils() + /** * Overrides the feature flags with the ones provided by the given provider * (generally one that extends `ReactNativeFeatureFlagsDefaults`). diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index 98b94a42ab97ee..0e756c2fbf1380 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<9b6d83d6ea0acbc13bce19d869699079>> + * @generated SignedSource<<3716e04e3220977d4f1be506ac6c8201>> */ /** @@ -67,10 +67,13 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null + private var preventShadowTreeCommitExhaustionCache: Boolean? = null private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null private var useFabricInteropCache: Boolean? = null + private var useNativeEqualsInNativeReadableArrayAndroidCache: Boolean? = null + private var useNativeTransformHelperAndroidCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null private var useRawPropsJsiValueCache: Boolean? = null @@ -78,6 +81,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var useTurboModuleInteropCache: Boolean? = null private var useTurboModulesCache: Boolean? = null private var virtualViewPrerenderRatioCache: Double? = null + private var runtimeCrashUiThreadUtilsCache: Boolean? = null override fun commonTestFlag(): Boolean { var cached = commonTestFlagCache @@ -502,6 +506,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun preventShadowTreeCommitExhaustion(): Boolean { + var cached = preventShadowTreeCommitExhaustionCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.preventShadowTreeCommitExhaustion() + preventShadowTreeCommitExhaustionCache = cached + } + return cached + } + override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean { var cached = traceTurboModulePromiseRejectionsOnAndroidCache if (cached == null) { @@ -538,6 +551,24 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean { + var cached = useNativeEqualsInNativeReadableArrayAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.useNativeEqualsInNativeReadableArrayAndroid() + useNativeEqualsInNativeReadableArrayAndroidCache = cached + } + return cached + } + + override fun useNativeTransformHelperAndroid(): Boolean { + var cached = useNativeTransformHelperAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.useNativeTransformHelperAndroid() + useNativeTransformHelperAndroidCache = cached + } + return cached + } + override fun useNativeViewConfigsInBridgelessMode(): Boolean { var cached = useNativeViewConfigsInBridgelessModeCache if (cached == null) { @@ -601,6 +632,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun runtimeCrashUiThreadUtils(): Boolean { + var cached = runtimeCrashUiThreadUtilsCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.runtimeCrashUiThreadUtils() + runtimeCrashUiThreadUtilsCache = cached + } + return cached + } + override fun override(provider: ReactNativeFeatureFlagsProvider): Unit = ReactNativeFeatureFlagsCxxInterop.override(provider as Any) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index 005c34d70c1328..ce1988e7772c88 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<75760457dea789ab0951d3a22be3341c>> + * @generated SignedSource<<3e77a3faa835b76ce4618699340bf153>> */ /** @@ -122,6 +122,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun preparedTextCacheSize(): Double + @DoNotStrip @JvmStatic public external fun preventShadowTreeCommitExhaustion(): Boolean + @DoNotStrip @JvmStatic public external fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean @DoNotStrip @JvmStatic public external fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean @@ -130,6 +132,10 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun useFabricInterop(): Boolean + @DoNotStrip @JvmStatic public external fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean + + @DoNotStrip @JvmStatic public external fun useNativeTransformHelperAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): Boolean @DoNotStrip @JvmStatic public external fun useOptimizedEventBatchingOnAndroid(): Boolean @@ -144,6 +150,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun virtualViewPrerenderRatio(): Double + @DoNotStrip @JvmStatic public external fun runtimeCrashUiThreadUtils(): Boolean + @DoNotStrip @JvmStatic public external fun override(provider: Any) @DoNotStrip @JvmStatic public external fun dangerouslyReset() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 8fb7c868f421a4..4a522463ec7e49 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<48fa8921cc2947a713974c9926e1d806>> + * @generated SignedSource<<854ea702c230ffc221d192b28470a967>> */ /** @@ -33,7 +33,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun disableMainQueueSyncDispatchIOS(): Boolean = false - override fun disableMountItemReorderingAndroid(): Boolean = false + override fun disableMountItemReorderingAndroid(): Boolean = true override fun disableTextLayoutManagerCacheAndroid(): Boolean = false @@ -117,6 +117,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun preparedTextCacheSize(): Double = 200.0 + override fun preventShadowTreeCommitExhaustion(): Boolean = false + override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean = false override fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean = false @@ -125,6 +127,10 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun useFabricInterop(): Boolean = true + override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = false + + override fun useNativeTransformHelperAndroid(): Boolean = false + override fun useNativeViewConfigsInBridgelessMode(): Boolean = false override fun useOptimizedEventBatchingOnAndroid(): Boolean = false @@ -138,4 +144,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun useTurboModules(): Boolean = false override fun virtualViewPrerenderRatio(): Double = 5.0 + + override fun runtimeCrashUiThreadUtils(): Boolean = false } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 40f181990047fe..a0bf982ff44b0b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<356261385b837def94ac5a4ca7ffd05d>> + * @generated SignedSource<> */ /** @@ -71,10 +71,13 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fuseboxNetworkInspectionEnabledCache: Boolean? = null private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null private var preparedTextCacheSizeCache: Double? = null + private var preventShadowTreeCommitExhaustionCache: Boolean? = null private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null private var useFabricInteropCache: Boolean? = null + private var useNativeEqualsInNativeReadableArrayAndroidCache: Boolean? = null + private var useNativeTransformHelperAndroidCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null private var useRawPropsJsiValueCache: Boolean? = null @@ -82,6 +85,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var useTurboModuleInteropCache: Boolean? = null private var useTurboModulesCache: Boolean? = null private var virtualViewPrerenderRatioCache: Double? = null + private var runtimeCrashUiThreadUtilsCache: Boolean? = null override fun commonTestFlag(): Boolean { var cached = commonTestFlagCache @@ -553,6 +557,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun preventShadowTreeCommitExhaustion(): Boolean { + var cached = preventShadowTreeCommitExhaustionCache + if (cached == null) { + cached = currentProvider.preventShadowTreeCommitExhaustion() + accessedFeatureFlags.add("preventShadowTreeCommitExhaustion") + preventShadowTreeCommitExhaustionCache = cached + } + return cached + } + override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean { var cached = traceTurboModulePromiseRejectionsOnAndroidCache if (cached == null) { @@ -593,6 +607,26 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean { + var cached = useNativeEqualsInNativeReadableArrayAndroidCache + if (cached == null) { + cached = currentProvider.useNativeEqualsInNativeReadableArrayAndroid() + accessedFeatureFlags.add("useNativeEqualsInNativeReadableArrayAndroid") + useNativeEqualsInNativeReadableArrayAndroidCache = cached + } + return cached + } + + override fun useNativeTransformHelperAndroid(): Boolean { + var cached = useNativeTransformHelperAndroidCache + if (cached == null) { + cached = currentProvider.useNativeTransformHelperAndroid() + accessedFeatureFlags.add("useNativeTransformHelperAndroid") + useNativeTransformHelperAndroidCache = cached + } + return cached + } + override fun useNativeViewConfigsInBridgelessMode(): Boolean { var cached = useNativeViewConfigsInBridgelessModeCache if (cached == null) { @@ -663,6 +697,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun runtimeCrashUiThreadUtils(): Boolean { + var cached = runtimeCrashUiThreadUtilsCache + if (cached == null) { + cached = currentProvider.runtimeCrashUiThreadUtils() + accessedFeatureFlags.add("runtimeCrashUiThreadUtils") + runtimeCrashUiThreadUtilsCache = cached + } + return cached + } + override fun override(provider: ReactNativeFeatureFlagsProvider) { if (accessedFeatureFlags.isNotEmpty()) { val accessedFeatureFlagsStr = accessedFeatureFlags.joinToString(separator = ", ") { it } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt index 32dc0a1e2d0d41..1b8ad3bdc5c60e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<0bafb0a2fb79c4220d21f1736894af14>> */ /** @@ -23,5 +23,9 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android : // We could use JNI to get the defaults from C++, // but that is more expensive than just duplicating the defaults here. + override fun preventShadowTreeCommitExhaustion(): Boolean = true + override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = true + + override fun useNativeTransformHelperAndroid(): Boolean = true } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index e7c491089b2cd4..a8af04115c2463 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<8abf9bfb81265ae0c840457eb6c199bd>> + * @generated SignedSource<> */ /** @@ -117,6 +117,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun preparedTextCacheSize(): Double + @DoNotStrip public fun preventShadowTreeCommitExhaustion(): Boolean + @DoNotStrip public fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean @DoNotStrip public fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean @@ -125,6 +127,10 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun useFabricInterop(): Boolean + @DoNotStrip public fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean + + @DoNotStrip public fun useNativeTransformHelperAndroid(): Boolean + @DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): Boolean @DoNotStrip public fun useOptimizedEventBatchingOnAndroid(): Boolean @@ -138,4 +144,6 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun useTurboModules(): Boolean @DoNotStrip public fun virtualViewPrerenderRatio(): Double + + @DoNotStrip public fun runtimeCrashUiThreadUtils(): Boolean } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.kt index 656f601f292078..5678db935c6d1b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.kt @@ -7,6 +7,8 @@ package com.facebook.react.modules.appstate +import android.app.ActivityManager +import com.facebook.common.logging.FLog import com.facebook.fbreact.specs.NativeAppStateSpec import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Callback @@ -26,11 +28,27 @@ internal class AppStateModule(reactContext: ReactApplicationContext) : init { reactContext.addLifecycleEventListener(this) reactContext.addWindowFocusChangeListener(this) + val isAppForegroundedByMemoryState = isAppForegroundedByMemoryState() + // pasten: temporary debug log - remove after we validate with real users + FLog.w("AppStateModule", "initial isAppForegroundedByMemoryState = $isAppForegroundedByMemoryState, " + + "reactContext.lifecycleState = ${reactContext.lifecycleState}") + appState = if (reactContext.lifecycleState === LifecycleState.RESUMED) APP_STATE_ACTIVE + // pasten: during cold start appState=APP_STATE_BACKGROUND while tha is actually in the foreground + // best effort foreground detection when LifecycleState.BEFORE_CREATE (which is the initial state) + else if (reactContext.lifecycleState === LifecycleState.BEFORE_CREATE && isAppForegroundedByMemoryState) { + APP_STATE_ACTIVE + } else if (isAppForegroundedByMemoryState) APP_STATE_ACTIVE else APP_STATE_BACKGROUND } + private fun isAppForegroundedByMemoryState(): Boolean { + return ActivityManager.RunningAppProcessInfo().apply { + ActivityManager.getMyMemoryState(this) + }.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + } + public override fun getTypedExportedConstants(): Map = mapOf(INITIAL_STATE to appState) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.kt index c153eda77334df..0d94958392de2d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/blob/BlobProvider.kt @@ -53,7 +53,7 @@ public class BlobProvider : ContentProvider() { var blobModule: BlobModule? = null val context = context?.applicationContext if (context is ReactApplication) { - val host = (context as ReactApplication).reactNativeHost + @Suppress("DEPRECATION") val host = (context as ReactApplication).reactNativeHost val reactContext = host.reactInstanceManager.currentReactContext ?: throw RuntimeException("No ReactContext associated with BlobProvider") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.kt index 536912d2f533bd..c3f11db37e1ee9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/fresco/FrescoModule.kt @@ -7,6 +7,7 @@ package com.facebook.react.modules.fresco +import android.content.Context import com.facebook.common.logging.FLog import com.facebook.drawee.backends.pipeline.DraweeConfig import com.facebook.drawee.backends.pipeline.Fresco @@ -17,7 +18,6 @@ import com.facebook.imagepipeline.core.ImagePipelineConfig import com.facebook.imagepipeline.listener.RequestListener import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.common.ReactConstants import com.facebook.react.module.annotations.ReactModule @@ -138,7 +138,7 @@ constructor( */ @JvmStatic public fun hasBeenInitialized(): Boolean = hasBeenInitialized - private fun getDefaultConfig(context: ReactContext): ImagePipelineConfig = + private fun getDefaultConfig(context: Context): ImagePipelineConfig = getDefaultConfigBuilder(context).build() /** @@ -149,7 +149,7 @@ constructor( * initialized with default values */ @JvmStatic - public fun getDefaultConfigBuilder(context: ReactContext): ImagePipelineConfig.Builder { + public fun getDefaultConfigBuilder(context: Context): ImagePipelineConfig.Builder { val requestListeners = HashSet() requestListeners.add(SystraceRequestListener()) val client = OkHttpClientProvider.createClient() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt index 2405d32ce682d5..3348a5fa91365d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.kt @@ -12,9 +12,9 @@ package com.facebook.react.modules.systeminfo public object ReactNativeVersion { @JvmField public val VERSION: Map = mapOf( - "major" to 1000, - "minor" to 0, - "patch" to 0, + "major" to 0, + "minor" to 81, + "patch" to 4, "prerelease" to null ) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt index 8dcd8c7c6f4c3d..41e60622819659 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/websocket/WebSocketModule.kt @@ -45,10 +45,17 @@ public class WebSocketModule(context: ReactApplicationContext) : public fun onMessage(byteString: ByteString, params: WritableMap) } + public interface OnOpenHandler { + + public fun onOpen(webSocket: WebSocket, socketId: Int) + } + private val webSocketConnections: MutableMap = ConcurrentHashMap() private val contentHandlers: MutableMap = ConcurrentHashMap() private val cookieHandler: ForwardingCookieHandler = ForwardingCookieHandler() + public var mOnOpenHandler: OnOpenHandler? = null + override fun invalidate() { for (socket in webSocketConnections.values) { socket.close(1_001 /* endpoint is going away */, null) @@ -147,6 +154,7 @@ public class WebSocketModule(context: ReactApplicationContext) : put("protocol", response.header("Sec-WebSocket-Protocol", "")) } sendEvent("websocketOpen", params) + mOnOpenHandler?.onOpen(webSocket, id) } override fun onClosing(websocket: WebSocket, code: Int, reason: String) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostDelegate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostDelegate.kt index 10190871f42f45..828db77a9f7a21 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostDelegate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostDelegate.kt @@ -7,11 +7,14 @@ package com.facebook.react.runtime +import android.content.Context import com.facebook.infer.annotation.ThreadSafe import com.facebook.react.ReactPackage import com.facebook.react.ReactPackageTurboModuleManagerDelegate import com.facebook.react.bridge.JSBundleLoader import com.facebook.react.common.annotations.UnstableReactNativeAPI +import com.facebook.react.runtime.ReactSurfaceImpl +import com.facebook.react.runtime.ReactSurfaceView /** * [ReactHostDelegate] is an interface that defines parameters required to initialize React Native. @@ -53,4 +56,6 @@ public interface ReactHostDelegate { * the internals of React Native. */ public fun handleInstanceException(error: Exception) + + public fun createReactSurfaceView(context: Context, surfaceImpl: ReactSurfaceImpl): ReactSurfaceView } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt index 536b0542f78678..39399a66744f39 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt @@ -67,6 +67,7 @@ import com.facebook.react.uimanager.events.BlackHoleEventDispatcher import com.facebook.react.uimanager.events.EventDispatcher import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper import java.lang.ref.WeakReference +import java.util.WeakHashMap import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executor import java.util.concurrent.Executors @@ -129,6 +130,9 @@ public class ReactHostImpl( private var memoryPressureListener: MemoryPressureListener? = null private var defaultHardwareBackBtnHandler: DefaultHardwareBackBtnHandler? = null + // Discord added fix for https://app.asana.com/1/236888843494340/project/1199705967702853/task/1211580756398579?focus=true + private val activeActivities: MutableMap = WeakHashMap() + private val reactInstanceEventListeners: MutableList = CopyOnWriteArrayList() private val beforeDestroyListeners: MutableList<() -> Unit> = CopyOnWriteArrayList() @@ -232,6 +236,11 @@ public class ReactHostImpl( val method = "onHostResume(activity)" log(method) + if (activity != null) { + // It's possible that multiple activities are active at the same time + activeActivities[activity] = true + } + currentActivity = activity maybeEnableDevSupport(true) @@ -251,18 +260,29 @@ public class ReactHostImpl( val method = "onHostPause(activity)" log(method) - val currentActivity = this.currentActivity - if (currentActivity != null) { - val currentActivityClass = currentActivity.javaClass.simpleName - val activityClass = if (activity == null) "null" else activity.javaClass.simpleName - Assertions.assertCondition( - activity === currentActivity, - "Pausing an activity that is not the current activity, this is incorrect! Current activity: $currentActivityClass Paused activity: $activityClass") + + if (activity != null) { + activeActivities.remove(activity) + if (activeActivities.size > 0) { + // There is still at least one activity active, so we don't want to pause RN yet. + return + } } + // Note: this code was here previously to our added "activeActivities" logic, but it seems wrong + // upstream they added a feature flag to disable this check + // val currentActivity = this.currentActivity + // if (currentActivity != null) { + // val currentActivityClass = currentActivity.javaClass.simpleName + // val activityClass = if (activity == null) "null" else activity.javaClass.simpleName + // Assertions.assertCondition( + // activity === currentActivity, + // "Pausing an activity that is not the current activity, this is incorrect! Current activity: $currentActivityClass Paused activity: $activityClass") + // } + maybeEnableDevSupport(false) defaultHardwareBackBtnHandler = null - reactLifecycleStateManager.moveToOnHostPause(currentReactContext, currentActivity) + reactLifecycleStateManager.moveToOnHostPause(currentReactContext, activity) } /** To be called when the host activity is paused. */ @@ -319,7 +339,7 @@ public class ReactHostImpl( initialProps: Bundle? ): ReactSurface { val surface = ReactSurfaceImpl(context, moduleName, initialProps) - val surfaceView = ReactSurfaceView(context, surface) + val surfaceView = reactHostDelegate.createReactSurfaceView(context, surface); surfaceView.setShouldLogContentAppeared(true) surface.attachView(surfaceView) surface.attach(this) @@ -514,7 +534,8 @@ public class ReactHostImpl( internal fun hasNativeModule(nativeModuleInterface: Class): Boolean = reactInstance?.hasNativeModule(nativeModuleInterface) ?: false - internal val nativeModules: Collection = reactInstance?.nativeModules ?: listOf() + internal val nativeModules: Collection + get() = reactInstance?.nativeModules ?: listOf() internal fun getNativeModule(nativeModuleInterface: Class): T? { if (!ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE && diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt index ff0cdb1d5ac5cc..6761feb943fa9c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt @@ -63,6 +63,7 @@ import com.facebook.react.uimanager.DisplayMetricsHolder import com.facebook.react.uimanager.IllegalViewOperationException import com.facebook.react.uimanager.UIConstantsProviderBinding import com.facebook.react.uimanager.UIConstantsProviderBinding.ConstantsForViewManagerProvider +import com.facebook.react.uimanager.UIManagerConstantsCache import com.facebook.react.uimanager.UIManagerModuleConstantsHelper import com.facebook.react.uimanager.ViewManager import com.facebook.react.uimanager.ViewManagerRegistry @@ -222,16 +223,19 @@ internal class ReactInstance( getConstantsForViewManager(viewManager, customDirectEvents) }, { - val viewManagers: List> = - ArrayList(viewManagerResolver.eagerViewManagerMap.values) - val constants = createConstants(viewManagers, customDirectEvents) - - val lazyViewManagers = viewManagerResolver.lazyViewManagerNames - if (!lazyViewManagers.isEmpty()) { - constants["ViewManagerNames"] = ArrayList(lazyViewManagers) - constants["LazyViewManagersEnabled"] = true - } - Arguments.makeNativeMap(constants) + UIManagerConstantsCache.getInstance().getUIManagerConstantsAsWritableMap() + ?: run { + val viewManagers: List> = + ArrayList(viewManagerResolver.eagerViewManagerMap.values) + val constants = createConstants(viewManagers, customDirectEvents) + + val lazyViewManagers = viewManagerResolver.lazyViewManagerNames + if (!lazyViewManagers.isEmpty()) { + constants["ViewManagerNames"] = ArrayList(lazyViewManagers) + constants["LazyViewManagersEnabled"] = true + } + Arguments.makeNativeMap(constants) + } }) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt index 0948dc932ee2b4..28df7cd309d722 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactSurfaceView.kt @@ -33,7 +33,7 @@ import kotlin.math.max * rendering a React component. */ @OptIn(FrameworkAPI::class, UnstableReactNativeAPI::class) -public class ReactSurfaceView(context: Context?, private val surface: ReactSurfaceImpl) : +public open class ReactSurfaceView(context: Context?, private val surface: ReactSurfaceImpl) : ReactRootView(context) { private val jsTouchDispatcher: JSTouchDispatcher = JSTouchDispatcher(this) private var jsPointerDispatcher: JSPointerDispatcher? = null diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt index 2089157dc49411..69ed81ec4b0c1f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.kt @@ -139,7 +139,7 @@ constructor(private val config: MainPackageConfig? = null) : ReactHorizontalScrollViewManager(), ReactHorizontalScrollContainerViewManager(), ReactProgressBarViewManager(), - ReactScrollViewManager(), +// ReactScrollViewManager(), ReactSwitchManager(), ReactSafeAreaViewManager(), SwipeRefreshLayoutManager(), @@ -172,8 +172,8 @@ constructor(private val config: MainPackageConfig? = null) : ModuleSpec.viewManagerSpec { ReactProgressBarViewManager() }, ReactSafeAreaViewManager.REACT_CLASS to ModuleSpec.viewManagerSpec { ReactSafeAreaViewManager() }, - ReactScrollViewManager.REACT_CLASS to - ModuleSpec.viewManagerSpec { ReactScrollViewManager() }, +// ReactScrollViewManager.REACT_CLASS to +// ModuleSpec.viewManagerSpec { ReactScrollViewManager() }, ReactSwitchManager.REACT_CLASS to ModuleSpec.viewManagerSpec { ReactSwitchManager() }, SwipeRefreshLayoutManager.REACT_CLASS to ModuleSpec.viewManagerSpec { SwipeRefreshLayoutManager() }, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.kt index 86250eaa6040b1..0f18034c7b6d6e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/JSTouchDispatcher.kt @@ -45,6 +45,10 @@ public class JSTouchDispatcher(private val viewGroup: ViewGroup) { } dispatchCancelEvent(androidEvent, eventDispatcher) + // TODO: can be removed once we land https://github.com/facebook/react-native/commit/87749470ccf596c5b3bc06fe46ba3239b684fd1b + val surfaceId = UIManagerHelper.getSurfaceId(viewGroup) + val reactContext = UIManagerHelper.getReactContext(viewGroup) + sweepActiveTouchForTag(surfaceId, targetTag, reactContext) childIsHandlingNativeGesture = true targetTag = -1 } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt index 88313b35fea562..f72ab667427d18 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/TransformHelper.kt @@ -8,10 +8,12 @@ package com.facebook.react.uimanager import com.facebook.common.logging.FLog +import com.facebook.react.bridge.NativeArray import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.ReadableType import com.facebook.react.common.ReactConstants +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags public object TransformHelper { @@ -69,6 +71,14 @@ public object TransformHelper { transformOrigin: ReadableArray?, allowPercentageResolution: Boolean ) { + if (allowPercentageResolution && + ReactNativeFeatureFlags.useNativeTransformHelperAndroid() && + transforms is NativeArray && + transformOrigin is NativeArray?) { + nativeProcessTransform(transforms, result, viewWidth, viewHeight, transformOrigin) + return + } + val helperMatrix = helperMatrix.get()!! MatrixMathHelper.resetIdentityMatrix(result) val offsets = @@ -220,4 +230,13 @@ public object TransformHelper { return doubleArrayOf(newTranslateX, newTranslateY, newTranslateZ) } + + @JvmStatic + private external fun nativeProcessTransform( + transforms: NativeArray, + result: DoubleArray, + viewWidth: Float, + viewHeight: Float, + transformOrigin: NativeArray? + ) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerConstantsCache.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerConstantsCache.java new file mode 100644 index 00000000000000..9f44918d1607b2 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerConstantsCache.java @@ -0,0 +1,390 @@ +package com.facebook.react.uimanager; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.Log; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags; +import com.tencent.mmkv.MMKV; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; + +/** + * Caches two separate JSON blobs in MMKV: + * • Full UIManager constants (under MMKV_KEY_CONSTANTS) + * • bubblingEventTypes map only (under MMKV_KEY_BUBBLING) + * + * On init(...), a background thread: + * 1) calls MMKV.initialize(...) + * 2) reads both keys, parses them into Maps + * 3) builds a WritableNativeMap from the full constants map via Arguments.makeNativeMap(...) + * 4) countDown()s loadLatch + * + * Exposed methods all block until that single background load finishes: + * • getCachedConstants() → Map or null + * • getCachedBubblingEventsTypes() → Map or null + * • getUIManagerConstantsAsWritableMap() → WritableNativeMap or null + * + * To persist: + * • call saveConstantsAndBubblingEventsTypes(freshConstantsMap, freshBubblingEventsMap) + * which writes two JSON strings (one under each key) off the main thread, + * and updates in‐memory maps immediately. + */ +public class UIManagerConstantsCache { + private static final String TAG = "UIManagerConstantsCache"; + + // MMKV keys for two separate blobs + private static final String MMKV_KEY_VERSION = "UIManagerConstantsCacheVersion"; + private static final String MMKV_KEY_CONSTANTS = "UIManagerModuleConstants_v1"; + private static final String MMKV_KEY_BUBBLING = "UIManagerModuleBubbling_v1"; + + private static final UIManagerConstantsCache INSTANCE = new UIManagerConstantsCache(); + + /** In-memory store of the parsed Map (full constants). */ + private Map cachedConstants = null; + + /** In-memory store of the parsed Map (bubblingEventTypes only). */ + private Map cachedBubblingEventsTypes = null; + + /** In-memory store of the pre-built WritableNativeMap for full constants. */ + private WritableNativeMap cachedNativeMap = null; + + /** Latch that background-loads both JSON blobs exactly once. */ + private final CountDownLatch loadLatch = new CountDownLatch(1); + + /** Ensures init(...) is only done once. */ + private volatile boolean initCalled = false; + + private String cacheVersionName = null; + + private UIManagerConstantsCache() { + // private constructor + } + + public static UIManagerConstantsCache getInstance() { + return INSTANCE; + } + + /** + * Must be called (once) from Application or MainActivity before any UIManager + * constants are accessed. This kicks off: + * 1) MMKV.initialize(...) + * 2) A background thread that reads two MMKV keys, parses them → Maps, + * then builds a WritableNativeMap from the full constants, and finally + * countDown()s loadLatch. + */ + public synchronized void init(Context appContext) { + if (initCalled) { + return; + } + initCalled = true; + + // Its possible that the constants change between versions, so we have to keep track of the version + // and reset the cache if it changes. + // TODO: This can be improved by us compiling the UIManager constants at compile time + try { + cacheVersionName = appContext.getPackageManager() + .getPackageInfo(appContext.getPackageName(), 0) + .versionName; + cacheVersionName += "-" + (ReactNativeFeatureFlags.enableFabricRenderer() ? + "Fabric" : "NonFabric"); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to get app version name; continuing without caching!", e); + return; + } + + // 1) Initialize MMKV + MMKV.initialize(appContext.getApplicationContext()); + + // 2) Background thread to load both blobs + String finalCacheVersionName = cacheVersionName; + new Thread(() -> { + try { + MMKV mmkv = MMKV.defaultMMKV(); + + // 2a) Get the version the cache was generated with and see if its matching + String cacheVersion = mmkv.decodeString(MMKV_KEY_VERSION, null); + if (cacheVersion == null || !cacheVersion.equals(finalCacheVersionName)) { + Log.w(TAG, "UIManagerConstantsCache version mismatch! Expected: " + + finalCacheVersionName + ", found: " + cacheVersion + + ". Will regenerate constants and bubblingEventTypes."); + + mmkv.removeValueForKey(MMKV_KEY_CONSTANTS); + mmkv.removeValueForKey(MMKV_KEY_BUBBLING); + } else { + Log.v(TAG, "UIManagerConstantsCache version matches: " + finalCacheVersionName); + } + + + // 2a) Read full-constants JSON + String jsonConstants = mmkv.decodeString(MMKV_KEY_CONSTANTS, null); + if (jsonConstants != null) { + try { + JSONObject rootConsts = new JSONObject(jsonConstants); + Map mapConsts = jsonToMap(rootConsts); + synchronized (this) { + cachedConstants = mapConsts; + } + Log.v(TAG, "Background-loaded full UIManager constants (size=" + + (mapConsts == null ? 0 : mapConsts.size()) + ")"); + } catch (JSONException je) { + Log.w(TAG, "Invalid JSON in MMKV (constants). Will regenerate.\n" + + jsonConstants, je); + synchronized (this) { + cachedConstants = null; + } + } + } else { + synchronized (this) { + cachedConstants = null; + } + Log.v(TAG, "No UIManager constants found in MMKV."); + } + + // 2b) Read bubblingEventTypes JSON + String jsonBubbling = mmkv.decodeString(MMKV_KEY_BUBBLING, null); + if (jsonBubbling != null) { + try { + JSONObject rootBub = new JSONObject(jsonBubbling); + Map mapBub = jsonToMap(rootBub); + synchronized (this) { + cachedBubblingEventsTypes = mapBub; + } + Log.v(TAG, "Background-loaded bubblingEventTypes (size=" + + (mapBub == null ? 0 : mapBub.size()) + ")"); + } catch (JSONException je) { + Log.w(TAG, "Invalid JSON in MMKV (bubblingEventTypes). Will regenerate.\n" + + jsonBubbling, je); + synchronized (this) { + cachedBubblingEventsTypes = null; + } + } + } else { + synchronized (this) { + cachedBubblingEventsTypes = null; + } + Log.v(TAG, "No bubblingEventTypes found in MMKV."); + } + + // 2c) Build WritableNativeMap from full constants (if available) + synchronized (this) { + if (cachedConstants != null) { + cachedNativeMap = Arguments.makeNativeMap(cachedConstants); + } else { + cachedNativeMap = null; + } + } + } finally { + // Signal load completion + loadLatch.countDown(); + } + }, "UIManagerConstantsCache-Loader").start(); + } + + /** + * Blocks until background-load finishes, then returns the full constants map. + * @return parsed Map or null if none stored / parse failed. + */ + public Map getCachedConstants() { + try { + loadLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.w(TAG, "getCachedConstants() interrupted while waiting."); + return null; + } + synchronized (this) { + return cachedConstants; + } + } + + /** + * Blocks until background-load finishes, then returns the bubblingEventTypes map. + * @return parsed Map or null if none stored / parse failed. + */ + public Map getCachedBubblingEventsTypes() { + try { + loadLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.w(TAG, "getCachedBubblingEventsTypes() interrupted while waiting."); + return null; + } + synchronized (this) { + return cachedBubblingEventsTypes; + } + } + + /** + * Blocks until background-load (and WritableNativeMap build) finishes. + * @return pre-built WritableNativeMap or null if no full-constants available. + */ + public WritableNativeMap getUIManagerConstantsAsWritableMap() { + try { + loadLatch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Log.w(TAG, "getUIManagerConstantsAsWritableMap() interrupted while waiting."); + return null; + } + synchronized (this) { + return cachedNativeMap; + } + } + + /** + * Takes freshly-built maps (full constants & bubblingEventTypes) and: + * 1) Spawns a background thread to JSON-serialize + write each to its own MMKV key, + * 2) Updates in-memory caches (constants, bubbling types, and native map) so getters return immediately. + */ + public void saveConstantsAndBubblingEventsTypes( + final Map constants, + final Map bubblingEventsTypes) { + if (constants == null) { + return; + } + if (cacheVersionName == null) { + throw new IllegalStateException( + "UIManagerConstantsCache not initialized! Call init(...) first." + ); + } + + try { + // Serialize full constants + JSONObject jsonConsts = mapToJson(constants); + long t0 = System.currentTimeMillis(); + MMKV.defaultMMKV().encode(MMKV_KEY_CONSTANTS, jsonConsts.toString()); + long t1 = System.currentTimeMillis(); + Log.v(TAG, "Saved UIManager constants to MMKV in " + (t1 - t0) + "ms"); + } catch (JSONException e) { + Log.e(TAG, "Failed to JSON-serialize UIManager constants; not caching.", e); + } + + if (bubblingEventsTypes != null) { + try { + // Serialize bubblingEventTypes + JSONObject jsonBub = mapToJson(bubblingEventsTypes); + long t2 = System.currentTimeMillis(); + MMKV.defaultMMKV().encode(MMKV_KEY_BUBBLING, jsonBub.toString()); + long t3 = System.currentTimeMillis(); + Log.v(TAG, "Saved bubblingEventTypes to MMKV in " + (t3 - t2) + "ms"); + } catch (JSONException e) { + Log.e(TAG, "Failed to JSON-serialize bubblingEventTypes; not caching.", e); + } + } + + MMKV.defaultMMKV().encode(MMKV_KEY_VERSION, cacheVersionName); + } + + // ──────────────────────────────────────────────────────────────────────────────── + // JSON ↔ Map Helpers (identical to before) + // ──────────────────────────────────────────────────────────────────────────────── + + private static JSONObject mapToJson(Map map) throws JSONException { + JSONObject json = new JSONObject(); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Object val = entry.getValue(); + if (val == null) { + json.put(key, JSONObject.NULL); + } else if (val instanceof String) { + json.put(key, (String) val); + } else if (val instanceof Boolean) { + json.put(key, (Boolean) val); + } else if (val instanceof Number) { + json.put(key, (Number) val); + } else if (val instanceof Map) { + //noinspection unchecked + json.put(key, mapToJson((Map) val)); + } else if (val instanceof List) { + //noinspection unchecked + json.put(key, listToJsonArray((List) val)); + } else { + throw new JSONException( + "Unsupported value type for key \"" + key + "\": " + val.getClass() + ); + } + } + return json; + } + + private static JSONArray listToJsonArray(List list) throws JSONException { + JSONArray arr = new JSONArray(); + for (Object elem : list) { + if (elem == null) { + arr.put(JSONObject.NULL); + } else if (elem instanceof String) { + arr.put((String) elem); + } else if (elem instanceof Boolean) { + arr.put((Boolean) elem); + } else if (elem instanceof Number) { + arr.put((Number) elem); + } else if (elem instanceof Map) { + //noinspection unchecked + arr.put(mapToJson((Map) elem)); + } else if (elem instanceof List) { + //noinspection unchecked + arr.put(listToJsonArray((List) elem)); + } else { + throw new JSONException("Unsupported list element type: " + elem.getClass()); + } + } + return arr; + } + + private static Map jsonToMap(JSONObject json) throws JSONException { + Map result = new HashMap<>(); + Iterator keys = json.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object raw = json.get(key); + if (raw == JSONObject.NULL) { + result.put(key, null); + } else if (raw instanceof Boolean || raw instanceof Number || raw instanceof String) { + result.put(key, raw); + } else if (raw instanceof JSONObject) { + result.put(key, jsonToMap((JSONObject) raw)); + } else if (raw instanceof JSONArray) { + result.put(key, jsonArrayToList((JSONArray) raw)); + } else { + throw new JSONException( + "Unsupported JSON type in UIManager constants for key \"" + key + "\": " + + raw.getClass() + ); + } + } + return result; + } + + private static List jsonArrayToList(JSONArray arr) throws JSONException { + List result = new ArrayList<>(); + for (int i = 0; i < arr.length(); i++) { + Object raw = arr.get(i); + if (raw == JSONObject.NULL) { + result.add(null); + } else if (raw instanceof Boolean || raw instanceof Number || raw instanceof String) { + result.add(raw); + } else if (raw instanceof JSONObject) { + result.add(jsonToMap((JSONObject) raw)); + } else if (raw instanceof JSONArray) { + result.add(jsonArrayToList((JSONArray) raw)); + } else { + throw new JSONException( + "Unsupported JSON array element at index " + i + ": " + raw.getClass() + ); + } + } + return result; + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.kt index 25b644329044fe..0ce0ff76bc9cf5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerHelper.kt @@ -14,6 +14,7 @@ import android.content.ContextWrapper import android.view.View import android.widget.EditText import androidx.core.view.ViewCompat +import com.facebook.react.ReactRootView import com.facebook.react.bridge.CatalystInstance import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.ReactNoCrashSoftException @@ -160,6 +161,13 @@ public object UIManagerHelper { */ @JvmStatic public fun getReactContext(view: View): ReactContext { + // TODO: this if can be removed once we land https://github.com/facebook/react-native/commit/87749470ccf596c5b3bc06fe46ba3239b684fd1b + if (view is ReactRootView) { + val reactContext: ReactContext? = view.currentReactContext + return requireNotNull(reactContext) { + "ReactRootView should always have a ReactContext associated with it, but it was null." + } + } var context = view.context if (context !is ReactContext && context is ContextWrapper) { context = context.baseContext diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 671abca4ba1de5..c705f153f3f8df 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -12,6 +12,7 @@ import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; import static com.facebook.react.uimanager.common.UIManagerType.LEGACY; +import android.util.Log; import android.content.ComponentCallbacks2; import android.content.res.Configuration; import android.view.View; @@ -146,17 +147,18 @@ public UIManagerModule( List viewManagersList, int minTimeLeftInFrameForNonBatchedOperationMs) { super(reactContext); + Systrace.beginSection(Systrace.TRACE_TAG_REACT, "UIManagerModule.init"); DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); mEventDispatcher = new EventDispatcherImpl(reactContext); mCustomDirectEvents = MapBuilder.newHashMap(); mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents); mViewManagerRegistry = new ViewManagerRegistry(viewManagersList); mUIImplementation = - new UIImplementation( - reactContext, - mViewManagerRegistry, - mEventDispatcher, - minTimeLeftInFrameForNonBatchedOperationMs); + new UIImplementation( + reactContext, + mViewManagerRegistry, + mEventDispatcher, + minTimeLeftInFrameForNonBatchedOperationMs); if (ReactBuildConfig.DEBUG) { for (ViewManager viewManager : viewManagersList) { @@ -233,6 +235,12 @@ public void sweepActiveTouchForTag(int surfaceId, int reactTag) { // Not implemented for Paper. } + + @Override + public void markViewAsInTransition(int surfaceId, int reactTag, boolean isTransitioning) { + // Not implemented for Paper. + } + /** * This method is intended to reuse the {@link ViewManagerRegistry} with FabricUIManager. Do not * use this method as this will be removed in the near future. @@ -264,8 +272,24 @@ public static Map createConstants( .arg("Lazy", false) .flush(); try { - return UIManagerModuleConstantsHelper.internal_createConstants( + // If the background load is still happening, this will wait. If it completed, returns immediately. + Map cached = UIManagerConstantsCache.getInstance().getCachedConstants(); + Map cachedCustomBubblingEvents = UIManagerConstantsCache.getInstance().getCachedBubblingEventsTypes(); + if (cached != null) { + if (customBubblingEvents != null) { + customBubblingEvents.putAll(cachedCustomBubblingEvents); + } + + return cached; + } + + // Otherwise, build fresh on this thread… + Map fresh = UIManagerModuleConstantsHelper.internal_createConstants( viewManagers, customBubblingEvents, customDirectEvents); + + // …and save to MMKV in the background for next time: + UIManagerConstantsCache.getInstance().saveConstantsAndBubblingEventsTypes(fresh, customBubblingEvents); + return fresh; } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT); ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.kt index ebbdbeb598a78a..d377f54e9ec3df 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewGroupManager.kt @@ -9,79 +9,82 @@ package com.facebook.react.uimanager import android.view.View import android.view.ViewGroup +import androidx.core.view.doOnDetach +import com.facebook.common.logging.FLog import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.UiThreadUtil import java.util.WeakHashMap +@Suppress("DEPRECATION") public abstract class ViewGroupManager @JvmOverloads constructor(reactContext: ReactApplicationContext? = null) : BaseViewManager(reactContext), IViewGroupManager { - public override fun createShadowNodeInstance(): LayoutShadowNode = LayoutShadowNode() + public override fun createShadowNodeInstance(): LayoutShadowNode = LayoutShadowNode() - public override fun getShadowNodeClass(): Class = - LayoutShadowNode::class.java + public override fun getShadowNodeClass(): Class = + LayoutShadowNode::class.java - public override fun updateExtraData(root: T, extraData: Any): Unit = Unit + public override fun updateExtraData(root: T, extraData: Any): Unit = Unit - public override fun addView(parent: T, child: View, index: Int): Unit = - parent.addView(child, index) + public override fun addView(parent: T, child: View, index: Int): Unit = + parent.addView(child, index) - /** - * Convenience method for batching a set of addView calls Note that this adds the views to the - * beginning of the ViewGroup - * - * @param parent the parent ViewGroup - * @param views the set of views to add - */ - public fun addViews(parent: T, views: List) { - UiThreadUtil.assertOnUiThread() - views.forEachIndexed { i, view -> addView(parent, view, i) } - } + /** + * Convenience method for batching a set of addView calls Note that this adds the views to the + * beginning of the ViewGroup + * + * @param parent the parent ViewGroup + * @param views the set of views to add + */ + public fun addViews(parent: T, views: List) { + UiThreadUtil.assertOnUiThread() + views.forEachIndexed { i, view -> addView(parent, view, i) } + } + + public override fun getChildCount(parent: T): Int = parent.childCount - public override fun getChildCount(parent: T): Int = parent.childCount + public override fun getChildAt(parent: T, index: Int): View? = parent.getChildAt(index) - public override fun getChildAt(parent: T, index: Int): View? = parent.getChildAt(index) + public override fun removeViewAt(parent: T, index: Int) { + UiThreadUtil.assertOnUiThread() + parent.removeViewAt(index) + } + + /** + * Expo overrides this function GroupViewManagerWrapper.kt`, which is a replacement view manager + * adding support for delegates receiving callbacks whenever one of the methods in the view + * manager are called. + */ + public open fun removeView(parent: T, view: View) { + UiThreadUtil.assertOnUiThread() + + for (i in 0 until getChildCount(parent)) { + if (getChildAt(parent, i) === view) { + + removeViewAt(parent, i) + break + } + } + } - public override fun removeViewAt(parent: T, index: Int) { - UiThreadUtil.assertOnUiThread() - parent.removeViewAt(index) - } + /** + * Returns whether this View type needs to handle laying out its own children instead of deferring + * to the standard css-layout algorithm. Returns true for the layout to *not* be automatically + * invoked. Instead onLayout will be invoked as normal and it is the View instance's + * responsibility to properly call layout on its children. Returns false for the default behavior + * of automatically laying out children without going through the ViewGroup's onLayout method. In + * that case, onLayout for this View type must *not* call layout on its children. + */ + public override fun needsCustomLayoutForChildren(): Boolean = false - /** - * Expo overrides this function GroupViewManagerWrapper.kt`, which is a replacement view manager - * adding support for delegates receiving callbacks whenever one of the methods in the view - * manager are called. - */ - public open fun removeView(parent: T, view: View) { - UiThreadUtil.assertOnUiThread() + public companion object { + private val zIndexHash: WeakHashMap = WeakHashMap() - for (i in 0 until getChildCount(parent)) { - if (getChildAt(parent, i) === view) { + @JvmStatic + public fun setViewZIndex(view: View, zIndex: Int): Unit = zIndexHash.set(view, zIndex) - removeViewAt(parent, i) - break - } + @JvmStatic public fun getViewZIndex(view: View?): Int? = zIndexHash[view] } - } - - /** - * Returns whether this View type needs to handle laying out its own children instead of deferring - * to the standard css-layout algorithm. Returns true for the layout to *not* be automatically - * invoked. Instead onLayout will be invoked as normal and it is the View instance's - * responsibility to properly call layout on its children. Returns false for the default behavior - * of automatically laying out children without going through the ViewGroup's onLayout method. In - * that case, onLayout for this View type must *not* call layout on its children. - */ - public override fun needsCustomLayoutForChildren(): Boolean = false - - public companion object { - private val zIndexHash: WeakHashMap = WeakHashMap() - - @JvmStatic - public fun setViewZIndex(view: View, zIndex: Int): Unit = zIndexHash.set(view, zIndex) - - @JvmStatic public fun getViewZIndex(view: View?): Int? = zIndexHash[view] - } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt index d61f559e8a76b1..d0fac3071cfa0a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt @@ -10,7 +10,6 @@ package com.facebook.react.uimanager import android.view.View import com.facebook.common.logging.FLog import com.facebook.react.bridge.ReadableArray -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture import com.facebook.react.uimanager.ViewManagersPropertyCache.PropSetter import java.util.HashMap @@ -70,8 +69,8 @@ public object ViewManagerPropertyUpdater { } } - @DeprecatedInNewArchitecture @JvmStatic + @Deprecated("Use ViewManager#updateProperties to update a view's properties") public fun > updateProps(node: T, props: ReactStylesDiffMap) { val setter = findNodeSetter(node.javaClass) val iterator = props.backingMap.entryIterator diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/common/UIManagerType.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/common/UIManagerType.kt index f0c97b73222253..bd2464d010f905 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/common/UIManagerType.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/common/UIManagerType.kt @@ -8,12 +8,10 @@ package com.facebook.react.uimanager.common import androidx.annotation.IntDef -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture @Retention(AnnotationRetention.SOURCE) @Suppress("DEPRECATION") @IntDef(UIManagerType.DEFAULT, UIManagerType.LEGACY, UIManagerType.FABRIC) -@DeprecatedInNewArchitecture public annotation class UIManagerType { public companion object { @Deprecated( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.kt index d90bcdc98afe0f..20743cd076a0a3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.kt @@ -40,6 +40,9 @@ public abstract class Event> { public var viewTag: Int = 0 private set + /** @return whether this event is dispatched during a drawing pass */ + public var isDrawing: Boolean = false + /** * @return the time at which the event happened in the [android.os.SystemClock.uptimeMillis] base. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt index 63942f03f6ee54..403583f205acf3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt @@ -93,6 +93,11 @@ public constructor( public override fun getName(): String = REACT_CLASS + @ReactProp(name = "useSmallCache") + public fun setUseSmallCache(view: ReactImageView, useSmallCache: Boolean) { + view.setUseSmallCache(useSmallCache) + } + @ReactProp(name = "accessible") public fun setAccessible(view: ReactImageView, accessible: Boolean) { view.isFocusable = accessible diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index ad634f0b90fccd..db7a890ffdaa9d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -22,6 +22,7 @@ import android.graphics.Shader.TileMode import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable import android.net.Uri +import android.widget.Toast import com.facebook.common.references.CloseableReference import com.facebook.common.util.UriUtil import com.facebook.drawee.backends.pipeline.Fresco @@ -97,6 +98,7 @@ public class ReactImageView( private var scaleType = defaultValue() private var tileMode = defaultTileMode() private var isDirty = false + private var useSmallCache = false private var tilePostprocessor: TilePostprocessor? = null private var iterativeBoxBlurPostProcessor: IterativeBoxBlurPostProcessor? = null private var downloadListener: ReactImageDownloadListener? = null @@ -120,6 +122,13 @@ public class ReactImageView( } } + public fun setUseSmallCache(useSmallCache: Boolean) { + if (this.useSmallCache != useSmallCache) { + this.useSmallCache = useSmallCache + isDirty = true + } + } + public fun setShouldNotifyLoadEvents(shouldNotify: Boolean) { // Skip update if shouldNotify is already in sync with the download listener if (shouldNotify == (downloadListener != null)) { @@ -282,15 +291,32 @@ public class ReactImageView( for (idx in 0 until sources.size()) { val source = sources.getMap(idx) ?: continue val cacheControl = computeCacheControl(source.getString("cache")) + val uri = source.getString("uri") + val isForceCached = if (source.hasKey("isForceCached")) { + source.getBoolean("isForceCached") + } else { + false + } + val width = if (source.hasKey("width")) { + source.getDouble("width") + } else { + 0.0 + } + val height = if (source.hasKey("height")) { + source.getDouble("height") + } else { + 0.0 + } var imageSource = ImageSource( context, - source.getString("uri"), - source.getDouble("width"), - source.getDouble("height"), - cacheControl) + uri, + width, + height, + cacheControl, + isForceCached) if (Uri.EMPTY == imageSource.uri) { - warnImageSource(source.getString("uri")) + warnImageSource(uri) imageSource = getTransparentBitmapImageSource(context) } tmpSources.add(imageSource) @@ -462,6 +488,12 @@ public class ReactImageView( imageRequestBuilder.setDownsampleOverride(DownsampleMode.NEVER) } + if (useSmallCache) { + imageRequestBuilder.setCacheChoice(ImageRequest.CacheChoice.SMALL); + } else { + imageRequestBuilder.setCacheChoice(ImageRequest.CacheChoice.DEFAULT); + } + val imageRequest: ImageRequest = ReactNetworkImageRequest.fromBuilderWithHeaders(imageRequestBuilder, headers, cacheControl) @@ -491,7 +523,15 @@ public class ReactImageView( if (resizeMethod == ImageResizeMethod.NONE) { cachedImageRequestBuilder.setDownsampleOverride(DownsampleMode.NEVER) } - builder.setLowResImageRequest(cachedImageRequestBuilder.build()) + + if (useSmallCache) { + cachedImageRequestBuilder.setCacheChoice(ImageRequest.CacheChoice.SMALL); + } else { + cachedImageRequestBuilder.setCacheChoice(ImageRequest.CacheChoice.DEFAULT); + } + + val cachedImageRequest = cachedImageRequestBuilder.build() + builder.setLowResImageRequest(cachedImageRequest) } if (downloadListener != null && controllerForTesting != null) { @@ -584,6 +624,12 @@ public class ReactImageView( if (ReactBuildConfig.DEBUG && !ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) { RNLog.w(context as ReactContext, "ReactImageView: Image source \"$uri\" doesn't exist") + + Toast.makeText( + context, + "Warning: Image source \"$uri\" doesn't exist", + Toast.LENGTH_SHORT) + .show(); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt index f8d82543ee853d..a9f0fe3c732fe5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ImageSource.kt @@ -10,6 +10,7 @@ package com.facebook.react.views.imagehelper import android.content.Context import android.net.Uri import com.facebook.react.modules.fresco.ImageCacheControl +import androidx.arch.core.util.Function import java.util.Objects /** Class describing an image source (network URI or resource) and size. */ @@ -18,10 +19,11 @@ public open class ImageSource constructor( context: Context, /** Get the source of this image, as it was passed to the constructor. */ - public val source: String?, + public var source: String?, width: Double = 0.0, height: Double = 0.0, public val cacheControl: ImageCacheControl = ImageCacheControl.DEFAULT, + public val isForceCached: Boolean = false ) { /** Get the URI for this image - can be either a parsed network URI or a resource URI. */ @@ -55,6 +57,9 @@ constructor( private fun computeUri(context: Context): Uri = try { + sourceOverride?.let { + source = it.apply(source) + } val uri = Uri.parse(source) // Verify scheme is set, so that relative uri (used by static resources) are not handled. if (uri.scheme == null) computeLocalUri(context) else uri @@ -68,6 +73,7 @@ constructor( } public companion object { + public var sourceOverride: Function? = null private const val TRANSPARENT_BITMAP_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt index 566c0fc35d39ba..3f5188bc637534 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/MultiSourceHelper.kt @@ -57,8 +57,10 @@ internal object MultiSourceHelper { for (source in sources) { val precision = abs(1.0 - source.size / viewArea) if (precision < bestPrecision) { - bestPrecision = precision - best = source + if (!source.isForceCached) { + bestPrecision = precision + best = source + } } if (precision < bestCachePrecision && source.cacheControl != ImageCacheControl.RELOAD && @@ -69,6 +71,10 @@ internal object MultiSourceHelper { bestCachePrecision = precision bestCached = source } + if (source.isForceCached) { + bestCachePrecision = 0.0 + bestCached = source + } } if (bestCached != null && best != null && bestCached.source == best.source) { bestCached = null diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt index c711027d172bc2..f53350b4c55d21 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt @@ -29,6 +29,8 @@ import androidx.activity.OnBackPressedCallback import androidx.annotation.UiThread import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat +import androidx.core.content.ContextCompat +import androidx.core.view.WindowCompat import com.facebook.common.logging.FLog import com.facebook.react.R import com.facebook.react.bridge.GuardedRunnable @@ -277,6 +279,15 @@ public class ReactModalHostView(context: ThemedReactContext) : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) + // Enabled Edge to Edge modal when transparent/translucent system UI. + if (transparent && statusBarTranslucent) { + newDialog.window?.let { + it.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + it.navigationBarColor = ContextCompat.getColor(context, android.R.color.transparent); + WindowCompat.setDecorFitsSystemWindows(it, false) + }; + } + newDialog.setContentView(contentView) updateProperties() @@ -549,10 +560,21 @@ public class ReactModalHostView(context: ThemedReactContext) : updateState(viewWidth, viewHeight) } + /** + * Updates the shadow tree via the local fabric view state manager. Can noop if the same values + * are passed multiple times. Can also ignore params if the local variables for viewWidth & + * viewHeight are non-zero. + * + * @param width target width of the container as pixels. Will be converted to DIP. + * @param height target height of the container as pixels. Will be converted to DIP. + */ @UiThread public fun updateState(width: Int, height: Int) { - val realWidth: Float = width.toFloat().pxToDp() - val realHeight: Float = height.toFloat().pxToDp() + // Once viewWidth & viewHeight are set by an onSizeChanged callback they become our source + // of truth. This makes the fabric renderer function like the paper renderer is currently + // functioning. + val realWidth = (if ((viewWidth > 0)) viewWidth else width).toFloat().pxToDp() + val realHeight = (if ((viewHeight > 0)) viewHeight else height).toFloat().pxToDp() val sw = stateWrapper if (sw != null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 2a462c438f863b..4329255408b2b1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -542,7 +542,9 @@ protected void onScrollChanged(int x, int y, int oldX, int oldY) { ReactScrollViewHelper.updateStateOnScrollChanged( this, mOnScrollDispatchHelper.getXFlingVelocity(), - mOnScrollDispatchHelper.getYFlingVelocity()); + mOnScrollDispatchHelper.getYFlingVelocity(), + false // TODO: potentially needs same change! + ); } } finally { Systrace.endSection(Systrace.TRACE_TAG_REACT); @@ -718,6 +720,38 @@ public boolean dispatchGenericMotionEvent(MotionEvent ev) { return false; } + // Handle ACTION_SCROLL events (mouse wheel, trackpad, joystick) + if (ev.getActionMasked() == MotionEvent.ACTION_SCROLL) { + float hScroll = ev.getAxisValue(MotionEvent.AXIS_HSCROLL); + if (hScroll != 0) { + // Perform the scroll + boolean result = super.dispatchGenericMotionEvent(ev); + // Schedule snap alignment to run after scrolling stops + if (result + && (mPagingEnabled + || mSnapInterval != 0 + || mSnapOffsets != null + || mSnapToAlignment != SNAP_ALIGNMENT_DISABLED)) { + // Cancel any pending runnable and reschedule + if (mPostTouchRunnable != null) { + removeCallbacks(mPostTouchRunnable); + } + mPostTouchRunnable = + new Runnable() { + @Override + public void run() { + mPostTouchRunnable = null; + // Trigger snap alignment now that scrolling has stopped + handlePostTouchScrolling(0, 0); + } + }; + ViewCompat.postOnAnimationDelayed( + this, mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY); + } + return result; + } + } + return super.dispatchGenericMotionEvent(ev); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index f708543cf45784..25a0ed2c1f0408 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -88,6 +88,8 @@ public class ReactScrollView extends ScrollView HasScrollEventThrottle, HasSmoothScroll { + static Integer MAX_FLING_VELOCITY = null; + private static @Nullable Field sScrollerField; private static boolean sTriedToGetScrollerField = false; @@ -461,24 +463,29 @@ private void scrollToChild(View child) { @Override protected void onScrollChanged(int x, int y, int oldX, int oldY) { - Systrace.beginSection(Systrace.TRACE_TAG_REACT, "ReactScrollView.onScrollChanged"); - try { - super.onScrollChanged(x, y, oldX, oldY); + onScrollChanged(x, y, oldX, oldY, false); + } - mActivelyScrolling = true; + protected void onScrollChanged(int x, int y, int oldX, int oldY, boolean isDrawing) { + Systrace.beginSection(Systrace.TRACE_TAG_REACT, "ReactScrollView.onScrollChanged"); + try { + super.onScrollChanged(x, y, oldX, oldY); - if (mOnScrollDispatchHelper.onScrollChanged(x, y)) { - if (mRemoveClippedSubviews) { - updateClippingRect(); - } - ReactScrollViewHelper.updateStateOnScrollChanged( - this, - mOnScrollDispatchHelper.getXFlingVelocity(), - mOnScrollDispatchHelper.getYFlingVelocity()); + mActivelyScrolling = true; + + if (mOnScrollDispatchHelper.onScrollChanged(x, y)) { + if (mRemoveClippedSubviews) { + updateClippingRect(); + } + ReactScrollViewHelper.updateStateOnScrollChanged( + this, + mOnScrollDispatchHelper.getXFlingVelocity(), + mOnScrollDispatchHelper.getYFlingVelocity(), + isDrawing); + } + } finally { + Systrace.endSection(Systrace.TRACE_TAG_REACT); } - } finally { - Systrace.endSection(Systrace.TRACE_TAG_REACT); - } } @Override @@ -555,6 +562,38 @@ public boolean dispatchGenericMotionEvent(MotionEvent ev) { return false; } + // Handle ACTION_SCROLL events (mouse wheel, trackpad, joystick) + if (ev.getActionMasked() == MotionEvent.ACTION_SCROLL) { + float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL); + if (vScroll != 0) { + // Perform the scroll + boolean result = super.dispatchGenericMotionEvent(ev); + // Schedule snap alignment to run after scrolling stops + if (result + && (mPagingEnabled + || mSnapInterval != 0 + || mSnapOffsets != null + || mSnapToAlignment != SNAP_ALIGNMENT_DISABLED)) { + // Cancel any pending post-touch runnable and reschedule + if (mPostTouchRunnable != null) { + removeCallbacks(mPostTouchRunnable); + } + mPostTouchRunnable = + new Runnable() { + @Override + public void run() { + mPostTouchRunnable = null; + // Trigger snap alignment now that scrolling has stopped + handlePostTouchScrolling(0, 0); + } + }; + ViewCompat.postOnAnimationDelayed( + this, mPostTouchRunnable, ReactScrollViewHelper.MOMENTUM_DELAY); + } + return result; + } + } + return super.dispatchGenericMotionEvent(ev); } @@ -620,7 +659,14 @@ public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point of @Override public void fling(int velocityY) { - final int correctedVelocityY = correctFlingVelocityY(velocityY); + final int correctedVelocityY; + if (MAX_FLING_VELOCITY != null) { + int velocityYBeforeMaxFling = correctFlingVelocityY(velocityY); + correctedVelocityY = (int) ((Math.min(Math.abs(velocityYBeforeMaxFling), MAX_FLING_VELOCITY)) * + Math.signum(velocityYBeforeMaxFling)); + } else { + correctedVelocityY = correctFlingVelocityY(velocityY); + } if (mPagingEnabled) { flingAndSnap(correctedVelocityY); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt index 55e4d53fb94cb1..f2360e0e9c62fc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt @@ -65,10 +65,10 @@ public object ReactScrollViewHelper { /** Shared by [ReactScrollView] and [ReactHorizontalScrollView]. */ @JvmStatic - public fun emitScrollEvent(scrollView: T, xVelocity: Float, yVelocity: Float) where + public fun emitScrollEvent(scrollView: T, xVelocity: Float, yVelocity: Float, isDrawing: Boolean = false) where T : HasScrollEventThrottle?, T : ViewGroup { - emitScrollEvent(scrollView, ScrollEventType.SCROLL, xVelocity, yVelocity) + emitScrollEvent(scrollView, ScrollEventType.SCROLL, xVelocity, yVelocity, isDrawing) } @JvmStatic @@ -111,6 +111,7 @@ public object ReactScrollViewHelper { scrollEventType: ScrollEventType, xVelocity: Float, yVelocity: Float, + isDrawing: Boolean = false ) where T : HasScrollEventThrottle?, T : ViewGroup { val now = System.currentTimeMillis() // Throttle the scroll event if scrollEventThrottle is set to be equal or more than 17 ms. @@ -146,7 +147,9 @@ public object ReactScrollViewHelper { contentView.width, contentView.height, scrollView.width, - scrollView.height)) + scrollView.height).also { + it.isDrawing = isDrawing + }) if (scrollEventType == ScrollEventType.SCROLL) { scrollView.lastScrollDispatchTime = now } @@ -368,7 +371,8 @@ public object ReactScrollViewHelper { public fun updateStateOnScrollChanged( scrollView: T, xVelocity: Float, - yVelocity: Float + yVelocity: Float, + isDrawing: Boolean = false ) where T : HasFlingAnimator?, T : HasScrollEventThrottle?, @@ -380,7 +384,7 @@ public object ReactScrollViewHelper { // "more correct" scroll position. It will frequently be /incorrect/ but this decreases // the error as much as possible. updateFabricScrollState(scrollView, scrollView.scrollX, scrollView.scrollY) - emitScrollEvent(scrollView, xVelocity, yVelocity) + emitScrollEvent(scrollView, xVelocity, yVelocity, isDrawing) } public fun registerFlingAnimator(scrollView: T) where diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/CreateTypefaceObject.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/CreateTypefaceObject.kt new file mode 100644 index 00000000000000..604f4162151fc6 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/CreateTypefaceObject.kt @@ -0,0 +1,9 @@ +package com.facebook.react.views.text + +import android.content.res.AssetManager + +public class CreateTypefaceObject( + public var fontFamilyName: String, + public var style: Int, + public var assetManager: AssetManager +) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextView.kt index b3c5aeacad4e40..4214d3a1440f49 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/PreparedLayoutTextView.kt @@ -28,6 +28,7 @@ import com.facebook.react.uimanager.BackgroundStyleApplicator import com.facebook.react.uimanager.ReactCompoundView import com.facebook.react.uimanager.style.Overflow import com.facebook.react.views.text.internal.span.ReactTagSpan +import com.facebook.react.views.text.internal.span.StrokeStyleSpan import kotlin.collections.ArrayList import kotlin.math.roundToInt @@ -99,25 +100,35 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re } override fun onDraw(canvas: Canvas) { + val layout = preparedLayout?.layout + if (overflow != Overflow.VISIBLE) { BackgroundStyleApplicator.clipToPaddingBox(this, canvas) } super.onDraw(canvas) + canvas.translate( - paddingLeft.toFloat(), paddingTop.toFloat() + (preparedLayout?.verticalOffset ?: 0f)) + paddingLeft.toFloat(), + paddingTop.toFloat() + (preparedLayout?.verticalOffset ?: 0f)) - val layout = preparedLayout?.layout if (layout != null) { if (selection != null) { selectionPaint.setColor( selectionColor ?: DefaultStyleValuesUtil.getDefaultTextColorHighlight(context)) } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - Api34Utils.draw(layout, canvas, selection?.path, selectionPaint) - } else { - layout.draw(canvas, selection?.path, selectionPaint, 0) + val drawLayout = Runnable { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + Api34Utils.draw(layout, canvas, selection?.path, selectionPaint) + } else { + layout.draw(canvas, selection?.path, selectionPaint, 0) + } + } + + val strokeSpan = StrokeStyleSpan.getStrokeSpan(layout.text as? Spanned) + if (strokeSpan == null || !strokeSpan.draw(layout.paint, drawLayout)) { + drawLayout.run() } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java index 5bdd5cbaba7a7b..414419e911a53f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java @@ -13,7 +13,9 @@ import android.text.Layout; import android.text.Spannable; import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; +import android.text.style.LeadingMarginSpan; import android.view.Gravity; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; @@ -21,6 +23,7 @@ import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.internal.LegacyArchitecture; import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel; @@ -37,6 +40,7 @@ import com.facebook.react.views.text.internal.span.CustomLetterSpacingSpan; import com.facebook.react.views.text.internal.span.CustomLineHeightSpan; import com.facebook.react.views.text.internal.span.CustomStyleSpan; +import com.facebook.react.views.text.internal.span.LinearGradientSpan; import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan; import com.facebook.react.views.text.internal.span.ReactBackgroundColorSpan; import com.facebook.react.views.text.internal.span.ReactClickableSpan; @@ -47,6 +51,7 @@ import com.facebook.react.views.text.internal.span.ReactUnderlineSpan; import com.facebook.react.views.text.internal.span.SetSpanOperation; import com.facebook.react.views.text.internal.span.ShadowStyleSpan; +import com.facebook.react.views.text.internal.span.StrokeStyleSpan; import com.facebook.react.views.text.internal.span.TextInlineImageSpan; import com.facebook.react.views.text.internal.span.TextInlineViewPlaceholderSpan; import com.facebook.yoga.YogaDirection; @@ -169,8 +174,14 @@ private static void buildSpannedFromShadowNode( int end = sb.length(); if (end >= start) { if (textShadowNode.mIsColorSet) { - ops.add( - new SetSpanOperation(start, end, new ReactForegroundColorSpan(textShadowNode.mColor))); + ops.add(new SetSpanOperation(start, end, new ReactForegroundColorSpan(textShadowNode.mColor))); + } + if (textShadowNode.mGradientColors != null && textShadowNode.mGradientColors.length >= 2) { + int effectiveFontSize = textAttributes.getEffectiveFontSize(); + float gradientAngle = Float.isNaN(textShadowNode.mGradientAngle) ? 0f : textShadowNode.mGradientAngle; + float gradientWidth = textShadowNode.mGradientWidth; + String gradientMode = textShadowNode.mGradientMode; + ops.add(new SetSpanOperation(start, end, new LinearGradientSpan(start * effectiveFontSize, textShadowNode.mGradientColors, gradientAngle, gradientWidth, gradientMode))); } if (textShadowNode.mIsBackgroundColorSet) { ops.add( @@ -219,20 +230,29 @@ private static void buildSpannedFromShadowNode( if (textShadowNode.mIsLineThroughTextDecorationSet) { ops.add(new SetSpanOperation(start, end, new ReactStrikethroughSpan())); } + ShadowStyleSpan shadowSpan = null; if ((textShadowNode.mTextShadowOffsetDx != 0 || textShadowNode.mTextShadowOffsetDy != 0 || textShadowNode.mTextShadowRadius != 0) && Color.alpha(textShadowNode.mTextShadowColor) != 0) { - ops.add( - new SetSpanOperation( - start, - end, - new ShadowStyleSpan( - textShadowNode.mTextShadowOffsetDx, - textShadowNode.mTextShadowOffsetDy, - textShadowNode.mTextShadowRadius, - textShadowNode.mTextShadowColor))); + shadowSpan = new ShadowStyleSpan( + textShadowNode.mTextShadowOffsetDx, + textShadowNode.mTextShadowOffsetDy, + textShadowNode.mTextShadowRadius, + textShadowNode.mTextShadowColor); + ops.add(new SetSpanOperation(start, end, shadowSpan)); + } + + StrokeStyleSpan strokeSpan = null; + if (!Float.isNaN(textShadowNode.mTextStrokeWidth) + && textShadowNode.mTextStrokeWidth > 0 + && textShadowNode.mIsTextStrokeColorSet) { + strokeSpan = new StrokeStyleSpan( + textShadowNode.mTextStrokeWidth, + textShadowNode.mTextStrokeColor); + ops.add(new SetSpanOperation(start, end, strokeSpan)); } + float effectiveLineHeight = textAttributes.getEffectiveLineHeight(); if (!Float.isNaN(effectiveLineHeight) && (parentTextAttributes == null @@ -317,6 +337,20 @@ protected Spannable spannedFromShadowNode( textShadowNode.mTextAttributes.setHeightOfTallestInlineViewOrImage( heightOfTallestInlineViewOrImage); + // Add leading margin for stroke/shadow that extends beyond text bounds + StrokeStyleSpan strokeSpan = StrokeStyleSpan.getStrokeSpan(sb); + ShadowStyleSpan shadowSpan = ShadowStyleSpan.getShadowSpan(sb); + float strokeOffset = strokeSpan != null ? strokeSpan.getLeftOffset() : 0f; + float shadowOffset = shadowSpan != null ? shadowSpan.getLeftOffset() : 0f; + int leadingMargin = (int) Math.max(strokeOffset, shadowOffset); + if (leadingMargin > 0) { + sb.setSpan( + new LeadingMarginSpan.Standard(leadingMargin), + 0, + sb.length(), + Spanned.SPAN_INCLUSIVE_INCLUSIVE); + } + if (mReactTextViewManagerCallback != null) { mReactTextViewManagerCallback.onPostProcessSpannable(sb); } @@ -331,6 +365,11 @@ protected Spannable spannedFromShadowNode( protected boolean mIsBackgroundColorSet = false; protected int mBackgroundColor; + protected @Nullable int[] mGradientColors = null; + protected float mGradientAngle = Float.NaN; + protected float mGradientWidth = Float.NaN; + protected @Nullable String mGradientMode = null; + protected @Nullable AccessibilityRole mAccessibilityRole = null; protected @Nullable Role mRole = null; @@ -346,6 +385,10 @@ protected Spannable spannedFromShadowNode( protected float mTextShadowRadius = 0; protected int mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR; + protected float mTextStrokeWidth = Float.NaN; + protected boolean mIsTextStrokeColorSet = false; + protected int mTextStrokeColor; + protected boolean mIsUnderlineTextDecorationSet = false; protected boolean mIsLineThroughTextDecorationSet = false; protected boolean mIncludeFontPadding = true; @@ -492,6 +535,48 @@ public void setColor(@Nullable Integer color) { markUpdated(); } + @ReactProp(name = "gradientColors") + public void setGradientColors(@Nullable ReadableArray gradientColors) { + if (gradientColors != null) { + ArrayList colors = new ArrayList(); + + for (int i = 0; i < gradientColors.size(); i++) { + if (!gradientColors.isNull(i) && gradientColors.getType(i) == ReadableType.Number) { + int color = gradientColors.getInt(i); + colors.add(color); + } + } + + int colorsSize = colors.size(); + if (colorsSize >= 2) { + int[] colorsAsList = new int[colorsSize]; + for (int i = 0; i < colorsSize; i++) { + colorsAsList[i] = colors.get(i); + } + + mGradientColors = colorsAsList; + } + } + } + + @ReactProp(name = "gradientAngle", defaultFloat = Float.NaN) + public void setGradientAngle(float gradientAngle) { + mGradientAngle = gradientAngle; + markUpdated(); + } + + @ReactProp(name = "gradientWidth", defaultFloat = Float.NaN) + public void setGradientWidth(float gradientWidth) { + mGradientWidth = gradientWidth; + markUpdated(); + } + + @ReactProp(name = "gradientMode") + public void setGradientMode(@Nullable String gradientMode) { + mGradientMode = gradientMode; + markUpdated(); + } + @ReactProp(name = ViewProps.BACKGROUND_COLOR, customType = "Color") public void setBackgroundColor(@Nullable Integer color) { // Background color needs to be handled here for virtual nodes so it can be incorporated into @@ -631,6 +716,23 @@ public void setTextShadowColor(int textShadowColor) { } } + @ReactProp(name = "textStrokeWidth", defaultFloat = Float.NaN) + public void setTextStrokeWidth(float textStrokeWidth) { + if (textStrokeWidth != mTextStrokeWidth) { + mTextStrokeWidth = textStrokeWidth; + markUpdated(); + } + } + + @ReactProp(name = "textStrokeColor", customType = "Color") + public void setTextStrokeColor(int textStrokeColor) { + if (textStrokeColor != mTextStrokeColor) { + mTextStrokeColor = textStrokeColor; + mIsTextStrokeColorSet = true; + markUpdated(); + } + } + @ReactProp(name = PROP_TEXT_TRANSFORM) public void setTextTransform(@Nullable String textTransform) { TextTransform textTransformEnum = TextTransform.UNSET; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.kt index 5b4cee489b41a7..c67a11a617b97b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactFontManager.kt @@ -10,51 +10,270 @@ package com.facebook.react.views.text import android.content.Context import android.content.res.AssetManager import android.graphics.Typeface -import com.facebook.react.common.assets.ReactFontManager as ReactFontAssetManager +import android.graphics.fonts.Font +import android.graphics.fonts.FontFamily +import android.os.Build +import android.util.SparseArray +import androidx.annotation.Nullable +import androidx.annotation.RequiresApi +import androidx.core.content.res.ResourcesCompat +import com.facebook.infer.annotation.Nullsafe +import androidx.arch.core.util.Function +import com.facebook.react.common.ReactConstants +import java.io.IOException +import java.util.ArrayList +import java.util.HashMap -/** Responsible for loading and caching Typeface objects. */ -@Deprecated( - message = - "This class is deprecated and will be deleted in the near future. Please use [com.facebook.react.common.assets.ReactFontManager] instead.") -@Suppress("DEPRECATION") -public class ReactFontManager private constructor(private val delegate: ReactFontAssetManager) { +/** + * Responsible for loading and caching Typeface objects. + * + * @deprecated This class is deprecated and it will be deleted in the near future. Please use + * [com.facebook.react.common.assets.ReactFontManager] instead. + */ +@Nullsafe(Nullsafe.Mode.LOCAL) +public class ReactFontManager private constructor() { + + public companion object { + public var createAssetTypefaceOverride: Function? = null + + // NOTE: Indices in `EXTENSIONS` correspond to the `TypeFace` style constants. + private val EXTENSIONS = arrayOf("", "_bold", "_italic", "_bold_italic") + private val FILE_EXTENSIONS = arrayOf(".ttf", ".otf") + private const val FONTS_ASSET_PATH = "fonts/" + + private var sReactFontManagerInstance: ReactFontManager? = null + + @JvmStatic + public fun getInstance(): ReactFontManager { + return sReactFontManagerInstance ?: ReactFontManager().also { sReactFontManagerInstance = it } + } + + private fun createAssetTypeface( + fontFamilyName: String, + style: Int, + assetManager: AssetManager + ): Typeface { + createAssetTypefaceOverride?.let { override -> + return override.apply(CreateTypefaceObject(fontFamilyName, style, assetManager)) + } + + // This is the original RN logic for getting the typeface. + val extension = EXTENSIONS[style] + for (fileExtension in FILE_EXTENSIONS) { + val fileName = StringBuilder() + .append(FONTS_ASSET_PATH) + .append(fontFamilyName) + .append(extension) + .append(fileExtension) + .toString() + try { + return Typeface.createFromAsset(assetManager, fileName) + } catch (e: RuntimeException) { + // If the typeface asset does not exist, try another extension. + continue + } + } + return Typeface.create(fontFamilyName, style) + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun createAssetTypefaceWithFallbacks( + fontFamilyNames: Array, + style: Int, + assetManager: AssetManager + ): Typeface { + val fontFamilies = ArrayList() + + // Iterate over the list of fontFamilyNames, constructing new FontFamily objects + // for use in the CustomFallbackBuilder below. + for (fontFamilyName in fontFamilyNames) { + for (fileExtension in FILE_EXTENSIONS) { + val fileName = StringBuilder() + .append(FONTS_ASSET_PATH) + .append(fontFamilyName) + .append(fileExtension) + .toString() + try { + val font = Font.Builder(assetManager, fileName).build() + val family = FontFamily.Builder(font).build() + fontFamilies.add(family) + } catch (e: RuntimeException) { + // If the typeface asset does not exist, try another extension. + continue + } catch (e: IOException) { + // If the font asset does not exist, try another extension. + continue + } + } + } - public fun getTypeface(fontFamilyName: String, style: Int, assetManager: AssetManager): Typeface = - delegate.getTypeface(fontFamilyName, style, assetManager) + // If there's some problem constructing fonts, fall back to the default behavior. + if (fontFamilies.isEmpty()) { + return createAssetTypeface(fontFamilyNames[0], style, assetManager) + } + + val fallbackBuilder = Typeface.CustomFallbackBuilder(fontFamilies[0]) + for (i in 1 until fontFamilies.size) { + fallbackBuilder.addCustomFallback(fontFamilies[i]) + } + return fallbackBuilder.build() + } + } + + private val mFontCache = HashMap() + private val mCustomTypefaceCache = HashMap() + + public fun getTypeface(fontFamilyName: String, style: Int, assetManager: AssetManager): Typeface { + return getTypeface(fontFamilyName, TypefaceStyle(style), assetManager) + } public fun getTypeface( fontFamilyName: String, weight: Int, italic: Boolean, assetManager: AssetManager - ): Typeface = delegate.getTypeface(fontFamilyName, weight, italic, assetManager) + ): Typeface { + return getTypeface(fontFamilyName, TypefaceStyle(weight, italic), assetManager) + } public fun getTypeface( fontFamilyName: String, style: Int, weight: Int, assetManager: AssetManager - ): Typeface = delegate.getTypeface(fontFamilyName, style, weight, assetManager) + ): Typeface { + return getTypeface(fontFamilyName, TypefaceStyle(style, weight), assetManager) + } + public fun getTypeface( + fontFamilyName: String, + typefaceStyle: TypefaceStyle, + assetManager: AssetManager + ): Typeface { + mCustomTypefaceCache[fontFamilyName]?.let { customTypeface -> + // Apply `typefaceStyle` because custom fonts configure variants using `app:fontStyle` and + // `app:fontWeight` in their resource XML configuration file. + return typefaceStyle.apply(customTypeface) + } + + var assetFontFamily = mFontCache[fontFamilyName] + if (assetFontFamily == null) { + assetFontFamily = AssetFontFamily() + mFontCache[fontFamilyName] = assetFontFamily + } + + val style = typefaceStyle.getNearestStyle() + + var assetTypeface = assetFontFamily.getTypefaceForStyle(style) + if (assetTypeface == null) { + assetTypeface = createAssetTypeface(fontFamilyName, style, assetManager) + assetFontFamily.setTypefaceForStyle(style, assetTypeface) + } + // Do not apply `typefaceStyle` because asset font files already incorporate the style. + return assetTypeface + } + + /* + * This method allows you to load custom fonts from res/font folder as provided font family name. + * Fonts may be one of .ttf, .otf or XML (https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml). + * To support multiple font styles or weights, you must provide a font in XML format. + * + * ReactFontManager.getInstance().addCustomFont(this, "Srisakdi", R.font.srisakdi); + */ public fun addCustomFont(context: Context, fontFamily: String, fontId: Int) { - delegate.addCustomFont(context, fontFamily, fontId) + ResourcesCompat.getFont(context, fontId)?.let { font -> + mCustomTypefaceCache[fontFamily] = font + } } + /** + * Equivalent method to [addCustomFont] which accepts a Typeface object. + */ public fun addCustomFont(fontFamily: String, font: Typeface?) { - delegate.addCustomFont(fontFamily, font) + font?.let { mCustomTypefaceCache[fontFamily] = it } } - public fun setTypeface(fontFamilyName: String, style: Int, typeface: Typeface) { - delegate.setTypeface(fontFamilyName, style, typeface) + /** + * Add additional font family, or replace the exist one in the font memory cache. + * + * @param style see [Typeface.DEFAULT], [Typeface.BOLD], [Typeface.ITALIC], [Typeface.BOLD_ITALIC] + */ + public fun setTypeface(fontFamilyName: String, style: Int, typeface: Typeface?) { + typeface?.let { font -> + var assetFontFamily = mFontCache[fontFamilyName] + if (assetFontFamily == null) { + assetFontFamily = AssetFontFamily() + mFontCache[fontFamilyName] = assetFontFamily + } + assetFontFamily.setTypefaceForStyle(style, font) + } } - public companion object { - private var instance: ReactFontManager? = null + public class TypefaceStyle { + public companion object { + public const val BOLD: Int = 700 + public const val NORMAL: Int = 400 + private const val MIN_WEIGHT: Int = 1 + private const val MAX_WEIGHT: Int = 1000 + } - @JvmStatic - public fun getInstance(): ReactFontManager { - return instance - ?: ReactFontManager(ReactFontAssetManager.getInstance()).also { instance = it } + private val mItalic: Boolean + private val mWeight: Int + + public constructor(weight: Int, italic: Boolean) { + mItalic = italic + mWeight = if (weight == ReactConstants.UNSET) NORMAL else weight + } + + public constructor(style: Int) { + var styleValue = if (style == ReactConstants.UNSET) Typeface.NORMAL else style + + mItalic = (styleValue and Typeface.ITALIC) != 0 + mWeight = if ((styleValue and Typeface.BOLD) != 0) BOLD else NORMAL + } + + /** + * If `weight` is supplied, it will be combined with the italic bit from `style`. Otherwise, any + * existing weight bit in `style` will be used. + */ + public constructor(style: Int, weight: Int) { + var styleValue = if (style == ReactConstants.UNSET) Typeface.NORMAL else style + + mItalic = (styleValue and Typeface.ITALIC) != 0 + mWeight = when { + weight != ReactConstants.UNSET -> weight + (styleValue and Typeface.BOLD) != 0 -> BOLD + else -> NORMAL + } + } + + public fun getNearestStyle(): Int { + return if (mWeight < BOLD) { + if (mItalic) Typeface.ITALIC else Typeface.NORMAL + } else { + if (mItalic) Typeface.BOLD_ITALIC else Typeface.BOLD + } + } + + public fun apply(typeface: Typeface): Typeface { + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + Typeface.create(typeface, getNearestStyle()) + } else { + Typeface.create(typeface, mWeight, mItalic) + } + } + } + + /** Responsible for caching typefaces for each custom font family. */ + private class AssetFontFamily { + private val mTypefaceSparseArray = SparseArray(4) + + fun getTypefaceForStyle(style: Int): Typeface? { + return mTypefaceSparseArray.get(style) + } + + fun setTypefaceForStyle(style: Int, typeface: Typeface) { + mTypefaceSparseArray.put(style, typeface) } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 906dfbce04905d..472e1141415946 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -9,12 +9,14 @@ import android.content.Context; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Layout; import android.text.Spannable; import android.text.Spanned; +import android.text.TextPaint; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; @@ -53,6 +55,7 @@ import com.facebook.react.uimanager.style.LogicalEdge; import com.facebook.react.uimanager.style.Overflow; import com.facebook.react.views.text.internal.span.ReactTagSpan; +import com.facebook.react.views.text.internal.span.StrokeStyleSpan; import com.facebook.react.views.text.internal.span.TextInlineImageSpan; import com.facebook.react.views.text.internal.span.TextInlineViewPlaceholderSpan; import com.facebook.yoga.YogaMeasureMode; @@ -361,10 +364,18 @@ protected void onDraw(Canvas canvas) { } if (mOverflow != Overflow.VISIBLE) { + canvas.save(); BackgroundStyleApplicator.clipToPaddingBox(this, canvas); } - super.onDraw(canvas); + StrokeStyleSpan strokeSpan = StrokeStyleSpan.getStrokeSpan((Spanned) getText()); + if (strokeSpan == null || !strokeSpan.draw(getPaint(), () -> super.onDraw(canvas))) { + super.onDraw(canvas); + } + + if (mOverflow != Overflow.VISIBLE) { + canvas.restore(); + } } } @@ -791,4 +802,5 @@ public void setOverflow(@Nullable String overflow) { invalidate(); } + } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.kt index 517466055c7217..db5feca6671511 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTypefaceUtils.kt @@ -12,7 +12,6 @@ import android.graphics.Typeface import android.text.TextUtils import com.facebook.react.bridge.ReadableArray import com.facebook.react.common.ReactConstants -import com.facebook.react.common.assets.ReactFontManager public object ReactTypefaceUtils { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java index 67baca4e116570..ba1c5ac7d183cd 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java @@ -18,6 +18,7 @@ import com.facebook.infer.annotation.Nullsafe; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.mapbuffer.MapBuffer; import com.facebook.react.uimanager.PixelUtil; @@ -64,6 +65,12 @@ public class TextAttributeProps { public static final short TA_KEY_ROLE = 26; public static final short TA_KEY_TEXT_TRANSFORM = 27; public static final short TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29; + public static final short TA_KEY_GRADIENT_COLORS = 30; + public static final short TA_KEY_TEXT_STROKE_WIDTH = 31; + public static final short TA_KEY_TEXT_STROKE_COLOR = 32; + public static final short TA_KEY_GRADIENT_ANGLE = 33; + public static final short TA_KEY_GRADIENT_WIDTH = 34; + public static final short TA_KEY_GRADIENT_MODE = 35; public static final int UNSET = -1; @@ -107,6 +114,10 @@ public class TextAttributeProps { protected float mTextShadowRadius = 0; protected int mTextShadowColor = DEFAULT_TEXT_SHADOW_COLOR; + protected float mTextStrokeWidth = Float.NaN; + protected boolean mIsTextStrokeColorSet = false; + protected int mTextStrokeColor; + protected boolean mIsUnderlineTextDecorationSet = false; protected boolean mIsLineThroughTextDecorationSet = false; protected boolean mIncludeFontPadding = true; @@ -148,6 +159,11 @@ public class TextAttributeProps { protected boolean mContainsImages = false; protected float mHeightOfTallestInlineImage = Float.NaN; + protected @Nullable int[] mGradientColors = null; + protected float mGradientAngle = Float.NaN; + protected float mGradientWidth = Float.NaN; + protected @Nullable String mGradientMode = null; + private TextAttributeProps() {} /** Build a TextAttributeProps using data from the {@link MapBuffer} received as a parameter. */ @@ -217,6 +233,12 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) { case TA_KEY_TEXT_SHADOW_OFFSET_DY: result.setTextShadowOffsetDy((float) entry.getDoubleValue()); break; + case TA_KEY_TEXT_STROKE_WIDTH: + result.setTextStrokeWidth((float) entry.getDoubleValue()); + break; + case TA_KEY_TEXT_STROKE_COLOR: + result.setTextStrokeColor(entry.getIntValue()); + break; case TA_KEY_IS_HIGHLIGHTED: break; case TA_KEY_LAYOUT_DIRECTION: @@ -231,6 +253,18 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) { case TA_KEY_TEXT_TRANSFORM: result.setTextTransform(entry.getStringValue()); break; + case TA_KEY_GRADIENT_COLORS: + result.setGradientColors(entry.getMapBufferValue()); + break; + case TA_KEY_GRADIENT_ANGLE: + result.setGradientAngle((float) entry.getDoubleValue()); + break; + case TA_KEY_GRADIENT_WIDTH: + result.setGradientWidth((float) entry.getDoubleValue()); + break; + case TA_KEY_GRADIENT_MODE: + result.setGradientMode(entry.getStringValue()); + break; case TA_KEY_MAX_FONT_SIZE_MULTIPLIER: result.setMaxFontSizeMultiplier((float) entry.getDoubleValue()); break; @@ -277,6 +311,14 @@ public static TextAttributeProps fromReadableMap(ReactStylesDiffMap props) { result.setLayoutDirection(getStringProp(props, ViewProps.LAYOUT_DIRECTION)); result.setAccessibilityRole(getStringProp(props, ViewProps.ACCESSIBILITY_ROLE)); result.setRole(getStringProp(props, ViewProps.ROLE)); + result.setGradientColors(getArrayProp(props, "gradientColors")); + result.setGradientAngle(getFloatProp(props, "gradientAngle", Float.NaN)); + result.setGradientWidth(getFloatProp(props, "gradientWidth", Float.NaN)); + result.setGradientMode(getStringProp(props, "gradientMode")); + result.setTextStrokeWidth(getFloatProp(props, "textStrokeWidth", Float.NaN)); + if (props.hasKey("textStrokeColor")) { + result.setTextStrokeColor(props.getInt("textStrokeColor", 0)); + } return result; } @@ -753,6 +795,63 @@ private void setRole(Role role) { mRole = role; } + private void setGradientColors(@Nullable ReadableArray gradientColors) { + if (gradientColors == null) return; + + ArrayList colors = new ArrayList<>(); + for (int i = 0; i < gradientColors.size(); i++) { + if (!gradientColors.isNull(i) && gradientColors.getType(i) == ReadableType.Number) { + colors.add(gradientColors.getInt(i)); + } + } + setGradientColorsFromList(colors); + } + + private void setGradientColors(@Nullable MapBuffer gradientColors) { + if (gradientColors == null) return; + + ArrayList colors = new ArrayList<>(); + Iterator iterator = gradientColors.iterator(); + while (iterator.hasNext()) { + colors.add(iterator.next().getIntValue()); + } + setGradientColorsFromList(colors); + } + + private void setGradientColorsFromList(ArrayList colors) { + if (colors.size() >= 2) { + mGradientColors = colors.stream().mapToInt(Integer::intValue).toArray(); + } + } + + public @Nullable int[] getGradientColors() { + return mGradientColors; + } + + public float getGradientAngle() { + return mGradientAngle; + } + + private void setGradientAngle(float gradientAngle) { + mGradientAngle = gradientAngle; + } + + public float getGradientWidth() { + return mGradientWidth; + } + + private void setGradientWidth(float gradientWidth) { + mGradientWidth = gradientWidth; + } + + public @Nullable String getGradientMode() { + return mGradientMode; + } + + private void setGradientMode(@Nullable String gradientMode) { + mGradientMode = gradientMode; + } + public static int getTextBreakStrategy(@Nullable String textBreakStrategy) { int androidTextBreakStrategy = DEFAULT_BREAK_STRATEGY; if (textBreakStrategy != null) { @@ -808,4 +907,27 @@ public static int getHyphenationFrequency(@Nullable String hyphenationFrequency) } return truncateAt; } + + public float getTextStrokeWidth() { + return mTextStrokeWidth; + } + + private void setTextStrokeWidth(float textStrokeWidth) { + mTextStrokeWidth = textStrokeWidth; + } + + public int getTextStrokeColor() { + return mTextStrokeColor; + } + + public boolean isTextStrokeColorSet() { + return mIsTextStrokeColorSet; + } + + private void setTextStrokeColor(int textStrokeColor) { + if (textStrokeColor != mTextStrokeColor) { + mTextStrokeColor = textStrokeColor; + mIsTextStrokeColorSet = true; + } + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt index 7598ae75233483..1950b32a15dedd 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt @@ -21,6 +21,7 @@ import android.text.StaticLayout import android.text.TextDirectionHeuristics import android.text.TextPaint import android.text.TextUtils +import android.text.style.LeadingMarginSpan import android.util.LayoutDirection import android.view.Gravity import android.view.View @@ -42,13 +43,15 @@ import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan import com.facebook.react.views.text.internal.span.ReactBackgroundColorSpan import com.facebook.react.views.text.internal.span.ReactClickableSpan import com.facebook.react.views.text.internal.span.ReactForegroundColorSpan +import com.facebook.react.views.text.internal.span.LinearGradientSpan import com.facebook.react.views.text.internal.span.ReactOpacitySpan import com.facebook.react.views.text.internal.span.ReactStrikethroughSpan import com.facebook.react.views.text.internal.span.ReactTagSpan import com.facebook.react.views.text.internal.span.ReactTextPaintHolderSpan +import com.facebook.react.views.text.internal.span.ShadowStyleSpan +import com.facebook.react.views.text.internal.span.StrokeStyleSpan import com.facebook.react.views.text.internal.span.ReactUnderlineSpan import com.facebook.react.views.text.internal.span.SetSpanOperation -import com.facebook.react.views.text.internal.span.ShadowStyleSpan import com.facebook.react.views.text.internal.span.TextInlineViewPlaceholderSpan import com.facebook.yoga.YogaMeasureMode import com.facebook.yoga.YogaMeasureOutput @@ -254,6 +257,17 @@ internal object TextLayoutManager { if (textAttributes.mIsColorSet) { ops.add(SetSpanOperation(start, end, ReactForegroundColorSpan(textAttributes.mColor))) } + if (textAttributes.gradientColors != null && textAttributes.gradientColors!!.size >= 2) { + val effectiveFontSize = textAttributes.effectiveFontSize + val gradientAngle = if (textAttributes.gradientAngle.isNaN()) 0f else textAttributes.gradientAngle + val gradientWidth = textAttributes.gradientWidth + val gradientMode = textAttributes.gradientMode + ops.add( + SetSpanOperation( + start, + end, + LinearGradientSpan(start * effectiveFontSize.toFloat(), textAttributes.gradientColors!!, gradientAngle, gradientWidth, gradientMode))) + } if (textAttributes.mIsBackgroundColorSet) { ops.add( SetSpanOperation( @@ -301,6 +315,18 @@ internal object TextLayoutManager { textAttributes.mTextShadowRadius, textAttributes.mTextShadowColor))) } + if (!textAttributes.textStrokeWidth.isNaN() && + textAttributes.textStrokeWidth > 0 && + textAttributes.isTextStrokeColorSet) { + ops.add( + SetSpanOperation( + start, + end, + StrokeStyleSpan( + textAttributes.textStrokeWidth, + textAttributes.textStrokeColor))) + } + if (!textAttributes.effectiveLineHeight.isNaN()) { ops.add( SetSpanOperation( @@ -393,6 +419,18 @@ internal object TextLayoutManager { spannable.setSpan(ReactForegroundColorSpan(fragment.props.color), start, end, spanFlags) } + if (fragment.props.gradientColors != null && fragment.props.gradientColors!!.size >= 2) { + val effectiveFontSize = fragment.props.effectiveFontSize + val gradientAngle = if (fragment.props.gradientAngle.isNaN()) 0f else fragment.props.gradientAngle + val gradientWidth = fragment.props.gradientWidth + val gradientMode = fragment.props.gradientMode + spannable.setSpan( + LinearGradientSpan(start * effectiveFontSize.toFloat(), fragment.props.gradientColors!!, gradientAngle, gradientWidth, gradientMode), + start, + end, + spanFlags) + } + if (fragment.props.isBackgroundColorSet) { spannable.setSpan( ReactBackgroundColorSpan(fragment.props.backgroundColor), start, end, spanFlags) @@ -448,6 +486,18 @@ internal object TextLayoutManager { spanFlags) } + if (!fragment.props.textStrokeWidth.isNaN() && + fragment.props.textStrokeWidth > 0 && + fragment.props.isTextStrokeColorSet) { + spannable.setSpan( + StrokeStyleSpan( + fragment.props.textStrokeWidth, + fragment.props.textStrokeColor), + start, + end, + spanFlags) + } + if (!fragment.props.effectiveLineHeight.isNaN()) { spannable.setSpan( CustomLineHeightSpan(fragment.props.effectiveLineHeight), start, end, spanFlags) @@ -459,9 +509,25 @@ internal object TextLayoutManager { start = end } + addLeadingMarginForTextEffects(spannable) return spannable } + private fun addLeadingMarginForTextEffects(spannable: Spannable) { + val strokeSpan = StrokeStyleSpan.getStrokeSpan(spannable) + val shadowSpan = ShadowStyleSpan.getShadowSpan(spannable) + val strokeOffset = strokeSpan?.getLeftOffset() ?: 0f + val shadowOffset = shadowSpan?.getLeftOffset() ?: 0f + val leadingMargin = max(strokeOffset, shadowOffset).toInt() + if (leadingMargin > 0) { + spannable.setSpan( + LeadingMarginSpan.Standard(leadingMargin), + 0, + spannable.length, + Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } + } + fun getOrCreateSpannableForText( context: Context, attributedString: MapBuffer, @@ -512,6 +578,7 @@ internal object TextLayoutManager { op.execute(sb, priorityIndex) } + addLeadingMarginForTextEffects(sb) reactTextViewManagerCallback?.onPostProcessSpannable(sb) return sb } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt index 5d30f853c3b21f..a4d78b669f921e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomLineHeightSpan.kt @@ -9,6 +9,7 @@ package com.facebook.react.views.text.internal.span import android.graphics.Paint.FontMetricsInt import android.text.style.LineHeightSpan +import com.facebook.react.bridge.Callback import kotlin.math.ceil import kotlin.math.floor @@ -17,17 +18,17 @@ import kotlin.math.floor * LineHeightSpan.Standard which only effects space between the baselines of adjacent line boxes * (does not impact space before the first line or after the last). */ -internal class CustomLineHeightSpan(height: Float) : LineHeightSpan, ReactSpan { - val lineHeight: Int = ceil(height.toDouble()).toInt() +public class CustomLineHeightSpan public constructor(height: Float) : LineHeightSpan, ReactSpan { + public val lineHeight: Int = ceil(height.toDouble()).toInt() - override fun chooseHeight( + public override fun chooseHeight( text: CharSequence, start: Int, end: Int, spanstartv: Int, v: Int, fm: FontMetricsInt, - ) { + ): Unit { // https://www.w3.org/TR/css-inline-3/#inline-height // When its computed line-height is not normal, its layout bounds are derived solely from // metrics of its first available font (ignoring glyphs from other fonts), and leading is used @@ -37,6 +38,10 @@ internal class CustomLineHeightSpan(height: Float) : LineHeightSpan, ReactSpan { // ascent above the baseline of A′ = A + L/2, and an effective descent of D′ = D + L/2. However, // if line-fit-edge is not leading and this is not the root inline box, if the half-leading is // positive, treat it as zero. The layout bounds exactly encloses this effective A′ and D′. + chooseHeightOverride?.let { + it.invoke(fm, lineHeight) + return + } val leading = lineHeight - ((-fm.ascent) + fm.descent) fm.ascent -= ceil(leading / 2.0f).toInt() @@ -53,4 +58,8 @@ internal class CustomLineHeightSpan(height: Float) : LineHeightSpan, ReactSpan { fm.bottom = fm.descent } } + + public companion object { + public var chooseHeightOverride: Callback? = null + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.kt index 7be93b440eb2fb..f9ea93f9d4b39d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/CustomStyleSpan.kt @@ -13,7 +13,7 @@ import android.graphics.Typeface import android.text.TextPaint import android.text.style.MetricAffectingSpan import com.facebook.react.common.ReactConstants -import com.facebook.react.common.assets.ReactFontManager +import com.facebook.react.views.text.ReactFontManager import com.facebook.react.views.text.ReactTypefaceUtils /** diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/LinearGradientSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/LinearGradientSpan.kt new file mode 100644 index 00000000000000..3f66818ecc2047 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/LinearGradientSpan.kt @@ -0,0 +1,70 @@ +package com.facebook.react.views.text.internal.span + +import android.graphics.LinearGradient +import android.graphics.Shader +import android.text.TextPaint +import android.text.style.CharacterStyle +import android.text.style.UpdateAppearance + +/** + * Span that applies a linear gradient to text. + * + * @param start The x-offset for the gradient start position + * @param colors Array of gradient colors + * @param angle Gradient angle in degrees (0 = horizontal) + * @param gradientWidth Width of the gradient pattern in pixels. Default is 100. + * @param gradientMode "mirror" (default) or "clamp" - controls tiling behavior + */ +public class LinearGradientSpan( + private val start: Float, + private val colors: IntArray, + private val angle: Float = 0f, + private val gradientWidth: Float = Float.NaN, + private val gradientMode: String? = null, +) : CharacterStyle(), ReactSpan, + UpdateAppearance { + public override fun updateDrawState(tp: TextPaint) { + // without setting the paint color, the gradient appears "faded" if no foreground color span is also applied + // https://stackoverflow.com/a/52289927 + tp.setColor(colors[0]) + + val radians = Math.toRadians(angle.toDouble()) + val width = if (gradientWidth.isNaN()) 100f else gradientWidth + val height = tp.textSize + + val centerX = start + width / 2 + val centerY = height / 2 + val length = Math.sqrt((width * width + height * height).toDouble()).toFloat() / 2 + + val startX = centerX - length * Math.cos(radians).toFloat() + val startY = centerY - length * Math.sin(radians).toFloat() + val endX = centerX + length * Math.cos(radians).toFloat() + val endY = centerY + length * Math.sin(radians).toFloat() + + val isClampMode = gradientMode == "clamp" + val tileMode = if (isClampMode) Shader.TileMode.CLAMP else Shader.TileMode.MIRROR + + // For mirror mode, duplicate first color at end to match iOS (RCTTextAttributes.mm) + // For clamp mode, use colors as-is + val finalColors = if (isClampMode) { + colors + } else { + val adjustedColors = IntArray(colors.size + 1) + System.arraycopy(colors, 0, adjustedColors, 0, colors.size) + adjustedColors[colors.size] = colors[0] + adjustedColors + } + + val textShader: Shader = + LinearGradient( + startX, + startY, + endX, + endY, + finalColors, + null, + tileMode, + ) + tp.setShader(textShader) + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.kt index 638f2bb89e3acb..cb6c0bade11ffc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/ShadowStyleSpan.kt @@ -7,8 +7,10 @@ package com.facebook.react.views.text.internal.span +import android.text.Spanned import android.text.TextPaint import android.text.style.CharacterStyle +import kotlin.math.max internal class ShadowStyleSpan( private val dx: Float, @@ -19,4 +21,15 @@ internal class ShadowStyleSpan( override fun updateDrawState(textPaint: TextPaint) { textPaint.setShadowLayer(radius, dx, dy, color) } + + public fun getLeftOffset(): Float = max(0f, radius - dx) + + public companion object { + @JvmStatic + public fun getShadowSpan(spanned: Spanned?): ShadowStyleSpan? { + if (spanned == null) return null + val spans = spanned.getSpans(0, spanned.length, ShadowStyleSpan::class.java) + return spans.firstOrNull() + } + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/StrokeStyleSpan.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/StrokeStyleSpan.kt new file mode 100644 index 00000000000000..ac07de3b2c4da7 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/StrokeStyleSpan.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.text.internal.span + +import android.graphics.Paint +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.text.Spanned +import android.text.TextPaint +import android.text.style.CharacterStyle + +public class StrokeStyleSpan( + public val width: Float, + public val color: Int +) : CharacterStyle(), ReactSpan { + + override fun updateDrawState(textPaint: TextPaint) { + // No-op - stroke drawing is handled by the view's onDraw + } + + public fun hasStroke(): Boolean = width > 0 && color != 0 + + public fun getLeftOffset(): Float = if (hasStroke()) width / 2f else 0f + + public fun draw(paint: Paint, drawCallback: Runnable): Boolean { + if (!hasStroke()) { + return false + } + + val originalStyle = paint.style + val originalStrokeWidth = paint.strokeWidth + val originalStrokeJoin = paint.strokeJoin + val originalStrokeCap = paint.strokeCap + val originalColor = paint.color + val originalColorFilter = paint.colorFilter + + // Stroke pass + paint.style = Paint.Style.STROKE + paint.strokeWidth = width + paint.strokeJoin = Paint.Join.ROUND + paint.strokeCap = Paint.Cap.ROUND + paint.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + drawCallback.run() + + // Fill pass + paint.style = Paint.Style.FILL + paint.strokeWidth = 0f + paint.color = originalColor + paint.colorFilter = originalColorFilter + drawCallback.run() + + // Restore + paint.style = originalStyle + paint.strokeWidth = originalStrokeWidth + paint.strokeJoin = originalStrokeJoin + paint.strokeCap = originalStrokeCap + paint.color = originalColor + paint.colorFilter = originalColorFilter + + return true + } + + public companion object { + @JvmStatic + public fun getStrokeSpan(spanned: Spanned?): StrokeStyleSpan? { + if (spanned == null) return null + val spans = spanned.getSpans(0, spanned.length, StrokeStyleSpan::class.java) + return spans.firstOrNull() + } + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.kt index 735d69bd2909f7..58448140809291 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactDrawableHelper.kt @@ -14,6 +14,8 @@ import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.RippleDrawable +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.RoundRectShape import android.util.TypedValue import com.facebook.react.bridge.JSApplicationIllegalArgumentException import com.facebook.react.bridge.ReadableMap @@ -101,6 +103,22 @@ public object ReactDrawableHelper { } private fun getMask(drawableDescriptionDict: ReadableMap): Drawable? { + if (drawableDescriptionDict.hasKey("borderless") && drawableDescriptionDict.getBoolean("borderless")) { + // Borderless ripples don't have masks. + return null + } + + if (drawableDescriptionDict.hasKey("rippleCornerRadius")) { + val rippleRadius = PixelUtil.toPixelFromDIP(drawableDescriptionDict.getDouble("rippleCornerRadius")) + return ShapeDrawable( + RoundRectShape( + FloatArray(8) { rippleRadius }, + null, + null + ) + ) + } + if (!drawableDescriptionDict.hasKey("borderless") || drawableDescriptionDict.isNull("borderless") || !drawableDescriptionDict.getBoolean("borderless")) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt index 89b666dcf0258d..6ec2d4ec4d4988 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt @@ -69,6 +69,8 @@ import java.util.ArrayList import kotlin.concurrent.Volatile import kotlin.math.max +import com.facebook.react.bridge.UiThreadUtil + /** * Backing for a React View. Has support for borders, but since borders aren't common, lazy * initializes most of the storage needed for them. @@ -154,6 +156,12 @@ public open class ReactViewGroup public constructor(context: Context?) : AccessibilityManager.AccessibilityStateChangeListener? = null + /** + * When set to true on a child [ReactViewGroup], that child will never be clipped by + * parent clipping logic (e.g., when `removeClippedSubviews` is enabled on the parent). + */ + public var preventClipping: Boolean = false + init { initView() } @@ -181,6 +189,7 @@ public open class ReactViewGroup public constructor(context: Context?) : backfaceOpacity = 1f backfaceVisible = true childrenRemovedWhileTransitioning = null + preventClipping = false } internal open fun recycleView() { @@ -478,6 +487,8 @@ public open class ReactViewGroup public constructor(context: Context?) : // it won't be size and located properly. val isAnimating = child.animation?.hasEnded() == false + val childPreventClipping = (child as? ReactViewGroup)?.preventClipping == true + val shouldSkipView = excludedViewsSet?.contains(child.id) == true if (excludedViewsSet != null) { needUpdateClippingRecursive = true @@ -487,20 +498,34 @@ public open class ReactViewGroup public constructor(context: Context?) : !isViewClipped(child, idx) && !isAnimating && child !== focusedChild && - !shouldSkipView) { + !shouldSkipView && + !childPreventClipping) { setViewClipped(child, true) + if (!customDrawOrderDisabled()) { + drawingOrderHelper.handleRemoveView(child) + isChildrenDrawingOrderEnabled = drawingOrderHelper.shouldEnableCustomDrawingOrder() + } else { + isChildrenDrawingOrderEnabled = false + } // We can try saving on invalidate call here as the view that we remove is out of visible area // therefore invalidation is not necessary. removeViewInLayout(child) needUpdateClippingRecursive = true - } else if ((shouldSkipView || intersects) && isViewClipped(child, idx)) { + } else if ((((shouldSkipView || intersects) && isViewClipped(child, idx)) || childPreventClipping) && + child.parent == null) { val adjustedIdx = idx - clippedSoFar check(adjustedIdx >= 0) + if (!customDrawOrderDisabled()) { + drawingOrderHelper.handleAddView(child) + isChildrenDrawingOrderEnabled = drawingOrderHelper.shouldEnableCustomDrawingOrder() + } else { + isChildrenDrawingOrderEnabled = false + } setViewClipped(child, false) addViewInLayout(child, adjustedIdx, defaultLayoutParam, true) invalidate() needUpdateClippingRecursive = true - } else if (intersects) { + } else if (intersects || childPreventClipping) { // If there is any intersection we need to inform the child to update its clipping rect needUpdateClippingRecursive = true } @@ -1003,6 +1028,216 @@ public open class ReactViewGroup public constructor(context: Context?) : accessibilityStateChangeListener = null } + //#region debug helper + override fun removeView(view: View?) { + UiThreadUtil.assertOnUiThread() + getNativeId()?.let { nativeId -> + val viewClass = view?.javaClass?.simpleName ?: "null" + val viewIndex = indexOfChild(view) + val stack = getStack() + val hierarchy = viewHierarchyDescription() + FLog.w( + "ReactViewGroup", + "[$nativeId] removeView called for view of class $viewClass at index $viewIndex\nStack trace:\n$stack\nHierarchy:\n$hierarchy", + ) + } + + super.removeView(view) + } + + override fun removeViewAt(index: Int) { + UiThreadUtil.assertOnUiThread() + getNativeId()?.let { nativeId -> + val view = getChildAt(index) + val viewClass = view?.javaClass?.simpleName ?: "null" + val stack = getStack() + val hierarchy = viewHierarchyDescription() + FLog.w( + "ReactViewGroup", + "[$nativeId] removeViewAt called for view of class $viewClass at index $index\nStack trace:\n$stack\nHierarchy:\n$hierarchy", + ) + } + + super.removeViewAt(index) + } + + override fun removeViews(start: Int, count: Int) { + UiThreadUtil.assertOnUiThread() + getNativeId()?.let { nativeId -> + val views = (start until start + count).map { getChildAt(it) } + val viewClasses = views.map { it?.javaClass?.simpleName ?: "null" } + val stack = getStack() + val hierarchy = viewHierarchyDescription() + FLog.w( + "ReactViewGroup", + "[$nativeId] removeViews called for views of classes $viewClasses starting at index $start count $count\nStack trace:\n$stack\nHierarchy:\n$hierarchy", + ) + } + + super.removeViews(start, count) + } + + override fun removeViewInLayout(view: View?) { + UiThreadUtil.assertOnUiThread() + getNativeId()?.let { nativeId -> + val viewClass = view?.javaClass?.simpleName ?: "null" + val viewIndex = indexOfChild(view) + val stack = getStack() + FLog.w( + "ReactViewGroup", + "[$nativeId] removeViewInLayout called for view of class $viewClass at index $viewIndex\nStack trace:\n$stack", + ) + } + + super.removeViewInLayout(view) + } + + override fun removeViewsInLayout(start: Int, count: Int) { + UiThreadUtil.assertOnUiThread() + getNativeId()?.let { nativeId -> + val views = (start until start + count).map { getChildAt(it) } + val viewClasses = views.map { it?.javaClass?.simpleName ?: "null" } + val stack = getStack() + val hierarchy = viewHierarchyDescription() + FLog.w( + "ReactViewGroup", + "[$nativeId] removeViewsInLayout called for views of classes $viewClasses starting at index $start count $count\nStack trace:\n$stack\nHierarchy:\n$hierarchy", + ) + } + + super.removeViewsInLayout(start, count) + } + + override fun removeAllViewsInLayout() { + UiThreadUtil.assertOnUiThread() + getNativeId()?.let { nativeId -> + val views = (0 until childCount).map { getChildAt(it) } + val viewClasses = views.map { it?.javaClass?.simpleName ?: "null" } + val stack = getStack() + val hierarchy = viewHierarchyDescription() + FLog.w( + "ReactViewGroup", + "[$nativeId] removeAllViewsInLayout called for views of classes $viewClasses\nStack trace:\n$stack\nHierarchy:\n$hierarchy", + ) + } + + super.removeAllViewsInLayout() + } + + override fun removeAllViews() { + UiThreadUtil.assertOnUiThread() + getNativeId()?.let { nativeId -> + val views = (0 until childCount).map { getChildAt(it) } + val viewClasses = views.map { it?.javaClass?.simpleName ?: "null" } + val stack = getStack() + val hierarchy = viewHierarchyDescription() + FLog.w( + "ReactViewGroup", + "[$nativeId] removeAllViews called for views of classes $viewClasses\nStack trace:\n$stack\nHierarchy:\n$hierarchy", + ) + } + + super.removeAllViews() + } + + public fun getNativeId(): String? = getTag(com.facebook.react.R.id.view_tag_native_id) as? String + + public fun getStack(): String = Thread + .currentThread() + .stackTrace + .drop(2) // Skip the call itself + .joinToString("\n") { " at ${it.className}.${it.methodName}(${it.fileName}:${it.lineNumber})" } + + private fun viewHierarchyDescription(): String { + val childrenViewInfo = describeChildren() + val childrenCountInfo = "children count info: getChildCount: $childCount" + val ancestry = try { + describeViewAncestry(this) + } catch (_: Exception) { + null + } + + return "View ancestry:\n$ancestry\n$childrenCountInfo\n$childrenViewInfo" + } + + public fun describeViewAncestry(start: View): String { + val sb = StringBuilder() + var v: View? = start + var depth = 0 + while (v != null) { + val nativeId = v.getTag(R.id.view_tag_native_id) + val testId = v.getTag(R.id.react_test_id) + val uiType = getUIManagerType(v).toString() + val cd = v.contentDescription + sb.append("#").append(depth) + .append(" class=").append(v::class.java.name) + .append(" tag=").append(v.id) + .append(" ui=").append(uiType) + if (nativeId != null) sb.append(" nativeID=").append(nativeId) + if (testId != null) sb.append(" testID=").append(testId) + if (cd != null) sb.append(" contentDesc=").append(cd) + sb.append('\n') + val p = v.parent + v = if (p is View) p else null + depth++ + } + return sb.toString() + } + + public fun describeChildren(): String { + val sb = StringBuilder() + + sb.append(" Attached children: ") + val childCount = childCount + if (childCount == 0) { + sb.append(" (none)\n") + } else { + for (i in 0 until childCount) { + try { + val child = getChildAt(i) + if (child == null) { + sb.append(" [$i] NULL CHILD!\n") + } else { + val childNativeId = child.getTag(R.id.view_tag_native_id) + val childTestId = child.getTag(R.id.react_test_id) + sb.append(" [$i] ") + .append(child::class.java.simpleName) + .append(" id=").append(child.id) + if (childNativeId != null) sb.append(" nativeID=").append(childNativeId) + if (childTestId != null) sb.append(" testID=").append(childTestId) + sb.append(" parent=").append(if (child.parent != null) "attached" else "DETACHED") + sb.append('\n') + } + } catch (e: Exception) { + sb.append(" [$i] ERROR: ").append(e.message).append('\n') + } + } + } + + try { + // If removeClippedSubviews is enabled, show clipped children too + if (removeClippedSubviews && allChildren != null && allChildrenCount > childCount) { + sb.append(" Clipped children:\n") + for (i in 0 until allChildrenCount) { + val child = allChildren!![i] + if (child != null && child.parent == null) { + val childNativeId = child.getTag(R.id.view_tag_native_id) + sb.append(" [$i] ") + .append(child::class.java.simpleName) + .append(" id=").append(child.id) + if (childNativeId != null) sb.append(" nativeID=").append(childNativeId) + sb.append(" (CLIPPED)\n") + } + } + } + } catch (e: Exception) { + sb.append(" Error describing clipped children: ").append(e.message).append('\n') + } + + return sb.toString() + } + //#endregion + private companion object { private const val ARRAY_CAPACITY_INCREMENT = 12 private val defaultLayoutParam = LayoutParams(0, 0) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt index 6bfe3206bccb8b..a534bf5831fba9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt @@ -118,6 +118,11 @@ public open class ReactViewManager : ReactClippingViewManager() view.axOrderList = axOrderList } + + @ReactProp(name = "preventClipping") + public open fun setPreventClipping(view: ReactViewGroup, preventClipping: Boolean) { + view.preventClipping = preventClipping + } @ReactProp(name = "hasTVPreferredFocus") public open fun setTVPreferredFocus(view: ReactViewGroup, hasTVPreferredFocus: Boolean) { diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index 8af445da612ed0..af57701b835dd6 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -32,6 +32,13 @@ endif(CCACHE_FOUND) # Make sure every shared lib includes a .note.gnu.build-id header add_link_options(-Wl,--build-id) +# Message whether RN_SERIALIZABLE_STATE is on +if(RN_SERIALIZABLE_STATE) + message(STATUS "RN_SERIALIZABLE_STATE is on") +else() + message(STATUS "RN_SERIALIZABLE_STATE is off") +endif() + function(add_react_android_subdir relative_path) add_subdirectory(${REACT_ANDROID_DIR}/${relative_path} ReactAndroid/${relative_path}) endfunction() diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp index 16ab55a3991603..411d6f7e482838 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp @@ -21,6 +21,8 @@ #include #include +#include + #include #include @@ -1082,4 +1084,14 @@ void FabricMountingManager::synchronouslyUpdateViewOnUIThread( synchronouslyUpdateViewOnUIThreadJNI(javaUIManager_, viewTag, propsMap); } +void FabricMountingManager::measure(const facebook::react::ShadowView& shadowView, std::function jsCallback) { + static auto measureJNI = + JFabricUIManager::javaClassStatic()->getMethod)>( + "measure"); + + auto javaCallback = JCxxCallbackImpl::newObjectCxxArgs(jsCallback); + + measureJNI(javaUIManager_, shadowView.surfaceId, shadowView.tag, javaCallback); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h index 61978317ff5eda..67f1102480e59c 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.h @@ -63,6 +63,8 @@ class FabricMountingManager final { void synchronouslyUpdateViewOnUIThread( Tag viewTag, const folly::dynamic& props); + + void measure(const ShadowView& shadowView, std::function callback); private: bool isOnMainThread(); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp index 3ccff28c083fd7..884b108faded81 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.cpp @@ -724,6 +724,14 @@ void FabricUIManagerBinding::schedulerDidUpdateShadowTree( // no-op } +void FabricUIManagerBinding::schedulerMeasure(const ShadowView& shadowView, std::function jsCallback) { + auto mountingManager = getMountingManager("schedulerMeasure"); + if (!mountingManager) { + return; + } + mountingManager->measure(shadowView, jsCallback); +} + void FabricUIManagerBinding::onAnimationStarted() { auto mountingManager = getMountingManager("onAnimationStarted"); if (!mountingManager) { diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h index 39e724038c6534..ca3140ec363efc 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricUIManagerBinding.h @@ -128,6 +128,8 @@ class FabricUIManagerBinding : public jni::HybridClass, void schedulerDidUpdateShadowTree( const std::unordered_map& tagToProps) override; + + void schedulerMeasure(const ShadowView& shadowView, std::function jsCallback) override; void setPixelDensity(float pointScaleFactor); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index ea1b239441a048..619bc19f8d64c9 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<5effd7d4ac8034424144ea68c82b61a7>> + * @generated SignedSource<> */ /** @@ -321,6 +321,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool preventShadowTreeCommitExhaustion() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("preventShadowTreeCommitExhaustion"); + return method(javaProvider_); + } + bool traceTurboModulePromiseRejectionsOnAndroid() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("traceTurboModulePromiseRejectionsOnAndroid"); @@ -345,6 +351,18 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool useNativeEqualsInNativeReadableArrayAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useNativeEqualsInNativeReadableArrayAndroid"); + return method(javaProvider_); + } + + bool useNativeTransformHelperAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useNativeTransformHelperAndroid"); + return method(javaProvider_); + } + bool useNativeViewConfigsInBridgelessMode() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useNativeViewConfigsInBridgelessMode"); @@ -387,6 +405,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool runtimeCrashUiThreadUtils() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("runtimeCrashUiThreadUtils"); + return method(javaProvider_); + } + private: jni::global_ref javaProvider_; }; @@ -626,6 +650,11 @@ double JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize( return ReactNativeFeatureFlags::preparedTextCacheSize(); } +bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustion( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion(); +} + bool JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid(); @@ -646,6 +675,16 @@ bool JReactNativeFeatureFlagsCxxInterop::useFabricInterop( return ReactNativeFeatureFlags::useFabricInterop(); } +bool JReactNativeFeatureFlagsCxxInterop::useNativeEqualsInNativeReadableArrayAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid(); +} + +bool JReactNativeFeatureFlagsCxxInterop::useNativeTransformHelperAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::useNativeTransformHelperAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::useNativeViewConfigsInBridgelessMode( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode(); @@ -681,6 +720,11 @@ double JReactNativeFeatureFlagsCxxInterop::virtualViewPrerenderRatio( return ReactNativeFeatureFlags::virtualViewPrerenderRatio(); } +bool JReactNativeFeatureFlagsCxxInterop::runtimeCrashUiThreadUtils( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::runtimeCrashUiThreadUtils(); +} + void JReactNativeFeatureFlagsCxxInterop::override( facebook::jni::alias_ref /*unused*/, jni::alias_ref provider) { @@ -853,6 +897,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "preparedTextCacheSize", JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize), + makeNativeMethod( + "preventShadowTreeCommitExhaustion", + JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustion), makeNativeMethod( "traceTurboModulePromiseRejectionsOnAndroid", JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid), @@ -865,6 +912,12 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "useFabricInterop", JReactNativeFeatureFlagsCxxInterop::useFabricInterop), + makeNativeMethod( + "useNativeEqualsInNativeReadableArrayAndroid", + JReactNativeFeatureFlagsCxxInterop::useNativeEqualsInNativeReadableArrayAndroid), + makeNativeMethod( + "useNativeTransformHelperAndroid", + JReactNativeFeatureFlagsCxxInterop::useNativeTransformHelperAndroid), makeNativeMethod( "useNativeViewConfigsInBridgelessMode", JReactNativeFeatureFlagsCxxInterop::useNativeViewConfigsInBridgelessMode), @@ -886,6 +939,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "virtualViewPrerenderRatio", JReactNativeFeatureFlagsCxxInterop::virtualViewPrerenderRatio), + makeNativeMethod( + "runtimeCrashUiThreadUtils", + JReactNativeFeatureFlagsCxxInterop::runtimeCrashUiThreadUtils), }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index 9e894e2497836b..9dde8d95a03bca 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<425f1171eab19457655a1e44c70ccce9>> */ /** @@ -171,6 +171,9 @@ class JReactNativeFeatureFlagsCxxInterop static double preparedTextCacheSize( facebook::jni::alias_ref); + static bool preventShadowTreeCommitExhaustion( + facebook::jni::alias_ref); + static bool traceTurboModulePromiseRejectionsOnAndroid( facebook::jni::alias_ref); @@ -183,6 +186,12 @@ class JReactNativeFeatureFlagsCxxInterop static bool useFabricInterop( facebook::jni::alias_ref); + static bool useNativeEqualsInNativeReadableArrayAndroid( + facebook::jni::alias_ref); + + static bool useNativeTransformHelperAndroid( + facebook::jni::alias_ref); + static bool useNativeViewConfigsInBridgelessMode( facebook::jni::alias_ref); @@ -204,6 +213,9 @@ class JReactNativeFeatureFlagsCxxInterop static double virtualViewPrerenderRatio( facebook::jni::alias_ref); + static bool runtimeCrashUiThreadUtils( + facebook::jni::alias_ref); + static void override( facebook::jni::alias_ref, jni::alias_ref provider); diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt index d0d015ccc55d4c..aa973151b94921 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/hermes/reactexecutor/CMakeLists.txt @@ -25,4 +25,6 @@ target_link_libraries( reactnative ) target_compile_reactnative_options(hermes_executor PRIVATE) -target_compile_options(hermes_executor PRIVATE $<$:-DHERMES_ENABLE_DEBUGGER=1>) +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) + target_compile_options(hermes_executor PRIVATE -DHERMES_ENABLE_DEBUGGER=1) +endif() diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt index f315cca6dd1f62..61cc16c08345be 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/CMakeLists.txt @@ -31,13 +31,14 @@ add_library( OnLoad-common.cpp ReadableNativeArray.cpp ReadableNativeMap.cpp + TransformHelper.cpp WritableNativeArray.cpp WritableNativeMap.cpp ) target_merge_so(reactnativejni_common) target_include_directories(reactnativejni_common PUBLIC ../../) -target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact) +target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact yoga react_renderer_graphics) target_compile_reactnative_options(reactnativejni_common PRIVATE) target_compile_options(reactnativejni_common PRIVATE -Wno-unused-lambda-capture) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeArray.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeArray.h index c70874855211d9..738738437b0179 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeArray.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/NativeArray.h @@ -21,6 +21,10 @@ class NativeArray : public jni::HybridClass { jni::local_ref toString(); + const folly::dynamic& getArray() const { + return array_; + } + RN_EXPORT folly::dynamic consume(); // Whether this array has been added to another array or map and no longer diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp index 293d2ffc8abf5f..ef069ed7e41d51 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad-common.cpp @@ -11,6 +11,7 @@ #include "JReactMarker.h" #include "NativeArray.h" #include "NativeMap.h" +#include "TransformHelper.h" #include "WritableNativeArray.h" #include "WritableNativeMap.h" @@ -27,6 +28,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { ReadableNativeMap::registerNatives(); WritableNativeArray::registerNatives(); WritableNativeMap::registerNatives(); + TransformHelper::registerNatives(); }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp index 9add683e2a4443..bdc4d6bbbbb413 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.cpp @@ -40,10 +40,16 @@ local_ref> ReadableNativeArray::importTypeArray() { return jarray; } +bool ReadableNativeArray::equals( + jni::alias_ref other) { + return array_ == other->cthis()->array_; +} + void ReadableNativeArray::registerNatives() { registerHybrid({ makeNativeMethod("importArray", ReadableNativeArray::importArray), makeNativeMethod("importTypeArray", ReadableNativeArray::importTypeArray), + makeNativeMethod("nativeEquals", ReadableNativeArray::equals), }); } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h index 02d9445192e29e..1ab3243ad12ba4 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/ReadableNativeArray.h @@ -35,6 +35,7 @@ class ReadableNativeArray static void mapException(std::exception_ptr ex); static void registerNatives(); + bool equals(jni::alias_ref other); jni::local_ref> importArray(); jni::local_ref> importTypeArray(); }; diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.cpp new file mode 100644 index 00000000000000..b33d269ac298de --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "TransformHelper.h" + +#include +#include + +#include "NativeArray.h" + +using namespace facebook::jni; + +namespace facebook::react { + +namespace { +void processTransform( + jni::alias_ref /*unused*/, + NativeArray* jTransforms, + jni::alias_ref jResult, + float viewWidth, + float viewHeight, + NativeArray* jTransformOrigin) { + // Assuming parsing transforms doesn't require a real PropsParserContext + static ContextContainer contextContainer; + static PropsParserContext context(0, contextContainer); + + RawValue transformValue(jTransforms->getArray()); + Transform transform; + fromRawValue(context, transformValue, transform); + + TransformOrigin transformOrigin; + if (jTransformOrigin != nullptr) { + RawValue transformOriginValue(jTransformOrigin->getArray()); + fromRawValue(context, transformOriginValue, transformOrigin); + } + + auto result = BaseViewProps::resolveTransform( + Size(viewWidth, viewHeight), transform, transformOrigin); + + // Convert from matrix of floats to double matrix + constexpr size_t MatrixSize = std::tuple_size_v; + std::array doubleTransform{}; + std::copy( + result.matrix.begin(), result.matrix.end(), doubleTransform.begin()); + jResult->setRegion(0, MatrixSize, doubleTransform.data()); +} + +} // namespace + +void TransformHelper::registerNatives() { + javaClassLocal()->registerNatives({ + makeNativeMethod("nativeProcessTransform", processTransform), + }); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.h b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.h new file mode 100644 index 00000000000000..342a8ee1100445 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/jni/react/jni/TransformHelper.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook::react { + +class TransformHelper : public jni::JavaClass { + public: + static auto constexpr* kJavaDescriptor = + "Lcom/facebook/react/uimanager/TransformHelper;"; + + static void registerNatives(); +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt index 6b182daa3acb65..63c1a937341b3f 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/hermes/jni/CMakeLists.txt @@ -27,4 +27,6 @@ target_link_libraries(hermesinstancejni ) target_compile_reactnative_options(hermesinstancejni PRIVATE) -target_compile_options(hermesinstancejni PRIVATE $<$:-DHERMES_ENABLE_DEBUGGER=1>) +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) + target_compile_options(hermesinstancejni PRIVATE -DHERMES_ENABLE_DEBUGGER=1) +endif () diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt index 0633be11254ec2..8987aed037633a 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/react/runtime/jni/CMakeLists.txt @@ -17,7 +17,9 @@ add_library(rninstance ) target_compile_reactnative_options(rninstance PRIVATE) -target_compile_options(rninstance PRIVATE $<$:-DHERMES_ENABLE_DEBUGGER=1>) +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) + target_compile_options(rninstance PRIVATE -DHERMES_ENABLE_DEBUGGER=1) +endif () target_merge_so(rninstance) target_include_directories(rninstance PUBLIC .) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricUIManagerTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricUIManagerTest.kt index 3403b6d8f278ec..38033aae0a543e 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricUIManagerTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/FabricUIManagerTest.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.fabric import com.facebook.react.bridge.BridgeReactContext diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/MountingManagerTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/MountingManagerTest.kt index 4f009cb1adf21f..473e2eb4c0bad2 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/MountingManagerTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/MountingManagerTest.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.fabric import com.facebook.react.ReactRootView diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/SurfaceMountingManagerIntegrationTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/SurfaceMountingManagerIntegrationTest.kt new file mode 100644 index 00000000000000..558b3b53971aa8 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/SurfaceMountingManagerIntegrationTest.kt @@ -0,0 +1,783 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.fabric.mounting + +import android.app.Activity +import android.os.Looper +import android.view.ViewGroup +import androidx.core.view.doOnDetach +import com.facebook.common.logging.FLog +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.BridgeReactContext +import com.facebook.react.bridge.ReactTestHelper.createMockCatalystInstance +import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.react.touch.JSResponderHandler +import com.facebook.react.uimanager.RootViewManager +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewManager +import com.facebook.react.uimanager.ViewManagerRegistry +import com.facebook.react.views.view.ReactViewManager +import com.facebook.testutils.shadows.ShadowArguments +import com.facebook.testutils.shadows.ShadowNativeArray +import com.facebook.testutils.shadows.ShadowNativeLoader +import com.facebook.testutils.shadows.ShadowNativeMap +import com.facebook.testutils.shadows.ShadowReadableNativeArray +import com.facebook.testutils.shadows.ShadowReadableNativeMap +import com.facebook.testutils.shadows.ShadowSoLoader +import com.facebook.testutils.shadows.ShadowWritableNativeArray +import com.facebook.testutils.shadows.ShadowWritableNativeMap +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.Robolectric +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows +import org.robolectric.annotation.Config +import org.robolectric.shadows.ShadowLog +import java.util.concurrent.CountDownLatch + +@RunWith(RobolectricTestRunner::class) +@Config( + shadows = [ + ShadowArguments::class, + ShadowSoLoader::class, + ShadowNativeLoader::class, + ShadowNativeArray::class, + ShadowNativeMap::class, + ShadowWritableNativeMap::class, + ShadowWritableNativeArray::class, + ShadowReadableNativeMap::class, + ShadowReadableNativeArray::class, + ] +) +class SurfaceMountingManagerIntegrationTest { + + private lateinit var surfaceMountingManager: SurfaceMountingManager + private lateinit var reactContext: BridgeReactContext + private lateinit var themedReactContext: ThemedReactContext + private lateinit var viewManagerRegistry: ViewManagerRegistry + private lateinit var rootViewManager: RootViewManager + private lateinit var mountItemExecutor: MountItemExecutor + private val surfaceId = 1 + private lateinit var activity: Activity + + @Before + fun setup() { + ReactNativeFeatureFlagsForTests.setUp() + ReactNativeFeatureFlags.override(object : ReactNativeFeatureFlagsDefaults() { + override fun enableFabricLogs() = true + }) + FLog.setMinimumLoggingLevel(android.util.Log.DEBUG) + reactContext = BridgeReactContext(RuntimeEnvironment.getApplication()) + reactContext.initializeWithInstance(createMockCatalystInstance()) + themedReactContext = ThemedReactContext(reactContext, reactContext, null, -1) + + val viewManagers = listOf>(ReactViewManager()) + viewManagerRegistry = ViewManagerRegistry(viewManagers) + rootViewManager = RootViewManager() + mountItemExecutor = MountItemExecutor { } + + surfaceMountingManager = SurfaceMountingManager( + surfaceId, + JSResponderHandler(), + viewManagerRegistry, + rootViewManager, + mountItemExecutor, + themedReactContext + ) + + val controller = Robolectric.buildActivity(Activity::class.java) + controller.setup() // Moves the activity to the "resumed" state + activity = controller.get() + + ShadowLog.stream = System.out + } + + @Test + fun testAddViewAt_immediateExecution_whenNoTransition() { + val parentTag = 100 + val childTag = 200 + + val parentView = createView(parentTag) + val childView = createView(childTag) + + // Verify parent starts empty + assertThat(parentView.childCount).isEqualTo(0) + + // Add child to parent (should execute immediately since no transition) + surfaceMountingManager.addViewAt(parentTag, childTag, 0) + + // Verify child was added immediately + assertThat(parentView.childCount).isEqualTo(1) + assertThat(parentView.getChildAt(0)).isEqualTo(childView) + } + + @Test + fun testRemoveViewAt_queuesOperation_whenParentHasQueuedOperations() { + val parentTag = 100 + val childTag = 200 + + val parentView = createView(parentTag) + val childView = createView(childTag) + + activity.setContentView(parentView) + + // Add child to parent first + surfaceMountingManager.addViewAt(parentTag, childTag, 0) + assertThat(parentView.childCount).isEqualTo(1) + assertThat(childView.isAttachedToWindow).isTrue() + + // Mark child as in transition + parentView.startViewTransition(childView) + surfaceMountingManager.markViewInTransition(childTag, true) + + val latch = CountDownLatch(1) + childView.doOnDetach { + latch.countDown() + } + + // Remove child (should queue since child is in transition) + surfaceMountingManager.removeViewAt(childTag, parentTag, 0) + + // Confusing part: the child has been removed from the parent BUT the child still has its mParent set + assertThat(parentView.childCount).isEqualTo(0) + assertThat(childView.parent).isEqualTo(parentView) + + // Mark child as not transitioning (should drain operations) + parentView.endViewTransition(childView) + surfaceMountingManager.markViewInTransition(childTag, false) + + assertThat(latch.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + assertThat(parentView.childCount).isEqualTo(0) + assertThat(childView.parent).isNull() + } + + @Test + fun testAddViewAt_maintainsParentOrderForSameChild() { + val parent = 10 + val parent1Tag = 100 + val parent2Tag = 200 + val childTag = 300 + + val parentView = createView(parent) + val parent1View = createView(parent1Tag) + val parent2View = createView(parent2Tag) + val childView = createView(childTag) + + activity.setContentView(parentView) + + surfaceMountingManager.addViewAt(parent, parent1Tag, 0) + surfaceMountingManager.addViewAt(parent, parent2Tag, 1) + + // Add child to parent one + surfaceMountingManager.addViewAt(parent1Tag, childTag, 0) + + // Mark child as in transition + parent1View.startViewTransition(childView) + surfaceMountingManager.markViewInTransition(childTag, true) + + val latch = CountDownLatch(1) + childView.doOnDetach { + latch.countDown() + } + + // Queue remove from parent1, then add to parent2 + surfaceMountingManager.removeViewAt(childTag, parent1Tag, 0) + surfaceMountingManager.addViewAt(parent2Tag, childTag, 0) + + assertThat(parent1View.childCount).isEqualTo(0) + assertThat(childView.parent).isEqualTo(parent1View) + assertThat(parent2View.childCount).isEqualTo(0) + + // Mark child as not transitioning (should drain operations) + surfaceMountingManager.markViewInTransition(childTag, false) + parent1View.endViewTransition(childView) + + // Wait for doOnDetach to fire, but don't run the shadow looper inside it … + assertThat(latch.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + // … otherwise ViewTransitionCoordinator's code would run its UIThreadUtil.runOnUiThread + // during dispatchDetachedFromWindow, which is before the parent fields are cleared. + // So run the looper after the detach here: + Shadows.shadowOf(Looper.getMainLooper()).idle() + + // Verify child ends up in parent2 (the last operation) + // Operations should execute: add to parent1, then remove from parent1 and add to parent2 + assertThat(parent1View.childCount).isEqualTo(0) + assertThat(parent2View.childCount).isEqualTo(1) + assertThat(parent2View.getChildAt(0)).isEqualTo(childView) + assertThat(childView.parent).isEqualTo(parent2View) + } + + @Test + fun testRemoveAndDelete_shouldFullyDrain() { + // 1. Parent A has remove operations for child1 and child2 + // 2. Both children also have delete operations queued + // 3. Parent A's queue drains completely (both removes execute) + // 4. Only child2 (last operation) gets to drain and clean up + // 5. Child1 is stuck with parent A in its childToParentOrder + // 6. Child1's delete operation can never execute because it's not "first in line" + + val parentTag = 300 + val child1Tag = 100 + val child2Tag = 200 + + val parentView = createView(parentTag) + val childView1 = createView(child1Tag) + val childView2 = createView(child2Tag) + + activity.setContentView(parentView) + + surfaceMountingManager.addViewAt(parentTag, child1Tag, 0) + surfaceMountingManager.addViewAt(parentTag, child2Tag, 1) + assertThat(parentView.childCount).isEqualTo(2) + assertThat(childView1.isAttachedToWindow).isTrue() + assertThat(childView2.isAttachedToWindow).isTrue() + + parentView.startViewTransition(childView1) + parentView.startViewTransition(childView2) + surfaceMountingManager.markViewInTransition(child1Tag, true) + surfaceMountingManager.markViewInTransition(child2Tag, true) + + val latch = CountDownLatch(2) + childView1.doOnDetach { + latch.countDown() + } + childView2.doOnDetach { + latch.countDown() + } + + // Queue remove operations for both children, important: reverse order + surfaceMountingManager.removeViewAt(child2Tag, parentTag, 1) + surfaceMountingManager.removeViewAt(child1Tag, parentTag, 0) + + // Queue delete operations for both children + surfaceMountingManager.deleteView(child1Tag) + surfaceMountingManager.deleteView(child2Tag) + + // At this point: + // - childToParentOrder[100] = [-1337] + // - childToParentOrder[200] = [-1337] + // - parentQueues[-1337] = [Delete(child1), Delete(child2)] + // remove operations are executed right away if there is no queue yet (to trigger ending of transition)! + + // Verify operations are queued + assertThat(parentView.childCount).isEqualTo(0) + assertThat(childView1.parent).isEqualTo(parentView) + assertThat(childView2.parent).isEqualTo(parentView) + + // Mark children as not transitioning, triggering drains + // This will cause parent 300 to drain completely + surfaceMountingManager.markViewInTransition(child1Tag, false) + surfaceMountingManager.markViewInTransition(child2Tag, false) + parentView.endViewTransition(childView1) + parentView.endViewTransition(childView2) + + assertThat(latch.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + // Both removes should have executed + assertThat(parentView.childCount).isEqualTo(0) + assertThat(childView1.parent).isNull() + assertThat(childView2.parent).isNull() + + // Both deletes should eventually execute + assertThatThrownBy { + surfaceMountingManager.getView(child1Tag) + } + + assertThatThrownBy { + surfaceMountingManager.getView(child2Tag) + } + + val coordinator = getCoordinator() + assertThat(coordinator.isEmpty()).isTrue() + } + + @Test + fun testRemoveAndReparent_shouldNotDeadlock() { + // Scenario: Remove from parent A, then add to parent B + + val parentATag = 100 + val parentBTag = 200 + val child1Tag = 300 + val child2Tag = 400 + + val parentAView = createView(parentATag) + val parentBView = createView(parentBTag) + val childView1 = createView(child1Tag) + val childView2 = createView(child2Tag) + + activity.setContentView(parentAView) + + // Add both children to parent A + surfaceMountingManager.addViewAt(parentATag, child1Tag, 0) + surfaceMountingManager.addViewAt(parentATag, child2Tag, 1) + assertThat(parentAView.childCount).isEqualTo(2) + + // Mark children as in transition + parentAView.startViewTransition(childView1) + parentAView.startViewTransition(childView2) + surfaceMountingManager.markViewInTransition(child1Tag, true) + surfaceMountingManager.markViewInTransition(child2Tag, true) + + val latch = CountDownLatch(2) + childView1.doOnDetach { + latch.countDown() + } + childView2.doOnDetach { + latch.countDown() + } + + // Remove from parent A + surfaceMountingManager.removeViewAt(child2Tag, parentATag, 1) + surfaceMountingManager.removeViewAt(child1Tag, parentATag, 0) + + // Reparent to parent B + surfaceMountingManager.addViewAt(parentBTag, child1Tag, 0) + surfaceMountingManager.addViewAt(parentBTag, child2Tag, 1) + + // At this point: + // - childToParentOrder[child1] = [parentA, parentB] + // - childToParentOrder[child2] = [parentA, parentB] + + assertThat(parentAView.childCount).isEqualTo(0) + assertThat(childView1.parent).isEqualTo(parentAView) + assertThat(childView2.parent).isEqualTo(parentAView) + assertThat(parentBView.childCount).isEqualTo(0) + + // Mark children as not transitioning + surfaceMountingManager.markViewInTransition(child1Tag, false) + surfaceMountingManager.markViewInTransition(child2Tag, false) + parentAView.endViewTransition(childView1) + parentAView.endViewTransition(childView2) + + assertThat(latch.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + // Both removes should execute + assertThat(parentAView.childCount).isEqualTo(0) + + // Both adds should execute (deadlock would prevent this) + assertThat(parentBView.childCount).isEqualTo(2) + assertThat(parentBView.getChildAt(0)).isEqualTo(childView1) + assertThat(parentBView.getChildAt(1)).isEqualTo(childView2) + + // Verify coordinator is fully clean + val coordinator = getCoordinator() + assertThat(coordinator.isEmpty()).isTrue() + } + + @Test + fun testMultipleReparenting_shouldNotDeadlock() { + // Bug scenario: Reparent through multiple parents A -> B -> C + // When parent A drains, only child2 cleans up, leaving child1 deadlocked + + val parentATag = 100 + val parentBTag = 200 + val parentCTag = 300 + val child1Tag = 400 + val child2Tag = 500 + + val parentAView = createView(parentATag) + val parentBView = createView(parentBTag) + val parentCView = createView(parentCTag) + val childView1 = createView(child1Tag) + val childView2 = createView(child2Tag) + + activity.setContentView(parentAView) + + // Add both children to parent A + surfaceMountingManager.addViewAt(parentATag, child1Tag, 0) + surfaceMountingManager.addViewAt(parentATag, child2Tag, 1) + assertThat(parentAView.childCount).isEqualTo(2) + + // Mark children as in transition + parentAView.startViewTransition(childView1) + parentAView.startViewTransition(childView2) + surfaceMountingManager.markViewInTransition(child1Tag, true) + surfaceMountingManager.markViewInTransition(child2Tag, true) + + val latch = CountDownLatch(2) + childView1.doOnDetach { + latch.countDown() + } + childView2.doOnDetach { + latch.countDown() + } + + // Reparent A -> B + surfaceMountingManager.removeViewAt(child2Tag, parentATag, 1) + surfaceMountingManager.removeViewAt(child1Tag, parentATag, 0) + surfaceMountingManager.addViewAt(parentBTag, child1Tag, 0) + surfaceMountingManager.addViewAt(parentBTag, child2Tag, 1) + + // Reparent B -> C + surfaceMountingManager.removeViewAt(child2Tag, parentBTag, 1) + surfaceMountingManager.removeViewAt(child1Tag, parentBTag, 0) + surfaceMountingManager.addViewAt(parentCTag, child1Tag, 0) + surfaceMountingManager.addViewAt(parentCTag, child2Tag, 1) + + // At this point: + // - childToParentOrder[child1] = [A, B, C] + // - childToParentOrder[child2] = [A, B, C] + + // Mark children as not transitioning + surfaceMountingManager.markViewInTransition(child1Tag, false) + surfaceMountingManager.markViewInTransition(child2Tag, false) + parentAView.endViewTransition(childView1) + parentAView.endViewTransition(childView2) + + assertThat(latch.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + // All operations should execute + assertThat(parentAView.childCount).isEqualTo(0) + assertThat(parentBView.childCount).isEqualTo(0) + assertThat(parentCView.childCount).isEqualTo(2) + assertThat(parentCView.getChildAt(0)).isEqualTo(childView1) + assertThat(parentCView.getChildAt(1)).isEqualTo(childView2) + + val coordinator = getCoordinator() + assertThat(coordinator.isEmpty()).isTrue() + } + + @Test + fun testPartialDrainThenFullDrain_shouldCleanupAllChildren() { + // If a queue partially drains (some ops execute, then blocks on one), + // then later fully drains, all children should be cleaned up from childToParentOrder! + // + // Scenario: + // 1. Queue: [Remove(child1), Remove(child2), Remove(child3), Remove(child4)] + // 2. First drain: child1, child2 execute, then blocks on child3 (still in transition) + // 3. Second drain: child3, child4 execute (queue fully drained) + // 4. Only child3 and child4 get cleaned up from childToParentOrder + // 5. child1 and child2 are left with stale parent reference + + val parentATag = 100 + val parentBTag = 200 + val child1Tag = 300 + val child2Tag = 400 + val child3Tag = 500 + val child4Tag = 600 + + val parentAView = createView(parentATag) + val parentBView = createView(parentBTag) + val childView1 = createView(child1Tag) + val childView2 = createView(child2Tag) + val childView3 = createView(child3Tag) + val childView4 = createView(child4Tag) + + activity.setContentView(parentAView) + + // Add all children to parent A + surfaceMountingManager.addViewAt(parentATag, child1Tag, 0) + surfaceMountingManager.addViewAt(parentATag, child2Tag, 1) + surfaceMountingManager.addViewAt(parentATag, child3Tag, 2) + surfaceMountingManager.addViewAt(parentATag, child4Tag, 3) + assertThat(parentAView.childCount).isEqualTo(4) + + // Mark all children as in transition + parentAView.startViewTransition(childView1) + parentAView.startViewTransition(childView2) + parentAView.startViewTransition(childView3) + parentAView.startViewTransition(childView4) + surfaceMountingManager.markViewInTransition(child1Tag, true) + surfaceMountingManager.markViewInTransition(child2Tag, true) + surfaceMountingManager.markViewInTransition(child3Tag, true) + surfaceMountingManager.markViewInTransition(child4Tag, true) + + val latchA = CountDownLatch(2) + childView1.doOnDetach { + latchA.countDown() + } + childView2.doOnDetach { + latchA.countDown() + } + val latchB = CountDownLatch(2) + childView3.doOnDetach { + latchB.countDown() + } + childView4.doOnDetach { + latchB.countDown() + } + + // Queue removes from parent A + surfaceMountingManager.removeViewAt(child4Tag, parentATag, 3) + surfaceMountingManager.removeViewAt(child3Tag, parentATag, 2) + surfaceMountingManager.removeViewAt(child2Tag, parentATag, 1) + surfaceMountingManager.removeViewAt(child1Tag, parentATag, 0) + + // Queue adds to parent B (this is where we'll detect the bug) + surfaceMountingManager.addViewAt(parentBTag, child1Tag, 0) + surfaceMountingManager.addViewAt(parentBTag, child2Tag, 1) + surfaceMountingManager.addViewAt(parentBTag, child3Tag, 2) + surfaceMountingManager.addViewAt(parentBTag, child4Tag, 3) + + // At this point: + // - parentQueues[parentA] = [Remove(child4), Remove(child3), Remove(child2), Remove(child1)] + // - parentQueues[parentB] = [Add(child1), Add(child2), Add(child3), Add(child4)] + // - childToParentOrder[child1] = [parentA, parentB] + // - childToParentOrder[child2] = [parentA, parentB] + // - childToParentOrder[child3] = [parentA, parentB] + // - childToParentOrder[child4] = [parentA, parentB] + + // First partial drain: Mark child4 and child3 as ready + // This will execute Remove(child4) and Remove(child3), then block on Remove(child2) + surfaceMountingManager.markViewInTransition(child4Tag, false) + surfaceMountingManager.markViewInTransition(child3Tag, false) + parentAView.endViewTransition(childView4) + parentAView.endViewTransition(childView3) + + // Partial drain happened: child1 and child2 removed, but child3 blocks the queue + // Bug: child1 and child2 are NOT cleaned up from childToParentOrder yet + assertThat(latchB.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + // Second full drain: Mark child2 and child1 as ready + // This will execute Remove(child2) and Remove(child1), fully draining parent A's queue + surfaceMountingManager.markViewInTransition(child2Tag, false) + surfaceMountingManager.markViewInTransition(child1Tag, false) + parentAView.endViewTransition(childView2) + parentAView.endViewTransition(childView1) + + assertThat(latchA.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + // Parent A should be fully drained + assertThat(parentAView.childCount).isEqualTo(0) + + // Now all adds to parent B should execute + // Bug: If child1 and child2 still have [parentA, parentB] in childToParentOrder, + // their Add operations to parent B will be blocked (not "first in line") + assertThat(parentBView.childCount).isEqualTo(4) + assertThat(parentBView.getChildAt(0)).isEqualTo(childView1) + assertThat(parentBView.getChildAt(1)).isEqualTo(childView2) + assertThat(parentBView.getChildAt(2)).isEqualTo(childView3) + assertThat(parentBView.getChildAt(3)).isEqualTo(childView4) + + // Verify coordinator is fully clean + val coordinator = getCoordinator() + assertThat(coordinator.isEmpty()).isTrue() + } + + @Test + fun testViewNotMarkedButWithParent_worksAsWell() { + val parent1Id = 100 + val parent2Id = 200 + val childId = 300 + + val parent1View = createView(parent1Id) + val parent2View = createView(parent2Id) + val childView = createView(childId) + + activity.setContentView(parent1View) + + surfaceMountingManager.addViewAt(parent1Id, childId, 0) + assertThat(parent1View.childCount).isEqualTo(1) + + // Manual transaction, not using markViewInTransition + parent1View.startViewTransition(childView) + parent1View.removeView(childView) + + // Now try adding to new parent, this should be queued + assertThat(childView.parent).isNotNull() + surfaceMountingManager.addViewAt(parent2Id, childId, 0) + + val latch = CountDownLatch(1) + childView.doOnDetach { + latch.countDown() + } + + parent1View.endViewTransition(childView) + assertThat(latch.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + assertThat(parent1View.childCount).isEqualTo(0) + assertThat(parent2View.childCount).isEqualTo(1) + assertThat(parent2View.getChildAt(0)).isEqualTo(childView) + } + + @Test + fun testTagReuse_queuedDeleteShouldNotAffectNewView() { + // Test case: + // 1. Create view hierarchy: + // Window(10) + // Unrelated(999) | Parent(20) + // Intermediate(16) + // Child(14) + // + // 2. Start transition on Unrelated(999) + // 3. While transition is ongoing, remove Intermediate(16) and reattach Child(14) directly to Parent(20) + // 4. Then, create a NEW Intermediate(16) and add Child(14) back under it + // 5. End transition on Unrelated(999), which drains its queued operations (including delete of old Intermediate(16)) + // 6. Verify that the NEW Intermediate(16) still exists and Child(14) is correctly parented under it + + val windowParent = 10 + val parentTag20 = 20 + val intermediateViewGroupTag16 = 16 + val childTag14 = 14 + val unrelatedTag = 999 + + val windowView = createView(windowParent) + activity.setContentView(windowView + ) + val parentView = createView(parentTag20) + surfaceMountingManager.addViewAt(windowParent, parentTag20, 0) + + val intermediateChildView1 = createView(intermediateViewGroupTag16) + surfaceMountingManager.addViewAt(parentTag20, intermediateViewGroupTag16, 0) + val childView = createView(childTag14) + surfaceMountingManager.addViewAt(intermediateViewGroupTag16, childTag14, 0) + val unrelatedView = createView(unrelatedTag) + surfaceMountingManager.addViewAt(windowParent, unrelatedTag, 1) + + // Start a transition on "some other view" + windowView.startViewTransition(unrelatedView) + surfaceMountingManager.markViewInTransition(unrelatedTag, true) + val latch = CountDownLatch(1) + unrelatedView.doOnDetach { + latch.countDown() + } + // Important: queue a remove+delete for the unrelated view to populate the delete queue + surfaceMountingManager.removeViewAt(unrelatedTag, windowParent, 1) + surfaceMountingManager.deleteView(unrelatedTag) + + // Now, while the transition is ongoing, remove the intermediate child and reattach the child directly to parent + surfaceMountingManager.removeViewAt(childTag14, intermediateViewGroupTag16, 0) + surfaceMountingManager.removeViewAt(intermediateViewGroupTag16, parentTag20, 0) + surfaceMountingManager.deleteView(intermediateViewGroupTag16) + surfaceMountingManager.addViewAt(parentTag20, childTag14, 0) + + // Now, we create a new intermediate child with the same tag as before, to add the child back under it + surfaceMountingManager.removeViewAt(childTag14, parentTag20, 0) + val intermediateChildView2 = createView(intermediateViewGroupTag16) + surfaceMountingManager.addViewAt(parentTag20, intermediateViewGroupTag16, 0) + surfaceMountingManager.addViewAt(intermediateViewGroupTag16, childTag14, 0) + + // Now end the transition on the unrelated view, which will drain its queued operations + windowView.endViewTransition(unrelatedView) + surfaceMountingManager.markViewInTransition(unrelatedTag, false) + assertThat(latch.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + // Assert that the newly created intermediate view still exists in SurfaceMountingManager and points to the correct instance + val view = surfaceMountingManager.getView(intermediateViewGroupTag16) + assertThat(view).isEqualTo(intermediateChildView2) + + // Now remove the intermediate view again and make sure thats works too + surfaceMountingManager.removeViewAt(childTag14, intermediateViewGroupTag16, 0) + surfaceMountingManager.removeViewAt(intermediateViewGroupTag16, parentTag20, 0) + surfaceMountingManager.deleteView(intermediateViewGroupTag16) + assertThat(childView.parent).isNull() + surfaceMountingManager.addViewAt(parentTag20, childTag14, 0) + assertThat(childView.parent).isEqualTo(parentView) + } + + @Test + fun testTagReuse_queuedDeleteCancelsWhenTagRecreates() { + // Test case: + // 1. Create view hierarchy: + // Window(10) + // Parent(20) + // Intermediate(16) + // Child(14) + // 2. Start transition on Intermediate(16) + // 3. While transition is ongoing, remove Intermediate(16) and reattach Child(14) directly to Parent(20) + // 4. Then, create a NEW Intermediate(16) and add Child(14) back under it + // 5. End transition on Intermediate(16), which drains its queued operations (including delete of old Intermediate(16)) + // 6. Verify that the NEW Intermediate(16) still exists and Child(14) is correctly parented under it + + val windowParent = 10 + val parentTag20 = 20 + val intermediateViewGroupTag16 = 16 + val childTag14 = 14 + + val windowView = createView(windowParent) + activity.setContentView(windowView + ) + val parentView = createView(parentTag20) + surfaceMountingManager.addViewAt(windowParent, parentTag20, 0) + + val intermediateChildView1 = createView(intermediateViewGroupTag16) + surfaceMountingManager.addViewAt(parentTag20, intermediateViewGroupTag16, 0) + val childView = createView(childTag14) + surfaceMountingManager.addViewAt(intermediateViewGroupTag16, childTag14, 0) + + // Start a transition on 16 + parentView.startViewTransition(intermediateChildView1) + surfaceMountingManager.markViewInTransition(intermediateViewGroupTag16, true) + val latch = CountDownLatch(1) + intermediateChildView1.doOnDetach { + latch.countDown() + } + + // Remove intermediate group, add child directly to parent + surfaceMountingManager.removeViewAt(childTag14, intermediateViewGroupTag16, 0) // remove child from intermediate + surfaceMountingManager.removeViewAt(intermediateViewGroupTag16, parentTag20, 0) + surfaceMountingManager.deleteView(intermediateViewGroupTag16) + surfaceMountingManager.addViewAt(parentTag20, childTag14, 0) + + // Now, we create a new intermediate child with the same tag as before, to add the child back under it + surfaceMountingManager.removeViewAt(childTag14, parentTag20, 0) + val intermediateChildView2 = createView(intermediateViewGroupTag16) + surfaceMountingManager.addViewAt(parentTag20, intermediateViewGroupTag16, 0) + surfaceMountingManager.addViewAt(intermediateViewGroupTag16, childTag14, 0) + + parentView.endViewTransition(intermediateChildView1) + surfaceMountingManager.markViewInTransition(intermediateViewGroupTag16, false) + assertThat(latch.await(1, java.util.concurrent.TimeUnit.SECONDS)).isTrue() + Shadows.shadowOf(Looper.getMainLooper()).idle() + + // Assert that the newly created intermediate view still exists in SurfaceMountingManager and points to the correct instance + val view = surfaceMountingManager.getView(intermediateViewGroupTag16) + assertThat(view).isEqualTo(intermediateChildView2) + // Interesting observation: the surfaceMountingManager will reuse the old instance! This seems fine. + assertThat(view).isEqualTo(intermediateChildView1) + + // Now remove the intermediate view again and make sure thats works too + surfaceMountingManager.removeViewAt(childTag14, intermediateViewGroupTag16, 0) + surfaceMountingManager.removeViewAt(intermediateViewGroupTag16, parentTag20, 0) + surfaceMountingManager.deleteView(intermediateViewGroupTag16) + assertThat(childView.parent).isNull() + surfaceMountingManager.addViewAt(parentTag20, childTag14, 0) + assertThat(childView.parent).isEqualTo(parentView) + } + + private fun createView(tag: Int): ViewGroup { + val viewType = "RCTView" + val props = Arguments.createMap() + val stateWrapper = null + val eventEmitter = null + + surfaceMountingManager.createView( + viewType, + tag, + props, + stateWrapper, + eventEmitter, + true, + ) + + return surfaceMountingManager.getView(tag) as ViewGroup + } + + + private fun getCoordinator(): ViewTransitionCoordinator { + val coordinatorField = SurfaceMountingManager::class.java.getDeclaredField("mViewTransitionCoordinator") + coordinatorField.isAccessible = true + return coordinatorField.get(surfaceMountingManager) as ViewTransitionCoordinator + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/ViewTransitionCoordinatorTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/ViewTransitionCoordinatorTest.kt new file mode 100644 index 00000000000000..c4391933daa620 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/mounting/ViewTransitionCoordinatorTest.kt @@ -0,0 +1,215 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.fabric.mounting + +import android.view.View +import android.widget.FrameLayout +import com.facebook.react.bridge.BridgeReactContext +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.testutils.shadows.ShadowSoLoader +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(shadows = [ShadowSoLoader::class]) +class ViewTransitionCoordinatorTest { + + private lateinit var coordinator: ViewTransitionCoordinator + private lateinit var mockManager: SurfaceMountingManager + private lateinit var reactContext: ReactApplicationContext + + @Before + fun setup() { + ReactNativeFeatureFlagsForTests.setUp() + reactContext = BridgeReactContext(RuntimeEnvironment.getApplication()) + coordinator = ViewTransitionCoordinator() + mockManager = mock(SurfaceMountingManager::class.java) + } + + @Test + fun testViewInTransition_shouldEnqueueOperations() { + val childTag = 100 + val parentTag = 200 + + // Initially, operations should not be enqueued + assertThat(coordinator.shouldEnqueueOperation(childTag, parentTag)) + .isFalse() + + // Mark view as in transition + coordinator.markViewInTransition( + tag = childTag, + transitioning = true, + view = null, + onDetach = {} + ) + + // Now operations should be enqueued + assertThat(coordinator.shouldEnqueueOperation(childTag, parentTag)) + .isTrue() + } + + @Test + fun testEnqueueAndDrainAddOperation() { + val childTag = 100 + val parentTag = 200 + val index = 0 + + val parentView = FrameLayout(reactContext) + val childView = View(reactContext) + + // Mark view as transitioning + coordinator.markViewInTransition( + tag = childTag, + transitioning = true, + view = null, + onDetach = {} + ) + + // Create and enqueue an add operation + val operation = AddViewOperation( + parentTag = parentTag, + childTag = childTag, + index = index, + parent = parentView, + child = childView + ) + + coordinator.enqueueOperation(operation) + + // Operation should be queued + assertThat(coordinator.shouldEnqueueOperation(childTag, parentTag)) + .isTrue() + + // Mark view as not transitioning anymore + coordinator.markViewInTransition( + tag = childTag, + transitioning = false, + view = null, + onDetach = { + // This would normally be called when view detaches + coordinator.drainOperationsForChild(childTag, mockManager) + } + ) + } + + @Test + fun testQueueMaintainsOrderForParent() { + val parent1Tag = 200 + val parent2Tag = 300 + val childTag = 100 + + val parentView1 = FrameLayout(reactContext) + val parentView2 = FrameLayout(reactContext) + val childView = View(reactContext) + + // Mark child as transitioning + coordinator.markViewInTransition( + tag = childTag, + transitioning = true, + view = null, + onDetach = {} + ) + + // Enqueue operations to different parents + val op1 = AddViewOperation( + childTag = childTag, + parentTag = parent1Tag, + index = 0, + parent = parentView1, + child = childView + ) + + val op2 = AddViewOperation( + childTag = childTag, + parentTag = parent2Tag, + index = 0, + parent = parentView2, + child = childView + ) + + coordinator.enqueueOperation(op1) + coordinator.enqueueOperation(op2) + + // Both parents should have queues now + assertThat(coordinator.shouldEnqueueOperation(999, parent1Tag)) + .isTrue() + assertThat(coordinator.shouldEnqueueOperation(999, parent2Tag)) + .isTrue() + + // The first parent should be first in line for the child + assertThat(coordinator.isFirstInLineForChild(childTag, parent1Tag)) + .isTrue() + assertThat(coordinator.isFirstInLineForChild(childTag, parent2Tag)) + .isFalse() + } + + @Test + fun testClearAllPending() { + val childTag = 100 + val parentTag = 200 + + // Mark view as transitioning + coordinator.markViewInTransition( + tag = childTag, + transitioning = true, + view = null, + onDetach = {} + ) + + val operation = AddViewOperation( + childTag = childTag, + parentTag = parentTag, + index = 0, + parent = FrameLayout(reactContext), + child = View(reactContext) + ) + + coordinator.enqueueOperation(operation) + + // Verify queue exists + assertThat(coordinator.shouldEnqueueOperation(childTag, parentTag)) + .isTrue() + + // Clear all pending + coordinator.clearAllPending() + + // Queue should be cleared, and view should not be in transition + assertThat(coordinator.shouldEnqueueOperation(childTag, parentTag)) + .isFalse() + } + + @Test + fun testDeleteQueueShouldNotImpactUnrelatedViews() { + // when a remove+delete is queued for child A under parent P1, + // it should not affect a delete operation for child B under parent P2 + val childATag = 100 + val childBTag = 101 + + + coordinator.markViewInTransition( + tag = childATag, + transitioning = true, + view = null, + onDetach = {} + ) + val deleteOpA = DeleteViewOperation( + reactTag = childATag + ) + coordinator.enqueueOperation(deleteOpA) + + assertThat(coordinator.shouldEnqueueOperation(childBTag, DELETE_VIEW_PARENT_TAG, false)) + .isFalse() + } +} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.kt index e3488696fcc429..2e7ccf0b10d9d9 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/clipboard/ClipboardModuleTest.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.modules.clipboard import android.annotation.SuppressLint diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt index c908d39aae38a6..66c90ef5e12cba 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.modules.deviceinfo import com.facebook.react.bridge.BridgeReactContext diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostDelegateTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostDelegateTest.kt index 0407c0ae127f25..3bbb43909802c5 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostDelegateTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/runtime/ReactHostDelegateTest.kt @@ -7,6 +7,7 @@ package com.facebook.react.runtime +import android.content.Context import com.facebook.react.ReactPackageTurboModuleManagerDelegate import com.facebook.react.bridge.JSBundleLoader import com.facebook.react.common.annotations.UnstableReactNativeAPI @@ -36,12 +37,15 @@ class ReactHostDelegateTest { Mockito.mock(ReactPackageTurboModuleManagerDelegate.Builder::class.java) val hermesInstance: JSRuntimeFactory = Mockito.mock(HermesInstance::class.java) val jsMainModulePathMocked = "mockedJSMainModulePath" + val createReactSurfaceViewCallback: (Context, ReactSurfaceImpl) -> ReactSurfaceView = + { ctx, surfaceImpl -> ReactSurfaceView(ctx, surfaceImpl) } val delegate = DefaultReactHostDelegate( jsMainModulePath = jsMainModulePathMocked, jsBundleLoader = jsBundleLoader, jsRuntimeFactory = hermesInstance, - turboModuleManagerDelegateBuilder = turboModuleManagerDelegateBuilderMock) + turboModuleManagerDelegateBuilder = turboModuleManagerDelegateBuilderMock, + createReactSurfaceViewCallback = createReactSurfaceViewCallback) assertThat(delegate.jsMainModulePath).isEqualTo(jsMainModulePathMocked) } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt index 39aae4363332df..f538b5475ee44a 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/BaseViewManagerTest.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.uimanager import android.view.View.OnFocusChangeListener diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt index e2b2f5865edc67..ac8639e22045a2 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.uimanager import android.view.View diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.kt index 47d10eb489a624..b02a717e09e70d 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropForShadowNodeSetterTest.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.uimanager import com.facebook.react.bridge.BridgeReactContext diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.kt index e1bf44598acae1..6c43884936a905 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.kt @@ -5,6 +5,8 @@ * LICENSE file in the root directory of this source tree. */ +@file:Suppress("DEPRECATION") + package com.facebook.react.uimanager import android.view.View diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt index f0841bf89d07d0..ef97fd84f145fd 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/testutils/fakes/FakeUIManager.kt @@ -120,6 +120,10 @@ class FakeUIManager : UIManager, UIBlockViewResolver { error("Not yet implemented") } + override fun markViewAsInTransition(surfaceId: Int, reactTag: Int, isTransitioning: Boolean) { + error("Not yet implemented") + } + override val performanceCounters: Map? get() = null } diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index f4756ab4af6e5f..28a6fc6e78b9ac 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -32,10 +32,7 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = './' - s.module_name = 'React_Fabric' - end + resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_Fabric") s.dependency "React-jsiexecutor" s.dependency "RCTRequired" @@ -56,6 +53,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.subspec "animations" do |ss| ss.source_files = podspec_sources("react/renderer/animations/**/*.{m,mm,cpp,h}", "react/renderer/animations/**/*.{h}") diff --git a/packages/react-native/ReactCommon/React-FabricComponents.podspec b/packages/react-native/ReactCommon/React-FabricComponents.podspec index 2c9e811738bac4..e0d883ab8ce3e1 100644 --- a/packages/react-native/ReactCommon/React-FabricComponents.podspec +++ b/packages/react-native/ReactCommon/React-FabricComponents.podspec @@ -16,6 +16,10 @@ else source[:tag] = "v#{version}" end +folly_config = get_folly_config() +folly_compiler_flags = folly_config[:compiler_flags] +folly_version = folly_config[:version] +folly_dep_name = folly_config[:dep_name] react_native_path = ".." header_search_path = [ @@ -49,10 +53,7 @@ Pod::Spec.new do |s| "HEADER_SEARCH_PATHS" => header_search_path.join(" "), } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = './' - s.module_name = 'React_FabricComponents' - end + resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_FabricComponents") s.dependency "React-jsiexecutor" s.dependency "RCTRequired" @@ -79,6 +80,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.subspec "components" do |ss| @@ -89,8 +91,12 @@ Pod::Spec.new do |s| end ss.subspec "modal" do |sss| + sss.dependency folly_dep_name, folly_version + sss.compiler_flags = folly_compiler_flags sss.source_files = podspec_sources("react/renderer/components/modal/*.{m,mm,cpp,h}", "react/renderer/components/modal/*.h") - sss.exclude_files = "react/renderer/components/modal/tests" + sss.exclude_files = "react/renderer/components/modal/tests", + "react/renderer/components/modal/platform/android", + "react/renderer/components/modal/platform/cxx" sss.header_dir = "react/renderer/components/modal" end @@ -126,6 +132,14 @@ Pod::Spec.new do |s| sss.header_dir = "react/renderer/components/iostextinput" end + ss.subspec "switch" do |sss| + sss.source_files = podspec_sources( + ["react/renderer/components/switch/iosswitch/**/*.{m,mm,cpp,h}"], + ["react/renderer/components/switch/iosswitch/**/*.h"]) + sss.exclude_files = "react/renderer/components/switch/iosswitch/**/MacOS*.{m,mm,cpp,h}" + sss.header_dir = "react/renderer/components/switch/" + end + ss.subspec "textinput" do |sss| sss.source_files = podspec_sources("react/renderer/components/textinput/*.{m,mm,cpp,h}", "react/renderer/components/textinput/**/*.h") sss.header_dir = "react/renderer/components/textinput" diff --git a/packages/react-native/ReactCommon/React-FabricImage.podspec b/packages/react-native/ReactCommon/React-FabricImage.podspec index b2884cff041db6..588b4c4d104b0d 100644 --- a/packages/react-native/ReactCommon/React-FabricImage.podspec +++ b/packages/react-native/ReactCommon/React-FabricImage.podspec @@ -50,10 +50,7 @@ Pod::Spec.new do |s| "HEADER_SEARCH_PATHS" => header_search_path.join(" ") } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = './' - s.module_name = 'React_FabricImage' - end + resolve_use_frameworks(s, header_mappings_dir: './', module_name: "React_FabricImage") s.dependency "React-jsiexecutor", version s.dependency "RCTRequired", version @@ -78,4 +75,5 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/React-Mapbuffer.podspec b/packages/react-native/ReactCommon/React-Mapbuffer.podspec index bd9af5b75f8f35..6df5afd9f1e221 100644 --- a/packages/react-native/ReactCommon/React-Mapbuffer.podspec +++ b/packages/react-native/ReactCommon/React-Mapbuffer.podspec @@ -32,12 +32,9 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => ["\"$(PODS_TARGET_SRCROOT)\""], "USE_HEADERMAP" => "YES", "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard() } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = './' - s.module_name = 'React_Mapbuffer' - end + resolve_use_frameworks(s, header_mappings_dir: './', module_name: "React_Mapbuffer") add_dependency(s, "React-debug") add_rn_third_party_dependencies(s) - + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/ReactCommon.podspec b/packages/react-native/ReactCommon/ReactCommon.podspec index 345d97686fe6cb..83e81864640d54 100644 --- a/packages/react-native/ReactCommon/ReactCommon.podspec +++ b/packages/react-native/ReactCommon/ReactCommon.podspec @@ -32,11 +32,11 @@ Pod::Spec.new do |s| "DEFINES_MODULE" => "YES", "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "GCC_WARN_PEDANTIC" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = './' - end + + resolve_use_frameworks(s, header_mappings_dir: './') add_rn_third_party_dependencies(s) + add_rncore_dependency(s) # TODO (T48588859): Restructure this target to align with dir structure: "react/nativemodule/..." # Note: Update this only when ready to minimize breaking changes. diff --git a/packages/react-native/ReactCommon/cxxreact/React-cxxreact.podspec b/packages/react-native/ReactCommon/cxxreact/React-cxxreact.podspec index 127dc2cec705e4..d6282664acffca 100644 --- a/packages/react-native/ReactCommon/cxxreact/React-cxxreact.podspec +++ b/packages/react-native/ReactCommon/cxxreact/React-cxxreact.podspec @@ -52,4 +52,5 @@ Pod::Spec.new do |s| end add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/cxxreact/ReactNativeVersion.h b/packages/react-native/ReactCommon/cxxreact/ReactNativeVersion.h index 0c876d971a62c4..1f2c801b003549 100644 --- a/packages/react-native/ReactCommon/cxxreact/ReactNativeVersion.h +++ b/packages/react-native/ReactCommon/cxxreact/ReactNativeVersion.h @@ -12,16 +12,16 @@ #include #include -#define REACT_NATIVE_VERSION_MAJOR 1000 -#define REACT_NATIVE_VERSION_MINOR 0 -#define REACT_NATIVE_VERSION_PATCH 0 +#define REACT_NATIVE_VERSION_MAJOR 0 +#define REACT_NATIVE_VERSION_MINOR 81 +#define REACT_NATIVE_VERSION_PATCH 4 namespace facebook::react { constexpr struct { - int32_t Major = 1000; - int32_t Minor = 0; - int32_t Patch = 0; + int32_t Major = 0; + int32_t Minor = 81; + int32_t Patch = 4; std::string_view Prerelease = ""; } ReactNativeVersion; diff --git a/packages/react-native/ReactCommon/hermes/React-hermes.podspec b/packages/react-native/ReactCommon/hermes/React-hermes.podspec index 687d25201ec61a..3014edde8e8404 100644 --- a/packages/react-native/ReactCommon/hermes/React-hermes.podspec +++ b/packages/react-native/ReactCommon/hermes/React-hermes.podspec @@ -48,4 +48,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"]) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt b/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt index b512daeafd8fcf..4c1d72be560bc2 100644 --- a/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt +++ b/packages/react-native/ReactCommon/hermes/executor/CMakeLists.txt @@ -26,7 +26,7 @@ target_link_libraries(hermes_executor_common ) target_compile_reactnative_options(hermes_executor_common PRIVATE) -if(${CMAKE_BUILD_TYPE} MATCHES Debug) +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options( hermes_executor_common PRIVATE diff --git a/packages/react-native/ReactCommon/hermes/executor/React-jsitracing.podspec b/packages/react-native/ReactCommon/hermes/executor/React-jsitracing.podspec index 9bfa3d1ef78897..9ee322ba4489b8 100644 --- a/packages/react-native/ReactCommon/hermes/executor/React-jsitracing.podspec +++ b/packages/react-native/ReactCommon/hermes/executor/React-jsitracing.podspec @@ -32,10 +32,7 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "GCC_WARN_PEDANTIC" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = './' - s.module_name = 'React_jsitracing' - end + resolve_use_frameworks(s, header_mappings_dir: './', module_name: "React_jsitracing") s.dependency "React-jsi" end diff --git a/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt b/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt index 37dbf64c1876d9..3344bf7c4d8c1d 100644 --- a/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt +++ b/packages/react-native/ReactCommon/hermes/inspector-modern/CMakeLists.txt @@ -17,7 +17,7 @@ add_library(hermes_inspector_modern target_compile_reactnative_options(hermes_inspector_modern PRIVATE) -if(${CMAKE_BUILD_TYPE} MATCHES Debug) +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options( hermes_inspector_modern PRIVATE diff --git a/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec b/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec index 7510bb3db467a8..a5cc33ecc99ac2 100644 --- a/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec +++ b/packages/react-native/ReactCommon/jserrorhandler/React-jserrorhandler.podspec @@ -33,10 +33,8 @@ Pod::Spec.new do |s| "USE_HEADERMAP" => "YES", "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard() } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = '../' - s.module_name = 'React_jserrorhandler' - end + + resolve_use_frameworks(s, header_mappings_dir: '../', module_name: "React_jserrorhandler") s.dependency "React-jsi" s.dependency "React-cxxreact" @@ -49,5 +47,6 @@ Pod::Spec.new do |s| end add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/jsi/React-jsi.podspec b/packages/react-native/ReactCommon/jsi/React-jsi.podspec index 45a8697a1d6b86..60e18530c97a82 100644 --- a/packages/react-native/ReactCommon/jsi/React-jsi.podspec +++ b/packages/react-native/ReactCommon/jsi/React-jsi.podspec @@ -46,4 +46,5 @@ Pod::Spec.new do |s| s.exclude_files = files_to_exclude add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/jsiexecutor/React-jsiexecutor.podspec b/packages/react-native/ReactCommon/jsiexecutor/React-jsiexecutor.podspec index 6059f09ef1e976..9b127209819a52 100644 --- a/packages/react-native/ReactCommon/jsiexecutor/React-jsiexecutor.podspec +++ b/packages/react-native/ReactCommon/jsiexecutor/React-jsiexecutor.podspec @@ -41,4 +41,5 @@ Pod::Spec.new do |s| end add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/jsinspector-modern/CMakeLists.txt b/packages/react-native/ReactCommon/jsinspector-modern/CMakeLists.txt index 67168982baaa63..bb3a461c8a1c20 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/CMakeLists.txt +++ b/packages/react-native/ReactCommon/jsinspector-modern/CMakeLists.txt @@ -27,7 +27,9 @@ target_link_libraries(jsinspector runtimeexecutor ) target_compile_reactnative_options(jsinspector PRIVATE) -target_compile_options(jsinspector PRIVATE - $<$:-DREACT_NATIVE_DEBUGGER_ENABLED=1> - $<$:-DREACT_NATIVE_DEBUGGER_ENABLED_DEVONLY=1> -) +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) + target_compile_options(jsinspector PRIVATE + -DREACT_NATIVE_DEBUGGER_ENABLED=1 + -DREACT_NATIVE_DEBUGGER_ENABLED_DEVONLY=1 + ) +endif () diff --git a/packages/react-native/ReactCommon/jsinspector-modern/ForwardingConsoleMethods.def b/packages/react-native/ReactCommon/jsinspector-modern/ForwardingConsoleMethods.h similarity index 100% rename from packages/react-native/ReactCommon/jsinspector-modern/ForwardingConsoleMethods.def rename to packages/react-native/ReactCommon/jsinspector-modern/ForwardingConsoleMethods.h diff --git a/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec b/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec index dc6c8c37cf1c4a..78d77ffa539afb 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/React-jsinspector.podspec @@ -44,10 +44,9 @@ Pod::Spec.new do |s| "PUBLIC_HEADERS_FOLDER_PATH" => "#{module_name}.framework/Headers/#{header_dir}" } : {}) - if ENV['USE_FRAMEWORKS'] - s.module_name = module_name - end + resolve_use_frameworks(s, module_name: module_name) + add_dependency(s, "React-oscompat") # Needed for USE_FRAMEWORKS=dynamic s.dependency "React-featureflags" add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"]) s.dependency "React-jsi" @@ -60,4 +59,5 @@ Pod::Spec.new do |s| end add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp index c5994609bd11f5..6db1845a00af66 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp +++ b/packages/react-native/ReactCommon/jsinspector-modern/RuntimeTargetConsole.cpp @@ -322,7 +322,7 @@ void consoleAssert( runtime, \ {timestampMs, type, std::move(argsVec), std::move(stackTrace)}); \ } -#include "ForwardingConsoleMethods.def" +#include "ForwardingConsoleMethods.h" #undef FORWARDING_CONSOLE_METHOD /* @@ -599,7 +599,7 @@ void RuntimeTarget::installConsoleHandler() { // Install forwarding console methods. #define FORWARDING_CONSOLE_METHOD(name, type) \ installConsoleMethod(#name, console_##name); -#include "ForwardingConsoleMethods.def" +#include "ForwardingConsoleMethods.h" #undef FORWARDING_CONSOLE_METHOD runtime.global().setProperty(runtime, "console", console); diff --git a/packages/react-native/ReactCommon/jsinspector-modern/cdp/React-jsinspectorcdp.podspec b/packages/react-native/ReactCommon/jsinspector-modern/cdp/React-jsinspectorcdp.podspec index 2de4f6afd63de4..cff367fda21ea8 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/cdp/React-jsinspectorcdp.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/cdp/React-jsinspectorcdp.podspec @@ -41,10 +41,8 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "DEFINES_MODULE" => "YES"} - if ENV['USE_FRAMEWORKS'] - s.module_name = module_name - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: module_name) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec b/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec index 91dbb1d6ce91a8..d17a80a0b00ef6 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/network/React-jsinspectornetwork.podspec @@ -41,10 +41,7 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "DEFINES_MODULE" => "YES"} - if ENV['USE_FRAMEWORKS'] - s.module_name = module_name - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: module_name) add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp') add_dependency(s, "React-featureflags") @@ -52,4 +49,5 @@ Pod::Spec.new do |s| s.dependency "React-timing" add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec b/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec index 63efbdaf465004..8e3c2d61b93472 100644 --- a/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec +++ b/packages/react-native/ReactCommon/jsinspector-modern/tracing/React-jsinspectortracing.podspec @@ -41,13 +41,11 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "DEFINES_MODULE" => "YES"} - if ENV['USE_FRAMEWORKS'] - s.module_name = module_name - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: module_name) s.dependency "React-oscompat" s.dependency "React-timing" add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/jsitooling/React-jsitooling.podspec b/packages/react-native/ReactCommon/jsitooling/React-jsitooling.podspec index e6eda1f7e44119..4b73c020dcee77 100644 --- a/packages/react-native/ReactCommon/jsitooling/React-jsitooling.podspec +++ b/packages/react-native/ReactCommon/jsitooling/React-jsitooling.podspec @@ -28,10 +28,7 @@ Pod::Spec.new do |s| s.source_files = podspec_sources("react/runtime/*.{cpp,h}", "react/runtime/*.h") s.header_dir = "react/runtime" - if ENV['USE_FRAMEWORKS'] - s.module_name = "JSITooling" - s.header_mappings_dir = "./" - end + resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "JSITooling") s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), @@ -46,4 +43,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing') add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/logger/React-logger.podspec b/packages/react-native/ReactCommon/logger/React-logger.podspec index cb3cbba45bc326..d83e10202c6159 100644 --- a/packages/react-native/ReactCommon/logger/React-logger.podspec +++ b/packages/react-native/ReactCommon/logger/React-logger.podspec @@ -30,4 +30,5 @@ Pod::Spec.new do |s| s.header_dir = "logger" add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/debug/CMakeLists.txt b/packages/react-native/ReactCommon/react/debug/CMakeLists.txt index 35e81ec635034b..448f163308c616 100644 --- a/packages/react-native/ReactCommon/react/debug/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/debug/CMakeLists.txt @@ -21,6 +21,6 @@ endif() target_compile_reactnative_options(react_debug PRIVATE) target_compile_options(react_debug PRIVATE -Wpedantic) -if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug) +if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug AND NOT REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options(react_debug PUBLIC -DNDEBUG) endif() diff --git a/packages/react-native/ReactCommon/react/debug/React-debug.podspec b/packages/react-native/ReactCommon/react/debug/React-debug.podspec index ea0932fa40c475..7b61bc7d0be430 100644 --- a/packages/react-native/ReactCommon/react/debug/React-debug.podspec +++ b/packages/react-native/ReactCommon/react/debug/React-debug.podspec @@ -30,8 +30,5 @@ Pod::Spec.new do |s| s.pod_target_xcconfig = { "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_debug" - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_debug") end diff --git a/packages/react-native/ReactCommon/react/featureflags/React-featureflags.podspec b/packages/react-native/ReactCommon/react/featureflags/React-featureflags.podspec index c1a55996c9284b..2741779951788a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/React-featureflags.podspec +++ b/packages/react-native/ReactCommon/react/featureflags/React-featureflags.podspec @@ -37,10 +37,8 @@ Pod::Spec.new do |s| "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_featureflags" - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_featureflags") add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 3d5ae99d1fbc58..dcb49bb5c99dd2 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<20c25bf5541e37cd5c918684925726df>> + * @generated SignedSource<<8ad0f05f80746e940f1a328cc740b74e>> */ /** @@ -214,6 +214,10 @@ double ReactNativeFeatureFlags::preparedTextCacheSize() { return getAccessor().preparedTextCacheSize(); } +bool ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion() { + return getAccessor().preventShadowTreeCommitExhaustion(); +} + bool ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid() { return getAccessor().traceTurboModulePromiseRejectionsOnAndroid(); } @@ -230,6 +234,14 @@ bool ReactNativeFeatureFlags::useFabricInterop() { return getAccessor().useFabricInterop(); } +bool ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid() { + return getAccessor().useNativeEqualsInNativeReadableArrayAndroid(); +} + +bool ReactNativeFeatureFlags::useNativeTransformHelperAndroid() { + return getAccessor().useNativeTransformHelperAndroid(); +} + bool ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode() { return getAccessor().useNativeViewConfigsInBridgelessMode(); } @@ -258,6 +270,10 @@ double ReactNativeFeatureFlags::virtualViewPrerenderRatio() { return getAccessor().virtualViewPrerenderRatio(); } +bool ReactNativeFeatureFlags::runtimeCrashUiThreadUtils() { + return getAccessor().runtimeCrashUiThreadUtils(); +} + void ReactNativeFeatureFlags::override( std::unique_ptr provider) { getAccessor().override(std::move(provider)); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 632a9878a1b62b..01b5220576a6da 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<20809734183aa7bfd7aad9b8d01ea080>> + * @generated SignedSource<<785cec50c339623ac36278c7eb2fb960>> */ /** @@ -274,6 +274,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static double preparedTextCacheSize(); + /** + * Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again. + */ + RN_EXPORT static bool preventShadowTreeCommitExhaustion(); + /** * Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause. */ @@ -294,6 +299,16 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool useFabricInterop(); + /** + * Use a native implementation of equals in NativeReadableArray. + */ + RN_EXPORT static bool useNativeEqualsInNativeReadableArrayAndroid(); + + /** + * Use a native implementation of TransformHelper + */ + RN_EXPORT static bool useNativeTransformHelperAndroid(); + /** * When enabled, the native view configs are used in bridgeless mode. */ @@ -329,6 +344,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static double virtualViewPrerenderRatio(); + /** + * Instead of logging a soft exception crash the app in UiThreadUtils. + */ + RN_EXPORT static bool runtimeCrashUiThreadUtils(); + /** * Overrides the feature flags with the ones provided by the given provider * (generally one that extends `ReactNativeFeatureFlagsDefaults`). diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index cc991214e13d79..6e3309301acd81 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<59ec29e038344c52eaa10845efc5240b>> + * @generated SignedSource<<0b1c7aa30d0a6ac67cb7d9fbec1f4782>> */ /** @@ -875,6 +875,24 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { + auto flagValue = preventShadowTreeCommitExhaustion_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(47, "preventShadowTreeCommitExhaustion"); + + flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); + preventShadowTreeCommitExhaustion_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid() { auto flagValue = traceTurboModulePromiseRejectionsOnAndroid_.load(); @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(48, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(49, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(50, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "useFabricInterop"); + markFlagAsAccessed(51, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -947,6 +965,42 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroid() { + auto flagValue = useNativeEqualsInNativeReadableArrayAndroid_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(52, "useNativeEqualsInNativeReadableArrayAndroid"); + + flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid(); + useNativeEqualsInNativeReadableArrayAndroid_ = flagValue; + } + + return flagValue.value(); +} + +bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() { + auto flagValue = useNativeTransformHelperAndroid_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(53, "useNativeTransformHelperAndroid"); + + flagValue = currentProvider_->useNativeTransformHelperAndroid(); + useNativeTransformHelperAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { auto flagValue = useNativeViewConfigsInBridgelessMode_.load(); @@ -956,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(54, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -974,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "useOptimizedEventBatchingOnAndroid"); + markFlagAsAccessed(55, "useOptimizedEventBatchingOnAndroid"); flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid(); useOptimizedEventBatchingOnAndroid_ = flagValue; @@ -992,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "useRawPropsJsiValue"); + markFlagAsAccessed(56, "useRawPropsJsiValue"); flagValue = currentProvider_->useRawPropsJsiValue(); useRawPropsJsiValue_ = flagValue; @@ -1010,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "useShadowNodeStateOnClone"); + markFlagAsAccessed(57, "useShadowNodeStateOnClone"); flagValue = currentProvider_->useShadowNodeStateOnClone(); useShadowNodeStateOnClone_ = flagValue; @@ -1028,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "useTurboModuleInterop"); + markFlagAsAccessed(58, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1046,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "useTurboModules"); + markFlagAsAccessed(59, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1064,7 +1118,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "virtualViewPrerenderRatio"); + markFlagAsAccessed(60, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; @@ -1073,6 +1127,24 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::runtimeCrashUiThreadUtils() { + auto flagValue = runtimeCrashUiThreadUtils_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(61, "runtimeCrashUiThreadUtils"); + + flagValue = currentProvider_->runtimeCrashUiThreadUtils(); + runtimeCrashUiThreadUtils_ = flagValue; + } + + return flagValue.value(); +} + void ReactNativeFeatureFlagsAccessor::override( std::unique_ptr provider) { if (wasOverridden_) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 8ee25afabd3726..f17a0683c46f72 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<043e1a56e7a302fbca38151b5d079616>> + * @generated SignedSource<<9390960564d09a73a63256c0d70c0fe3>> */ /** @@ -79,10 +79,13 @@ class ReactNativeFeatureFlagsAccessor { bool fuseboxNetworkInspectionEnabled(); bool hideOffscreenVirtualViewsOnIOS(); double preparedTextCacheSize(); + bool preventShadowTreeCommitExhaustion(); bool traceTurboModulePromiseRejectionsOnAndroid(); bool updateRuntimeShadowNodeReferencesOnCommit(); bool useAlwaysAvailableJSErrorHandling(); bool useFabricInterop(); + bool useNativeEqualsInNativeReadableArrayAndroid(); + bool useNativeTransformHelperAndroid(); bool useNativeViewConfigsInBridgelessMode(); bool useOptimizedEventBatchingOnAndroid(); bool useRawPropsJsiValue(); @@ -90,6 +93,7 @@ class ReactNativeFeatureFlagsAccessor { bool useTurboModuleInterop(); bool useTurboModules(); double virtualViewPrerenderRatio(); + bool runtimeCrashUiThreadUtils(); void override(std::unique_ptr provider); std::optional getAccessedFeatureFlagNames() const; @@ -101,7 +105,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 58> accessedFeatureFlags_; + std::array, 62> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> animatedShouldSignalBatch_; @@ -150,10 +154,13 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fuseboxNetworkInspectionEnabled_; std::atomic> hideOffscreenVirtualViewsOnIOS_; std::atomic> preparedTextCacheSize_; + std::atomic> preventShadowTreeCommitExhaustion_; std::atomic> traceTurboModulePromiseRejectionsOnAndroid_; std::atomic> updateRuntimeShadowNodeReferencesOnCommit_; std::atomic> useAlwaysAvailableJSErrorHandling_; std::atomic> useFabricInterop_; + std::atomic> useNativeEqualsInNativeReadableArrayAndroid_; + std::atomic> useNativeTransformHelperAndroid_; std::atomic> useNativeViewConfigsInBridgelessMode_; std::atomic> useOptimizedEventBatchingOnAndroid_; std::atomic> useRawPropsJsiValue_; @@ -161,6 +168,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> useTurboModuleInterop_; std::atomic> useTurboModules_; std::atomic> virtualViewPrerenderRatio_; + std::atomic> runtimeCrashUiThreadUtils_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index f80b0b991138da..c2a0452c8f791c 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7b5caffd8f748384aa32ed6e153ee9c1>> + * @generated SignedSource<<2a54f4c7b1e548f6efec9dbf69628964>> */ /** @@ -48,7 +48,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { } bool disableMountItemReorderingAndroid() override { - return false; + return true; } bool disableTextLayoutManagerCacheAndroid() override { @@ -215,6 +215,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return 200.0; } + bool preventShadowTreeCommitExhaustion() override { + return false; + } + bool traceTurboModulePromiseRejectionsOnAndroid() override { return false; } @@ -231,6 +235,14 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return true; } + bool useNativeEqualsInNativeReadableArrayAndroid() override { + return false; + } + + bool useNativeTransformHelperAndroid() override { + return false; + } + bool useNativeViewConfigsInBridgelessMode() override { return false; } @@ -258,6 +270,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { double virtualViewPrerenderRatio() override { return 5.0; } + + bool runtimeCrashUiThreadUtils() override { + return false; + } }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 2ee1c5dd3835c0..13158df63c45e7 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -468,6 +468,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::preparedTextCacheSize(); } + bool preventShadowTreeCommitExhaustion() override { + auto value = values_["preventShadowTreeCommitExhaustion"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::preventShadowTreeCommitExhaustion(); + } + bool traceTurboModulePromiseRejectionsOnAndroid() override { auto value = values_["traceTurboModulePromiseRejectionsOnAndroid"]; if (!value.isNull()) { @@ -504,6 +513,24 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::useFabricInterop(); } + bool useNativeEqualsInNativeReadableArrayAndroid() override { + auto value = values_["useNativeEqualsInNativeReadableArrayAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::useNativeEqualsInNativeReadableArrayAndroid(); + } + + bool useNativeTransformHelperAndroid() override { + auto value = values_["useNativeTransformHelperAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::useNativeTransformHelperAndroid(); + } + bool useNativeViewConfigsInBridgelessMode() override { auto value = values_["useNativeViewConfigsInBridgelessMode"]; if (!value.isNull()) { @@ -566,6 +593,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::virtualViewPrerenderRatio(); } + + bool runtimeCrashUiThreadUtils() override { + auto value = values_["runtimeCrashUiThreadUtils"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::runtimeCrashUiThreadUtils(); + } }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h index 765468e4ee8977..230dc476cefea9 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<1de02178e1be302bb4b19501950b260a>> + * @generated SignedSource<> */ /** @@ -27,7 +27,17 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur public: ReactNativeFeatureFlagsOverridesOSSExperimental() = default; + bool preventShadowTreeCommitExhaustion() override { + return true; + } + bool useNativeEqualsInNativeReadableArrayAndroid() override { + return true; + } + + bool useNativeTransformHelperAndroid() override { + return true; + } }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 2f0a127f62e26e..3bff20ec4a611c 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -72,10 +72,13 @@ class ReactNativeFeatureFlagsProvider { virtual bool fuseboxNetworkInspectionEnabled() = 0; virtual bool hideOffscreenVirtualViewsOnIOS() = 0; virtual double preparedTextCacheSize() = 0; + virtual bool preventShadowTreeCommitExhaustion() = 0; virtual bool traceTurboModulePromiseRejectionsOnAndroid() = 0; virtual bool updateRuntimeShadowNodeReferencesOnCommit() = 0; virtual bool useAlwaysAvailableJSErrorHandling() = 0; virtual bool useFabricInterop() = 0; + virtual bool useNativeEqualsInNativeReadableArrayAndroid() = 0; + virtual bool useNativeTransformHelperAndroid() = 0; virtual bool useNativeViewConfigsInBridgelessMode() = 0; virtual bool useOptimizedEventBatchingOnAndroid() = 0; virtual bool useRawPropsJsiValue() = 0; @@ -83,6 +86,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool useTurboModuleInterop() = 0; virtual bool useTurboModules() = 0; virtual double virtualViewPrerenderRatio() = 0; + virtual bool runtimeCrashUiThreadUtils() = 0; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp index 0478f532243a55..13d6b48d013377 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp @@ -463,7 +463,9 @@ jsi::Value convertFromJMapToValue(JNIEnv* env, jsi::Runtime& rt, jobject arg) { */ jsi::JSError convertThrowableToJSError( jsi::Runtime& runtime, - jni::alias_ref throwable) { + jni::alias_ref throwable, + const std::string& functionName = "", + const std::string& moduleName = "") { auto stackTrace = throwable->getStackTrace(); jsi::Array stackElements(runtime, stackTrace->size()); @@ -486,8 +488,21 @@ jsi::JSError convertThrowableToJSError( cause.setProperty(runtime, "message", message); cause.setProperty(runtime, "stackElements", std::move(stackElements)); - jsi::Value error = - createJSRuntimeError(runtime, "Exception in HostFunction: " + message); + std::string errorMessage = "Exception in HostFunction: " + message; + if (!functionName.empty() || !moduleName.empty()) { + errorMessage += " ["; + if (!moduleName.empty()) { + errorMessage += "Module: " + moduleName; + if (!functionName.empty()) { + errorMessage += ", "; + } + } + if (!functionName.empty()) { + errorMessage += "Function: " + functionName; + } + errorMessage += "]"; + } + jsi::Value error = createJSRuntimeError(runtime, errorMessage); error.asObject(runtime).setProperty(runtime, "cause", std::move(cause)); return {runtime, std::move(error)}; } @@ -578,7 +593,7 @@ jsi::Value JavaTurboModule::invokeJavaMethod( } auto exception = std::current_exception(); auto throwable = jni::getJavaExceptionForCppException(exception); - throw convertThrowableToJSError(runtime, throwable); + throw convertThrowableToJSError(runtime, throwable, methodNameStr, name_); } }; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/React-NativeModulesApple.podspec b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/React-NativeModulesApple.podspec index edc219a3d51644..089f80ea3c987c 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/React-NativeModulesApple.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/React-NativeModulesApple.podspec @@ -31,9 +31,8 @@ Pod::Spec.new do |s| "USE_HEADERMAP" => "YES", "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "GCC_WARN_PEDANTIC" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = './' - end + + resolve_use_frameworks(s, header_mappings_dir: './') s.source_files = podspec_sources("ReactCommon/**/*.{mm,cpp,h}", "ReactCommon/**/*.{h}") @@ -51,4 +50,5 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec index 8641c8d067944c..88cfdc7f30caa5 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/defaults/React-defaultsnativemodule.podspec @@ -38,15 +38,13 @@ Pod::Spec.new do |s| "OTHER_CFLAGS" => "$(inherited)", "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_defaultsnativemodule" - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_defaultsnativemodule") s.dependency "React-jsi" s.dependency "React-jsiexecutor" depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.dependency "React-domnativemodule" s.dependency "React-featureflagsnativemodule" diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/React-domnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/dom/React-domnativemodule.podspec index f7b860087b766a..6119ccb41057e9 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/React-domnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/React-domnativemodule.podspec @@ -40,16 +40,14 @@ Pod::Spec.new do |s| "OTHER_CFLAGS" => "$(inherited)", "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_domnativemodule" - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_domnativemodule") s.dependency "React-jsi" s.dependency "React-jsiexecutor" depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.dependency "Yoga" s.dependency "ReactCommon/turbomodule/core" diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 3819fbf4579d5c..49741ba03ce32e 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<838c1a240c704205ec9874459e8aad7c>> */ /** @@ -279,6 +279,11 @@ double NativeReactNativeFeatureFlags::preparedTextCacheSize( return ReactNativeFeatureFlags::preparedTextCacheSize(); } +bool NativeReactNativeFeatureFlags::preventShadowTreeCommitExhaustion( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion(); +} + bool NativeReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid(); @@ -299,6 +304,16 @@ bool NativeReactNativeFeatureFlags::useFabricInterop( return ReactNativeFeatureFlags::useFabricInterop(); } +bool NativeReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid(); +} + +bool NativeReactNativeFeatureFlags::useNativeTransformHelperAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::useNativeTransformHelperAndroid(); +} + bool NativeReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode(); @@ -334,4 +349,9 @@ double NativeReactNativeFeatureFlags::virtualViewPrerenderRatio( return ReactNativeFeatureFlags::virtualViewPrerenderRatio(); } +bool NativeReactNativeFeatureFlags::runtimeCrashUiThreadUtils( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::runtimeCrashUiThreadUtils(); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index f34e0c2707159c..968fc0b929ede8 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<45145b6c6a668a32a803275425582d0f>> */ /** @@ -130,6 +130,8 @@ class NativeReactNativeFeatureFlags double preparedTextCacheSize(jsi::Runtime& runtime); + bool preventShadowTreeCommitExhaustion(jsi::Runtime& runtime); + bool traceTurboModulePromiseRejectionsOnAndroid(jsi::Runtime& runtime); bool updateRuntimeShadowNodeReferencesOnCommit(jsi::Runtime& runtime); @@ -138,6 +140,10 @@ class NativeReactNativeFeatureFlags bool useFabricInterop(jsi::Runtime& runtime); + bool useNativeEqualsInNativeReadableArrayAndroid(jsi::Runtime& runtime); + + bool useNativeTransformHelperAndroid(jsi::Runtime& runtime); + bool useNativeViewConfigsInBridgelessMode(jsi::Runtime& runtime); bool useOptimizedEventBatchingOnAndroid(jsi::Runtime& runtime); @@ -151,6 +157,8 @@ class NativeReactNativeFeatureFlags bool useTurboModules(jsi::Runtime& runtime); double virtualViewPrerenderRatio(jsi::Runtime& runtime); + + bool runtimeCrashUiThreadUtils(jsi::Runtime& runtime); }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/React-featureflagsnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/featureflags/React-featureflagsnativemodule.podspec index 3579b4b32a9814..6d4dafcc7076c0 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/React-featureflagsnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/React-featureflagsnativemodule.podspec @@ -38,16 +38,14 @@ Pod::Spec.new do |s| "OTHER_CFLAGS" => "$(inherited)", "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_featureflagsnativemodule" - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_featureflagsnativemodule") s.dependency "React-jsi" s.dependency "React-jsiexecutor" depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.dependency "ReactCommon/turbomodule/core" s.dependency "React-RCTFBReactNativeSpec" diff --git a/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/React-idlecallbacksnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/React-idlecallbacksnativemodule.podspec index 222f33f67605d7..46327b2c74d648 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/React-idlecallbacksnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/idlecallbacks/React-idlecallbacksnativemodule.podspec @@ -38,16 +38,14 @@ Pod::Spec.new do |s| "OTHER_CFLAGS" => "$(inherited)", "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "idlecallbacksnativemodule" - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "idlecallbacksnativemodule") s.dependency "React-jsi" s.dependency "React-jsiexecutor" depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.dependency "ReactCommon/turbomodule/core" s.dependency "React-runtimescheduler" diff --git a/packages/react-native/ReactCommon/react/nativemodule/microtasks/React-microtasksnativemodule.podspec b/packages/react-native/ReactCommon/react/nativemodule/microtasks/React-microtasksnativemodule.podspec index 4bfaae3f62eb69..a41e3d3a3526e4 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/microtasks/React-microtasksnativemodule.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/microtasks/React-microtasksnativemodule.podspec @@ -38,16 +38,14 @@ Pod::Spec.new do |s| "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_microtasksnativemodule" - s.header_mappings_dir = "../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_microtasksnativemodule") s.dependency "React-jsi" s.dependency "React-jsiexecutor" depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.dependency "ReactCommon/turbomodule/core" add_dependency(s, "React-RCTFBReactNativeSpec") diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/ReactCommon-Samples.podspec b/packages/react-native/ReactCommon/react/nativemodule/samples/ReactCommon-Samples.podspec index 305ab3ac1beb1f..051ee3fc86c33f 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/ReactCommon-Samples.podspec +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/ReactCommon-Samples.podspec @@ -40,6 +40,8 @@ Pod::Spec.new do |s| s.framework = "UIKit" if ENV['USE_FRAMEWORKS'] + # Do not use resolve_use_frameworks here - since we're including source files. + # Then it is not needed. s.header_mappings_dir = './' end @@ -57,4 +59,5 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp index 02787bc1e5e1f0..e3fd453d55901d 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/ReactCommon/SampleTurboModuleSpec.cpp @@ -379,7 +379,7 @@ NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI( std::make_shared>(); eventEmitterMap_["onSubmit"] = std::make_shared>(); - configureEventEmitterCallback(); +// configureEventEmitterCallback(); } std::shared_ptr SampleTurboModuleSpec_ModuleProvider( diff --git a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec index 0c4e9748752cf3..6e190bbfe6d9c6 100644 --- a/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec +++ b/packages/react-native/ReactCommon/react/performance/timeline/React-performancetimeline.podspec @@ -38,10 +38,7 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "HEADER_SEARCH_PATHS" => header_search_paths.join(' ')} - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_performancetimeline" - s.header_mappings_dir = "../../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_performancetimeline") s.dependency "React-featureflags" add_dependency(s, "React-jsinspectortracing", :framework_name => 'jsinspector_moderntracing') @@ -49,4 +46,5 @@ Pod::Spec.new do |s| s.dependency "React-perflogger" add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp index 2982e5923693b1..1dbb48147632d0 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.cpp @@ -27,6 +27,18 @@ void TextAttributes::apply(TextAttributes textAttributes) { : backgroundColor; opacity = !std::isnan(textAttributes.opacity) ? textAttributes.opacity : opacity; + gradientColors = textAttributes.gradientColors.has_value() + ? textAttributes.gradientColors + : gradientColors; + gradientAngle = !std::isnan(textAttributes.gradientAngle) + ? textAttributes.gradientAngle + : gradientAngle; + gradientWidth = !std::isnan(textAttributes.gradientWidth) + ? textAttributes.gradientWidth + : gradientWidth; + gradientMode = textAttributes.gradientMode.has_value() + ? textAttributes.gradientMode + : gradientMode; // Font fontFamily = !textAttributes.fontFamily.empty() ? textAttributes.fontFamily @@ -97,6 +109,14 @@ void TextAttributes::apply(TextAttributes textAttributes) { ? textAttributes.textShadowColor : textShadowColor; + // Stroke + textStrokeWidth = !std::isnan(textAttributes.textStrokeWidth) + ? textAttributes.textStrokeWidth + : textStrokeWidth; + textStrokeColor = textAttributes.textStrokeColor + ? textAttributes.textStrokeColor + : textStrokeColor; + // Special isHighlighted = textAttributes.isHighlighted.has_value() ? textAttributes.isHighlighted @@ -136,6 +156,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const { textDecorationStyle, textShadowOffset, textShadowColor, + textStrokeColor, isHighlighted, isPressable, layoutDirection, @@ -159,6 +180,7 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const { rhs.textDecorationStyle, rhs.textShadowOffset, rhs.textShadowColor, + rhs.textStrokeColor, rhs.isHighlighted, rhs.isPressable, rhs.layoutDirection, @@ -171,7 +193,12 @@ bool TextAttributes::operator==(const TextAttributes& rhs) const { floatEquality(fontSizeMultiplier, rhs.fontSizeMultiplier) && floatEquality(letterSpacing, rhs.letterSpacing) && floatEquality(lineHeight, rhs.lineHeight) && - floatEquality(textShadowRadius, rhs.textShadowRadius); + floatEquality(textShadowRadius, rhs.textShadowRadius) && + floatEquality(textStrokeWidth, rhs.textStrokeWidth) && + floatEquality(gradientAngle, rhs.gradientAngle) && + floatEquality(gradientWidth, rhs.gradientWidth) && + gradientColors == rhs.gradientColors && + gradientMode == rhs.gradientMode; } TextAttributes TextAttributes::defaultTextAttributes() { diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h index 8a7e26c57b66dd..805b8ba26cb1ff 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/TextAttributes.h @@ -36,12 +36,16 @@ class TextAttributes : public DebugStringConvertible { */ static TextAttributes defaultTextAttributes(); -#pragma mark - Fields +#pragma mark - Fields // Color SharedColor foregroundColor{}; SharedColor backgroundColor{}; Float opacity{std::numeric_limits::quiet_NaN()}; + std::optional> gradientColors{}; + Float gradientAngle{std::numeric_limits::quiet_NaN()}; + Float gradientWidth{std::numeric_limits::quiet_NaN()}; + std::optional gradientMode{}; // "mirror" or "clamp" // Font std::string fontFamily{""}; @@ -74,6 +78,10 @@ class TextAttributes : public DebugStringConvertible { Float textShadowRadius{std::numeric_limits::quiet_NaN()}; SharedColor textShadowColor{}; + // Stroke + Float textStrokeWidth{std::numeric_limits::quiet_NaN()}; + SharedColor textStrokeColor{}; + // Special std::optional isHighlighted{}; std::optional isPressable{}; @@ -134,6 +142,11 @@ struct hash { textAttributes.textShadowOffset, textAttributes.textShadowRadius, textAttributes.textShadowColor, + textAttributes.textStrokeWidth, + textAttributes.textStrokeColor, + textAttributes.gradientAngle, + textAttributes.gradientWidth, + textAttributes.gradientMode, textAttributes.isHighlighted, textAttributes.isPressable, textAttributes.layoutDirection, diff --git a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h index 639e44c3443599..4dc7b954389ffc 100644 --- a/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +++ b/packages/react-native/ReactCommon/react/renderer/attributedstring/conversions.h @@ -1055,6 +1055,12 @@ constexpr static MapBuffer::Key TA_KEY_ROLE = 26; constexpr static MapBuffer::Key TA_KEY_TEXT_TRANSFORM = 27; constexpr static MapBuffer::Key TA_KEY_ALIGNMENT_VERTICAL = 28; constexpr static MapBuffer::Key TA_KEY_MAX_FONT_SIZE_MULTIPLIER = 29; +constexpr static MapBuffer::Key TA_KEY_GRADIENT_COLORS = 30; +constexpr static MapBuffer::Key TA_KEY_TEXT_STROKE_WIDTH = 31; +constexpr static MapBuffer::Key TA_KEY_TEXT_STROKE_COLOR = 32; +constexpr static MapBuffer::Key TA_KEY_GRADIENT_ANGLE = 33; +constexpr static MapBuffer::Key TA_KEY_GRADIENT_WIDTH = 34; +constexpr static MapBuffer::Key TA_KEY_GRADIENT_MODE = 35; // constants for ParagraphAttributes serialization constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0; @@ -1128,6 +1134,22 @@ inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) { builder.putInt( TA_KEY_BACKGROUND_COLOR, toAndroidRepr(textAttributes.backgroundColor)); } + if (textAttributes.gradientColors.has_value()) { + auto gradientColorsBuilder = MapBufferBuilder(); + for (size_t i = 0; i < textAttributes.gradientColors->size(); ++i) { + gradientColorsBuilder.putInt(static_cast(i), toAndroidRepr((*textAttributes.gradientColors)[i])); + } + builder.putMapBuffer(TA_KEY_GRADIENT_COLORS, gradientColorsBuilder.build()); + } + if (!std::isnan(textAttributes.gradientAngle)) { + builder.putDouble(TA_KEY_GRADIENT_ANGLE, textAttributes.gradientAngle); + } + if (!std::isnan(textAttributes.gradientWidth)) { + builder.putDouble(TA_KEY_GRADIENT_WIDTH, textAttributes.gradientWidth); + } + if (textAttributes.gradientMode.has_value()) { + builder.putString(TA_KEY_GRADIENT_MODE, *textAttributes.gradientMode); + } if (!std::isnan(textAttributes.opacity)) { builder.putDouble(TA_KEY_OPACITY, textAttributes.opacity); } @@ -1216,6 +1238,16 @@ inline MapBuffer toMapBuffer(const TextAttributes& textAttributes) { builder.putDouble( TA_KEY_TEXT_SHADOW_OFFSET_DY, textAttributes.textShadowOffset->height); } + // Stroke + if (!std::isnan(textAttributes.textStrokeWidth)) { + builder.putDouble( + TA_KEY_TEXT_STROKE_WIDTH, textAttributes.textStrokeWidth); + } + if (textAttributes.textStrokeColor) { + builder.putInt( + TA_KEY_TEXT_STROKE_COLOR, + toAndroidRepr(textAttributes.textStrokeColor)); + } // Special if (textAttributes.isHighlighted.has_value()) { builder.putBool(TA_KEY_IS_HIGHLIGHTED, *textAttributes.isHighlighted); diff --git a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorProviderRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorProviderRegistry.cpp index 0a949f0fb081ce..fc1db6b78f1706 100644 --- a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorProviderRegistry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorProviderRegistry.cpp @@ -9,6 +9,8 @@ namespace facebook::react { +static int8_t isBridgeless = -1; + void ComponentDescriptorProviderRegistry::add( const ComponentDescriptorProvider& provider) const { std::unique_lock lock(mutex_); @@ -68,11 +70,33 @@ ComponentDescriptorProviderRegistry::createComponentDescriptorRegistry( const ComponentDescriptorParameters& parameters) const { std::shared_lock lock(mutex_); + if (isBridgeless == -1) { + isBridgeless = ReactNativeFeatureFlags::enableBridgelessArchitecture() ? 1 : 0; + } + auto registry = std::make_shared( parameters, *this, parameters.contextContainer); + if (!isBridgeless) { + // Eagerly add all + for (const auto& pair : componentDescriptorProviders_) { + registry->add(pair.second); + } + } + + std::vector providers; + providers.reserve(componentDescriptorProviders_.size()); for (const auto& pair : componentDescriptorProviders_) { - registry->add(pair.second); + if (isBridgeless) { + providers.push_back(pair.second); + } else { + registry->add(pair.second); + } + } + + if (isBridgeless) { + // 2) enqueue async add + registry->addMultipleAsync(std::move(providers)); } componentDescriptorRegistries_.push_back(registry); diff --git a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp index 469fc5c23357c6..73724c38a28bf6 100644 --- a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp +++ b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.cpp @@ -17,6 +17,12 @@ #include #include #include +#include + +#ifdef __ANDROID__ +#include +#endif + namespace facebook::react { @@ -28,6 +34,41 @@ ComponentDescriptorRegistry::ComponentDescriptorRegistry( providerRegistry_(providerRegistry), contextContainer_(std::move(contextContainer)) {} +void ComponentDescriptorRegistry::addMultipleAsync( + std::vector providers) const { + // Copy everything we need before the thread starts + auto parametersCopy = parameters_; + auto contextContainerCopy = contextContainer_; + + auto self = shared_from_this(); + + // Start detached thread - registry stays alive until completion due to strong self reference + std::thread([self, providers = std::move(providers), parametersCopy, contextContainerCopy]() { + // Ensure this C++ thread is attached to the JVM before touching JNI + #ifdef __ANDROID__ + facebook::jni::Environment::ensureCurrentThreadIsAttached(); + #endif + + std::unique_lock lock(self->mutex_); + + for (const auto& provider : providers) { + auto componentDescriptor = provider.constructor( + {parametersCopy.eventDispatcher, + contextContainerCopy, + provider.flavor}); + + react_native_assert(componentDescriptor->getComponentHandle() == provider.handle); + react_native_assert(componentDescriptor->getComponentName() == provider.name); + + auto sharedComponentDescriptor = + std::shared_ptr(std::move(componentDescriptor)); + + self->_registryByHandle[provider.handle] = sharedComponentDescriptor; + self->_registryByName[provider.name] = sharedComponentDescriptor; + } + }).detach(); +} + void ComponentDescriptorRegistry::add( ComponentDescriptorProvider componentDescriptorProvider) const { std::unique_lock lock(mutex_); @@ -50,6 +91,7 @@ void ComponentDescriptorRegistry::add( _registryByName[componentDescriptorProvider.name] = sharedComponentDescriptor; } +// Callers must hold the mutex void ComponentDescriptorRegistry::registerComponentDescriptor( const SharedComponentDescriptor& componentDescriptor) const { ComponentHandle componentHandle = componentDescriptor->getComponentHandle(); @@ -85,12 +127,15 @@ const ComponentDescriptor& ComponentDescriptorRegistry::at( if (it == _registryByName.end()) { if (ReactNativeFeatureFlags::useFabricInterop()) { + lock.unlock(); // When interop is enabled, if the component is not found we rely on // UnstableLegacyViewManagerAutomaticComponentDescriptor to support legacy // components in new architecture. auto componentDescriptor = std::make_shared< const UnstableLegacyViewManagerAutomaticComponentDescriptor>( parameters_, unifiedComponentName); + + std::unique_lock writeLock(mutex_); registerComponentDescriptor(componentDescriptor); return *_registryByName.find(unifiedComponentName)->second; } else { @@ -143,6 +188,7 @@ bool ComponentDescriptorRegistry::hasComponentDescriptorAt( void ComponentDescriptorRegistry::setFallbackComponentDescriptor( const SharedComponentDescriptor& descriptor) { + std::unique_lock lock(mutex_); _fallbackComponentDescriptor = descriptor; registerComponentDescriptor(descriptor); } diff --git a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.h b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.h index 20bce41f2dcf47..5ac03785f483db 100644 --- a/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.h +++ b/packages/react-native/ReactCommon/react/renderer/componentregistry/ComponentDescriptorRegistry.h @@ -27,7 +27,7 @@ using SharedComponentDescriptorRegistry = /* * Registry of particular `ComponentDescriptor`s. */ -class ComponentDescriptorRegistry { +class ComponentDescriptorRegistry : public std::enable_shared_from_this { public: using Shared = std::shared_ptr; @@ -40,6 +40,8 @@ class ComponentDescriptorRegistry { const ComponentDescriptorProviderRegistry& providerRegistry, ContextContainer::Shared contextContainer); + void addMultipleAsync(std::vector providers) const; + /* * This is broken. Please do not use. * If you requesting a ComponentDescriptor and unsure that it's there, you are diff --git a/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/AppleSwitchComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/AppleSwitchComponentDescriptor.h new file mode 100644 index 00000000000000..de4cef29aaa36b --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/AppleSwitchComponentDescriptor.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include "AppleSwitchShadowNode.h" + +#include + +namespace facebook::react { + +/* + * Descriptor for component. + */ +class SwitchComponentDescriptor final + : public ConcreteComponentDescriptor { + public: + SwitchComponentDescriptor(const ComponentDescriptorParameters& parameters) + : ConcreteComponentDescriptor(parameters) {} + + void adopt(ShadowNode& shadowNode) const override { + ConcreteComponentDescriptor::adopt(shadowNode); + } +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/AppleSwitchShadowNode.h b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/AppleSwitchShadowNode.h new file mode 100644 index 00000000000000..2cffdf139f5d6f --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/AppleSwitchShadowNode.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +extern const char AppleSwitchComponentName[]; + +/* + * `ShadowNode` for component. + */ +class SwitchShadowNode final : public ConcreteViewShadowNode< + AppleSwitchComponentName, + SwitchProps, + SwitchEventEmitter> { + public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + + static ShadowNodeTraits BaseTraits() { + auto traits = ConcreteViewShadowNode::BaseTraits(); + traits.set(ShadowNodeTraits::Trait::LeafYogaNode); + traits.set(ShadowNodeTraits::Trait::MeasurableYogaNode); + return traits; + } + +#pragma mark - LayoutableShadowNode + + Size measureContent( + const LayoutContext& layoutContext, + const LayoutConstraints& layoutConstraints) const override; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm new file mode 100644 index 00000000000000..d1981f17c06266 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/IOSSwitchShadowNode.mm @@ -0,0 +1,28 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import +#include "AppleSwitchShadowNode.h" + +namespace facebook::react { + +extern const char AppleSwitchComponentName[] = "Switch"; + +#pragma mark - LayoutableShadowNode + +Size SwitchShadowNode::measureContent( + const LayoutContext & /*layoutContext*/, + const LayoutConstraints & /*layoutConstraints*/) const +{ + CGSize uiSwitchSize = RCTSwitchSize(); + // Apple has some error when returning the width of the component and it doesn't + // account for the borders. + return {.width = uiSwitchSize.width + 2, .height = uiSwitchSize.height}; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm new file mode 100644 index 00000000000000..cd62f626ba68bb --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/switch/iosswitch/react/renderer/components/switch/MacOSSwitchShadowNode.mm @@ -0,0 +1,32 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#include "AppleSwitchShadowNode.h" + +namespace facebook::react { + +extern const char AppleSwitchComponentName[] = "Switch"; + +#pragma mark - LayoutableShadowNode + +Size SwitchShadowNode::measureContent( + const LayoutContext & /*layoutContext*/, + const LayoutConstraints & /*layoutConstraints*/) const +{ + static CGSize nsSwitchSize = CGSizeZero; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dispatch_sync(dispatch_get_main_queue(), ^{ + nsSwitchSize = [NSSwitch new].intrinsicContentSize; + }); + }); + + return {.width = nsSwitchSize.width, .height = nsSwitchSize.height}; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp index 01bbdbccdf4aae..ee1f1a8327aefb 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/text/BaseTextProps.cpp @@ -30,6 +30,30 @@ static TextAttributes convertRawProp( "color", sourceTextAttributes.foregroundColor, defaultTextAttributes.foregroundColor); + textAttributes.gradientColors = convertRawProp( + context, + rawProps, + "gradientColors", + sourceTextAttributes.gradientColors, + defaultTextAttributes.gradientColors); + textAttributes.gradientAngle = convertRawProp( + context, + rawProps, + "gradientAngle", + sourceTextAttributes.gradientAngle, + defaultTextAttributes.gradientAngle); + textAttributes.gradientWidth = convertRawProp( + context, + rawProps, + "gradientWidth", + sourceTextAttributes.gradientWidth, + defaultTextAttributes.gradientWidth); + textAttributes.gradientMode = convertRawProp( + context, + rawProps, + "gradientMode", + sourceTextAttributes.gradientMode, + defaultTextAttributes.gradientMode); // Font textAttributes.fontFamily = convertRawProp( @@ -171,6 +195,20 @@ static TextAttributes convertRawProp( sourceTextAttributes.textShadowColor, defaultTextAttributes.textShadowColor); + // Stroke + textAttributes.textStrokeWidth = convertRawProp( + context, + rawProps, + "textStrokeWidth", + sourceTextAttributes.textStrokeWidth, + defaultTextAttributes.textStrokeWidth); + textAttributes.textStrokeColor = convertRawProp( + context, + rawProps, + "textStrokeColor", + sourceTextAttributes.textStrokeColor, + defaultTextAttributes.textStrokeColor); + // Special textAttributes.isHighlighted = convertRawProp( context, @@ -237,7 +275,7 @@ BaseTextProps::BaseTextProps( context, rawProps, sourceProps.textAttributes, - TextAttributes{})){}; + TextAttributes{})){} void BaseTextProps::setProp( const PropsParserContext& context, @@ -249,6 +287,14 @@ void BaseTextProps::setProp( switch (hash) { REBUILD_FIELD_SWITCH_CASE( defaults, value, textAttributes, foregroundColor, "color"); + REBUILD_FIELD_SWITCH_CASE( + defaults, value, textAttributes, gradientColors, "gradientColors"); + REBUILD_FIELD_SWITCH_CASE( + defaults, value, textAttributes, gradientAngle, "gradientAngle"); + REBUILD_FIELD_SWITCH_CASE( + defaults, value, textAttributes, gradientWidth, "gradientWidth"); + REBUILD_FIELD_SWITCH_CASE( + defaults, value, textAttributes, gradientMode, "gradientMode"); REBUILD_FIELD_SWITCH_CASE( defaults, value, textAttributes, fontFamily, "fontFamily"); REBUILD_FIELD_SWITCH_CASE( diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.h index 150f3e5fd55072..5802350a2ee9ff 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/BaseTextInputProps.h @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace facebook::react { @@ -60,7 +61,7 @@ class BaseTextInputProps : public ViewProps, public BaseTextProps { // TODO: Rename to `tintColor` and make universal. SharedColor underlineColorAndroid{}; - int maxLength{}; + int maxLength = std::numeric_limits::max(); /* * "Private" (only used by TextInput.js) props diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp index 690aedc35011f9..5bf3c6e4179ef5 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.cpp @@ -550,6 +550,13 @@ BorderMetrics BaseViewProps::resolveBorderMetrics( Transform BaseViewProps::resolveTransform( const LayoutMetrics& layoutMetrics) const { const auto& frameSize = layoutMetrics.frame.size; + return resolveTransform(frameSize, transform, transformOrigin); +} + +Transform BaseViewProps::resolveTransform( + const Size& frameSize, + const Transform& transform, + const TransformOrigin& transformOrigin) { auto transformMatrix = Transform{}; if (frameSize.width == 0 && frameSize.height == 0) { return transformMatrix; @@ -562,8 +569,7 @@ Transform BaseViewProps::resolveTransform( } else { for (const auto& operation : transform.operations) { transformMatrix = transformMatrix * - Transform::FromTransformOperation( - operation, layoutMetrics.frame.size, transform); + Transform::FromTransformOperation(operation, frameSize, transform); } } diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h index f7e56418ef0b6c..6258d1375dae93 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/view/BaseViewProps.h @@ -115,6 +115,11 @@ class BaseViewProps : public YogaStylableProps, public AccessibilityProps { Transform resolveTransform(const LayoutMetrics& layoutMetrics) const; bool getClipsContentToBounds() const; + static Transform resolveTransform( + const Size& frameSize, + const Transform& transform, + const TransformOrigin& transformOrigin); + #if RN_DEBUG_STRING_CONVERTIBLE SharedDebugStringConvertibleList getDebugProps() const override; #endif diff --git a/packages/react-native/ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec b/packages/react-native/ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec index 3dd85feeb2c2bc..216fd443ad3cf8 100644 --- a/packages/react-native/ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec +++ b/packages/react-native/ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec @@ -38,8 +38,6 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "HEADER_SEARCH_PATHS" => header_search_paths.join(' ')} - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_rendererconsistency" - s.header_mappings_dir = "../../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_rendererconsistency") + end diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeTraits.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeTraits.h index ef60eba2ad147f..a6896b846783c4 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeTraits.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNodeTraits.h @@ -82,6 +82,15 @@ class ShadowNodeTraits { // Must not be set directly. It is used by the view culling algorithm to // efficiently determine if a node is uncullable. Unstable_uncullableTrace = 1 << 13, + + // Indicates that the `YogaLayoutableShadowNode` must set `isDirty` flag for + // Yoga node when a `ShadowNode` is being cloned. `ShadowNode`s that modify + // Yoga styles in the constructor (or later) *after* the `ShadowNode` + // is cloned must set this trait. + // Any Yoga node (not only Leaf ones) can have this trait. + // **Deprecated**: This trait is deprecated and will be removed in a future + // version of React Native. + DirtyYogaNode = 1 << 14, }; /* diff --git a/packages/react-native/ReactCommon/react/renderer/css/React-renderercss.podspec b/packages/react-native/ReactCommon/react/renderer/css/React-renderercss.podspec index ecd07167cd3ee6..c16a9a875b27cb 100644 --- a/packages/react-native/ReactCommon/react/renderer/css/React-renderercss.podspec +++ b/packages/react-native/ReactCommon/react/renderer/css/React-renderercss.podspec @@ -40,10 +40,7 @@ Pod::Spec.new do |s| "DEFINES_MODULE" => "YES", } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_renderercss" - s.header_mappings_dir = "../../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_renderercss") add_dependency(s, "React-debug") add_dependency(s, "React-utils") diff --git a/packages/react-native/ReactCommon/react/renderer/debug/React-rendererdebug.podspec b/packages/react-native/ReactCommon/react/renderer/debug/React-rendererdebug.podspec index 7f0216450e24ab..41df93a4f64a2c 100644 --- a/packages/react-native/ReactCommon/react/renderer/debug/React-rendererdebug.podspec +++ b/packages/react-native/ReactCommon/react/renderer/debug/React-rendererdebug.podspec @@ -40,11 +40,9 @@ Pod::Spec.new do |s| "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_rendererdebug" - s.header_mappings_dir = "../../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_rendererdebug") add_dependency(s, "React-debug") add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec b/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec index 6f351f597c4d1a..87e7cb21045c62 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec +++ b/packages/react-native/ReactCommon/react/renderer/graphics/React-graphics.podspec @@ -35,11 +35,11 @@ Pod::Spec.new do |s| s.framework = "UIKit" if ENV['USE_FRAMEWORKS'] - s.module_name = "React_graphics" - s.header_mappings_dir = "../../.." header_search_paths = header_search_paths + ["\"$(PODS_TARGET_SRCROOT)/platform/ios\""] end + resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_graphics") + s.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", "HEADER_SEARCH_PATHS" => header_search_paths.join(" "), "DEFINES_MODULE" => "YES", @@ -51,4 +51,5 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec index 412da4335dcb8e..50862be6838776 100644 --- a/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec +++ b/packages/react-native/ReactCommon/react/renderer/imagemanager/platform/ios/React-ImageManager.podspec @@ -34,10 +34,7 @@ Pod::Spec.new do |s| s.source_files = podspec_sources(source_files, "**/*.h") s.header_dir = "react/renderer/imagemanager" - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_ImageManager" - s.header_mappings_dir = "./" - end + resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_ImageManager") s.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", @@ -55,4 +52,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-rendererdebug") add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/MountingCoordinator.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/MountingCoordinator.cpp index 2f6627e1edec4f..73fff70aa66ac6 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/MountingCoordinator.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/MountingCoordinator.cpp @@ -15,8 +15,8 @@ #include #include "updateMountedFlag.h" -#ifdef RN_SHADOW_TREE_INTROSPECTION #include +#ifdef RN_SHADOW_TREE_INTROSPECTION #include #endif diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp index e91e643ccd22ec..b617a981683ca7 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.cpp @@ -19,9 +19,24 @@ #include "updateMountedFlag.h" #include "ShadowTreeDelegate.h" +#include + +// Discord - Wrap with ScopeGuard for a function to run on the end of the scope: +#include +struct ScopeGuard { + std::function fn; + ~ScopeGuard() { fn(); } +}; +#define CONCAT_IMPL(x, y) x##y +#define CONCAT(x, y) CONCAT_IMPL(x, y) +#define defer(code) ScopeGuard CONCAT(defer, LINE)([&](){code;}) namespace facebook::react { +namespace { +const int MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING = 3; +} // namespace + using CommitStatus = ShadowTree::CommitStatus; using CommitMode = ShadowTree::CommitMode; @@ -210,7 +225,8 @@ void ShadowTree::setCommitMode(CommitMode commitMode) const { auto revision = ShadowTreeRevision{}; { - std::unique_lock lock(commitMutex_); + ShadowTree::UniqueLock lock = uniqueCommitLock(); + if (commitMode_ == commitMode) { return; } @@ -227,7 +243,7 @@ void ShadowTree::setCommitMode(CommitMode commitMode) const { } CommitMode ShadowTree::getCommitMode() const { - std::shared_lock lock(commitMutex_); + SharedLock lock = sharedCommitLock(); return commitMode_; } @@ -241,17 +257,32 @@ CommitStatus ShadowTree::commit( const CommitOptions& commitOptions) const { [[maybe_unused]] int attempts = 0; - while (true) { - attempts++; + if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) { + while (attempts < MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING) { + auto status = tryCommit(transaction, commitOptions); + if (status != CommitStatus::Failed) { + return status; + } + attempts++; + } - auto status = tryCommit(transaction, commitOptions); - if (status != CommitStatus::Failed) { - return status; + { + std::unique_lock lock(commitMutexRecursive_); + return tryCommit(transaction, commitOptions); } + } else { + while (true) { + attempts++; + + auto status = tryCommit(transaction, commitOptions); + if (status != CommitStatus::Failed) { + return status; + } - // After multiple attempts, we failed to commit the transaction. - // Something internally went terribly wrong. - react_native_assert(attempts < 1024); + // After multiple attempts, we failed to commit the transaction. + // Something internally went terribly wrong. + react_native_assert(attempts < 1024); + } } } @@ -269,7 +300,7 @@ CommitStatus ShadowTree::tryCommit( { // Reading `currentRevision_` in shared manner. - std::shared_lock lock(commitMutex_); + SharedLock lock = sharedCommitLock(); commitMode = commitMode_; oldRevision = currentRevision_; } @@ -295,6 +326,7 @@ CommitStatus ShadowTree::tryCommit( *this, oldRootShadowNode, newRootShadowNode, commitOptions); if (!newRootShadowNode) { + delegate_.shadowTreeCommitFinalized(commitOptions); return CommitStatus::Cancelled; } @@ -310,7 +342,8 @@ CommitStatus ShadowTree::tryCommit( { // Updating `currentRevision_` in unique manner if it hasn't changed. - std::unique_lock lock(commitMutex_); + UniqueLock lock = uniqueCommitLock(); + defer(delegate_.shadowTreeCommitFinalized(commitOptions)); if (currentRevision_.number != oldRevision.number) { return CommitStatus::Failed; @@ -318,6 +351,8 @@ CommitStatus ShadowTree::tryCommit( auto newRevisionNumber = currentRevision_.number + 1; + delegate_.shadowTreeCommitSucceeded(commitOptions); + { std::scoped_lock dispatchLock(EventEmitter::DispatchMutex()); updateMountedFlag( @@ -349,7 +384,7 @@ CommitStatus ShadowTree::tryCommit( } ShadowTreeRevision ShadowTree::getCurrentRevision() const { - std::shared_lock lock(commitMutex_); + SharedLock lock = sharedCommitLock(); return currentRevision_; } @@ -397,4 +432,20 @@ void ShadowTree::notifyDelegatesOfUpdates() const { delegate_.shadowTreeDidFinishTransaction(mountingCoordinator_, true); } +inline ShadowTree::UniqueLock ShadowTree::uniqueCommitLock() const { + if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) { + return std::unique_lock{commitMutexRecursive_}; + } else { + return std::unique_lock{commitMutex_}; + } +} + +inline ShadowTree::SharedLock ShadowTree::sharedCommitLock() const { + if (ReactNativeFeatureFlags::preventShadowTreeCommitExhaustion()) { + return std::unique_lock{commitMutexRecursive_}; + } else { + return std::shared_lock{commitMutex_}; + } +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h index 87b6a7afa47df8..7e92bd3109dfd5 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h @@ -8,6 +8,8 @@ #pragma once #include +#include +#include #include #include @@ -149,11 +151,23 @@ class ShadowTree final { const SurfaceId surfaceId_; const ShadowTreeDelegate& delegate_; + mutable std::shared_mutex mountMutex_; mutable std::shared_mutex commitMutex_; + mutable std::recursive_mutex commitMutexRecursive_; mutable CommitMode commitMode_{ CommitMode::Normal}; // Protected by `commitMutex_`. mutable ShadowTreeRevision currentRevision_; // Protected by `commitMutex_`. std::shared_ptr mountingCoordinator_; + + using UniqueLock = std::variant< + std::unique_lock, + std::unique_lock>; + using SharedLock = std::variant< + std::shared_lock, + std::unique_lock>; + + inline UniqueLock uniqueCommitLock() const; + inline SharedLock sharedCommitLock() const; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h index 14402722045ec0..ed73511d4a8d6d 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTreeDelegate.h @@ -38,6 +38,11 @@ class ShadowTreeDelegate { std::shared_ptr mountingCoordinator, bool mountSynchronously) const = 0; + // Called after a commit is known to succeed, however, still under the commit lock + virtual void shadowTreeCommitSucceeded(const ShadowTreeCommitOptions& commitOptions) const = 0; + // Will be called in each case once we are done with the current commit attempt + virtual void shadowTreeCommitFinalized(const ShadowTreeCommitOptions& commitOptions) const = 0; + virtual ~ShadowTreeDelegate() noexcept = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp index 70d762b516c9fc..05060a23f07789 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/tests/StateReconciliationTest.cpp @@ -37,6 +37,9 @@ class DummyShadowTreeDelegate : public ShadowTreeDelegate { void shadowTreeDidFinishTransaction( std::shared_ptr mountingCoordinator, bool mountSynchronously) const override {}; + + void shadowTreeCommitSucceeded(const ShadowTreeCommitOptions& commitOptions) const override {}; + void shadowTreeCommitFinalized(const ShadowTreeCommitOptions& commitOptions) const override {}; }; namespace { diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec index cfeafdc923e64f..56823e61d623cd 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec @@ -38,10 +38,7 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "HEADER_SEARCH_PATHS" => header_search_paths.join(' ')} - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_runtimescheduler" - s.header_mappings_dir = "../../.." - end + resolve_use_frameworks(s, header_mappings_dir: "../../..", module_name: "React_runtimescheduler") add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"]) s.dependency "React-callinvoker" @@ -58,4 +55,5 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp index e8d9b4359ceaa4..bd25d702aa7314 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace facebook::react { @@ -25,6 +26,40 @@ RuntimeScheduler_Legacy::RuntimeScheduler_Legacy( now_(std::move(now)), onTaskError_(std::move(onTaskError)) {} +void RuntimeScheduler_Legacy::performMicrotaskCheckpoint(jsi::Runtime& runtime) noexcept { + if (performingMicrotaskCheckpoint_) { + return; + } + performingMicrotaskCheckpoint_ = true; + OnScopeExit restoreFlag([&]() { performingMicrotaskCheckpoint_ = false; }); + TraceSection s("RuntimeScheduler::performMicrotaskCheckpoint"); + + try { + // Get the global object + auto global = runtime.global(); + + // Check if _flushReactNativeMicrotasks exists on the global object + if (global.hasProperty(runtime, "_flushReactNativeMicrotasks")) { + auto flushFunction = global.getProperty(runtime, "_flushReactNativeMicrotasks"); + + // Check if it's actually a function + if (flushFunction.isObject() && flushFunction.getObject(runtime).isFunction(runtime)) { + auto function = flushFunction.getObject(runtime).getFunction(runtime); + + // Call the function with no arguments + function.call(runtime); + } + } + } catch (jsi::JSError& error) { + // If there's an error calling the function, handle it gracefully + onTaskError_(runtime, error); + } catch (std::exception& ex) { + // Handle any other exceptions + jsi::JSError error(runtime, std::string("Non-js exception in performMicrotaskCheckpoint: ") + ex.what()); + onTaskError_(runtime, error); + } +} + void RuntimeScheduler_Legacy::scheduleWork(RawCallback&& callback) noexcept { TraceSection s("RuntimeScheduler::scheduleWork"); @@ -240,6 +275,7 @@ void RuntimeScheduler_Legacy::startWorkLoop(jsi::Runtime& runtime) { } executeTask(runtime, topPriorityTask, didUserCallbackTimeout); + performMicrotaskCheckpoint(runtime); } } catch (jsi::JSError& error) { onTaskError_(runtime, error); diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h index 3856a0645ba557..397786bc1fef91 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h @@ -189,6 +189,14 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase { shadowTreeRevisionConsistencyManager_{nullptr}; RuntimeSchedulerTaskErrorHandler onTaskError_; + + /* + * This allows the C++ code to trigger microtask flushing from JavaScript. + */ + void performMicrotaskCheckpoint(jsi::Runtime& runtime) noexcept; + + private: + bool performingMicrotaskCheckpoint_{false}; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 49cd7d574c51da..ef42aa4f4ee92c 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -299,6 +299,13 @@ void Scheduler::uiManagerDidSendAccessibilityEvent( } } +void Scheduler::uiManagerMeasure(const std::shared_ptr& shadowNode, std::function jsCallback) { + if (delegate_ != nullptr) { + auto shadowView = ShadowView(*shadowNode); + delegate_->schedulerMeasure(shadowView, jsCallback); + } +}; + /* * Set JS responder for a view. */ diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h index e25f755ecd7838..20fc3211bb2ca8 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -104,6 +104,7 @@ class Scheduler final : public UIManagerDelegate { void uiManagerShouldRemoveEventListener( const std::shared_ptr& listener) final; void uiManagerDidStartSurface(const ShadowTree& shadowTree) override; + void uiManagerMeasure(const std::shared_ptr& shadowNode, std::function jsCallback) override; #pragma mark - ContextContainer ContextContainer::Shared getContextContainer() const; diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h index 0a88e2433937a8..c9dc06dfd87f4c 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/SchedulerDelegate.h @@ -70,6 +70,7 @@ class SchedulerDelegate { virtual void schedulerDidUpdateShadowTree( const std::unordered_map& tagToProps) = 0; + virtual void schedulerMeasure(const ShadowView& shadowView, std::function jsCallback) = 0; virtual ~SchedulerDelegate() noexcept = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm index 39093efef09c36..189b596210314e 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTFontUtils.mm @@ -332,8 +332,23 @@ static UIFontDescriptorSystemDesign RCTGetFontDescriptorSystemDesign(NSString *f fontNames = [UIFont fontNamesForFamilyName:font.familyName]; fontWeight = fontWeight ?: RCTGetFontWeight(font); } else { - // Failback to system font. - font = [UIFont systemFontOfSize:effectiveFontSize weight:fontProperties.weight]; + // Check if font string is a list of fonts comma separated + NSArray *rawFontFamilies = [fontProperties.family componentsSeparatedByString:@","]; + if (rawFontFamilies.count >= 1) { + NSArray *updatedFontNames = fontNames; + + for (NSString *name in rawFontFamilies) { + UIFont *font = [UIFont fontWithName:name size:effectiveFontSize]; + if (font) { + updatedFontNames = [updatedFontNames arrayByAddingObject:font.fontName]; + } + } + + fontNames = updatedFontNames; + } else { + // Failback to system font. + font = [UIFont systemFontOfSize:effectiveFontSize weight:fontProperties.weight]; + } } } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/AppRegistryBinding.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/AppRegistryBinding.cpp index 2a40e37149bb97..91f8922b850f10 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/AppRegistryBinding.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/AppRegistryBinding.cpp @@ -72,6 +72,11 @@ namespace facebook::react { jsi::Runtime& runtime, SurfaceId surfaceId) { auto global = runtime.global(); + if (!global.hasProperty(runtime, "RN$stopSurface")) { + // ReactFabric module has not been loaded yet; there's no surface to stop. + return; + } + auto stopFunction = global.getProperty(runtime, "RN$stopSurface"); if (!stopFunction.isObject() || !stopFunction.asObject(runtime).isFunction(runtime)) { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index edee8ac36bca40..b47f224b81895c 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -626,6 +626,22 @@ RootShadowNode::Unshared UIManager::shadowTreeWillCommit( return resultRootShadowNode; } +void UIManager::shadowTreeCommitSucceeded(const ShadowTreeCommitOptions& commitOptions) const { + std::shared_lock lock(commitHookMutex_); + + for (auto* commitHook : commitHooks_) { + commitHook->shadowTreeCommitSucceeded(commitOptions); + } +} + +void UIManager::shadowTreeCommitFinalized(const ShadowTreeCommitOptions& commitOptions) const { + std::shared_lock lock(commitHookMutex_); + + for (auto* commitHook : commitHooks_) { + commitHook->shadowTreeCommitFinalized(commitOptions); + } +} + void UIManager::shadowTreeDidFinishTransaction( std::shared_ptr mountingCoordinator, bool mountSynchronously) const { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index 43de6ef06f4863..5b794fcb458d9b 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -135,6 +135,9 @@ class UIManager final : public ShadowTreeDelegate { const RootShadowNode::Unshared& newRootShadowNode, const ShadowTree::CommitOptions& commitOptions) const override; + void shadowTreeCommitSucceeded(const ShadowTreeCommitOptions& commitOptions) const override; + void shadowTreeCommitFinalized(const ShadowTreeCommitOptions& commitOptions) const override; + std::shared_ptr createNode( Tag tag, const std::string& componentName, diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 9ab7416ffeeac0..757370899660e2 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -443,6 +444,7 @@ jsi::Value UIManagerBinding::get( } if (methodName == "completeRoot") { + using clock = std::chrono::steady_clock; auto paramCount = 2; return jsi::Function::createFromHostFunction( runtime, @@ -460,11 +462,15 @@ jsi::Value UIManagerBinding::get( auto surfaceId = surfaceIdFromValue(runtime, arguments[0]); auto shadowNodeList = shadowNodeListFromValue(runtime, arguments[1]); + + static const clock::time_point t0 = clock::now(); + const bool isWithinFirst5s = (clock::now() - t0) < std::chrono::seconds(5); + uiManager->completeSurface( surfaceId, shadowNodeList, {.enableStateReconciliation = true, - .mountSynchronously = false, + .mountSynchronously = isWithinFirst5s, // make sure we push first render as soon as possible otherwise we observe 200ms delay .source = ShadowTree::CommitSource::React}); return jsi::Value::undefined(); @@ -639,6 +645,7 @@ jsi::Value UIManagerBinding::get( runtime, arguments[0]); auto callbackFunction = arguments[1].getObject(runtime).getFunction(runtime); + auto measureOnUI = count == 2 ? false : arguments[2].getBool(); auto currentRevision = uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( @@ -648,16 +655,37 @@ jsi::Value UIManagerBinding::get( return jsi::Value::undefined(); } - auto measureRect = dom::measure(currentRevision, *shadowNode); + if (measureOnUI) { + auto sharedCallback = std::make_shared(std::move(callbackFunction)); + auto runtimeExecutor = uiManager->runtimeExecutor_; + std::function jsCallback = [sharedCallback, runtimeExecutor](folly::dynamic args) { + // Schedule call on JS + runtimeExecutor([sharedCallback, args](jsi::Runtime& jsRuntime) { + // Invoke the actual callback we got from JS + sharedCallback->call(jsRuntime, { + jsi::Value{jsRuntime, args.at(0).getDouble()}, + jsi::Value{jsRuntime, args.at(1).getDouble()}, + jsi::Value{jsRuntime, args.at(2).getDouble()}, + jsi::Value{jsRuntime, args.at(3).getDouble()}, + jsi::Value{jsRuntime, args.at(4).getDouble()}, + jsi::Value{jsRuntime, args.at(5).getDouble()}, + }); + }); + }; + // Ask the delegate to measure on the native platform hierarchy: + uiManager->getDelegate()->uiManagerMeasure(shadowNode, std::move(jsCallback)); + } else { + auto measureRect = dom::measure(currentRevision, *shadowNode); + callbackFunction.call( + runtime, + {jsi::Value{runtime, measureRect.x}, + jsi::Value{runtime, measureRect.y}, + jsi::Value{runtime, measureRect.width}, + jsi::Value{runtime, measureRect.height}, + jsi::Value{runtime, measureRect.pageX}, + jsi::Value{runtime, measureRect.pageY}}); + } - callbackFunction.call( - runtime, - {jsi::Value{runtime, measureRect.x}, - jsi::Value{runtime, measureRect.y}, - jsi::Value{runtime, measureRect.width}, - jsi::Value{runtime, measureRect.height}, - jsi::Value{runtime, measureRect.pageX}, - jsi::Value{runtime, measureRect.pageY}}); return jsi::Value::undefined(); }); } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h index 2e15c6ca89e595..e01238b853eb70 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerCommitHook.h @@ -54,6 +54,11 @@ class UIManagerCommitHook { return newRootShadowNode; } + // Discord - Called after a commit is known to succeed, however, still under the commit lock + virtual void shadowTreeCommitSucceeded(const ShadowTreeCommitOptions& commitOptions) = 0; + // Discord - Will be called in each case once we are done with the current commit attempt + virtual void shadowTreeCommitFinalized(const ShadowTreeCommitOptions& commitOptions) = 0; + virtual ~UIManagerCommitHook() noexcept = default; }; diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h index e649a273742e22..b2318b3e4f79ff 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerDelegate.h @@ -42,6 +42,8 @@ class UIManagerDelegate { const std::string& commandName, const folly::dynamic& args) = 0; + virtual void uiManagerMeasure(const std::shared_ptr& shadowNode, std::function jsCallback) = 0; + /* * Called when UIManager wants to dispatch some accessibility event * to the mounting layer. eventType is platform-specific and not all diff --git a/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt b/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt index 51ffc864b16fe2..0cf910be5bfda1 100644 --- a/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/runtime/CMakeLists.txt @@ -16,7 +16,9 @@ add_library(bridgeless ${bridgeless_SRC} ) target_compile_reactnative_options(bridgeless PRIVATE) -target_compile_options(bridgeless PRIVATE $<$:-DHERMES_ENABLE_DEBUGGER=1>) +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) + target_compile_options(bridgeless PRIVATE -DHERMES_ENABLE_DEBUGGER=1) +endif () target_include_directories(bridgeless PUBLIC .) react_native_android_selector(fabricjni fabricjni "") diff --git a/packages/react-native/ReactCommon/react/runtime/React-RuntimeCore.podspec b/packages/react-native/ReactCommon/react/runtime/React-RuntimeCore.podspec index b6ea134e8fccd0..24b163f5e4d46d 100644 --- a/packages/react-native/ReactCommon/react/runtime/React-RuntimeCore.podspec +++ b/packages/react-native/ReactCommon/react/runtime/React-RuntimeCore.podspec @@ -33,10 +33,7 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "GCC_WARN_PEDANTIC" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = '../../' - s.module_name = 'React_RuntimeCore' - end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_RuntimeCore") s.dependency "React-jsiexecutor" s.dependency "React-cxxreact" @@ -52,6 +49,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.dependency "React-jsinspector" add_dependency(s, "React-jsitooling", :framework_name => "JSITooling") diff --git a/packages/react-native/ReactCommon/react/runtime/React-RuntimeHermes.podspec b/packages/react-native/ReactCommon/react/runtime/React-RuntimeHermes.podspec index acc74f4e42d19b..ac7d4419db238d 100644 --- a/packages/react-native/ReactCommon/react/runtime/React-RuntimeHermes.podspec +++ b/packages/react-native/ReactCommon/react/runtime/React-RuntimeHermes.podspec @@ -32,10 +32,7 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "GCC_WARN_PEDANTIC" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = '../../' - s.module_name = 'React_RuntimeHermes' - end + resolve_use_frameworks(s, header_mappings_dir: "../../", module_name: "React_RuntimeHermes") s.dependency "React-jsitracing" s.dependency "React-jsi" @@ -52,4 +49,5 @@ Pod::Spec.new do |s| add_dependency(s, "React-jsitooling", :framework_name => "JSITooling") add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt b/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt index d531e870b851b1..dc1a5dd9cab836 100644 --- a/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/runtime/hermes/CMakeLists.txt @@ -29,7 +29,7 @@ target_link_libraries(bridgelesshermes ) target_compile_reactnative_options(bridgelesshermes PRIVATE) -if(${CMAKE_BUILD_TYPE} MATCHES Debug) +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED) target_compile_options( bridgelesshermes PRIVATE diff --git a/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp b/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp index d09f3a7ee3c590..00203fcb686a95 100644 --- a/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp +++ b/packages/react-native/ReactCommon/react/runtime/hermes/HermesInstance.cpp @@ -127,10 +127,12 @@ std::unique_ptr HermesInstance::createJSRuntime( auto gcConfig = ::hermes::vm::GCConfig::Builder() // Default to 3GB + .withInitHeapSize(150ll * 1024 * 1024) + .withShouldReleaseUnused(::hermes::vm::kReleaseUnusedNone) .withMaxHeapSize(3072 << 20) .withName("RNBridgeless"); - if (allocInOldGenBeforeTTI) { + if (true || allocInOldGenBeforeTTI) { // For the next two arguments: avoid GC before TTI // by initializing the runtime to allocate directly // in the old generation, but revert to normal diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec b/packages/react-native/ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec index ed8b6ee38a378a..7c84db0bca95d9 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/React-RuntimeApple.podspec @@ -38,10 +38,7 @@ Pod::Spec.new do |s| "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "GCC_WARN_PEDANTIC" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = './' - s.module_name = 'React_RuntimeApple' - end + resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_RuntimeApple") s.dependency "React-jsiexecutor" s.dependency "React-cxxreact" @@ -71,4 +68,5 @@ Pod::Spec.new do |s| end add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm index 029c4cf79643c3..7d621ac6bf86c3 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTInstance.mm @@ -366,6 +366,7 @@ - (void)_start RCTScreenSize(); RCTScreenScale(); + RCTSwitchSize(); std::lock_guard lock(*mutex); *isReady = true; diff --git a/packages/react-native/ReactCommon/react/timing/React-timing.podspec b/packages/react-native/ReactCommon/react/timing/React-timing.podspec index cb781783c81909..8168f2b46358c6 100644 --- a/packages/react-native/ReactCommon/react/timing/React-timing.podspec +++ b/packages/react-native/ReactCommon/react/timing/React-timing.podspec @@ -37,8 +37,7 @@ Pod::Spec.new do |s| "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), "DEFINES_MODULE" => "YES" } - if ENV['USE_FRAMEWORKS'] - s.module_name = "React_timing" - s.header_mappings_dir = "./" - end + resolve_use_frameworks(s, header_mappings_dir: "./", module_name: "React_timing") + + add_dependency(s, "React-debug") end diff --git a/packages/react-native/ReactCommon/react/utils/React-utils.podspec b/packages/react-native/ReactCommon/react/utils/React-utils.podspec index 2472be4235a578..d3061f63bb06d1 100644 --- a/packages/react-native/ReactCommon/react/utils/React-utils.podspec +++ b/packages/react-native/ReactCommon/react/utils/React-utils.podspec @@ -35,11 +35,11 @@ Pod::Spec.new do |s| s.exclude_files = "tests" if ENV['USE_FRAMEWORKS'] - s.module_name = "React_utils" - s.header_mappings_dir = "../.." header_search_paths = header_search_paths + ["\"$(PODS_TARGET_SRCROOT)/platform/ios\""] end + resolve_use_frameworks(s, header_mappings_dir: "../..", module_name: "React_utils") + s.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), @@ -49,6 +49,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) add_dependency(s, "React-debug") end diff --git a/packages/react-native/ReactCommon/reactperflogger/React-perflogger.podspec b/packages/react-native/ReactCommon/reactperflogger/React-perflogger.podspec index f8f0b70268f8e5..fb32c2a11eaa06 100644 --- a/packages/react-native/ReactCommon/reactperflogger/React-perflogger.podspec +++ b/packages/react-native/ReactCommon/reactperflogger/React-perflogger.podspec @@ -38,4 +38,5 @@ Pod::Spec.new do |s| } add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/react-native/ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec b/packages/react-native/ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec index fc73fed8a1637e..155735ca9d8a3c 100644 --- a/packages/react-native/ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec +++ b/packages/react-native/ReactCommon/runtimeexecutor/React-runtimeexecutor.podspec @@ -34,16 +34,18 @@ Pod::Spec.new do |s| s.header_dir = "ReactCommon" if ENV['USE_FRAMEWORKS'] - s.header_mappings_dir = '.' header_search_paths = header_search_paths + ["\"$(PODS_TARGET_SRCROOT)/platform/ios\""] end + resolve_use_frameworks(s, header_mappings_dir: ".") + s.pod_target_xcconfig = { "USE_HEADERMAP" => "NO", "CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(), "HEADER_SEARCH_PATHS" => header_search_paths.join(' '), "DEFINES_MODULE" => "YES" } add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.dependency "React-jsi", version add_dependency(s, "React-featureflags") diff --git a/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp b/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp index 8c4eaef934dbbb..db7904286d8738 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp @@ -478,16 +478,19 @@ static void zeroOutLayoutRecursively(yoga::Node* const node) { } static void cleanupContentsNodesRecursively(yoga::Node* const node) { - for (auto child : node->getChildren()) { - if (child->style().display() == Display::Contents) { - child->getLayout() = {}; - child->setLayoutDimension(0, Dimension::Width); - child->setLayoutDimension(0, Dimension::Height); - child->setHasNewLayout(true); - child->setDirty(false); - child->cloneChildrenIfNeeded(); - - cleanupContentsNodesRecursively(child); + if (node->hasContentsChildren()) [[unlikely]] { + node->cloneContentsChildrenIfNeeded(); + for (auto child : node->getChildren()) { + if (child->style().display() == Display::Contents) { + child->getLayout() = {}; + child->setLayoutDimension(0, Dimension::Width); + child->setLayoutDimension(0, Dimension::Height); + child->setHasNewLayout(true); + child->setDirty(false); + child->cloneChildrenIfNeeded(); + + cleanupContentsNodesRecursively(child); + } } } } diff --git a/packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp b/packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp index fbdf5c1bff7ac3..d42bd5f9e7072d 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/node/Node.cpp @@ -181,6 +181,17 @@ void Node::setDirty(bool isDirty) { } } +void Node::setChildren(const std::vector& children) { + children_ = children; + + contentsChildrenCount_ = 0; + for (const auto& child : children) { + if (child->style().display() == Display::Contents) { + contentsChildrenCount_++; + } + } +} + bool Node::removeChild(Node* child) { auto p = std::find(children_.begin(), children_.end(), child); if (p != children_.end()) { @@ -380,6 +391,23 @@ void Node::cloneChildrenIfNeeded() { if (child->getOwner() != this) { child = resolveRef(config_->cloneNode(child, this, i)); child->setOwner(this); + + if (child->hasContentsChildren()) [[unlikely]] { + child->cloneContentsChildrenIfNeeded(); + } + } + i += 1; + } +} + +void Node::cloneContentsChildrenIfNeeded() { + size_t i = 0; + for (Node*& child : children_) { + if (child->style().display() == Display::Contents && + child->getOwner() != this) { + child = resolveRef(config_->cloneNode(child, this, i)); + child->setOwner(this); + child->cloneChildrenIfNeeded(); } i += 1; } diff --git a/packages/react-native/ReactCommon/yoga/yoga/node/Node.h b/packages/react-native/ReactCommon/yoga/yoga/node/Node.h index 5ae7d432ebb320..8068c814973110 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/node/Node.h +++ b/packages/react-native/ReactCommon/yoga/yoga/node/Node.h @@ -96,6 +96,10 @@ class YG_EXPORT Node : public ::YGNode { return config_->hasErrata(errata); } + bool hasContentsChildren() const { + return contentsChildrenCount_ != 0; + } + YGDirtiedFunc getDirtiedFunc() const { return dirtiedFunc_; } @@ -244,15 +248,12 @@ class YG_EXPORT Node : public ::YGNode { owner_ = owner; } - void setChildren(const std::vector& children) { - children_ = children; - } - // TODO: rvalue override for setChildren void setConfig(Config* config); void setDirty(bool isDirty); + void setChildren(const std::vector& children); void setLayoutLastOwnerDirection(Direction direction); void setLayoutComputedFlexBasis(FloatOptional computedFlexBasis); void setLayoutComputedFlexBasisGeneration( @@ -286,6 +287,7 @@ class YG_EXPORT Node : public ::YGNode { void removeChild(size_t index); void cloneChildrenIfNeeded(); + void cloneContentsChildrenIfNeeded(); void markDirtyAndPropagate(); float resolveFlexGrow() const; float resolveFlexShrink() const; diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index c02c6ddd74ff3c..0ac0d5fe674750 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<0cdc59a5eb40a092a692cb81e76fe251>> * * This file was generated by scripts/build-types/index.js. */ @@ -559,6 +559,7 @@ declare const Vibration_default: { cancel: () => void vibrate: (pattern?: Array | number, repeat?: boolean) => void } +declare const View: typeof View_default declare const ViewNativeComponent_default: HostComponent declare const VirtualizedList: typeof VirtualizedListComponent_default declare const VirtualizedListComponent_default: VirtualizedListType @@ -5622,7 +5623,8 @@ declare type ValueXYListenerCallback = (value: { y: number }) => unknown declare type Vibration = typeof Vibration -declare function View( +declare type View = typeof View +declare function View_default( props: ViewProps & { ref?: React.Ref> }, @@ -6166,7 +6168,7 @@ export { UIManager, // 8d2c8281 UTFSequence, // baacd11b Vibration, // 315e131d - View, // 75d9e33b + View, // 39dd4de4 ViewProps, // 0ab8ceda ViewPropsAndroid, // f3d007c3 ViewPropsIOS, // 58ee19bf diff --git a/packages/react-native/jest/mockComponent.js b/packages/react-native/jest/mockComponent.js index 6ff4f6bd3e248c..ae440d7ab572c8 100644 --- a/packages/react-native/jest/mockComponent.js +++ b/packages/react-native/jest/mockComponent.js @@ -39,6 +39,7 @@ export default function mockComponent< React.ElementProps, > = typeof RealComponent === 'function' && + RealComponent.prototype != null && RealComponent.prototype.constructor instanceof React.Component ? RealComponent : React.Component; diff --git a/packages/react-native/package.json b/packages/react-native/package.json index 8c40a5c8a2486c..6b6b0ba2be7209 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -1,6 +1,6 @@ { "name": "react-native", - "version": "1000.0.0", + "version": "0.81.4", "description": "A framework for building native apps using React", "license": "MIT", "repository": { @@ -21,7 +21,7 @@ ], "bugs": "https://github.com/facebook/react-native/issues", "engines": { - "node": ">= 22.14.0" + "node": ">= 20.19.4" }, "bin": { "react-native": "cli.js" @@ -78,6 +78,7 @@ "files": [ "build.gradle.kts", "cli.js", + "flow", "gradle.properties", "gradle/libs.versions.toml", "index.js", @@ -106,6 +107,7 @@ "ReactCommon", "README.md", "rn-get-polyfills.js", + "scripts/replace-rncore-version.js", "scripts/bundle.js", "scripts/cocoapods", "scripts/codegen", @@ -160,13 +162,13 @@ }, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", - "@react-native/assets-registry": "0.81.0-main", - "@react-native/codegen": "0.81.0-main", - "@react-native/community-cli-plugin": "0.81.0-main", - "@react-native/gradle-plugin": "0.81.0-main", - "@react-native/js-polyfills": "0.81.0-main", - "@react-native/normalize-colors": "0.81.0-main", - "@react-native/virtualized-lists": "0.81.0-main", + "@react-native/assets-registry": "0.81.4", + "@react-native/codegen": "0.81.4", + "@react-native/community-cli-plugin": "0.81.4", + "@react-native/gradle-plugin": "0.81.4", + "@react-native/js-polyfills": "0.81.4", + "@react-native/normalize-colors": "0.81.4", + "@react-native/virtualized-lists": "0.81.4", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", @@ -179,8 +181,8 @@ "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", - "metro-runtime": "^0.82.5", - "metro-source-map": "^0.82.5", + "metro-runtime": "^0.83.1", + "metro-source-map": "^0.83.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", diff --git a/packages/react-native/scripts/cocoapods/autolinking.rb b/packages/react-native/scripts/cocoapods/autolinking.rb index b2e5600bc5d386..d8e9c7cec2f57c 100644 --- a/packages/react-native/scripts/cocoapods/autolinking.rb +++ b/packages/react-native/scripts/cocoapods/autolinking.rb @@ -40,6 +40,12 @@ def list_native_modules!(config_command) packages = config["dependencies"] ios_project_root = Pathname.new(config["project"]["ios"]["sourceDir"]) react_native_path = Pathname.new(config["reactNativePath"]) + codegen_output_path = ios_project_root.join("build/generated/autolinking/autolinking.json") + + # Write autolinking react-native-config output to codegen folder + FileUtils.mkdir_p(File.dirname(codegen_output_path)) + File.write(codegen_output_path, json) + found_pods = [] packages.each do |package_name, package| diff --git a/packages/react-native/scripts/cocoapods/codegen_utils.rb b/packages/react-native/scripts/cocoapods/codegen_utils.rb index 3ce12e28ebc7e6..3c51ad584e1187 100644 --- a/packages/react-native/scripts/cocoapods/codegen_utils.rb +++ b/packages/react-native/scripts/cocoapods/codegen_utils.rb @@ -87,7 +87,7 @@ def self.clean_up_build_folder(rn_path, codegen_dir, dir_manager: Dir, file_mana codegen_path = file_manager.join(ios_folder, codegen_dir) return if !dir_manager.exist?(codegen_path) - FileUtils.rm_rf(dir_manager.glob("#{codegen_path}/*")) + FileUtils.rm_rf("#{codegen_path}") base_provider_path = file_manager.join(rn_path, 'React', 'Fabric', 'RCTThirdPartyFabricComponentsProvider') FileUtils.rm_rf("#{base_provider_path}.h") FileUtils.rm_rf("#{base_provider_path}.mm") diff --git a/packages/react-native/scripts/cocoapods/new_architecture.rb b/packages/react-native/scripts/cocoapods/new_architecture.rb index f3ff4b90ed1f7c..7d8f8e64fc0f53 100644 --- a/packages/react-native/scripts/cocoapods/new_architecture.rb +++ b/packages/react-native/scripts/cocoapods/new_architecture.rb @@ -85,6 +85,7 @@ def self.install_modules_dependencies(spec, new_arch_enabled, folly_version = He .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-utils", "React_utils", [])) @@ -132,6 +133,7 @@ def self.install_modules_dependencies(spec, new_arch_enabled, folly_version = He depend_on_js_engine(spec) add_rn_third_party_dependencies(spec) + add_rncore_dependency(spec) spec.pod_target_xcconfig = current_config end diff --git a/packages/react-native/scripts/cocoapods/rncore.rb b/packages/react-native/scripts/cocoapods/rncore.rb index 8fc1c7a4d990c8..7108f147cf1ddb 100644 --- a/packages/react-native/scripts/cocoapods/rncore.rb +++ b/packages/react-native/scripts/cocoapods/rncore.rb @@ -9,6 +9,18 @@ require_relative './utils.rb' +### Adds ReactNativeCore-prebuilt as a dependency to the given podspec if we're not +### building ReactNativeCore from source (then this function does nothing). +def add_rncore_dependency(s) + if !ReactNativeCoreUtils.build_rncore_from_source() + current_pod_target_xcconfig = s.to_hash["pod_target_xcconfig"] || {} + current_pod_target_xcconfig = current_pod_target_xcconfig.to_h unless current_pod_target_xcconfig.is_a?(Hash) + s.dependency "React-Core-prebuilt" + current_pod_target_xcconfig["HEADER_SEARCH_PATHS"] ||= [] << "$(PODS_ROOT)/React-Core-prebuilt/React.xcframework/Headers" + s.pod_target_xcconfig = current_pod_target_xcconfig + end +end + ## - RCT_USE_PREBUILT_RNCORE: If set to 1, it will use the release tarball from Maven instead of building from source. ## - RCT_TESTONLY_RNCORE_TARBALL_PATH: **TEST ONLY** If set, it will use a local tarball of RNCore if it exists. ## - RCT_TESTONLY_RNCORE_VERSION: **TEST ONLY** If set, it will override the version of RNCore to be used. @@ -113,13 +125,18 @@ def self.podspec_source_download_prebuild_stable_tarball() url = stable_tarball_url(@@react_native_version, :debug) rncore_log("Using tarball from URL: #{url}") - download_stable_rndeps(@@react_native_path, @@react_native_version, :debug) - download_stable_rndeps(@@react_native_path, @@react_native_version, :release) + download_stable_rncore(@@react_native_path, @@react_native_version, :debug) + download_stable_rncore(@@react_native_path, @@react_native_version, :release) return {:http => url} end def self.stable_tarball_url(version, build_type) - maven_repo_url = "https://repo1.maven.org/maven2" + ## You can use the `ENTERPRISE_REPOSITORY` ariable to customise the base url from which artifacts will be downloaded. + ## The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central. + maven_repo_url = + ENV['ENTERPRISE_REPOSITORY'] != nil && ENV['ENTERPRISE_REPOSITORY'] != "" ? + ENV['ENTERPRISE_REPOSITORY'] : + "https://repo1.maven.org/maven2" group = "com/facebook/react" # Sample url from Maven: # https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.81.0/react-native-artifacts-0.81.0-reactnative-core-debug.tar.gz @@ -145,9 +162,9 @@ def self.nightly_tarball_url(version) end end - def self.download_stable_rndeps(react_native_path, version, configuration) + def self.download_stable_rncore(react_native_path, version, configuration) tarball_url = stable_tarball_url(version, configuration) - download_rndeps_tarball(react_native_path, tarball_url, version, configuration) + download_rncore_tarball(react_native_path, tarball_url, version, configuration) end def self.podspec_source_download_prebuilt_nightly_tarball(version) @@ -156,14 +173,14 @@ def self.podspec_source_download_prebuilt_nightly_tarball(version) return {:http => url} end - def self.download_rndeps_tarball(react_native_path, tarball_url, version, configuration) + def self.download_rncore_tarball(react_native_path, tarball_url, version, configuration) destination_path = configuration == nil ? - "#{artifacts_dir()}/reactnative-core-debug.tar.gz-#{version}.tar.gz" : - "#{artifacts_dir()}/reactnative-core-debug.tar.gz-#{version}-#{configuration}.tar.gz" + "#{artifacts_dir()}/reactnative-core-#{version}.tar.gz" : + "#{artifacts_dir()}/reactnative-core-#{version}-#{configuration}.tar.gz" unless File.exist?(destination_path) # Download to a temporary file first so we don't cache incomplete downloads. - tmp_file = "#{artifacts_dir()}/reactnative-core-debug.tar.gz.download" + tmp_file = "#{artifacts_dir()}/reactnative-core.download" `mkdir -p "#{artifacts_dir()}" && curl "#{tarball_url}" -Lo "#{tmp_file}" && mv "#{tmp_file}" "#{destination_path}"` end diff --git a/packages/react-native/scripts/cocoapods/rndependencies.rb b/packages/react-native/scripts/cocoapods/rndependencies.rb index 18308e8ac51343..2717ab09e6b7a8 100644 --- a/packages/react-native/scripts/cocoapods/rndependencies.rb +++ b/packages/react-native/scripts/cocoapods/rndependencies.rb @@ -166,7 +166,12 @@ def self.podspec_source_download_prebuild_release_tarball() end def self.release_tarball_url(version, build_type) - maven_repo_url = "https://repo1.maven.org/maven2" + ## You can use the `ENTERPRISE_REPOSITORY` ariable to customise the base url from which artifacts will be downloaded. + ## The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central. + maven_repo_url = + ENV['ENTERPRISE_REPOSITORY'] != nil && ENV['ENTERPRISE_REPOSITORY'] != "" ? + ENV['ENTERPRISE_REPOSITORY'] : + "https://repo1.maven.org/maven2" group = "com/facebook/react" # Sample url from Maven: # https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.79.0-rc.0/react-native-artifacts-0.79.0-rc.0-reactnative-dependencies-debug.tar.gz diff --git a/packages/react-native/scripts/cocoapods/utils.rb b/packages/react-native/scripts/cocoapods/utils.rb index f6226b00c07e0f..24c7ffd0ce512f 100644 --- a/packages/react-native/scripts/cocoapods/utils.rb +++ b/packages/react-native/scripts/cocoapods/utils.rb @@ -255,18 +255,27 @@ def self.detect_use_frameworks(target_definition) end def self.create_header_search_path_for_frameworks(base_folder, pod_name, framework_name, additional_paths, include_base_path = true) - platforms = $RN_PLATFORMS != nil ? $RN_PLATFORMS : [] search_paths = [] - if platforms.empty?() || platforms.length() == 1 - base_path = File.join("${#{base_folder}}", pod_name, "#{framework_name}.framework", "Headers") - self.add_search_path_to_result(search_paths, base_path, additional_paths, include_base_path) - else - platforms.each { |platform| - base_path = File.join("${#{base_folder}}", "#{pod_name}-#{platform}", "#{framework_name}.framework", "Headers") + # When building using the prebuilt rncore we can't use framework folders as search paths since these aren't created + # Except for when adding search path for ReactCodegen since it contains source code. + if ReactNativeCoreUtils.build_rncore_from_source() || pod_name === "ReactCodegen" + platforms = $RN_PLATFORMS != nil ? $RN_PLATFORMS : [] + + if platforms.empty?() || platforms.length() == 1 + base_path = File.join("${#{base_folder}}", pod_name, "#{framework_name}.framework", "Headers") self.add_search_path_to_result(search_paths, base_path, additional_paths, include_base_path) - } + else + platforms.each { |platform| + base_path = File.join("${#{base_folder}}", "#{pod_name}-#{platform}", "#{framework_name}.framework", "Headers") + self.add_search_path_to_result(search_paths, base_path, additional_paths, include_base_path) + } + end + else + base_path = File.join("${PODS_ROOT}", "#{pod_name}") + self.add_search_path_to_result(search_paths, base_path, additional_paths, include_base_path) end + return search_paths end @@ -305,6 +314,7 @@ def self.update_search_paths(installer) header_search_paths = config.build_settings["HEADER_SEARCH_PATHS"] ||= "$(inherited)" ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon-Samples", "ReactCommon_Samples", ["platform/ios"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-Fabric", "React_Fabric", ["react/renderer/components/view/platform/cxx"], false)) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", [])) @@ -555,6 +565,7 @@ def self.set_codegen_search_paths(target_installation_result) def self.set_reactcommon_searchpaths(target_installation_result) header_search_paths = ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"]) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"])) .map { |search_path| "\"#{search_path}\"" } ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "ReactCommon", header_search_paths) end @@ -691,4 +702,15 @@ def self.add_flag_to_map_with_inheritance(map, field, flag) end end end + + def self.resolve_use_frameworks(spec, header_mappings_dir: nil, module_name: nil) + return unless ENV['USE_FRAMEWORKS'] + if module_name + spec.module_name = module_name + end + + if header_mappings_dir != nil && ReactNativeCoreUtils.build_rncore_from_source() + spec.header_mappings_dir = header_mappings_dir + end + end end diff --git a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap index 6d32169ee71504..5b648740a601cf 100644 --- a/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap +++ b/packages/react-native/scripts/codegen/__tests__/__snapshots__/generate-artifacts-executor-test.js.snap @@ -361,7 +361,7 @@ exports[`execute test-app "ReactAppDependencyProvider.podspec" should match snap # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -version = \\"1000.0.0\\" +version = \\"0.81.4\\" source = { :git => 'https://github.com/facebook/react-native.git' } if version == '1000.0.0' # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. @@ -399,7 +399,7 @@ exports[`execute test-app "ReactCodegen.podspec" should match snapshot 1`] = ` # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -version = \\"1000.0.0\\" +version = \\"0.81.4\\" source = { :git => 'https://github.com/facebook/react-native.git' } if version == '1000.0.0' # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. @@ -429,6 +429,7 @@ if use_frameworks .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-runtimeexecutor\\", \\"React_runtimeexecutor\\", [\\"platform/ios\\"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", [])) @@ -478,6 +479,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.script_phases = { 'name' => 'Generate Specs', @@ -495,9 +497,9 @@ export RCT_SCRIPT_APP_PATH=\\"$RCT_SCRIPT_POD_INSTALLATION_ROOT/..\\" export RCT_SCRIPT_OUTPUT_DIR=\\"$RCT_SCRIPT_POD_INSTALLATION_ROOT\\" export RCT_SCRIPT_TYPE=\\"withCodegenDiscovery\\" -SCRIPT_PHASES_SCRIPT=\\"$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh\\" -WITH_ENVIRONMENT=\\"$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh\\" -/bin/sh -c \\"$WITH_ENVIRONMENT $SCRIPT_PHASES_SCRIPT\\" +export SCRIPT_PHASES_SCRIPT=\\"$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh\\" +export WITH_ENVIRONMENT=\\"$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh\\" +/bin/sh -c '\\"$WITH_ENVIRONMENT\\" \\"$SCRIPT_PHASES_SCRIPT\\"' SCRIPT } @@ -838,7 +840,7 @@ exports[`execute test-app-legacy "ReactAppDependencyProvider.podspec" should mat # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -version = \\"1000.0.0\\" +version = \\"0.81.4\\" source = { :git => 'https://github.com/facebook/react-native.git' } if version == '1000.0.0' # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. @@ -876,7 +878,7 @@ exports[`execute test-app-legacy "ReactCodegen.podspec" should match snapshot 1` # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. -version = \\"1000.0.0\\" +version = \\"0.81.4\\" source = { :git => 'https://github.com/facebook/react-native.git' } if version == '1000.0.0' # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. @@ -906,6 +908,7 @@ if use_frameworks .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-FabricImage\\", \\"React_FabricImage\\", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-graphics\\", \\"React_graphics\\", [\\"react/renderer/graphics/platform/ios\\"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"ReactCommon\\", \\"ReactCommon\\", [\\"react/nativemodule/core\\"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-runtimeexecutor\\", \\"React_runtimeexecutor\\", [\\"platform/ios\\"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-NativeModulesApple\\", \\"React_NativeModulesApple\\", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-RCTFabric\\", \\"RCTFabric\\", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks(\\"PODS_CONFIGURATION_BUILD_DIR\\", \\"React-debug\\", \\"React_debug\\", [])) @@ -955,6 +958,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.script_phases = { 'name' => 'Generate Specs', @@ -972,12 +976,12 @@ export RCT_SCRIPT_APP_PATH=\\"$RCT_SCRIPT_POD_INSTALLATION_ROOT/..\\" export RCT_SCRIPT_OUTPUT_DIR=\\"$RCT_SCRIPT_POD_INSTALLATION_ROOT\\" export RCT_SCRIPT_TYPE=\\"withCodegenDiscovery\\" -SCRIPT_PHASES_SCRIPT=\\"$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh\\" -WITH_ENVIRONMENT=\\"$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh\\" -/bin/sh -c \\"$WITH_ENVIRONMENT $SCRIPT_PHASES_SCRIPT\\" +export SCRIPT_PHASES_SCRIPT=\\"$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh\\" +export WITH_ENVIRONMENT=\\"$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh\\" +/bin/sh -c '\\"$WITH_ENVIRONMENT\\" \\"$SCRIPT_PHASES_SCRIPT\\"' SCRIPT } end " -`; +`; \ No newline at end of file diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js index d5d932229dcd31..554dbca091f78f 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/generateReactCodegenPodspec.js @@ -83,9 +83,9 @@ export RCT_SCRIPT_APP_PATH="$RCT_SCRIPT_POD_INSTALLATION_ROOT/${relativeAppPath. export RCT_SCRIPT_OUTPUT_DIR="$RCT_SCRIPT_POD_INSTALLATION_ROOT" export RCT_SCRIPT_TYPE="withCodegenDiscovery" -SCRIPT_PHASES_SCRIPT="$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh" -WITH_ENVIRONMENT="$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh" -/bin/sh -c "$WITH_ENVIRONMENT $SCRIPT_PHASES_SCRIPT" +export SCRIPT_PHASES_SCRIPT="$RCT_SCRIPT_RN_DIR/scripts/react_native_pods_utils/script_phases.sh" +export WITH_ENVIRONMENT="$RCT_SCRIPT_RN_DIR/scripts/xcode/with-environment.sh" +/bin/sh -c '"$WITH_ENVIRONMENT" "$SCRIPT_PHASES_SCRIPT"' SCRIPT`; } diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js index 002273410bd637..c64c256a459154 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/index.js @@ -86,16 +86,19 @@ function execute( buildCodegenIfNeeded(); } - const reactNativeConfig = readReactNativeConfig(projectRoot); + const reactNativeConfig = readReactNativeConfig( + projectRoot, + baseOutputPath, + ); const codegenEnabledLibraries = findCodegenEnabledLibraries( pkgJson, projectRoot, + baseOutputPath, reactNativeConfig, ); if (codegenEnabledLibraries.length === 0) { codegenLog('No codegen-enabled libraries found.', true); - return; } let platforms = @@ -110,10 +113,6 @@ function execute( ({name}) => !disabledLibraries.includes(name), ); - if (!libraries.length) { - continue; - } - const outputPath = computeOutputPath( projectRoot, baseOutputPath, diff --git a/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js b/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js index db611c8db17f7a..f2fa2fc852f651 100644 --- a/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js +++ b/packages/react-native/scripts/codegen/generate-artifacts-executor/utils.js @@ -97,15 +97,40 @@ function cleanupEmptyFilesAndFolders(filepath /*: string */) { } } -function readReactNativeConfig(projectRoot /*: string */) /*: $FlowFixMe */ { - const rnConfigFilePath = path.resolve(projectRoot, 'react-native.config.js'); +function readGeneratedAutolinkingOutput( + baseOutputPath /*: string */, +) /*: $FlowFixMe */ { + // NOTE: Generated by scripts/cocoapods/autolinking.rb in list_native_modules (called by use_native_modules) + const autolinkingGeneratedPath = path.resolve( + baseOutputPath, + 'build/generated/autolinking/autolinking.json', + ); + if (fs.existsSync(autolinkingGeneratedPath)) { + // $FlowFixMe[unsupported-syntax] + return require(autolinkingGeneratedPath); + } else { + codegenLog( + `Could not find generated autolinking output at: ${autolinkingGeneratedPath}`, + ); + return null; + } +} - if (!fs.existsSync(rnConfigFilePath)) { +function readReactNativeConfig( + projectRoot /*: string */, + baseOutputPath /*: string */, +) /*: $FlowFixMe */ { + const autolinkingOutput = readGeneratedAutolinkingOutput(baseOutputPath); + const rnConfigFilePath = path.resolve(projectRoot, 'react-native.config.js'); + if (autolinkingOutput) { + return autolinkingOutput; + } else if (fs.existsSync(rnConfigFilePath)) { + // $FlowIgnore[unsupported-syntax] + return require(rnConfigFilePath); + } else { + codegenLog(`Could not find React Native config at: ${rnConfigFilePath}`); return {}; } - - // $FlowIgnore[unsupported-syntax] - return require(rnConfigFilePath); } /** @@ -114,17 +139,23 @@ function readReactNativeConfig(projectRoot /*: string */) /*: $FlowFixMe */ { function findCodegenEnabledLibraries( pkgJson /*: $FlowFixMe */, projectRoot /*: string */, + baseOutputPath /*: string */, reactNativeConfig /*: $FlowFixMe */, ) /*: Array<$FlowFixMe> */ { const projectLibraries = findProjectRootLibraries(pkgJson, projectRoot); if (pkgJsonIncludesGeneratedCode(pkgJson)) { return projectLibraries; } else { - return [ - ...projectLibraries, - ...findExternalLibraries(pkgJson, projectRoot), + const libraries = [...projectLibraries]; + // If we ran autolinking, we shouldn't try to run our own "autolinking-like" + // library discovery + if (!readGeneratedAutolinkingOutput(baseOutputPath)) { + libraries.push(...findExternalLibraries(pkgJson, projectRoot)); + } + libraries.push( ...findLibrariesFromReactNativeConfig(projectRoot, reactNativeConfig), - ]; + ); + return libraries; } } diff --git a/packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template b/packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template index 20b40fdaccb4b7..de434d4b91d88c 100644 --- a/packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template +++ b/packages/react-native/scripts/codegen/templates/ReactCodegen.podspec.template @@ -33,6 +33,7 @@ if use_frameworks .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-FabricImage", "React_FabricImage", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-graphics", "React_graphics", ["react/renderer/graphics/platform/ios"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "ReactCommon", "ReactCommon", ["react/nativemodule/core"])) + .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-runtimeexecutor", "React_runtimeexecutor", ["platform/ios"])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-NativeModulesApple", "React_NativeModulesApple", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-RCTFabric", "RCTFabric", [])) .concat(ReactNativePodsUtils.create_header_search_path_for_frameworks("PODS_CONFIGURATION_BUILD_DIR", "React-debug", "React_debug", [])) @@ -82,6 +83,7 @@ Pod::Spec.new do |s| depend_on_js_engine(s) add_rn_third_party_dependencies(s) + add_rncore_dependency(s) s.script_phases = { 'name' => 'Generate Specs', diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 74e6001241f0fb..5fc31902859f33 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -102,7 +102,7 @@ const definitions: FeatureFlagDefinitions = { ossReleaseStage: 'none', }, disableMountItemReorderingAndroid: { - defaultValue: false, + defaultValue: true, metadata: { dateAdded: '2024-10-26', description: @@ -543,6 +543,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + preventShadowTreeCommitExhaustion: { + defaultValue: false, + metadata: { + dateAdded: '2025-07-23', + description: + 'Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'experimental', + }, traceTurboModulePromiseRejectionsOnAndroid: { defaultValue: false, metadata: { @@ -584,6 +595,27 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + useNativeEqualsInNativeReadableArrayAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2025-07-15', + description: + 'Use a native implementation of equals in NativeReadableArray.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'experimental', + }, + useNativeTransformHelperAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2025-07-15', + description: 'Use a native implementation of TransformHelper', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'experimental', + }, useNativeViewConfigsInBridgelessMode: { defaultValue: false, metadata: { @@ -659,6 +691,16 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + runtimeCrashUiThreadUtils: { + defaultValue: false, + metadata: { + dateAdded: '2025-12-11', + description: 'Instead of logging a soft exception crash the app in UiThreadUtils.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, }, jsOnly: { diff --git a/packages/react-native/scripts/ios-configure-glog.sh b/packages/react-native/scripts/ios-configure-glog.sh index 81eb319e50b641..7259c788bdc3e7 100755 --- a/packages/react-native/scripts/ios-configure-glog.sh +++ b/packages/react-native/scripts/ios-configure-glog.sh @@ -42,15 +42,15 @@ EOF patch -p1 config.sub fix_glog_0.3.5_apple_silicon.patch fi -XCRUN="$(which xcrun || true)" -if [ -n "$XCRUN" ]; then - export CC="$(xcrun -find -sdk $PLATFORM_NAME cc) -arch $CURRENT_ARCH -isysroot $(xcrun -sdk $PLATFORM_NAME --show-sdk-path)" - export CXX="$CC" -else - export CC="$CC:-$(which gcc)" - export CXX="$CXX:-$(which g++ || true)" -fi -export CXX="$CXX:-$CC" +# XCRUN="$(which xcrun || true)" +# if [ -n "$XCRUN" ]; then +# export CC="$(xcrun -find -sdk $PLATFORM_NAME cc) -arch $CURRENT_ARCH -isysroot $(xcrun -sdk $PLATFORM_NAME --show-sdk-path)" +# export CXX="$CC" +# else +# export CC="$CC:-$(which gcc)" +# export CXX="$CXX:-$(which g++ || true)" +# fi +# export CXX="$CXX:-$CC" # Remove automake symlink if it exists if [ -h "test-driver" ]; then diff --git a/packages/react-native/scripts/ios-prebuild/headers.js b/packages/react-native/scripts/ios-prebuild/headers.js index a3f1bad1c0e1b0..25b03daf1a67df 100644 --- a/packages/react-native/scripts/ios-prebuild/headers.js +++ b/packages/react-native/scripts/ios-prebuild/headers.js @@ -49,7 +49,7 @@ function getHeaderFilesFromPodspecs( let arg2 = match[2]?.trim().replace(/['"]/g, ''); if (!arg2) { // Skip - return; + continue; } // Check if arg2 is an array (e.g., ['a', 'b']) if (arg2.startsWith('[') && arg2.endsWith(']')) { diff --git a/packages/react-native/scripts/ios-prebuild/hermes.js b/packages/react-native/scripts/ios-prebuild/hermes.js index 356901ebdf220a..e5e67bfcb0a690 100644 --- a/packages/react-native/scripts/ios-prebuild/hermes.js +++ b/packages/react-native/scripts/ios-prebuild/hermes.js @@ -183,7 +183,10 @@ function getTarballUrl( version /*: string */, buildType /*: BuildFlavor */, ) /*: string */ { - const mavenRepoUrl = 'https://repo1.maven.org/maven2'; + // You can use the `ENTERPRISE_REPOSITORY` ariable to customise the base url from which artifacts will be downloaded. + // The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central. + const mavenRepoUrl = + process.env.ENTERPRISE_REPOSITORY ?? 'https://repo1.maven.org/maven2'; const namespace = 'com/facebook/react'; return `${mavenRepoUrl}/${namespace}/react-native-artifacts/${version}/react-native-artifacts-${version}-hermes-ios-${buildType.toLowerCase()}.tar.gz`; } diff --git a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js index bac3a0e051974a..6b5880e98c11de 100644 --- a/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js +++ b/packages/react-native/scripts/ios-prebuild/reactNativeDependencies.js @@ -179,7 +179,10 @@ function getTarballUrl( version /*: string */, buildType /*: BuildFlavor */, ) /*: string */ { - const mavenRepoUrl = 'https://repo1.maven.org/maven2'; + // You can use the `ENTERPRISE_REPOSITORY` ariable to customise the base url from which artifacts will be downloaded. + // The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central. + const mavenRepoUrl = + process.env.ENTERPRISE_REPOSITORY ?? 'https://repo1.maven.org/maven2'; const namespace = 'com/facebook/react'; return `${mavenRepoUrl}/${namespace}/react-native-artifacts/${version}/react-native-artifacts-${version}-reactnative-dependencies-${buildType.toLowerCase()}.tar.gz`; } diff --git a/packages/react-native/scripts/ios-prebuild/setup.js b/packages/react-native/scripts/ios-prebuild/setup.js index bd26c3634f4496..65e34bb6e482dc 100644 --- a/packages/react-native/scripts/ios-prebuild/setup.js +++ b/packages/react-native/scripts/ios-prebuild/setup.js @@ -10,6 +10,9 @@ /*:: import type {BuildFlavor} from './types'; */ +const { + generateFBReactNativeSpecIOS, +} = require('../codegen/generate-artifacts-executor/generateFBReactNativeSpecIOS'); const {prepareHermesArtifactsAsync} = require('./hermes'); const { prepareReactNativeDependenciesArtifactsAsync, @@ -25,6 +28,9 @@ async function setup( currentVersion /*: string */, buildType /*: BuildFlavor */, ) { + // First of all, let's run codegen to make sure that we have the FBreactNativeSpec files in the prebuilds + generateFBReactNativeSpecIOS('.'); + const prebuildLog = createLogger('prebuild'); createFolderIfNotExists(buildFolder); diff --git a/packages/react-native/scripts/ios-prebuild/xcframework.js b/packages/react-native/scripts/ios-prebuild/xcframework.js index b57075acdf4a18..236103b1a36153 100644 --- a/packages/react-native/scripts/ios-prebuild/xcframework.js +++ b/packages/react-native/scripts/ios-prebuild/xcframework.js @@ -10,6 +10,9 @@ /*:: import type {BuildFlavor} from './types'; */ +const { + generateFBReactNativeSpecIOS, +} = require('../codegen/generate-artifacts-executor/generateFBReactNativeSpecIOS'); const headers = require('./headers'); const utils = require('./utils'); const childProcess = require('child_process'); @@ -49,6 +52,9 @@ function buildXCFrameworks( buildType /*: BuildFlavor */, identity /*: ?string */, ) { + // Let's run codegen for FBReactNativeSpec otherwise some headers will be missing + generateFBReactNativeSpecIOS('.'); + const outputPath = path.join( buildFolder, 'output', @@ -106,10 +112,16 @@ function buildXCFrameworks( if (headerFiles.length > 0) { // Get podspec name without directory and extension and make sure it is a valid identifier // by replacing any non-alphanumeric characters with an underscore. - const podSpecName = path + let podSpecName = path .basename(podspec, '.podspec') .replace(/[^a-zA-Z0-9_]/g, '_'); + // Fix for FBReactNativeSpec. RN expect FBReactNative spec headers + // To be in a folder named FBReactNativeSpec. + if (podSpecName === 'React_RCTFBReactNativeSpec') { + podSpecName = 'FBReactNativeSpec'; + } + // Create a folder for the podspec in the output headers path const podSpecFolder = path.join(outputHeadersPath, podSpecName); createFolderIfNotExists(podSpecFolder); @@ -173,24 +185,68 @@ function buildXCFrameworks( ); // Copy Symbols to symbols folder - const symbolPaths = frameworkFolders.map(framework => - path.join(framework, `..`, `..`, `React.framework.dSYM`), - ); - - frameworkLog('Copying symbols to symbols folder...'); - const symbolOutput = path.join(outputPath, '..', 'Symbols'); - symbolPaths.forEach(symbol => { - const destination = extractDestinationFromPath(symbol); - const outputFolder = path.join(symbolOutput, destination); - fs.mkdirSync(outputFolder, {recursive: true}); - execSync(`cp -r ${symbol} ${outputFolder}`); - }); + copySymbols(outputPath, frameworkFolders); if (identity) { signXCFramework(identity, outputPath); } } +function copySymbols( + outputPath /*:string*/, + frameworkFolders /*:Array*/, +) { + frameworkLog('Copying symbols to symbols folder...'); + const targetArchFolders = fs + .readdirSync(outputPath) + .map(p => path.join(outputPath, p)) + .filter(folder => { + return ( + fs.statSync(folder).isDirectory() && + !folder.endsWith('Headers') && + !folder.endsWith('Modules') + ); + }); + + const symbolOutput = path.join(outputPath, '..', 'Symbols'); + frameworkFolders.forEach(frameworkFolder => { + // Get archs for current symbol slice + const frameworkPlatforms = getArchsFromFramework( + path.join(frameworkFolder, 'React'), + ); + if (frameworkPlatforms) { + const targetFolder = targetArchFolders.find( + targetArchFolder => + getArchsFromFramework( + path.join(targetArchFolder, 'React.framework', 'React'), + ) === frameworkPlatforms, + ); + if (!targetFolder) { + frameworkLog( + `No target folder found for symbol slice: ${frameworkFolder}`, + 'error', + ); + return; + } + const targetSymbolPath = path.join( + symbolOutput, + path.basename(targetFolder), + ); + const sourceSymbolPath = path.join( + frameworkFolder, + '..', + '..', + 'React.framework.dSYM', + ); + console.log( + ` ${path.relative(outputPath, sourceSymbolPath)} → ${path.basename(targetFolder)}`, + ); + fs.mkdirSync(targetSymbolPath, {recursive: true}); + execSync(`cp -r ${sourceSymbolPath} ${targetSymbolPath}`); + } + }); +} + function linkArchFolders( outputPath /*:string*/, moduleMapFile /*:string*/, @@ -307,22 +363,17 @@ function createModuleMapFile(outputPath /*: string */) { } } -function extractDestinationFromPath(symbolPath /*: string */) /*: string */ { - if (symbolPath.includes('iphoneos')) { - return 'iphoneos'; - } - - if (symbolPath.includes('iphonesimulator')) { - return 'iphonesimulator'; - } - - if (symbolPath.includes('maccatalyst')) { - return 'catalyst'; +function getArchsFromFramework(frameworkPath /*:string*/) { + try { + return execSync(`vtool -show-build ${frameworkPath}|grep platform`) + .toString() + .split('\n') + .map(p => p.trim().split(' ')[1]) + .sort((a, b) => a.localeCompare(b)) + .join(' '); + } catch (error) { + return ''; } - - throw new Error( - `Impossible to extract destination from ${symbolPath}. Valid destinations are iphoneos, iphonesimulator and catalyst.`, - ); } function signXCFramework( diff --git a/packages/react-native/scripts/react-native-xcode.sh b/packages/react-native/scripts/react-native-xcode.sh index b5d50ccb4bb496..1dcdf55e190221 100755 --- a/packages/react-native/scripts/react-native-xcode.sh +++ b/packages/react-native/scripts/react-native-xcode.sh @@ -10,7 +10,7 @@ # Print commands before executing them (useful for troubleshooting) set -x -e -DEST=$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH +DEST="$CONFIGURATION_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH" # Enables iOS devices to get the IP address of the machine running Metro if [[ ! "$SKIP_BUNDLING_METRO_IP" && "$CONFIGURATION" = *Debug* && ! "$PLATFORM_NAME" == *simulator ]]; then @@ -59,7 +59,7 @@ esac # Path to react-native folder inside node_modules REACT_NATIVE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # Most projects have their project root, one level up from their Xcode project dir (the "ios" directory) -PROJECT_ROOT=${PROJECT_ROOT:-"$PROJECT_DIR/.."} +PROJECT_ROOT="${PROJECT_ROOT:-"$PROJECT_DIR/.."}" cd "$PROJECT_ROOT" || exit diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 676d8b80eb4f9c..e9f5a89fd922b0 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -229,6 +229,20 @@ def folly_flags() return NewArchitectureHelper.folly_compiler_flags end +# Resolve the spec for use with the USE_FRAMEWORKS environment variable. To avoid each podspec +# to manually specify the header mappings and module name, we can use this helper function. +# This helper will also resolve header mappings if we're building from source. Precompiled +# React-Core will not generate frameworks since their podspec files only contains the +# header files and no source code - so header_mappings should be the same as for without USE_FRAMEWORKS +# +# Parameters: +# - s: the spec to modify +# - header_mappings_dir: the directory to map headers when building Pod header structure +# - module_name: the name of the module when exposed to swift +def resolve_use_frameworks(spec, header_mappings_dir: nil, module_name: nil) + ReactNativePodsUtils.resolve_use_frameworks(spec, :header_mappings_dir => header_mappings_dir, :module_name => module_name) +end + # Add a dependency to a spec, making sure that the HEADER_SERACH_PATHS are set properly. # This function automate the requirement to specify the HEADER_SEARCH_PATHS which was error prone # and hard to pull out properly to begin with. @@ -483,6 +497,13 @@ def react_native_post_install( ReactNativePodsUtils.updateOSDeploymentTarget(installer) ReactNativePodsUtils.set_dynamic_frameworks_flags(installer) ReactNativePodsUtils.add_ndebug_flag_to_pods_in_release(installer) + + if !ReactNativeCoreUtils.build_rncore_from_source() + # In XCode 26 we need to revert the new setting SWIFT_ENABLE_EXPLICIT_MODULES when building + # with precompiled binaries. + ReactNativePodsUtils.set_build_setting(installer, build_setting: "SWIFT_ENABLE_EXPLICIT_MODULES", value: "NO") + end + SPM.apply_on_post_install(installer) if privacy_file_aggregation_enabled diff --git a/packages/react-native/scripts/react_native_pods_utils/script_phases.sh b/packages/react-native/scripts/react_native_pods_utils/script_phases.sh index 92325f1ad078f4..ae3dd62eb0090f 100755 --- a/packages/react-native/scripts/react_native_pods_utils/script_phases.sh +++ b/packages/react-native/scripts/react_native_pods_utils/script_phases.sh @@ -21,6 +21,18 @@ error () { exit 1 } +# Determine path to react-native-codegen +# DISCORD FIX: Our repo path isn't valid (has the files under `src` +# rather than `lib`), so the order of this `if` was modified to prefer +# the NPM path. +if [ -d "$CODEGEN_NPM_PATH" ]; then + CODEGEN_CLI_PATH=$(cd "$CODEGEN_NPM_PATH" && pwd) +elif [ -d "$CODEGEN_REPO_PATH" ]; then + CODEGEN_CLI_PATH=$(cd "$CODEGEN_REPO_PATH" && pwd) +else + error "error: Could not determine react-native-codegen location in $CODEGEN_REPO_PATH or $CODEGEN_NPM_PATH. Try running 'yarn install' or 'npm install' in your project root." +fi + find_node () { NODE_BINARY="${NODE_BINARY:-$(command -v node || true)}" if [ -z "$NODE_BINARY" ]; then diff --git a/packages/react-native/scripts/replace-rncore-version.js b/packages/react-native/scripts/replace-rncore-version.js new file mode 100644 index 00000000000000..1385b71ffb4acb --- /dev/null +++ b/packages/react-native/scripts/replace-rncore-version.js @@ -0,0 +1,122 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const {spawnSync} = require('child_process'); +const fs = require('fs'); +const yargs = require('yargs'); + +const LAST_BUILD_FILENAME = 'React-Core-prebuilt/.last_build_configuration'; + +function validateBuildConfiguration(configuration /*: string */) { + if (!['Debug', 'Release'].includes(configuration)) { + throw new Error(`Invalid configuration ${configuration}`); + } +} + +function validateVersion(version /*: ?string */) { + if (version == null || version === '') { + throw new Error('Version cannot be empty'); + } +} + +function shouldReplaceRnCoreConfiguration(configuration /*: string */) { + const fileExists = fs.existsSync(LAST_BUILD_FILENAME); + + if (fileExists) { + console.log(`Found ${LAST_BUILD_FILENAME} file`); + const oldConfiguration = fs.readFileSync(LAST_BUILD_FILENAME).toString(); + if (oldConfiguration === configuration) { + console.log( + 'Same config of the previous build. No need to replace React-Core-prebuilt', + ); + return false; + } + } + + // Assumption: if there is no stored last build, we assume that it was build for debug. + if (!fileExists && configuration === 'Debug') { + console.log( + 'No previous build detected, but Debug Configuration. No need to replace React-Core-prebuilt', + ); + return false; + } + + return true; +} + +function replaceRNCoreConfiguration( + configuration /*: string */, + version /*: string */, + podsRoot /*: string */, +) { + // Filename comes from rncore.rb + const tarballURLPath = `${podsRoot}/ReactNativeCore-artifacts/reactnative-core-${version.toLowerCase()}-${configuration.toLowerCase()}.tar.gz`; + + const finalLocation = 'React-Core-prebuilt'; + console.log('Preparing the final location', finalLocation); + fs.rmSync(finalLocation, {force: true, recursive: true}); + fs.mkdirSync(finalLocation, {recursive: true}); + + console.log('Extracting the tarball', tarballURLPath); + spawnSync('tar', ['-xf', tarballURLPath, '-C', finalLocation], { + stdio: 'inherit', + }); +} + +function updateLastBuildConfiguration(configuration /*: string */) { + console.log(`Updating ${LAST_BUILD_FILENAME} with ${configuration}`); + fs.writeFileSync(LAST_BUILD_FILENAME, configuration); +} + +function main( + configuration /*: string */, + version /*: string */, + podsRoot /*: string */, +) { + validateBuildConfiguration(configuration); + validateVersion(version); + + if (!shouldReplaceRnCoreConfiguration(configuration)) { + return; + } + + replaceRNCoreConfiguration(configuration, version, podsRoot); + updateLastBuildConfiguration(configuration); + console.log('Done replacing React Native prebuilt'); +} + +// This script is executed in the Pods folder, which is usually not synched to Github, so it should be ok +const argv = yargs + .option('c', { + alias: 'configuration', + description: + 'Configuration to use to download the right React-Core prebuilt version. Allowed values are "Debug" and "Release".', + }) + .option('r', { + alias: 'reactNativeVersion', + description: + 'The Version of React Native associated with the React-Core prebuilt tarball.', + }) + .option('p', { + alias: 'podsRoot', + description: 'The path to the Pods root folder', + }) + .usage('Usage: $0 -c Debug -r -p ').argv; + +// $FlowFixMe[prop-missing] +const configuration = argv.configuration; +// $FlowFixMe[prop-missing] +const version = argv.reactNativeVersion; +// $FlowFixMe[prop-missing] +const podsRoot = argv.podsRoot; + +main(configuration, version, podsRoot); diff --git a/packages/react-native/scripts/xcode/with-environment.sh b/packages/react-native/scripts/xcode/with-environment.sh index c6bd62c87fc811..d2ddf3c02cabb4 100755 --- a/packages/react-native/scripts/xcode/with-environment.sh +++ b/packages/react-native/scripts/xcode/with-environment.sh @@ -43,5 +43,5 @@ fi # Execute argument, if present if [ -n "$1" ]; then - $1 + "$1" fi diff --git a/packages/react-native/sdks/.hermesversion b/packages/react-native/sdks/.hermesversion new file mode 100644 index 00000000000000..6cbff7e69d0f94 --- /dev/null +++ b/packages/react-native/sdks/.hermesversion @@ -0,0 +1 @@ +hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 \ No newline at end of file diff --git a/packages/react-native/sdks/hermes-engine/hermes-utils.rb b/packages/react-native/sdks/hermes-engine/hermes-utils.rb index 8ca229aa09e3cb..d01a1d330069de 100644 --- a/packages/react-native/sdks/hermes-engine/hermes-utils.rb +++ b/packages/react-native/sdks/hermes-engine/hermes-utils.rb @@ -204,7 +204,12 @@ def hermestag_file(react_native_path) end def release_tarball_url(version, build_type) - maven_repo_url = "https://repo1.maven.org/maven2" + ## You can use the `ENTERPRISE_REPOSITORY` ariable to customise the base url from which artifacts will be downloaded. + ## The mirror's structure must be the same of the Maven repo the react-native core team publishes on Maven Central. + maven_repo_url = + ENV['ENTERPRISE_REPOSITORY'] != nil && ENV['ENTERPRISE_REPOSITORY'] != "" ? + ENV['ENTERPRISE_REPOSITORY'] : + "https://repo1.maven.org/maven2" namespace = "com/facebook/react" # Sample url from Maven: # https://repo1.maven.org/maven2/com/facebook/react/react-native-artifacts/0.71.0/react-native-artifacts-0.71.0-hermes-ios-debug.tar.gz diff --git a/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js b/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js index 3dc996471318f7..18ef3877b563a3 100644 --- a/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js +++ b/packages/react-native/sdks/hermes-engine/utils/replace_hermes_version.js @@ -10,7 +10,7 @@ 'use strict'; -const {execSync} = require('child_process'); +const {spawnSync} = require('child_process'); const fs = require('fs'); const yargs = require('yargs'); @@ -62,7 +62,9 @@ function replaceHermesConfiguration(configuration, version, podsRoot) { fs.mkdirSync(finalLocation, {recursive: true}); console.log('Extracting the tarball'); - execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`); + spawnSync('tar', ['-xf', tarballURLPath, '-C', finalLocation], { + stdio: 'inherit', + }); } function updateLastBuildConfiguration(configuration) { diff --git a/packages/react-native/src/private/animated/NativeAnimatedHelper.js b/packages/react-native/src/private/animated/NativeAnimatedHelper.js index 6ad2ea8ec913f8..67a6ea74b7706a 100644 --- a/packages/react-native/src/private/animated/NativeAnimatedHelper.js +++ b/packages/react-native/src/private/animated/NativeAnimatedHelper.js @@ -150,7 +150,7 @@ const API = { } : (tag, saveValueCallback) => { NativeOperations.getValue(tag, saveValueCallback); - }) as $NonMaybeType['getValue'], + }), setWaitingForIdentifier(id: string): void { if (shouldSignalBatch) { @@ -237,7 +237,7 @@ const API = { if (Platform.OS === 'android' || shouldSignalBatch) { NativeAnimatedModule?.finishOperationBatch?.(); } - }) as () => void, + }), createAnimatedNode(tag: number, config: AnimatedNodeConfig): void { NativeOperations.createAnimatedNode(tag, config); @@ -279,7 +279,7 @@ const API = { config, endCallback, ); - }) as $NonMaybeType['startAnimatingNode'], + }), stopAnimation(animationId: number) { NativeOperations.stopAnimation(animationId); diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index f5720762666b34..c5dfc0b1e781f2 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<3f339630fb5cff04f64898f7140dd78d>> * @flow strict * @noformat */ @@ -98,10 +98,13 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ fuseboxNetworkInspectionEnabled: Getter, hideOffscreenVirtualViewsOnIOS: Getter, preparedTextCacheSize: Getter, + preventShadowTreeCommitExhaustion: Getter, traceTurboModulePromiseRejectionsOnAndroid: Getter, updateRuntimeShadowNodeReferencesOnCommit: Getter, useAlwaysAvailableJSErrorHandling: Getter, useFabricInterop: Getter, + useNativeEqualsInNativeReadableArrayAndroid: Getter, + useNativeTransformHelperAndroid: Getter, useNativeViewConfigsInBridgelessMode: Getter, useOptimizedEventBatchingOnAndroid: Getter, useRawPropsJsiValue: Getter, @@ -109,6 +112,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ useTurboModuleInterop: Getter, useTurboModules: Getter, virtualViewPrerenderRatio: Getter, + runtimeCrashUiThreadUtils: Getter, }>; /** @@ -218,7 +222,7 @@ export const disableMainQueueSyncDispatchIOS: Getter = createNativeFlag /** * Prevent FabricMountingManager from reordering mountItems, which may lead to invalid state on the UI thread */ -export const disableMountItemReorderingAndroid: Getter = createNativeFlagGetter('disableMountItemReorderingAndroid', false); +export const disableMountItemReorderingAndroid: Getter = createNativeFlagGetter('disableMountItemReorderingAndroid', true); /** * Turns off the global measurement cache used by TextLayoutManager on Android. */ @@ -383,6 +387,10 @@ export const hideOffscreenVirtualViewsOnIOS: Getter = createNativeFlagG * Number cached PreparedLayouts in TextLayoutManager cache */ export const preparedTextCacheSize: Getter = createNativeFlagGetter('preparedTextCacheSize', 200); +/** + * Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again. + */ +export const preventShadowTreeCommitExhaustion: Getter = createNativeFlagGetter('preventShadowTreeCommitExhaustion', false); /** * Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause. */ @@ -399,6 +407,14 @@ export const useAlwaysAvailableJSErrorHandling: Getter = createNativeFl * Should this application enable the Fabric Interop Layer for Android? If yes, the application will behave so that it can accept non-Fabric components and render them on Fabric. This toggle is controlling extra logic such as custom event dispatching that are needed for the Fabric Interop Layer to work correctly. */ export const useFabricInterop: Getter = createNativeFlagGetter('useFabricInterop', true); +/** + * Use a native implementation of equals in NativeReadableArray. + */ +export const useNativeEqualsInNativeReadableArrayAndroid: Getter = createNativeFlagGetter('useNativeEqualsInNativeReadableArrayAndroid', false); +/** + * Use a native implementation of TransformHelper + */ +export const useNativeTransformHelperAndroid: Getter = createNativeFlagGetter('useNativeTransformHelperAndroid', false); /** * When enabled, the native view configs are used in bridgeless mode. */ @@ -427,6 +443,10 @@ export const useTurboModules: Getter = createNativeFlagGetter('useTurbo * Initial prerender ratio for VirtualView. */ export const virtualViewPrerenderRatio: Getter = createNativeFlagGetter('virtualViewPrerenderRatio', 5); +/** + * Instead of logging a soft exception crash the app in UiThreadUtils. + */ +export const runtimeCrashUiThreadUtils: Getter = createNativeFlagGetter('runtimeCrashUiThreadUtils', false); /** * Overrides the feature flags with the provided methods. diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index d91d5dbad0c0e8..7cbf3572edc406 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<55c1f0223345b5680bbdd888a358f210>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -72,10 +72,13 @@ export interface Spec extends TurboModule { +fuseboxNetworkInspectionEnabled?: () => boolean; +hideOffscreenVirtualViewsOnIOS?: () => boolean; +preparedTextCacheSize?: () => number; + +preventShadowTreeCommitExhaustion?: () => boolean; +traceTurboModulePromiseRejectionsOnAndroid?: () => boolean; +updateRuntimeShadowNodeReferencesOnCommit?: () => boolean; +useAlwaysAvailableJSErrorHandling?: () => boolean; +useFabricInterop?: () => boolean; + +useNativeEqualsInNativeReadableArrayAndroid?: () => boolean; + +useNativeTransformHelperAndroid?: () => boolean; +useNativeViewConfigsInBridgelessMode?: () => boolean; +useOptimizedEventBatchingOnAndroid?: () => boolean; +useRawPropsJsiValue?: () => boolean; @@ -83,6 +86,7 @@ export interface Spec extends TurboModule { +useTurboModuleInterop?: () => boolean; +useTurboModules?: () => boolean; +virtualViewPrerenderRatio?: () => number; + +runtimeCrashUiThreadUtils?: () => boolean; } const NativeReactNativeFeatureFlags: ?Spec = TurboModuleRegistry.get( diff --git a/packages/react-native/src/private/specs_DEPRECATED/components/SwitchNativeComponent.js b/packages/react-native/src/private/specs_DEPRECATED/components/SwitchNativeComponent.js index 6ddb0cb20d977e..1f9f75a3bb4294 100644 --- a/packages/react-native/src/private/specs_DEPRECATED/components/SwitchNativeComponent.js +++ b/packages/react-native/src/private/specs_DEPRECATED/components/SwitchNativeComponent.js @@ -58,4 +58,5 @@ export const Commands: NativeCommands = codegenNativeCommands({ export default (codegenNativeComponent('Switch', { paperComponentName: 'RCTSwitch', excludedPlatforms: ['android'], + interfaceOnly: true, }): ComponentType); diff --git a/packages/react-native/third-party-podspecs/DoubleConversion.podspec b/packages/react-native/third-party-podspecs/DoubleConversion.podspec index 56923bcab13a3e..9c89ac48e77ea1 100644 --- a/packages/react-native/third-party-podspecs/DoubleConversion.podspec +++ b/packages/react-native/third-party-podspecs/DoubleConversion.podspec @@ -3,6 +3,8 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +require_relative "../scripts/react_native_pods" + double_conversion_config = get_double_conversion_config() double_conversion_git_url = double_conversion_config[:git] diff --git a/packages/react-native/third-party-podspecs/RCT-Folly.podspec b/packages/react-native/third-party-podspecs/RCT-Folly.podspec index 8852179b6ce2ef..fa126875cabab7 100644 --- a/packages/react-native/third-party-podspecs/RCT-Folly.podspec +++ b/packages/react-native/third-party-podspecs/RCT-Folly.podspec @@ -3,6 +3,8 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +require_relative "../scripts/react_native_pods" + folly_config = get_folly_config() folly_config_file = folly_config[:config_file] folly_release_version = folly_config[:version] diff --git a/packages/react-native/third-party-podspecs/ReactNativeDependencies.podspec b/packages/react-native/third-party-podspecs/ReactNativeDependencies.podspec index 32ebe58e05f46f..c10c2a2f6223af 100644 --- a/packages/react-native/third-party-podspecs/ReactNativeDependencies.podspec +++ b/packages/react-native/third-party-podspecs/ReactNativeDependencies.podspec @@ -64,7 +64,7 @@ Pod::Spec.new do |spec| cp -R "$HEADERS_PATH/" Headers mkdir -p framework/packages/react-native - cp -R "$XCFRAMEWORK_PATH/.." framework/packages/react-native/ + cp -R "$XCFRAMEWORK_PATH/../." framework/packages/react-native/ find "$XCFRAMEWORK_PATH/.." -type f -exec rm {} + find "$CURRENT_PATH" -type d -empty -delete CMD diff --git a/packages/react-native/third-party-podspecs/boost.podspec b/packages/react-native/third-party-podspecs/boost.podspec index e30a2305df8e9f..5c29c4c7e5f860 100644 --- a/packages/react-native/third-party-podspecs/boost.podspec +++ b/packages/react-native/third-party-podspecs/boost.podspec @@ -3,6 +3,8 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +require_relative "../scripts/react_native_pods" + boost_config = get_boost_config() boost_git_url = boost_config[:git] @@ -13,8 +15,8 @@ Pod::Spec.new do |spec| spec.homepage = 'http://www.boost.org' spec.summary = 'Boost provides free peer-reviewed portable C++ source libraries.' spec.authors = 'Rene Rivera' - spec.source = { :git => boost_git_url, - :tag => "v1.84.0" } + spec.source = { :http => 'https://archives.boost.io/release/1.84.0/source/boost_1_84_0.tar.gz', + :sha256 => 'a5800f405508f5df8114558ca9855d2640a2de8f0445f051fa1c7c3383045724' } # Pinning to the same version as React.podspec. spec.platforms = min_supported_versions diff --git a/packages/react-native/third-party-podspecs/fast_float.podspec b/packages/react-native/third-party-podspecs/fast_float.podspec index ba0bb7c565cb9d..10f9658ec6f575 100644 --- a/packages/react-native/third-party-podspecs/fast_float.podspec +++ b/packages/react-native/third-party-podspecs/fast_float.podspec @@ -3,6 +3,8 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +require_relative "../scripts/react_native_pods" + fast_float_config = get_fast_float_config() fast_float_git_url = fast_float_config[:git] diff --git a/packages/react-native/third-party-podspecs/fmt.podspec b/packages/react-native/third-party-podspecs/fmt.podspec index 2f38990e226c13..0c92fb26257628 100644 --- a/packages/react-native/third-party-podspecs/fmt.podspec +++ b/packages/react-native/third-party-podspecs/fmt.podspec @@ -3,6 +3,8 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +require_relative "../scripts/react_native_pods" + fmt_config = get_fmt_config() fmt_git_url = fmt_config[:git] diff --git a/packages/react-native/third-party-podspecs/glog.podspec b/packages/react-native/third-party-podspecs/glog.podspec index af7668077f9045..c03f6038d0fc0f 100644 --- a/packages/react-native/third-party-podspecs/glog.podspec +++ b/packages/react-native/third-party-podspecs/glog.podspec @@ -3,6 +3,8 @@ # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. +require_relative "../scripts/react_native_pods" + glog_config = get_glog_config() glog_git_url = glog_config[:git] diff --git a/packages/react-native/third-party-podspecs/replace_dependencies_version.js b/packages/react-native/third-party-podspecs/replace_dependencies_version.js index ac3f24a7b5708f..66fa888758ebb5 100644 --- a/packages/react-native/third-party-podspecs/replace_dependencies_version.js +++ b/packages/react-native/third-party-podspecs/replace_dependencies_version.js @@ -10,7 +10,7 @@ 'use strict'; -const {execSync} = require('child_process'); +const {spawnSync} = require('child_process'); const fs = require('fs'); const yargs = require('yargs'); @@ -66,7 +66,28 @@ function replaceRNDepsConfiguration( fs.mkdirSync(finalLocation, {recursive: true}); console.log('Extracting the tarball', tarballURLPath); - execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`); + spawnSync('tar', ['-xf', tarballURLPath, '-C', finalLocation], { + stdio: 'inherit', + }); + + // Now we need to remove the extra third-party folder as we do in the podspec's prepare-script + // We need to take the ReactNativeDependencies.xcframework folder and move it up one level + // from ${finalLocation}/packages/react-native/third-party/ to ${finalLocation}/packages/react-native/ + console.log('Resolving ReactNativeDependencies.xcframework folder structure'); + const thirdPartyPath = `${finalLocation}/packages/react-native/third-party`; + const sourcePath = `${thirdPartyPath}/ReactNativeDependencies.xcframework`; + const destinationPath = `${finalLocation}/packages/react-native/ReactNativeDependencies.xcframework`; + if (fs.existsSync(sourcePath)) { + fs.renameSync(sourcePath, destinationPath); + } else { + throw new Error( + `Expected ReactNativeDependencies.xcframework to be at ${sourcePath}, but it was not found.`, + ); + } + + if (fs.existsSync(thirdPartyPath)) { + fs.rmSync(thirdPartyPath, {force: true, recursive: true}); + } } function updateLastBuildConfiguration(configuration /*: string */) { diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index 2a568e1874e6b4..c140a0d4d73b37 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -2,21 +2,13 @@ PODS: - boost (1.84.0) - DoubleConversion (1.1.6) - fast_float (8.0.0) - - FBLazyVector (1000.0.0) + - FBLazyVector (0.81.4) - fmt (11.0.2) - glog (0.3.5) - - hermes-engine (1000.0.0): - - hermes-engine/cdp (= 1000.0.0) - - hermes-engine/Hermes (= 1000.0.0) - - hermes-engine/inspector (= 1000.0.0) - - hermes-engine/inspector_chrome (= 1000.0.0) - - hermes-engine/Public (= 1000.0.0) - - hermes-engine/cdp (1000.0.0) - - hermes-engine/Hermes (1000.0.0) - - hermes-engine/inspector (1000.0.0) - - hermes-engine/inspector_chrome (1000.0.0) - - hermes-engine/Public (1000.0.0) - - MyNativeView (0.80.0-main): + - hermes-engine (0.81.4): + - hermes-engine/Pre-built (= 0.81.4) + - hermes-engine/Pre-built (0.81.4) + - MyNativeView (0.81.0-main): - boost - DoubleConversion - fast_float @@ -44,7 +36,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - NativeCxxModuleExample (0.80.0-main): + - NativeCxxModuleExample (0.81.0-main): - boost - DoubleConversion - fast_float @@ -73,7 +65,7 @@ PODS: - SocketRocket - Yoga - OCMock (3.9.4) - - OSSLibraryExample (0.80.0-main): + - OSSLibraryExample (0.81.0-main): - boost - DoubleConversion - fast_float @@ -120,27 +112,27 @@ PODS: - fast_float (= 8.0.0) - fmt (= 11.0.2) - glog - - RCTDeprecation (1000.0.0) - - RCTRequired (1000.0.0) - - RCTTypeSafety (1000.0.0): - - FBLazyVector (= 1000.0.0) - - RCTRequired (= 1000.0.0) - - React-Core (= 1000.0.0) - - React (1000.0.0): - - React-Core (= 1000.0.0) - - React-Core/DevSupport (= 1000.0.0) - - React-Core/RCTWebSocket (= 1000.0.0) - - React-RCTActionSheet (= 1000.0.0) - - React-RCTAnimation (= 1000.0.0) - - React-RCTBlob (= 1000.0.0) - - React-RCTImage (= 1000.0.0) - - React-RCTLinking (= 1000.0.0) - - React-RCTNetwork (= 1000.0.0) - - React-RCTSettings (= 1000.0.0) - - React-RCTText (= 1000.0.0) - - React-RCTVibration (= 1000.0.0) - - React-callinvoker (1000.0.0) - - React-Core (1000.0.0): + - RCTDeprecation (0.81.4) + - RCTRequired (0.81.4) + - RCTTypeSafety (0.81.4): + - FBLazyVector (= 0.81.4) + - RCTRequired (= 0.81.4) + - React-Core (= 0.81.4) + - React (0.81.4): + - React-Core (= 0.81.4) + - React-Core/DevSupport (= 0.81.4) + - React-Core/RCTWebSocket (= 0.81.4) + - React-RCTActionSheet (= 0.81.4) + - React-RCTAnimation (= 0.81.4) + - React-RCTBlob (= 0.81.4) + - React-RCTImage (= 0.81.4) + - React-RCTLinking (= 0.81.4) + - React-RCTNetwork (= 0.81.4) + - React-RCTSettings (= 0.81.4) + - React-RCTText (= 0.81.4) + - React-RCTVibration (= 0.81.4) + - React-callinvoker (0.81.4) + - React-Core (0.81.4): - boost - DoubleConversion - fast_float @@ -150,7 +142,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - RCTDeprecation - - React-Core/Default (= 1000.0.0) + - React-Core/Default (= 0.81.4) - React-cxxreact - React-featureflags - React-hermes @@ -165,7 +157,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/CoreModulesHeaders (1000.0.0): + - React-Core/CoreModulesHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -190,7 +182,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/Default (1000.0.0): + - React-Core/Default (0.81.4): - boost - DoubleConversion - fast_float @@ -214,7 +206,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/DevSupport (1000.0.0): + - React-Core/DevSupport (0.81.4): - boost - DoubleConversion - fast_float @@ -224,8 +216,8 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - RCTDeprecation - - React-Core/Default (= 1000.0.0) - - React-Core/RCTWebSocket (= 1000.0.0) + - React-Core/Default (= 0.81.4) + - React-Core/RCTWebSocket (= 0.81.4) - React-cxxreact - React-featureflags - React-hermes @@ -240,7 +232,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTActionSheetHeaders (1000.0.0): + - React-Core/RCTActionSheetHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -265,7 +257,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTAnimationHeaders (1000.0.0): + - React-Core/RCTAnimationHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -290,7 +282,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTBlobHeaders (1000.0.0): + - React-Core/RCTBlobHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -315,7 +307,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTImageHeaders (1000.0.0): + - React-Core/RCTImageHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -340,7 +332,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTLinkingHeaders (1000.0.0): + - React-Core/RCTLinkingHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -365,7 +357,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTNetworkHeaders (1000.0.0): + - React-Core/RCTNetworkHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -390,7 +382,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTPushNotificationHeaders (1000.0.0): + - React-Core/RCTPushNotificationHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -415,7 +407,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTSettingsHeaders (1000.0.0): + - React-Core/RCTSettingsHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -440,7 +432,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTTextHeaders (1000.0.0): + - React-Core/RCTTextHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -465,7 +457,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTVibrationHeaders (1000.0.0): + - React-Core/RCTVibrationHeaders (0.81.4): - boost - DoubleConversion - fast_float @@ -490,7 +482,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-Core/RCTWebSocket (1000.0.0): + - React-Core/RCTWebSocket (0.81.4): - boost - DoubleConversion - fast_float @@ -500,7 +492,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - RCTDeprecation - - React-Core/Default (= 1000.0.0) + - React-Core/Default (= 0.81.4) - React-cxxreact - React-featureflags - React-hermes @@ -515,7 +507,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-CoreModules (1000.0.0): + - React-CoreModules (0.81.4): - boost - DoubleConversion - fast_float @@ -523,20 +515,20 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric - - RCTTypeSafety (= 1000.0.0) - - React-Core/CoreModulesHeaders (= 1000.0.0) - - React-jsi (= 1000.0.0) + - RCTTypeSafety (= 0.81.4) + - React-Core/CoreModulesHeaders (= 0.81.4) + - React-jsi (= 0.81.4) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - React-NativeModulesApple - React-RCTBlob - React-RCTFBReactNativeSpec - - React-RCTImage (= 1000.0.0) + - React-RCTImage (= 0.81.4) - React-runtimeexecutor - ReactCommon - SocketRocket - - React-cxxreact (1000.0.0): + - React-cxxreact (0.81.4): - boost - DoubleConversion - fast_float @@ -545,19 +537,19 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-callinvoker (= 1000.0.0) - - React-debug (= 1000.0.0) - - React-jsi (= 1000.0.0) + - React-callinvoker (= 0.81.4) + - React-debug (= 0.81.4) + - React-jsi (= 0.81.4) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - - React-logger (= 1000.0.0) - - React-perflogger (= 1000.0.0) + - React-logger (= 0.81.4) + - React-perflogger (= 0.81.4) - React-runtimeexecutor - - React-timing (= 1000.0.0) + - React-timing (= 0.81.4) - SocketRocket - - React-debug (1000.0.0) - - React-defaultsnativemodule (1000.0.0): + - React-debug (0.81.4) + - React-defaultsnativemodule (0.81.4): - boost - DoubleConversion - fast_float @@ -574,7 +566,7 @@ PODS: - React-microtasksnativemodule - React-RCTFBReactNativeSpec - SocketRocket - - React-domnativemodule (1000.0.0): + - React-domnativemodule (0.81.4): - boost - DoubleConversion - fast_float @@ -584,6 +576,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - React-Fabric + - React-Fabric/bridging - React-FabricComponents - React-graphics - React-jsi @@ -593,7 +586,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-Fabric (1000.0.0): + - React-Fabric (0.81.4): - boost - DoubleConversion - fast_float @@ -607,22 +600,23 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/animations (= 1000.0.0) - - React-Fabric/attributedstring (= 1000.0.0) - - React-Fabric/componentregistry (= 1000.0.0) - - React-Fabric/componentregistrynative (= 1000.0.0) - - React-Fabric/components (= 1000.0.0) - - React-Fabric/consistency (= 1000.0.0) - - React-Fabric/core (= 1000.0.0) - - React-Fabric/dom (= 1000.0.0) - - React-Fabric/imagemanager (= 1000.0.0) - - React-Fabric/leakchecker (= 1000.0.0) - - React-Fabric/mounting (= 1000.0.0) - - React-Fabric/observers (= 1000.0.0) - - React-Fabric/scheduler (= 1000.0.0) - - React-Fabric/telemetry (= 1000.0.0) - - React-Fabric/templateprocessor (= 1000.0.0) - - React-Fabric/uimanager (= 1000.0.0) + - React-Fabric/animations (= 0.81.4) + - React-Fabric/attributedstring (= 0.81.4) + - React-Fabric/bridging (= 0.81.4) + - React-Fabric/componentregistry (= 0.81.4) + - React-Fabric/componentregistrynative (= 0.81.4) + - React-Fabric/components (= 0.81.4) + - React-Fabric/consistency (= 0.81.4) + - React-Fabric/core (= 0.81.4) + - React-Fabric/dom (= 0.81.4) + - React-Fabric/imagemanager (= 0.81.4) + - React-Fabric/leakchecker (= 0.81.4) + - React-Fabric/mounting (= 0.81.4) + - React-Fabric/observers (= 0.81.4) + - React-Fabric/scheduler (= 0.81.4) + - React-Fabric/telemetry (= 0.81.4) + - React-Fabric/templateprocessor (= 0.81.4) + - React-Fabric/uimanager (= 0.81.4) - React-featureflags - React-graphics - React-jsi @@ -634,7 +628,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/animations (1000.0.0): + - React-Fabric/animations (0.81.4): - boost - DoubleConversion - fast_float @@ -659,7 +653,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/attributedstring (1000.0.0): + - React-Fabric/attributedstring (0.81.4): - boost - DoubleConversion - fast_float @@ -684,7 +678,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/componentregistry (1000.0.0): + - React-Fabric/bridging (0.81.4): - boost - DoubleConversion - fast_float @@ -709,7 +703,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/componentregistrynative (1000.0.0): + - React-Fabric/componentregistry (0.81.4): - boost - DoubleConversion - fast_float @@ -734,7 +728,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components (1000.0.0): + - React-Fabric/componentregistrynative (0.81.4): - boost - DoubleConversion - fast_float @@ -748,10 +742,6 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/components/legacyviewmanagerinterop (= 1000.0.0) - - React-Fabric/components/root (= 1000.0.0) - - React-Fabric/components/scrollview (= 1000.0.0) - - React-Fabric/components/view (= 1000.0.0) - React-featureflags - React-graphics - React-jsi @@ -763,7 +753,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components/legacyviewmanagerinterop (1000.0.0): + - React-Fabric/components (0.81.4): - boost - DoubleConversion - fast_float @@ -777,6 +767,10 @@ PODS: - React-Core - React-cxxreact - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.81.4) + - React-Fabric/components/root (= 0.81.4) + - React-Fabric/components/scrollview (= 0.81.4) + - React-Fabric/components/view (= 0.81.4) - React-featureflags - React-graphics - React-jsi @@ -788,7 +782,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components/root (1000.0.0): + - React-Fabric/components/legacyviewmanagerinterop (0.81.4): - boost - DoubleConversion - fast_float @@ -813,7 +807,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components/scrollview (1000.0.0): + - React-Fabric/components/root (0.81.4): - boost - DoubleConversion - fast_float @@ -838,7 +832,32 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/components/view (1000.0.0): + - React-Fabric/components/scrollview (0.81.4): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/components/view (0.81.4): - boost - DoubleConversion - fast_float @@ -865,7 +884,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-Fabric/consistency (1000.0.0): + - React-Fabric/consistency (0.81.4): - boost - DoubleConversion - fast_float @@ -890,7 +909,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/core (1000.0.0): + - React-Fabric/core (0.81.4): - boost - DoubleConversion - fast_float @@ -915,7 +934,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/dom (1000.0.0): + - React-Fabric/dom (0.81.4): - boost - DoubleConversion - fast_float @@ -940,7 +959,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/imagemanager (1000.0.0): + - React-Fabric/imagemanager (0.81.4): - boost - DoubleConversion - fast_float @@ -965,7 +984,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/leakchecker (1000.0.0): + - React-Fabric/leakchecker (0.81.4): - boost - DoubleConversion - fast_float @@ -990,7 +1009,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/mounting (1000.0.0): + - React-Fabric/mounting (0.81.4): - boost - DoubleConversion - fast_float @@ -1015,7 +1034,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/observers (1000.0.0): + - React-Fabric/observers (0.81.4): - boost - DoubleConversion - fast_float @@ -1029,7 +1048,7 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/observers/events (= 1000.0.0) + - React-Fabric/observers/events (= 0.81.4) - React-featureflags - React-graphics - React-jsi @@ -1041,7 +1060,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/observers/events (1000.0.0): + - React-Fabric/observers/events (0.81.4): - boost - DoubleConversion - fast_float @@ -1066,7 +1085,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/scheduler (1000.0.0): + - React-Fabric/scheduler (0.81.4): - boost - DoubleConversion - fast_float @@ -1093,7 +1112,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/telemetry (1000.0.0): + - React-Fabric/telemetry (0.81.4): - boost - DoubleConversion - fast_float @@ -1118,7 +1137,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/templateprocessor (1000.0.0): + - React-Fabric/templateprocessor (0.81.4): - boost - DoubleConversion - fast_float @@ -1143,7 +1162,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/uimanager (1000.0.0): + - React-Fabric/uimanager (0.81.4): - boost - DoubleConversion - fast_float @@ -1157,7 +1176,7 @@ PODS: - React-Core - React-cxxreact - React-debug - - React-Fabric/uimanager/consistency (= 1000.0.0) + - React-Fabric/uimanager/consistency (= 0.81.4) - React-featureflags - React-graphics - React-jsi @@ -1170,7 +1189,7 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-Fabric/uimanager/consistency (1000.0.0): + - React-Fabric/uimanager/consistency (0.81.4): - boost - DoubleConversion - fast_float @@ -1196,7 +1215,74 @@ PODS: - React-utils - ReactCommon/turbomodule/core - SocketRocket - - React-FabricComponents (1000.0.0): + - React-FabricComponents (0.81.4): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.81.4) + - React-FabricComponents/textlayoutmanager (= 0.81.4) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components (0.81.4): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.81.4) + - React-FabricComponents/components/iostextinput (= 0.81.4) + - React-FabricComponents/components/modal (= 0.81.4) + - React-FabricComponents/components/rncore (= 0.81.4) + - React-FabricComponents/components/safeareaview (= 0.81.4) + - React-FabricComponents/components/scrollview (= 0.81.4) + - React-FabricComponents/components/switch (= 0.81.4) + - React-FabricComponents/components/text (= 0.81.4) + - React-FabricComponents/components/textinput (= 0.81.4) + - React-FabricComponents/components/unimplementedview (= 0.81.4) + - React-FabricComponents/components/virtualview (= 0.81.4) + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/inputaccessory (0.81.4): - boost - DoubleConversion - fast_float @@ -1211,8 +1297,6 @@ PODS: - React-cxxreact - React-debug - React-Fabric - - React-FabricComponents/components (= 1000.0.0) - - React-FabricComponents/textlayoutmanager (= 1000.0.0) - React-featureflags - React-graphics - React-jsi @@ -1225,7 +1309,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components (1000.0.0): + - React-FabricComponents/components/iostextinput (0.81.4): - boost - DoubleConversion - fast_float @@ -1240,14 +1324,6 @@ PODS: - React-cxxreact - React-debug - React-Fabric - - React-FabricComponents/components/inputaccessory (= 1000.0.0) - - React-FabricComponents/components/iostextinput (= 1000.0.0) - - React-FabricComponents/components/modal (= 1000.0.0) - - React-FabricComponents/components/safeareaview (= 1000.0.0) - - React-FabricComponents/components/scrollview (= 1000.0.0) - - React-FabricComponents/components/text (= 1000.0.0) - - React-FabricComponents/components/textinput (= 1000.0.0) - - React-FabricComponents/components/unimplementedview (= 1000.0.0) - React-featureflags - React-graphics - React-jsi @@ -1260,7 +1336,34 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/inputaccessory (1000.0.0): + - React-FabricComponents/components/modal (0.81.4): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric (= 2024.11.18.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-RCTFBReactNativeSpec + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/rncore (0.81.4): - boost - DoubleConversion - fast_float @@ -1287,7 +1390,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/iostextinput (1000.0.0): + - React-FabricComponents/components/safeareaview (0.81.4): - boost - DoubleConversion - fast_float @@ -1314,7 +1417,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/modal (1000.0.0): + - React-FabricComponents/components/scrollview (0.81.4): - boost - DoubleConversion - fast_float @@ -1341,7 +1444,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/safeareaview (1000.0.0): + - React-FabricComponents/components/switch (0.81.4): - boost - DoubleConversion - fast_float @@ -1368,7 +1471,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/scrollview (1000.0.0): + - React-FabricComponents/components/text (0.81.4): - boost - DoubleConversion - fast_float @@ -1395,7 +1498,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/text (1000.0.0): + - React-FabricComponents/components/textinput (0.81.4): - boost - DoubleConversion - fast_float @@ -1422,7 +1525,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/textinput (1000.0.0): + - React-FabricComponents/components/unimplementedview (0.81.4): - boost - DoubleConversion - fast_float @@ -1449,7 +1552,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/components/unimplementedview (1000.0.0): + - React-FabricComponents/components/virtualview (0.81.4): - boost - DoubleConversion - fast_float @@ -1476,7 +1579,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricComponents/textlayoutmanager (1000.0.0): + - React-FabricComponents/textlayoutmanager (0.81.4): - boost - DoubleConversion - fast_float @@ -1503,7 +1606,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - React-FabricImage (1000.0.0): + - React-FabricImage (0.81.4): - boost - DoubleConversion - fast_float @@ -1512,21 +1615,21 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - RCTRequired (= 1000.0.0) - - RCTTypeSafety (= 1000.0.0) + - RCTRequired (= 0.81.4) + - RCTTypeSafety (= 0.81.4) - React-Fabric - React-featureflags - React-graphics - React-ImageManager - React-jsi - - React-jsiexecutor (= 1000.0.0) + - React-jsiexecutor (= 0.81.4) - React-logger - React-rendererdebug - React-utils - ReactCommon - SocketRocket - Yoga - - React-featureflags (1000.0.0): + - React-featureflags (0.81.4): - boost - DoubleConversion - fast_float @@ -1535,7 +1638,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-featureflagsnativemodule (1000.0.0): + - React-featureflagsnativemodule (0.81.4): - boost - DoubleConversion - fast_float @@ -1550,7 +1653,7 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - SocketRocket - - React-graphics (1000.0.0): + - React-graphics (0.81.4): - boost - DoubleConversion - fast_float @@ -1563,7 +1666,7 @@ PODS: - React-jsiexecutor - React-utils - SocketRocket - - React-hermes (1000.0.0): + - React-hermes (0.81.4): - boost - DoubleConversion - fast_float @@ -1572,16 +1675,16 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-cxxreact (= 1000.0.0) + - React-cxxreact (= 0.81.4) - React-jsi - - React-jsiexecutor (= 1000.0.0) + - React-jsiexecutor (= 0.81.4) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - - React-perflogger (= 1000.0.0) + - React-perflogger (= 0.81.4) - React-runtimeexecutor - SocketRocket - - React-idlecallbacksnativemodule (1000.0.0): + - React-idlecallbacksnativemodule (0.81.4): - boost - DoubleConversion - fast_float @@ -1597,7 +1700,7 @@ PODS: - React-runtimescheduler - ReactCommon/turbomodule/core - SocketRocket - - React-ImageManager (1000.0.0): + - React-ImageManager (0.81.4): - boost - DoubleConversion - fast_float @@ -1612,7 +1715,7 @@ PODS: - React-rendererdebug - React-utils - SocketRocket - - React-jserrorhandler (1000.0.0): + - React-jserrorhandler (0.81.4): - boost - DoubleConversion - fast_float @@ -1627,7 +1730,7 @@ PODS: - React-jsi - ReactCommon/turbomodule/bridging - SocketRocket - - React-jsi (1000.0.0): + - React-jsi (0.81.4): - boost - DoubleConversion - fast_float @@ -1637,7 +1740,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-jsiexecutor (1000.0.0): + - React-jsiexecutor (0.81.4): - boost - DoubleConversion - fast_float @@ -1646,15 +1749,15 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-cxxreact (= 1000.0.0) - - React-jsi (= 1000.0.0) + - React-cxxreact (= 0.81.4) + - React-jsi (= 0.81.4) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - - React-perflogger (= 1000.0.0) + - React-perflogger (= 0.81.4) - React-runtimeexecutor - SocketRocket - - React-jsinspector (1000.0.0): + - React-jsinspector (0.81.4): - boost - DoubleConversion - fast_float @@ -1668,10 +1771,11 @@ PODS: - React-jsinspectorcdp - React-jsinspectornetwork - React-jsinspectortracing - - React-perflogger (= 1000.0.0) + - React-oscompat + - React-perflogger (= 0.81.4) - React-runtimeexecutor - SocketRocket - - React-jsinspectorcdp (1000.0.0): + - React-jsinspectorcdp (0.81.4): - boost - DoubleConversion - fast_float @@ -1680,7 +1784,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-jsinspectornetwork (1000.0.0): + - React-jsinspectornetwork (0.81.4): - boost - DoubleConversion - fast_float @@ -1693,7 +1797,7 @@ PODS: - React-performancetimeline - React-timing - SocketRocket - - React-jsinspectortracing (1000.0.0): + - React-jsinspectortracing (0.81.4): - boost - DoubleConversion - fast_float @@ -1704,7 +1808,7 @@ PODS: - React-oscompat - React-timing - SocketRocket - - React-jsitooling (1000.0.0): + - React-jsitooling (0.81.4): - boost - DoubleConversion - fast_float @@ -1712,16 +1816,16 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric - - React-cxxreact (= 1000.0.0) - - React-jsi (= 1000.0.0) + - React-cxxreact (= 0.81.4) + - React-jsi (= 0.81.4) - React-jsinspector - React-jsinspectorcdp - React-jsinspectortracing - React-runtimeexecutor - SocketRocket - - React-jsitracing (1000.0.0): + - React-jsitracing (0.81.4): - React-jsi - - React-logger (1000.0.0): + - React-logger (0.81.4): - boost - DoubleConversion - fast_float @@ -1730,7 +1834,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-Mapbuffer (1000.0.0): + - React-Mapbuffer (0.81.4): - boost - DoubleConversion - fast_float @@ -1740,7 +1844,7 @@ PODS: - RCT-Folly/Fabric - React-debug - SocketRocket - - React-microtasksnativemodule (1000.0.0): + - React-microtasksnativemodule (0.81.4): - boost - DoubleConversion - fast_float @@ -1754,7 +1858,7 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon/turbomodule/core - SocketRocket - - React-NativeModulesApple (1000.0.0): + - React-NativeModulesApple (0.81.4): - boost - DoubleConversion - fast_float @@ -1774,8 +1878,8 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - SocketRocket - - React-oscompat (1000.0.0) - - React-perflogger (1000.0.0): + - React-oscompat (0.81.4) + - React-perflogger (0.81.4): - boost - DoubleConversion - fast_float @@ -1784,7 +1888,7 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - SocketRocket - - React-performancetimeline (1000.0.0): + - React-performancetimeline (0.81.4): - boost - DoubleConversion - fast_float @@ -1797,9 +1901,9 @@ PODS: - React-perflogger - React-timing - SocketRocket - - React-RCTActionSheet (1000.0.0): - - React-Core/RCTActionSheetHeaders (= 1000.0.0) - - React-RCTAnimation (1000.0.0): + - React-RCTActionSheet (0.81.4): + - React-Core/RCTActionSheetHeaders (= 0.81.4) + - React-RCTAnimation (0.81.4): - boost - DoubleConversion - fast_float @@ -1815,7 +1919,7 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - React-RCTAppDelegate (1000.0.0): + - React-RCTAppDelegate (0.81.4): - boost - DoubleConversion - fast_float @@ -1849,7 +1953,7 @@ PODS: - React-utils - ReactCommon - SocketRocket - - React-RCTBlob (1000.0.0): + - React-RCTBlob (0.81.4): - boost - DoubleConversion - fast_float @@ -1868,7 +1972,7 @@ PODS: - React-RCTNetwork - ReactCommon - SocketRocket - - React-RCTFabric (1000.0.0): + - React-RCTFabric (0.81.4): - boost - DoubleConversion - fast_float @@ -1903,7 +2007,7 @@ PODS: - React-utils - SocketRocket - Yoga - - React-RCTFBReactNativeSpec (1000.0.0): + - React-RCTFBReactNativeSpec (0.81.4): - boost - DoubleConversion - fast_float @@ -1917,10 +2021,10 @@ PODS: - React-Core - React-jsi - React-NativeModulesApple - - React-RCTFBReactNativeSpec/components (= 1000.0.0) + - React-RCTFBReactNativeSpec/components (= 0.81.4) - ReactCommon - SocketRocket - - React-RCTFBReactNativeSpec/components (1000.0.0): + - React-RCTFBReactNativeSpec/components (0.81.4): - boost - DoubleConversion - fast_float @@ -1943,7 +2047,7 @@ PODS: - ReactCommon - SocketRocket - Yoga - - React-RCTImage (1000.0.0): + - React-RCTImage (0.81.4): - boost - DoubleConversion - fast_float @@ -1959,14 +2063,14 @@ PODS: - React-RCTNetwork - ReactCommon - SocketRocket - - React-RCTLinking (1000.0.0): - - React-Core/RCTLinkingHeaders (= 1000.0.0) - - React-jsi (= 1000.0.0) + - React-RCTLinking (0.81.4): + - React-Core/RCTLinkingHeaders (= 0.81.4) + - React-jsi (= 0.81.4) - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - ReactCommon/turbomodule/core (= 1000.0.0) - - React-RCTNetwork (1000.0.0): + - ReactCommon/turbomodule/core (= 0.81.4) + - React-RCTNetwork (0.81.4): - boost - DoubleConversion - fast_float @@ -1984,14 +2088,14 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - React-RCTPushNotification (1000.0.0): + - React-RCTPushNotification (0.81.4): - RCTTypeSafety - React-Core/RCTPushNotificationHeaders - React-jsi - React-NativeModulesApple - React-RCTFBReactNativeSpec - ReactCommon - - React-RCTRuntime (1000.0.0): + - React-RCTRuntime (0.81.4): - boost - DoubleConversion - fast_float @@ -2011,7 +2115,7 @@ PODS: - React-runtimeexecutor - React-RuntimeHermes - SocketRocket - - React-RCTSettings (1000.0.0): + - React-RCTSettings (0.81.4): - boost - DoubleConversion - fast_float @@ -2026,7 +2130,7 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - React-RCTTest (1000.0.0): + - React-RCTTest (0.81.4): - boost - DoubleConversion - fast_float @@ -2034,15 +2138,15 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric - - React-Core (= 1000.0.0) - - React-CoreModules (= 1000.0.0) - - React-jsi (= 1000.0.0) - - ReactCommon/turbomodule/core (= 1000.0.0) + - React-Core (= 0.81.4) + - React-CoreModules (= 0.81.4) + - React-jsi (= 0.81.4) + - ReactCommon/turbomodule/core (= 0.81.4) - SocketRocket - - React-RCTText (1000.0.0): - - React-Core/RCTTextHeaders (= 1000.0.0) + - React-RCTText (0.81.4): + - React-Core/RCTTextHeaders (= 0.81.4) - Yoga - - React-RCTVibration (1000.0.0): + - React-RCTVibration (0.81.4): - boost - DoubleConversion - fast_float @@ -2056,11 +2160,11 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - React-rendererconsistency (1000.0.0) - - React-renderercss (1000.0.0): + - React-rendererconsistency (0.81.4) + - React-renderercss (0.81.4): - React-debug - React-utils - - React-rendererdebug (1000.0.0): + - React-rendererdebug (0.81.4): - boost - DoubleConversion - fast_float @@ -2070,7 +2174,7 @@ PODS: - RCT-Folly/Fabric - React-debug - SocketRocket - - React-RuntimeApple (1000.0.0): + - React-RuntimeApple (0.81.4): - boost - DoubleConversion - fast_float @@ -2099,7 +2203,7 @@ PODS: - React-runtimescheduler - React-utils - SocketRocket - - React-RuntimeCore (1000.0.0): + - React-RuntimeCore (0.81.4): - boost - DoubleConversion - fast_float @@ -2121,9 +2225,20 @@ PODS: - React-runtimescheduler - React-utils - SocketRocket - - React-runtimeexecutor (1000.0.0): - - React-jsi (= 1000.0.0) - - React-RuntimeHermes (1000.0.0): + - React-runtimeexecutor (0.81.4): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-debug + - React-featureflags + - React-jsi (= 0.81.4) + - React-utils + - SocketRocket + - React-RuntimeHermes (0.81.4): - boost - DoubleConversion - fast_float @@ -2144,7 +2259,7 @@ PODS: - React-runtimeexecutor - React-utils - SocketRocket - - React-runtimescheduler (1000.0.0): + - React-runtimescheduler (0.81.4): - boost - DoubleConversion - fast_float @@ -2166,8 +2281,9 @@ PODS: - React-timing - React-utils - SocketRocket - - React-timing (1000.0.0) - - React-utils (1000.0.0): + - React-timing (0.81.4): + - React-debug + - React-utils (0.81.4): - boost - DoubleConversion - fast_float @@ -2177,11 +2293,11 @@ PODS: - RCT-Folly - RCT-Folly/Fabric - React-debug - - React-jsi (= 1000.0.0) + - React-jsi (= 0.81.4) - SocketRocket - - ReactAppDependencyProvider (1000.0.0): + - ReactAppDependencyProvider (0.81.4): - ReactCodegen - - ReactCodegen (1000.0.0): + - ReactCodegen (0.81.4): - boost - DoubleConversion - fast_float @@ -2207,7 +2323,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - SocketRocket - - ReactCommon (1000.0.0): + - ReactCommon (0.81.4): - boost - DoubleConversion - fast_float @@ -2215,9 +2331,9 @@ PODS: - glog - RCT-Folly - RCT-Folly/Fabric - - ReactCommon/turbomodule (= 1000.0.0) + - ReactCommon/turbomodule (= 0.81.4) - SocketRocket - - ReactCommon-Samples (1000.0.0): + - ReactCommon-Samples (0.81.4): - boost - DoubleConversion - fast_float @@ -2233,7 +2349,7 @@ PODS: - React-RCTFBReactNativeSpec - ReactCommon - SocketRocket - - ReactCommon/turbomodule (1000.0.0): + - ReactCommon/turbomodule (0.81.4): - boost - DoubleConversion - fast_float @@ -2242,15 +2358,15 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-callinvoker (= 1000.0.0) - - React-cxxreact (= 1000.0.0) - - React-jsi (= 1000.0.0) - - React-logger (= 1000.0.0) - - React-perflogger (= 1000.0.0) - - ReactCommon/turbomodule/bridging (= 1000.0.0) - - ReactCommon/turbomodule/core (= 1000.0.0) + - React-callinvoker (= 0.81.4) + - React-cxxreact (= 0.81.4) + - React-jsi (= 0.81.4) + - React-logger (= 0.81.4) + - React-perflogger (= 0.81.4) + - ReactCommon/turbomodule/bridging (= 0.81.4) + - ReactCommon/turbomodule/core (= 0.81.4) - SocketRocket - - ReactCommon/turbomodule/bridging (1000.0.0): + - ReactCommon/turbomodule/bridging (0.81.4): - boost - DoubleConversion - fast_float @@ -2259,13 +2375,13 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-callinvoker (= 1000.0.0) - - React-cxxreact (= 1000.0.0) - - React-jsi (= 1000.0.0) - - React-logger (= 1000.0.0) - - React-perflogger (= 1000.0.0) + - React-callinvoker (= 0.81.4) + - React-cxxreact (= 0.81.4) + - React-jsi (= 0.81.4) + - React-logger (= 0.81.4) + - React-perflogger (= 0.81.4) - SocketRocket - - ReactCommon/turbomodule/core (1000.0.0): + - ReactCommon/turbomodule/core (0.81.4): - boost - DoubleConversion - fast_float @@ -2274,16 +2390,16 @@ PODS: - hermes-engine - RCT-Folly - RCT-Folly/Fabric - - React-callinvoker (= 1000.0.0) - - React-cxxreact (= 1000.0.0) - - React-debug (= 1000.0.0) - - React-featureflags (= 1000.0.0) - - React-jsi (= 1000.0.0) - - React-logger (= 1000.0.0) - - React-perflogger (= 1000.0.0) - - React-utils (= 1000.0.0) + - React-callinvoker (= 0.81.4) + - React-cxxreact (= 0.81.4) + - React-debug (= 0.81.4) + - React-featureflags (= 0.81.4) + - React-jsi (= 0.81.4) + - React-logger (= 0.81.4) + - React-perflogger (= 0.81.4) + - React-utils (= 0.81.4) - SocketRocket - - ScreenshotManager (0.80.0-main): + - ScreenshotManager (0.81.0-main): - boost - DoubleConversion - fast_float @@ -2417,7 +2533,7 @@ EXTERNAL SOURCES: :podspec: "../react-native/third-party-podspecs/glog.podspec" hermes-engine: :podspec: "../react-native/sdks/hermes-engine/hermes-engine.podspec" - :tag: '' + :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 MyNativeView: :path: NativeComponentExample NativeCxxModuleExample: @@ -2562,87 +2678,87 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon/yoga" SPEC CHECKSUMS: - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 + boost: 2510dbf7cb0576ee1373ade97a4a627a844364d0 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 - FBLazyVector: d3c2dd739a63c1a124e775df075dc7c517a719cb + FBLazyVector: 941bef1c8eeabd9fe1f501e30a5220beee913886 fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd - glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 - hermes-engine: fd5605ca4043c8317548d649d0b64e5dafc2a124 - MyNativeView: 9b03b64f5cd9220bf7ef49fcdfc468effe704fa0 - NativeCxxModuleExample: 2897d5f3aee8a02dd201a3dd41fbc94ada1aac5e + glog: b76040d18988c5978b0fa384619df2ec2227be90 + hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394 + MyNativeView: 0387bccdf96db2946b3e4f44df7370b55da40682 + NativeCxxModuleExample: 7d0b25d0b54a57727669cd7b06279499ced25daf OCMock: 589f2c84dacb1f5aaf6e4cec1f292551fe748e74 - OSSLibraryExample: bf5b5a4238613dae3c536faff58f87528e6191cf + OSSLibraryExample: e829af5c61a90a691f3e51c583d9c1236a12d8d3 RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 - RCTDeprecation: 3808e36294137f9ee5668f4df2e73dc079cd1dcf - RCTRequired: a00614e2da5344c2cda3d287050b6cee00e21dc6 - RCTTypeSafety: 459a16418c6b413060d35434ba3e83f5b0bd2651 - React: 170a01a19ba2525ab7f11243e2df6b19bf268093 - React-callinvoker: f08f425e4043cd1998a158b6e39a6aed1fd1d718 - React-Core: d35c5cf69898fd026e5cd93a0454b1d42e999d3e - React-CoreModules: 3ce1d43f6cc37f43759ec543ce1c0010080f1de1 - React-cxxreact: 3169b106af8f405e182d131d3a0844201e3252e2 - React-debug: 195df38487d3f48a7af04deddeb4a5c6d4440416 - React-defaultsnativemodule: 8afea5a4bd07addb523bf48489b8a684ea1bdff0 - React-domnativemodule: 8a813559774e65bc800fe489bbb454cd546f21d7 - React-Fabric: e9846203f11ab49be8b1329c76301bbd399ef2ad - React-FabricComponents: 032d6f01f1d6966633418c0fece18f698ddb7897 - React-FabricImage: 264c9ce5241e43e25b94c8de55ac6c3c8a046472 - React-featureflags: 595651ea13c63a9f77f06d9a1973b665b4a28b7e - React-featureflagsnativemodule: 06823479a2ee210cfa0e9c19447c2722a8d995f2 - React-graphics: 1f99b9b5515eac389f0cf9c85b03abc366d6a933 - React-hermes: 745eb45a6d0aae7e890d0aae0def54670bbd103c - React-idlecallbacksnativemodule: 4e65f183318b8a0fbabc481a4eafc0f0d62d1cbf - React-ImageManager: a6833445e17879933378b7c0ba45ee42115c14bc - React-jserrorhandler: bec134a192c50338193544404d45df24fb8a19ca - React-jsi: 4ad77650fb0ca4229569eb2532db7a87e3d12662 - React-jsiexecutor: fa5b80bdbe1ceffc33a892da20fc07b4dfa4df7a - React-jsinspector: 10b5dc4eef2a3d05b80be2114ed676496c5bf59c - React-jsinspectorcdp: 5fb266e5f23d3a2819ba848e9d4d0b6b00f95934 - React-jsinspectornetwork: 1655a81f3fe14789df41e063bd56dd130cc3562a - React-jsinspectortracing: 5b0be488e06958a572e1badfe8509929ae1cc83b - React-jsitooling: 9e563b89f94cf4baf872fe47105d60ae83f4ce4d - React-jsitracing: ce443686f52538d1033ce7db1e7d643e866262f0 - React-logger: 116c3ae5a9906671d157aa00882a5ee75a5a7ebc - React-Mapbuffer: fc937cfa41140d7724c559c3d16c50dd725361c8 - React-microtasksnativemodule: 09899c7389250279bdcc5384f0281bb069979855 - React-NativeModulesApple: d05b718ccd8b68c184e76dbc1efb63385197595b - React-oscompat: 7133e0e945cda067ae36b22502df663d73002864 - React-perflogger: ada3cdf3dfc8b7cd1fabe3c91b672e23981611ab - React-performancetimeline: e7d5849d89ee39557dcd56dfb6e7b0d49003d925 - React-RCTActionSheet: 1bf8cc8086ad1c15da3407dfb7bc9dd94dc7595d - React-RCTAnimation: 263593e66c89bf810604b1ace15dfa382a1ca2df - React-RCTAppDelegate: 3a99437ffa7460f85045de65f9bed6b1c47d5f09 - React-RCTBlob: 7b76230c53fe87d305eeeb250b0aae031bb6cbae - React-RCTFabric: 2fd2ef899c7219fd39fd61c39750510f88a81434 - React-RCTFBReactNativeSpec: 4ed3b463eb26726b04ac65c73797207ecab5634c - React-RCTImage: de404b6b0ebe53976a97e3a0dee819c83e12977b - React-RCTLinking: 06742cfad41c506091403a414370743a4ed75af3 - React-RCTNetwork: b4577eec0092c16d8996e415e4cac7a372d6d362 - React-RCTPushNotification: ea11178d499696516e0ff9ae335edbe99b06f94b - React-RCTRuntime: 925039e78fc530e0421c308ccc607f214f3c7be1 - React-RCTSettings: d3c2dd305ec81f7faf42762ec598d57f07fd43be - React-RCTTest: 2db46eda60bc2228cb67622a580e8e86b00088d9 - React-RCTText: e416825b80c530647040ef91d23ffd35ccc87981 - React-RCTVibration: 1837a27fc16eeffc9509779c3334fde54c012bcc - React-rendererconsistency: 777c894edc43dde01499189917ac54ee76ae6a6a - React-renderercss: a9cb6ba7f49a80dc4b4f7008bae1590d12f27049 - React-rendererdebug: fea8bde927403a198742b2d940a5f1cd8230c0b4 - React-RuntimeApple: 6a0c164a8855edb4987b90da2d4d8601302de72d - React-RuntimeCore: 6dec37113b759b76641bd028bfbbbec8cf923356 - React-runtimeexecutor: a16a24b964e964afe833a652d703e1bb39f10dc9 - React-RuntimeHermes: d4f661204d3061219a63951eb4efed4dcaf3f12f - React-runtimescheduler: ae44fe8b4170a9d59f62e8b7d7b060c179db739d - React-timing: 9d49179631e5e3c759e6e82d4c613c73da80a144 - React-utils: 0944df8d553d66b27f486282c42a84a969fd2f6c - ReactAppDependencyProvider: 68f2d2cefd6c9b9f2865246be2bfe86ebd49238d - ReactCodegen: 8fc4c8b6562a59409b5650e67f78719093d093bf - ReactCommon: a53973ab35d399560ace331ec9e2b26db0592cec - ReactCommon-Samples: dcc128cbf51ac38d2578791750d0a046d1b8a5e9 - ScreenshotManager: 6ac0b11c7bbd5cf1abd9448fb3b856fe6fd65ff7 + RCTDeprecation: c0ed3249a97243002615517dff789bf4666cf585 + RCTRequired: 58719f5124f9267b5f9649c08bf23d9aea845b23 + RCTTypeSafety: 4aefa8328ab1f86da273f08517f1f6b343f6c2cc + React: 2073376f47c71b7e9a0af7535986a77522ce1049 + React-callinvoker: 751b6f2c83347a0486391c3f266f291f0f53b27e + React-Core: dff5d29973349b11dd6631c9498456d75f846d5e + React-CoreModules: c0ae04452e4c5d30e06f8e94692a49107657f537 + React-cxxreact: 376fd672c95dfb64ad5cc246e6a1e9edb78dec4c + React-debug: 7b56a0a7da432353287d2eedac727903e35278f5 + React-defaultsnativemodule: 393b81aaa6211408f50a6ef00a277847256dd881 + React-domnativemodule: 5fb5829baa7a7a0f217019cbad1eb226d94f7062 + React-Fabric: a17c4ae35503673b57b91c2d1388429e7cbee452 + React-FabricComponents: f87e70a70576b7fc90bd6efaf98f57c264bb02aa + React-FabricImage: d806eb2695d7ef355ec28d1a21f5a14ac26b1cae + React-featureflags: 1690ec3c453920b6308e23a4e24eb9c3632f9c75 + React-featureflagsnativemodule: 7b7e8483fc671c5a33aefd699b7c7a3c0bdfdfec + React-graphics: ea146ee799dc816524a3a0922fc7be0b5a52dcc1 + React-hermes: fcbdc45ecf38259fe3b12642bd0757c52270a107 + React-idlecallbacksnativemodule: a353f9162eaa7ad787e68aba9f52a1cfa8154098 + React-ImageManager: ec5cf55ce9cc81719eb5f1f51d23d04db851c86c + React-jserrorhandler: 594c593f3d60f527be081e2cace7710c2bd9f524 + React-jsi: 59ec3190dd364cca86a58869e7755477d2468948 + React-jsiexecutor: b87d78a2e8dd7a6f56e9cdac038da45de98c944f + React-jsinspector: b9204adf1af622c98e78af96ec1bca615c2ce2bd + React-jsinspectorcdp: 4a356fa69e412d35d3a38c44d4a6cc555c5931e8 + React-jsinspectornetwork: 7820056773178f321cbf18689e1ffcd38276a878 + React-jsinspectortracing: b341c5ef6e031a33e0bd462d67fd397e8e9cd612 + React-jsitooling: 401655e05cb966b0081225c5201d90734a567cb9 + React-jsitracing: 67eff6dea0cb58a1e7bd8b49243012d88c0f511e + React-logger: a3cb5b29c32b8e447b5a96919340e89334062b48 + React-Mapbuffer: 9d2434a42701d6144ca18f0ca1c4507808ca7696 + React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b + React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3 + React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d + React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510 + React-performancetimeline: 9041c53efa07f537164dcfe7670a36642352f4c2 + React-RCTActionSheet: 42195ae666e6d79b4af2346770f765b7c29435b9 + React-RCTAnimation: fa103ccc3503b1ed8dedca7e62e7823937748843 + React-RCTAppDelegate: 665d4baf19424cef08276e9ac0d8771eec4519f9 + React-RCTBlob: 0fa9530c255644db095f2c4fd8d89738d9d9ecc0 + React-RCTFabric: 1fcd8af6e25f92532f56b4ba092e58662c14d156 + React-RCTFBReactNativeSpec: db171247585774f9f0a30f75109cc51568686213 + React-RCTImage: ba824e61ce2e920a239a65d130b83c3a1d426dff + React-RCTLinking: d2dc199c37e71e6f505d9eca3e5c33be930014d4 + React-RCTNetwork: 87137d4b9bd77e5068f854dd5c1f30d4b072faf6 + React-RCTPushNotification: ca9c096b09d90972dbf4d604ae181109671a9328 + React-RCTRuntime: 137fafaa808a8b7e76a510e8be45f9f827899daa + React-RCTSettings: 71f5c7fd7b5f4e725a4e2114a4b4373d0e46048f + React-RCTTest: 9a9be55957a94098d1b7227d8a9319edc605445f + React-RCTText: b94d4699b49285bee22b8ebf768924d607eccee3 + React-RCTVibration: 6e3993c4f6c36a3899059f9a9ead560ddaf5a7d7 + React-rendererconsistency: b4785e5ed837dc7c242bbc5fdd464b33ef5bfae7 + React-renderercss: e6fb0ba387b389c595ffa86b8b628716d31f58dc + React-rendererdebug: 60a03de5c7ea59bf2d39791eb43c4c0f5d8b24e3 + React-RuntimeApple: 3df6788cd9b938bb8cb28298d80b5fbd98a4d852 + React-RuntimeCore: fad8adb4172c414c00ff6980250caf35601a0f5d + React-runtimeexecutor: d2db7e72d97751855ea0bf5273d2ac84e5ea390c + React-RuntimeHermes: 04faa4cf9a285136a6d73738787fe36020170613 + React-runtimescheduler: f6a1c9555e7131b4a8b64cce01489ad0405f6e8d + React-timing: 1e6a8acb66e2b7ac9d418956617fd1fdb19322fd + React-utils: 52bbb03f130319ef82e4c3bc7a85eaacdb1fec87 + ReactAppDependencyProvider: 433ddfb4536948630aadd5bd925aff8a632d2fe3 + ReactCodegen: cb2a3dea7ad220568d9453e17d275dbd9f9bc044 + ReactCommon: 394c6b92765cf6d211c2c3f7f6bc601dffb316a6 + ReactCommon-Samples: d083c8092a5ad0df33b1ca589cb2d909fbd6914e + ScreenshotManager: cd9e43b0c749ba38101effc22be58f4a69440d5f SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Yoga: c4e80f1c2235fa6236d71a49e5bb2ee74d987921 + Yoga: a3ed390a19db0459bd6839823a6ac6d9c6db198d -PODFILE CHECKSUM: 8591f96a513620a2a83a0b9a125ad3fa32ea1369 +PODFILE CHECKSUM: b0abc972ac4c3a335250548a31500196b1a2c163 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/packages/rn-tester/RCTTest/React-RCTTest.podspec b/packages/rn-tester/RCTTest/React-RCTTest.podspec index a65d1032d41b74..e96768635781f4 100644 --- a/packages/rn-tester/RCTTest/React-RCTTest.podspec +++ b/packages/rn-tester/RCTTest/React-RCTTest.podspec @@ -41,4 +41,5 @@ Pod::Spec.new do |s| s.dependency "React-jsi", version add_rn_third_party_dependencies(s) + add_rncore_dependency(s) end diff --git a/packages/rn-tester/android/app/src/debug/AndroidManifest.xml b/packages/rn-tester/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index fa26aa56e1c144..00000000000000 --- a/packages/rn-tester/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/packages/rn-tester/android/app/src/main/AndroidManifest.xml b/packages/rn-tester/android/app/src/main/AndroidManifest.xml index bd35388167baa1..a842f3a0d29136 100644 --- a/packages/rn-tester/android/app/src/main/AndroidManifest.xml +++ b/packages/rn-tester/android/app/src/main/AndroidManifest.xml @@ -50,6 +50,7 @@ = emptyList() - override fun getViewManagerNames(reactContext: ReactApplicationContext) = - listOf("RNTMyNativeView", "RNTMyLegacyNativeView", "RNTReportFullyDrawnView") + listOf("RNTMyNativeView", "RNTMyLegacyNativeView", "RNTReportFullyDrawnView", CustomViewManager.REACT_CLASS) override fun createViewManagers( reactContext: ReactApplicationContext @@ -99,7 +101,8 @@ internal class RNTesterApplication : Application(), ReactApplication { listOf( MyNativeViewManager(), MyLegacyViewManager(reactContext), - ReportFullyDrawnViewManager()) + ReportFullyDrawnViewManager(), + CustomViewManager()) override fun createViewManager( reactContext: ReactApplicationContext, @@ -109,6 +112,7 @@ internal class RNTesterApplication : Application(), ReactApplication { "RNTMyNativeView" -> MyNativeViewManager() "RNTMyLegacyNativeView" -> MyLegacyViewManager(reactContext) "RNTReportFullyDrawnView" -> ReportFullyDrawnViewManager() + CustomViewManager.REACT_CLASS -> CustomViewManager() else -> null } }) diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/CustomViewManager.kt b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/CustomViewManager.kt new file mode 100644 index 00000000000000..b02b95bf23ef29 --- /dev/null +++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/component/CustomViewManager.kt @@ -0,0 +1,79 @@ +package com.facebook.react.uiapp.component + +import android.util.Log +import android.view.View +import android.view.ViewGroup +import androidx.core.view.children +import com.facebook.react.bridge.UiThreadUtil +import com.facebook.react.module.annotations.ReactModule +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.ViewManagerDelegate +import com.facebook.react.views.view.ReactViewGroup +import com.facebook.react.viewmanagers.CustomViewManagerInterface +import com.facebook.react.viewmanagers.CustomViewManagerDelegate + +@ReactModule(name = CustomViewManager.REACT_CLASS) +public class CustomViewManager : ViewGroupManager(), CustomViewManagerInterface { + private val delegate: CustomViewManagerDelegate = + CustomViewManagerDelegate(this) + + override fun getDelegate(): ViewManagerDelegate = delegate + + override fun getName(): String = REACT_CLASS + + override fun createViewInstance(reactContext: ThemedReactContext): ReactViewGroup = ReactViewGroup(reactContext) + + private val listOfTransitions = mutableListOf>() + public override fun startViewTransition(view: ReactViewGroup?) { + if (view == null) return + + val reactContext = UIManagerHelper.getReactContext(view) + val reactTag = view.id + val uiManager = UIManagerHelper.getUIManagerForReactTag(reactContext, reactTag) + val surfaceId = UIManagerHelper.getSurfaceId(reactContext) + + view.children.forEach { child -> + Log.d("HannoDebug", "Starting view transition for child: ${child.javaClass.simpleName}:${child.id}") + uiManager!!.markViewAsInTransition(surfaceId, child.id, true) + view.startViewTransition(child) + listOfTransitions.add(Pair(view, child)) + if (child is ViewGroup) { + child.children.forEach { + Log.d("HannoDebug", "Starting view transition for grandchild: ${it.javaClass.simpleName}:${it.id}") + uiManager.markViewAsInTransition(surfaceId, it.id, true) + child.startViewTransition(it) + listOfTransitions.add(Pair(child, it) ) + } + } + } + (view.parent as? ViewGroup)?.startViewTransition(view)?.also { + listOfTransitions.add(Pair(view.parent as ViewGroup, view) ) + } + } + + public override fun endViewTransition(view: ReactViewGroup?) { + if (view == null) return + + val reactContext = UIManagerHelper.getReactContext(view) + val reactTag = view.id + val uiManager = UIManagerHelper.getUIManagerForReactTag(reactContext, reactTag) + val surfaceId = UIManagerHelper.getSurfaceId(reactContext) + + // TODO: once fixed also stress test with .reversed(), it should work correctly then + listOfTransitions.reversed().forEach { (parent, child) -> + Log.d("HannoDebug", "Ending view transition for child: ${child.javaClass.simpleName}:${child.id}") + + UiThreadUtil.runOnUiThread({ + parent.endViewTransition(child) + uiManager!!.markViewAsInTransition(surfaceId, child.id, false) + }) + } + listOfTransitions.clear() + } + + public companion object { + public const val REACT_CLASS: String = "CustomView" + } +} diff --git a/packages/rn-tester/js/examples/Playground/CustomViewNativeComponent.js b/packages/rn-tester/js/examples/Playground/CustomViewNativeComponent.js new file mode 100644 index 00000000000000..9bfcccafcda289 --- /dev/null +++ b/packages/rn-tester/js/examples/Playground/CustomViewNativeComponent.js @@ -0,0 +1,22 @@ +// @flow strict-local + +import type {HostComponent, ViewProps} from 'react-native'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import {codegenNativeCommands} from 'react-native'; + +type NativeProps = $ReadOnly<{| + ...ViewProps, +|}>; + +export default (codegenNativeComponent( + 'CustomView', +): HostComponent); + +interface NativeCommands { + +startViewTransition: (viewRef: React.ElementRef>) => void; + +endViewTransition: (viewRef: React.ElementRef>) => void; +} + +export const Commands: NativeCommands = codegenNativeCommands({ + supportedCommands: ['startViewTransition', 'endViewTransition'], +}); diff --git a/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js b/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js index d37d0d4a9154a7..418eec0155d616 100644 --- a/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js +++ b/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js @@ -12,21 +12,124 @@ import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; import RNTesterText from '../../components/RNTesterText'; import * as React from 'react'; -import {StyleSheet, View} from 'react-native'; + +import {useMemo, useRef, useState} from 'react'; +import {View, Text, Button, StyleSheet} from 'react-native'; +// import CustomViewNativeComponent, {Commands} from '@discordapp/rtn-codegen/js/CustomViewNativeComponent'; +import CustomViewNativeComponent, { Commands } from './CustomViewNativeComponent'; function Playground() { + const [changeZIndex, setChangeZIndex] = useState(false); + const viewRef = useRef>(null); + + const startViewTransition = () => { + if (viewRef.current) { + Commands.startViewTransition(viewRef.current); + } + }; + const endViewTransition = () => { + if (viewRef.current) { + Commands.endViewTransition(viewRef.current); + } + }; + return ( - - - Edit "RNTesterPlayground.js" to change this file - + + +