mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
111
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c863979abc | ||
|
|
27d152cd82 | ||
|
|
b7b0013348 | ||
|
|
325217371f | ||
|
|
6f1e48d34d | ||
|
|
0aa79897e6 | ||
|
|
08a6a1ae65 | ||
|
|
ad732b84ab | ||
|
|
284a20b0de | ||
|
|
742c205e52 | ||
|
|
93dc62ac08 | ||
|
|
e0b07568fe | ||
|
|
5adfe16ad2 | ||
|
|
7b456c0e3f | ||
|
|
0e91eadc12 | ||
|
|
1e8c9639a0 | ||
|
|
269456cf55 | ||
|
|
ccc8ce0d01 | ||
|
|
1cc12ce7fd | ||
|
|
28986a7599 | ||
|
|
ce75271d47 | ||
|
|
99c60bfe0a | ||
|
|
a477ee6ced | ||
|
|
a17a7003b1 | ||
|
|
f70c01cc30 | ||
|
|
bc6b4f0525 | ||
|
|
73133a31d5 | ||
|
|
9258e90c92 | ||
|
|
f783d30beb | ||
|
|
2915902763 | ||
|
|
c31ff55b92 | ||
|
|
d405e306e3 | ||
|
|
ad88309843 | ||
|
|
fd29d68c16 | ||
|
|
5348f9ff1e | ||
|
|
e08e27a6fa | ||
|
|
ded4ae1f78 | ||
|
|
331fab0683 | ||
|
|
33aa7b9161 | ||
|
|
560ac23001 | ||
|
|
87cd1c0a0a | ||
|
|
3ea4f156c9 | ||
|
|
5e1768746a | ||
|
|
e259961e8b | ||
|
|
aeaae4294f | ||
|
|
74b6acb1f0 | ||
|
|
a91e598e6a | ||
|
|
10cabcc0a7 | ||
|
|
3bfbaa8455 | ||
|
|
742ffa39fb | ||
|
|
6b40f35032 | ||
|
|
4556bdf4fc | ||
|
|
98fdc965b9 | ||
|
|
e8cdb99baa | ||
|
|
a15fc102e6 | ||
|
|
6ddfa708b0 | ||
|
|
1456d06c86 | ||
|
|
7bfadb429c | ||
|
|
faaec467b9 | ||
|
|
64ce7695f5 | ||
|
|
3ae9328571 | ||
|
|
89401b78cd | ||
|
|
089fb10740 | ||
|
|
2528e48e93 | ||
|
|
cb24596c33 | ||
|
|
cfb6c968dd | ||
|
|
3c17e101ee | ||
|
|
8174d02811 | ||
|
|
7e2f17ffe2 | ||
|
|
e13ee7a3d0 | ||
|
|
9613d377de | ||
|
|
3a0a7b0701 | ||
|
|
c777ad0b84 | ||
|
|
3412215e3a | ||
|
|
19fd1536a4 | ||
|
|
f6b7bd93d8 | ||
|
|
2ade12d747 | ||
|
|
f53f6137e6 | ||
|
|
ffe928ce50 | ||
|
|
d20b9cfdf0 | ||
|
|
b6e1938959 | ||
|
|
ddafd94124 | ||
|
|
eecd07c58b | ||
|
|
0e42d33cbc | ||
|
|
abc8fe1c92 | ||
|
|
a6ea626255 | ||
|
|
1da608f6f1 | ||
|
|
98f5a4e118 | ||
|
|
92af97591b | ||
|
|
30d8a153e6 | ||
|
|
05a61e8161 | ||
|
|
feec8d0148 | ||
|
|
05521adbc8 | ||
|
|
cf6569bc18 | ||
|
|
c64f698e5f | ||
|
|
b5be57cb76 | ||
|
|
b0530f0abf | ||
|
|
b417b0c2d5 | ||
|
|
7ec2839955 | ||
|
|
f880bfd1ab | ||
|
|
fed27e71f8 | ||
|
|
0fb0bd1ae7 | ||
|
|
6b9d931a88 | ||
|
|
e61daa831d | ||
|
|
a8386aa878 | ||
|
|
e8b55a4456 | ||
|
|
70962ef3ed | ||
|
|
46eab9c509 | ||
|
|
50667eceb1 | ||
|
|
e82a677c79 | ||
|
|
42ca46b95c |
@@ -48,6 +48,7 @@ packages/react-native/flow/
|
||||
[options]
|
||||
enums=true
|
||||
casting_syntax=both
|
||||
component_syntax=true
|
||||
|
||||
emoji=true
|
||||
|
||||
|
||||
@@ -26,9 +26,6 @@ runs:
|
||||
- name: Lint code
|
||||
shell: bash
|
||||
run: ./.github/workflow-scripts/exec_swallow_error.sh yarn lint --format junit -o ./reports/junit/eslint/results.xml
|
||||
- name: Lint java
|
||||
shell: bash
|
||||
run: ./.github/workflow-scripts/exec_swallow_error.sh yarn lint-java --check
|
||||
- name: Lint file structure
|
||||
shell: bash
|
||||
run: ./.github/workflow-scripts/lint_files.sh
|
||||
|
||||
@@ -13,9 +13,6 @@ export GITHUB_REPO=-react-native
|
||||
|
||||
echo flow
|
||||
npm run flow-check --silent --json
|
||||
|
||||
echo google-java-format
|
||||
node scripts/lint-java.js --diff
|
||||
} | node private/react-native-bots/code-analysis-bot.js
|
||||
|
||||
STATUS=$?
|
||||
|
||||
@@ -23,8 +23,8 @@ jobs:
|
||||
id: restore-ios-slice
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: packages/react-native/third-party/
|
||||
key: v2-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
|
||||
key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
|
||||
path: packages/react-native/
|
||||
- name: Setup node.js
|
||||
if: steps.restore-ios-slice.outputs.cache-hit != 'true'
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -101,6 +101,12 @@ jobs:
|
||||
# This is going to be replaced by a CLI script
|
||||
cd packages/react-native
|
||||
node scripts/ios-prebuild -b -f "${{ matrix.flavor }}" -p "${{ matrix.slice }}"
|
||||
- name: Upload headers
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: prebuild-ios-core-headers-${{ matrix.flavor }}-${{ matrix.slice }}
|
||||
path:
|
||||
packages/react-native/.build/headers
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
with:
|
||||
@@ -111,10 +117,10 @@ jobs:
|
||||
uses: actions/cache/save@v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} # To avoid that the cache explode
|
||||
with:
|
||||
key: v2-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
|
||||
enableCrossOsArchive: true
|
||||
key: v3-ios-core-${{ matrix.slice }}-${{ matrix.flavor }}-${{ hashFiles('packages/react-native/Package.swift') }}-${{ hashFiles('packages/react-native/scripts/ios-prebuild/setup.js') }}
|
||||
path: |
|
||||
packages/react-native/.build/output/spm/${{ matrix.flavor }}/Build/Products
|
||||
packages/react-native/.build/headers
|
||||
|
||||
compose-xcframework:
|
||||
runs-on: macos-14
|
||||
@@ -153,6 +159,13 @@ jobs:
|
||||
pattern: prebuild-ios-core-slice-${{ matrix.flavor }}-*
|
||||
path: packages/react-native/.build/output/spm/${{ matrix.flavor }}/Build/Products
|
||||
merge-multiple: true
|
||||
- name: Download headers
|
||||
if: steps.restore-ios-xcframework.outputs.cache-hit != 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: prebuild-ios-core-headers-${{ matrix.flavor }}-*
|
||||
path: packages/react-native/.build/headers
|
||||
merge-multiple: true
|
||||
- name: Setup Keychain
|
||||
if: ${{ steps.restore-ios-xcframework.outputs.cache-hit != 'true' && env.REACT_ORG_CODE_SIGNING_P12_CERT != '' }}
|
||||
uses: apple-actions/import-codesign-certs@v3 # https://github.com/marketplace/actions/import-code-signing-certificates
|
||||
|
||||
@@ -140,27 +140,6 @@ jobs:
|
||||
hermes-version: ${{ needs.prepare_hermes_workspace.outputs.hermes-version }}
|
||||
react-native-version: ${{ needs.prepare_hermes_workspace.outputs.react-native-version }}
|
||||
|
||||
build_android:
|
||||
runs-on: 8-core-ubuntu
|
||||
needs: [set_release_type]
|
||||
container:
|
||||
image: reactnativecommunity/react-native-android:latest
|
||||
env:
|
||||
TERM: "dumb"
|
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||
ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }}
|
||||
ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }}
|
||||
ORG_GRADLE_PROJECT_SONATYPE_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_USERNAME }}
|
||||
ORG_GRADLE_PROJECT_SONATYPE_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_SONATYPE_PASSWORD }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Build Android
|
||||
uses: ./.github/actions/build-android
|
||||
with:
|
||||
release-type: ${{ needs.set_release_type.outputs.RELEASE_TYPE }}
|
||||
gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
|
||||
|
||||
build_npm_package:
|
||||
runs-on: 8-core-ubuntu
|
||||
needs:
|
||||
@@ -170,7 +149,6 @@ jobs:
|
||||
build_hermes_macos,
|
||||
build_hermesc_linux,
|
||||
build_hermesc_windows,
|
||||
build_android,
|
||||
prebuild_apple_dependencies,
|
||||
prebuild_react_native_core,
|
||||
]
|
||||
|
||||
@@ -139,7 +139,6 @@ vendor/
|
||||
/packages/react-native/React/FBReactNativeSpec/
|
||||
/packages/react-native-codegen/lib
|
||||
/packages/react-native-codegen/tmp/
|
||||
/packages/react-native/ReactCommon/react/renderer/components/rncore/
|
||||
/packages/rn-tester/NativeModuleExample/ScreenshotManagerSpec*
|
||||
/**/RCTThirdPartyFabricComponentsProvider.*
|
||||
|
||||
|
||||
@@ -321,6 +321,22 @@
|
||||
- Fix bug: unstable_hasComponent(*) = true for unregistered components for n > 1th call. ([f4d99d6a23](https://github.com/facebook/react-native/commit/f4d99d6a23a08c2ef2c2fff78dc966961608683b) by [@RSNara](https://github.com/RSNara))
|
||||
- RCTDeviceInfo: fix crash due to failure to get AccessibilityManager ([ac23323da1](https://github.com/facebook/react-native/commit/ac23323da1bda2ce271797aa58dd74ffb0a5992f) by Adam Ernst)
|
||||
|
||||
## v0.79.4
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Codegen:** Backports fix: exclusion of selectively disabled libraries from codegen generation ([3acfe01382](https://github.com/facebook/react-native/commit/3acfe01382e67b16386bb04f7730abc479fec1cc) by [@cipolleschi](https://github.com/cipolleschi))
|
||||
- **Flow:** Publish top-level Flow types for `react-native` ([50667eceb1](https://github.com/facebook/react-native/commit/50667eceb1be4771375d6a3cc2f4e42d4d8aad3a) by [@aswinandro](https://github.com/aswinandro))
|
||||
- **Runtime:** Disabling `updateRuntimeShadowNodeReferencesOnCommit` by default. ([2cd074d77a](https://github.com/facebook/react-native/commit/2cd074d77a706f4fe4396ffdf87408f781e935a4) by [@lenaic](https://github.com/lenaic))
|
||||
|
||||
#### Android specific
|
||||
|
||||
- **TextInput:** Fix broken focus behavior for TextInput in older Android versions (< 9) ([0ebaaed281](https://github.com/facebook/react-native/commit/0ebaaed281fcfa01b20a17a461c1d0b489a7f0a2) by [@joevilches](https://github.com/joevilches))
|
||||
|
||||
#### iOS specific
|
||||
|
||||
- **Dimensions:** Fix Dimensions not changing when orientation changes ([235ac2cca0](https://github.com/facebook/react-native/commit/235ac2cca0b173934d329f3a797f136a219d315f) by [@riteshshukla04](https://github.com/riteshshukla04))
|
||||
|
||||
## v0.79.3
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -55,6 +55,8 @@ nexusPublishing {
|
||||
sonatype {
|
||||
username.set(sonatypeUsername)
|
||||
password.set(sonatypePassword)
|
||||
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
|
||||
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -17,7 +17,6 @@
|
||||
"format": "npm run prettier && npm run clang-format",
|
||||
"featureflags": "yarn --cwd packages/react-native featureflags",
|
||||
"lint-ci": "./.github/workflow-scripts/analyze_code.sh && yarn shellcheck",
|
||||
"lint-java": "node ./scripts/lint-java.js",
|
||||
"lint-markdown": "markdownlint-cli2 2>&1",
|
||||
"lint": "eslint --max-warnings 0 .",
|
||||
"prettier": "prettier --write \"./**/*.{js,md,yml,ts,tsx}\"",
|
||||
@@ -55,7 +54,7 @@
|
||||
"@react-native/metro-babel-transformer": "0.80.0-main",
|
||||
"@react-native/metro-config": "0.80.0-main",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react": "^19.1.0",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"ansi-styles": "^4.2.1",
|
||||
"babel-plugin-minify-dead-code-elimination": "^0.5.2",
|
||||
@@ -74,7 +73,6 @@
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-native": "^4.0.0",
|
||||
"eslint-plugin-redundant-undefined": "^0.4.0",
|
||||
"eslint-plugin-relay": "^1.8.3",
|
||||
@@ -111,6 +109,7 @@
|
||||
"ws": "^6.2.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"eslint-plugin-react-hooks": "6.1.0-canary-12bc60f5-20250613",
|
||||
"react-is": "19.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.tasks.internal
|
||||
|
||||
import javax.inject.Inject
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.ConfigurableFileCollection
|
||||
import org.gradle.api.file.CopySpec
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.DuplicatesStrategy
|
||||
import org.gradle.api.file.FileSystemOperations
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.*
|
||||
|
||||
/**
|
||||
* A task that takes care of extracting gflags from a source folder/zip and preparing it to be
|
||||
* consumed by the NDK. This task will also take care of applying the mapping for gflags parameters.
|
||||
*/
|
||||
abstract class PrepareGflagsTask : DefaultTask() {
|
||||
|
||||
@get:InputFiles abstract val gflagsPath: ConfigurableFileCollection
|
||||
@get:InputDirectory abstract val gflagsThirdPartyPath: DirectoryProperty
|
||||
@get:Input abstract val gflagsVersion: Property<String>
|
||||
|
||||
@get:OutputDirectory abstract val outputDir: DirectoryProperty
|
||||
|
||||
@get:Inject abstract val fs: FileSystemOperations
|
||||
|
||||
@TaskAction
|
||||
fun taskAction() {
|
||||
val commonCopyConfig: (action: CopySpec) -> Unit = { action ->
|
||||
action.from(gflagsPath)
|
||||
action.from(gflagsThirdPartyPath)
|
||||
action.duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
action.includeEmptyDirs = false
|
||||
action.into(outputDir)
|
||||
}
|
||||
|
||||
fs.copy { action ->
|
||||
commonCopyConfig(action)
|
||||
action.include(
|
||||
"gflags-${gflagsVersion.get()}/src/*.h",
|
||||
"gflags-${gflagsVersion.get()}/src/*.cc",
|
||||
"CMakeLists.txt")
|
||||
action.filesMatching("*/src/*") { matchedFile ->
|
||||
matchedFile.path = "gflags/${matchedFile.name}"
|
||||
}
|
||||
}
|
||||
|
||||
fs.copy { action ->
|
||||
commonCopyConfig(action)
|
||||
action.include("gflags-${gflagsVersion.get()}/src/gflags_declare.h.in")
|
||||
action.filesMatching("*/src/*") { matchedFile ->
|
||||
matchedFile.filter { line ->
|
||||
// Replace all placeholders with appropriate values
|
||||
// see https://github.com/gflags/gflags/blob/v2.2.0/src/gflags_declare.h.in
|
||||
line
|
||||
.replace(Regex("@GFLAGS_NAMESPACE@"), "gflags")
|
||||
.replace(
|
||||
Regex(
|
||||
"@(HAVE_STDINT_H|HAVE_SYS_TYPES_H|HAVE_INTTYPES_H|GFLAGS_INTTYPES_FORMAT_C99)@"),
|
||||
"1")
|
||||
.replace(Regex("@([A-Z0-9_]+)@"), "1")
|
||||
}
|
||||
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
|
||||
}
|
||||
}
|
||||
|
||||
fs.copy { action ->
|
||||
commonCopyConfig(action)
|
||||
action.include("gflags-${gflagsVersion.get()}/src/config.h.in")
|
||||
action.filesMatching("*/src/*") { matchedFile ->
|
||||
matchedFile.filter { line -> line.replace(Regex("^#cmakedefine"), "//cmakedefine") }
|
||||
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
|
||||
}
|
||||
}
|
||||
|
||||
fs.copy { action ->
|
||||
commonCopyConfig(action)
|
||||
action.include("gflags-${gflagsVersion.get()}/src/gflags_ns.h.in")
|
||||
action.filesMatching("*/src/*") { matchedFile ->
|
||||
matchedFile.filter { line ->
|
||||
line.replace(Regex("@ns@"), "google").replace(Regex("@NS@"), "google".uppercase())
|
||||
}
|
||||
matchedFile.path = "gflags/gflags_google.h"
|
||||
}
|
||||
}
|
||||
|
||||
fs.copy { action ->
|
||||
commonCopyConfig(action)
|
||||
action.include("gflags-${gflagsVersion.get()}/src/gflags.h.in")
|
||||
action.filesMatching("*/src/*") { matchedFile ->
|
||||
matchedFile.filter { line ->
|
||||
line
|
||||
.replace(Regex("@GFLAGS_ATTRIBUTE_UNUSED@"), "")
|
||||
.replace(Regex("@INCLUDE_GFLAGS_NS_H@"), "#include \"gflags/gflags_google.h\"")
|
||||
}
|
||||
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
|
||||
}
|
||||
}
|
||||
|
||||
fs.copy { action ->
|
||||
commonCopyConfig(action)
|
||||
action.include("gflags-${gflagsVersion.get()}/src/gflags_completions.h.in")
|
||||
action.filesMatching("*/src/*") { matchedFile ->
|
||||
matchedFile.filter { line -> line.replace(Regex("@GFLAGS_NAMESPACE@"), "gflags") }
|
||||
matchedFile.path = "gflags/${matchedFile.name.removeSuffix(".in")}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -37,7 +37,7 @@ internal object DependencyUtils {
|
||||
}
|
||||
}
|
||||
// We add the snapshot for users on nightlies.
|
||||
mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/") { repo ->
|
||||
mavenRepoFromUrl("https://central.sonatype.com/repository/maven-snapshots/") { repo ->
|
||||
repo.content { it.excludeGroup("org.webkit") }
|
||||
}
|
||||
repositories.mavenCentral { repo ->
|
||||
|
||||
+2
@@ -126,6 +126,8 @@ internal object NdkConfiguratorUtils {
|
||||
): Pair<List<String>, List<String>> {
|
||||
val excludes = mutableListOf<String>()
|
||||
val includes = mutableListOf<String>()
|
||||
|
||||
// note: libjsctooling.so is kept here for backward compatibility.
|
||||
when {
|
||||
hermesEnabled -> {
|
||||
excludes.add("**/libjsc.so")
|
||||
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.tasks.internal
|
||||
|
||||
import com.facebook.react.tests.createProject
|
||||
import com.facebook.react.tests.createTestTask
|
||||
import java.io.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class PrepareGflagsTaskTest {
|
||||
|
||||
@get:Rule val tempFolder = TemporaryFolder()
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun prepareGflagsTask_withMissingConfiguration_fails() {
|
||||
val task = createTestTask<PrepareGflagsTask>()
|
||||
|
||||
task.taskAction()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun prepareGflagsTask_copiesCMakefile() {
|
||||
val gflagspath = tempFolder.newFolder("gflagspath")
|
||||
val output = tempFolder.newFolder("output")
|
||||
val project = createProject()
|
||||
val gflagsThirdPartyPath = File(project.projectDir, "src/main/jni/third-party/gflags/")
|
||||
val task =
|
||||
createTestTask<PrepareGflagsTask>(project = project) {
|
||||
it.gflagsPath.setFrom(gflagspath)
|
||||
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
|
||||
it.gflagsVersion.set("1.0.0")
|
||||
it.outputDir.set(output)
|
||||
}
|
||||
File(gflagsThirdPartyPath, "CMakeLists.txt").apply {
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
}
|
||||
task.taskAction()
|
||||
|
||||
assertThat(output.listFiles()!!.any { it.name == "CMakeLists.txt" }).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun prepareGflagsTask_copiesSourceCodeAndHeaders() {
|
||||
val gflagspath = tempFolder.newFolder("gflagspath")
|
||||
val gflagsThirdPartyPath = tempFolder.newFolder("gflagspath/jni")
|
||||
val output = tempFolder.newFolder("output")
|
||||
val task =
|
||||
createTestTask<PrepareGflagsTask> {
|
||||
it.gflagsPath.setFrom(gflagspath)
|
||||
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
|
||||
it.gflagsVersion.set("1.0.0")
|
||||
it.outputDir.set(output)
|
||||
}
|
||||
File(gflagspath, "gflags-1.0.0/src/gflags.cc").apply {
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
}
|
||||
File(gflagspath, "gflags-1.0.0/src/util.h").apply {
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
}
|
||||
|
||||
task.taskAction()
|
||||
|
||||
assertThat(File(output, "gflags/gflags.cc").exists()).isTrue()
|
||||
assertThat(File(output, "gflags/util.h").exists()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun prepareGflagsTask_replacesTokenCorrectly() {
|
||||
val gflagspath = tempFolder.newFolder("gflagspath")
|
||||
val gflagsThirdPartyPath = tempFolder.newFolder("gflagspath/jni")
|
||||
val output = tempFolder.newFolder("output")
|
||||
val task =
|
||||
createTestTask<PrepareGflagsTask> {
|
||||
it.gflagsPath.setFrom(gflagspath)
|
||||
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
|
||||
it.gflagsVersion.set("1.0.0")
|
||||
it.outputDir.set(output)
|
||||
}
|
||||
File(gflagspath, "gflags-1.0.0/src/gflags_declare.h.in").apply {
|
||||
parentFile.mkdirs()
|
||||
writeText(
|
||||
"""
|
||||
#define GFLAGS_NAMESPACE @GFLAGS_NAMESPACE@
|
||||
#include <string>
|
||||
#if @HAVE_STDINT_H@
|
||||
# include <stdint.h>
|
||||
#elif @HAVE_SYS_TYPES_H@
|
||||
# include <sys/types.h>
|
||||
#elif @HAVE_INTTYPES_H@
|
||||
# include <inttypes.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace GFLAGS_NAMESPACE {
|
||||
|
||||
#if @GFLAGS_INTTYPES_FORMAT_C99@ // C99
|
||||
typedef int32_t int32;
|
||||
typedef uint32_t uint32;
|
||||
typedef int64_t int64;
|
||||
typedef uint64_t uint64;
|
||||
#elif @GFLAGS_INTTYPES_FORMAT_BSD@ // BSD
|
||||
typedef int32_t int32;
|
||||
typedef u_int32_t uint32;
|
||||
typedef int64_t int64;
|
||||
typedef u_int64_t uint64;
|
||||
#elif @GFLAGS_INTTYPES_FORMAT_VC7@ // Windows
|
||||
typedef __int32 int32;
|
||||
typedef unsigned __int32 uint32;
|
||||
typedef __int64 int64;
|
||||
typedef unsigned __int64 uint64;
|
||||
#else
|
||||
# error Do not know how to define a 32-bit integer quantity on your system
|
||||
#endif
|
||||
|
||||
} // namespace GFLAGS_NAMESPACE
|
||||
""")
|
||||
}
|
||||
File(gflagspath, "gflags-1.0.0/src/config.h.in").apply {
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
writeText("#cmakedefine")
|
||||
}
|
||||
File(gflagspath, "gflags-1.0.0/src/gflags_ns.h.in").apply {
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
writeText("@ns@ @NS@")
|
||||
}
|
||||
File(gflagspath, "gflags-1.0.0/src/gflags.h.in").apply {
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
writeText("@GFLAGS_ATTRIBUTE_UNUSED@\n@INCLUDE_GFLAGS_NS_H@")
|
||||
}
|
||||
File(gflagspath, "gflags-1.0.0/src/gflags_completions.h.in").apply {
|
||||
parentFile.mkdirs()
|
||||
createNewFile()
|
||||
writeText("@GFLAGS_NAMESPACE@")
|
||||
}
|
||||
|
||||
task.taskAction()
|
||||
|
||||
val declareFile = File(output, "gflags/gflags_declare.h")
|
||||
assertThat(declareFile.exists()).isTrue()
|
||||
assertEquals(
|
||||
declareFile.readText(),
|
||||
"""
|
||||
#define GFLAGS_NAMESPACE gflags
|
||||
#include <string>
|
||||
#if 1
|
||||
# include <stdint.h>
|
||||
#elif 1
|
||||
# include <sys/types.h>
|
||||
#elif 1
|
||||
# include <inttypes.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace GFLAGS_NAMESPACE {
|
||||
|
||||
#if 1 // C99
|
||||
typedef int32_t int32;
|
||||
typedef uint32_t uint32;
|
||||
typedef int64_t int64;
|
||||
typedef uint64_t uint64;
|
||||
#elif 1 // BSD
|
||||
typedef int32_t int32;
|
||||
typedef u_int32_t uint32;
|
||||
typedef int64_t int64;
|
||||
typedef u_int64_t uint64;
|
||||
#elif 1 // Windows
|
||||
typedef __int32 int32;
|
||||
typedef unsigned __int32 uint32;
|
||||
typedef __int64 int64;
|
||||
typedef unsigned __int64 uint64;
|
||||
#else
|
||||
# error Do not know how to define a 32-bit integer quantity on your system
|
||||
#endif
|
||||
|
||||
} // namespace GFLAGS_NAMESPACE
|
||||
""")
|
||||
|
||||
val configFile = File(output, "gflags/config.h")
|
||||
assertThat(configFile.exists()).isTrue()
|
||||
assertEquals(configFile.readText(), "//cmakedefine")
|
||||
|
||||
val nsFile = File(output, "gflags/gflags_google.h")
|
||||
assertThat(nsFile.exists()).isTrue()
|
||||
assertEquals(nsFile.readText(), "google GOOGLE")
|
||||
|
||||
val gflagsFile = File(output, "gflags/gflags.h")
|
||||
assertThat(gflagsFile.exists()).isTrue()
|
||||
assertEquals(gflagsFile.readText(), "\n#include \"gflags/gflags_google.h\"")
|
||||
|
||||
val completionsFile = File(output, "gflags/gflags_completions.h")
|
||||
assertThat(completionsFile.exists()).isTrue()
|
||||
assertEquals(completionsFile.readText(), "gflags")
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -45,7 +45,7 @@ class DependencyUtilsTest {
|
||||
|
||||
@Test
|
||||
fun configureRepositories_containsSnapshotRepo() {
|
||||
val repositoryURI = URI.create("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
val repositoryURI = URI.create("https://central.sonatype.com/repository/maven-snapshots/")
|
||||
val project = createProject()
|
||||
|
||||
configureRepositories(project)
|
||||
@@ -176,7 +176,7 @@ class DependencyUtilsTest {
|
||||
|
||||
@Test
|
||||
fun configureRepositories_snapshotRepoHasHigherPriorityThanMavenCentral() {
|
||||
val repositoryURI = URI.create("https://oss.sonatype.org/content/repositories/snapshots/")
|
||||
val repositoryURI = URI.create("https://central.sonatype.com/repository/maven-snapshots/")
|
||||
val mavenCentralURI = URI.create("https://repo.maven.apache.org/maven2/")
|
||||
val project = createProject()
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
],
|
||||
"dependencies": {},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react": "^19.1.0",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
},
|
||||
|
||||
+2
-1
@@ -15,8 +15,9 @@ const lazyImports = require('./lazy-imports');
|
||||
|
||||
const EXCLUDED_FIRST_PARTY_PATHS = [
|
||||
/[/\\]node_modules[/\\]/,
|
||||
/[/\\]packages[/\\]react-native(?:-fantom)?[/\\]/,
|
||||
/[/\\]packages[/\\]react-native[/\\]/,
|
||||
/[/\\]packages[/\\]virtualized-lists[/\\]/,
|
||||
/[/\\]private[/\\]react-native-fantom[/\\]/,
|
||||
];
|
||||
|
||||
function isTypeScriptSource(fileName) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
export type PlatformType = 'iOS' | 'android';
|
||||
|
||||
export type SchemaType = $ReadOnly<{
|
||||
libraryName?: string,
|
||||
modules: $ReadOnly<{
|
||||
[hasteModuleName: string]: ComponentSchema | NativeModuleSchema,
|
||||
}>,
|
||||
|
||||
@@ -28,6 +28,11 @@ const argv = yargs
|
||||
alias: 'exclude',
|
||||
default: null,
|
||||
})
|
||||
.option('l', {
|
||||
describe: 'Library name to use for schema generation',
|
||||
alias: 'libraryName',
|
||||
default: null,
|
||||
})
|
||||
.parseSync();
|
||||
|
||||
const [outfile, ...fileList] = argv._;
|
||||
@@ -35,10 +40,12 @@ const platform: ?string = argv.platform;
|
||||
const exclude: string = argv.exclude;
|
||||
const excludeRegExp: ?RegExp =
|
||||
exclude != null && exclude !== '' ? new RegExp(exclude) : null;
|
||||
const libraryName: ?string = argv.libraryName;
|
||||
|
||||
combineSchemasInFileListAndWriteToFile(
|
||||
fileList,
|
||||
platform != null ? platform.toLowerCase() : platform,
|
||||
outfile,
|
||||
excludeRegExp,
|
||||
libraryName,
|
||||
);
|
||||
|
||||
@@ -21,8 +21,11 @@ const path = require('path');
|
||||
const flowParser = new FlowParser();
|
||||
const typescriptParser = new TypeScriptParser();
|
||||
|
||||
function combineSchemas(files: Array<string>): SchemaType {
|
||||
return files.reduce(
|
||||
function combineSchemas(
|
||||
files: Array<string>,
|
||||
libraryName: ?string,
|
||||
): SchemaType {
|
||||
const combined = files.reduce(
|
||||
(merged, filename) => {
|
||||
const contents = fs.readFileSync(filename, 'utf8');
|
||||
|
||||
@@ -46,6 +49,11 @@ function combineSchemas(files: Array<string>): SchemaType {
|
||||
},
|
||||
{modules: {}},
|
||||
);
|
||||
|
||||
return {
|
||||
libraryName: libraryName || '',
|
||||
modules: combined.modules,
|
||||
};
|
||||
}
|
||||
|
||||
function expandDirectoriesIntoFiles(
|
||||
@@ -74,13 +82,14 @@ function combineSchemasInFileList(
|
||||
fileList: Array<string>,
|
||||
platform: ?string,
|
||||
exclude: ?RegExp,
|
||||
libraryName: ?string,
|
||||
): SchemaType {
|
||||
const expandedFileList = expandDirectoriesIntoFiles(
|
||||
fileList,
|
||||
platform,
|
||||
exclude,
|
||||
);
|
||||
const combined = combineSchemas(expandedFileList);
|
||||
const combined = combineSchemas(expandedFileList, libraryName);
|
||||
if (Object.keys(combined.modules).length === 0) {
|
||||
console.error(
|
||||
'No modules to process in combine-js-to-schema-cli. If this is unexpected, please check if you set up your NativeComponent correctly. See combine-js-to-schema.js for how codegen finds modules.',
|
||||
@@ -94,8 +103,14 @@ function combineSchemasInFileListAndWriteToFile(
|
||||
platform: ?string,
|
||||
outfile: string,
|
||||
exclude: ?RegExp,
|
||||
libraryName: ?string,
|
||||
): void {
|
||||
const combined = combineSchemasInFileList(fileList, platform, exclude);
|
||||
const combined = combineSchemasInFileList(
|
||||
fileList,
|
||||
platform,
|
||||
exclude,
|
||||
libraryName,
|
||||
);
|
||||
const formattedSchema = JSON.stringify(combined);
|
||||
fs.writeFileSync(outfile, formattedSchema);
|
||||
}
|
||||
|
||||
@@ -99,8 +99,15 @@ for (const file of schemaFiles) {
|
||||
}
|
||||
}
|
||||
|
||||
modules[specName] = module;
|
||||
specNameToFile[specName] = file;
|
||||
if (
|
||||
module.type === 'Component' &&
|
||||
schema.libraryName === 'FBReactNativeSpec'
|
||||
) {
|
||||
continue;
|
||||
} else {
|
||||
modules[specName] = module;
|
||||
specNameToFile[specName] = file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-10
@@ -73,22 +73,37 @@ const ALL_GENERATORS = {
|
||||
generateViewConfigJs: generateViewConfigJs.generate,
|
||||
};
|
||||
|
||||
type LibraryOptions = $ReadOnly<{
|
||||
export type FilesOutput = Map<string, string>;
|
||||
|
||||
export type GenerateFunction = (
|
||||
libraryName: string,
|
||||
schema: SchemaType,
|
||||
packageName?: string,
|
||||
assumeNonnull: boolean,
|
||||
headerPrefix?: string,
|
||||
) => FilesOutput;
|
||||
|
||||
export type LibraryGeneratorsFunctions = $ReadOnly<{
|
||||
[string]: Array<GenerateFunction>,
|
||||
}>;
|
||||
|
||||
export type LibraryOptions = $ReadOnly<{
|
||||
libraryName: string,
|
||||
schema: SchemaType,
|
||||
outputDirectory: string,
|
||||
packageName?: string, // Some platforms have a notion of package, which should be configurable.
|
||||
assumeNonnull: boolean,
|
||||
useLocalIncludePaths?: boolean,
|
||||
libraryGenerators?: LibraryGeneratorsFunctions,
|
||||
}>;
|
||||
|
||||
type SchemasOptions = $ReadOnly<{
|
||||
export type SchemasOptions = $ReadOnly<{
|
||||
schemas: {[string]: SchemaType},
|
||||
outputDirectory: string,
|
||||
supportedApplePlatforms?: {[string]: {[string]: boolean}},
|
||||
}>;
|
||||
|
||||
type LibraryGenerators =
|
||||
export type LibraryGenerators =
|
||||
| 'componentsAndroid'
|
||||
| 'componentsIOS'
|
||||
| 'descriptors'
|
||||
@@ -101,19 +116,19 @@ type LibraryGenerators =
|
||||
| 'modulesCxx'
|
||||
| 'modulesIOS';
|
||||
|
||||
type SchemasGenerators = 'providerIOS';
|
||||
export type SchemasGenerators = 'providerIOS';
|
||||
|
||||
type LibraryConfig = $ReadOnly<{
|
||||
export type LibraryConfig = $ReadOnly<{
|
||||
generators: Array<LibraryGenerators>,
|
||||
test?: boolean,
|
||||
}>;
|
||||
|
||||
type SchemasConfig = $ReadOnly<{
|
||||
export type SchemasConfig = $ReadOnly<{
|
||||
generators: Array<SchemasGenerators>,
|
||||
test?: boolean,
|
||||
}>;
|
||||
|
||||
const LIBRARY_GENERATORS = {
|
||||
const LIBRARY_GENERATORS: LibraryGeneratorsFunctions = {
|
||||
descriptors: [
|
||||
generateComponentDescriptorCpp.generate,
|
||||
generateComponentDescriptorH.generate,
|
||||
@@ -231,8 +246,6 @@ function checkOrWriteFiles(
|
||||
|
||||
module.exports = {
|
||||
allGenerators: ALL_GENERATORS,
|
||||
libraryGenerators: LIBRARY_GENERATORS,
|
||||
schemaGenerators: SCHEMAS_GENERATORS,
|
||||
|
||||
generate(
|
||||
{
|
||||
@@ -242,6 +255,7 @@ module.exports = {
|
||||
packageName,
|
||||
assumeNonnull,
|
||||
useLocalIncludePaths,
|
||||
libraryGenerators = LIBRARY_GENERATORS,
|
||||
}: LibraryOptions,
|
||||
{generators, test}: LibraryConfig,
|
||||
): boolean {
|
||||
@@ -278,7 +292,7 @@ module.exports = {
|
||||
const generatedFiles: Array<CodeGenFile> = [];
|
||||
|
||||
for (const name of generators) {
|
||||
for (const generator of LIBRARY_GENERATORS[name]) {
|
||||
for (const generator of libraryGenerators[name]) {
|
||||
generator(
|
||||
libraryName,
|
||||
schema,
|
||||
|
||||
+4
-1
@@ -18,7 +18,10 @@ const flowFixtures = require('../../flow/modules/__test_fixtures__/fixtures.js')
|
||||
const tsFixtures = require('../../typescript/modules/__test_fixtures__/fixtures.js');
|
||||
const {compareSnaps, compareTsArraySnaps} = require('../compareSnaps.js');
|
||||
|
||||
const flowExtraCases = ['PROMISE_WITH_COMMONLY_USED_TYPES'];
|
||||
const flowExtraCases = [
|
||||
'NATIVE_MODULE_WITH_OPAQUE_TYPES',
|
||||
'PROMISE_WITH_COMMONLY_USED_TYPES',
|
||||
];
|
||||
const tsExtraCases = [
|
||||
'NATIVE_MODULE_WITH_ARRAY2_WITH_ALIAS',
|
||||
'NATIVE_MODULE_WITH_ARRAY2_WITH_UNION_AND_TOUPLE',
|
||||
|
||||
+32
@@ -691,6 +691,37 @@ export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
|
||||
|
||||
`;
|
||||
|
||||
const NATIVE_MODULE_WITH_OPAQUE_TYPES = `
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {TurboModule} from '../RCTExport';
|
||||
import * as TurboModuleRegistry from '../TurboModuleRegistry';
|
||||
|
||||
export opaque type Task = mixed;
|
||||
export opaque type TimeoutID = number;
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
+createTask: (callback: () => void) => Task;
|
||||
+cancelTask: (task: Task) => void;
|
||||
|
||||
+setTimeout: (callback: () => void) => TimeoutID;
|
||||
+clearTimeout: (timeoutID: TimeoutID) => void;
|
||||
}
|
||||
|
||||
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
|
||||
|
||||
`;
|
||||
|
||||
const ANDROID_ONLY_NATIVE_MODULE = `
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
@@ -976,6 +1007,7 @@ module.exports = {
|
||||
NATIVE_MODULE_WITH_UNION,
|
||||
NATIVE_MODULE_WITH_UNION_RETURN_TYPES,
|
||||
NATIVE_MODULE_WITH_EVENT_EMITTERS,
|
||||
NATIVE_MODULE_WITH_OPAQUE_TYPES,
|
||||
EMPTY_NATIVE_MODULE,
|
||||
ANDROID_ONLY_NATIVE_MODULE,
|
||||
IOS_ONLY_NATIVE_MODULE,
|
||||
|
||||
+102
@@ -2187,6 +2187,108 @@ exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OBJECT_W
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_OPAQUE_TYPES 1`] = `
|
||||
"{
|
||||
'modules': {
|
||||
'NativeSampleTurboModule': {
|
||||
'type': 'NativeModule',
|
||||
'aliasMap': {},
|
||||
'enumMap': {},
|
||||
'spec': {
|
||||
'eventEmitters': [],
|
||||
'methods': [
|
||||
{
|
||||
'name': 'createTask',
|
||||
'optional': false,
|
||||
'typeAnnotation': {
|
||||
'type': 'FunctionTypeAnnotation',
|
||||
'returnTypeAnnotation': {
|
||||
'type': 'GenericObjectTypeAnnotation'
|
||||
},
|
||||
'params': [
|
||||
{
|
||||
'name': 'callback',
|
||||
'optional': false,
|
||||
'typeAnnotation': {
|
||||
'type': 'FunctionTypeAnnotation',
|
||||
'returnTypeAnnotation': {
|
||||
'type': 'VoidTypeAnnotation'
|
||||
},
|
||||
'params': []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'cancelTask',
|
||||
'optional': false,
|
||||
'typeAnnotation': {
|
||||
'type': 'FunctionTypeAnnotation',
|
||||
'returnTypeAnnotation': {
|
||||
'type': 'VoidTypeAnnotation'
|
||||
},
|
||||
'params': [
|
||||
{
|
||||
'name': 'task',
|
||||
'optional': false,
|
||||
'typeAnnotation': {
|
||||
'type': 'GenericObjectTypeAnnotation'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'setTimeout',
|
||||
'optional': false,
|
||||
'typeAnnotation': {
|
||||
'type': 'FunctionTypeAnnotation',
|
||||
'returnTypeAnnotation': {
|
||||
'type': 'NumberTypeAnnotation'
|
||||
},
|
||||
'params': [
|
||||
{
|
||||
'name': 'callback',
|
||||
'optional': false,
|
||||
'typeAnnotation': {
|
||||
'type': 'FunctionTypeAnnotation',
|
||||
'returnTypeAnnotation': {
|
||||
'type': 'VoidTypeAnnotation'
|
||||
},
|
||||
'params': []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
'name': 'clearTimeout',
|
||||
'optional': false,
|
||||
'typeAnnotation': {
|
||||
'type': 'FunctionTypeAnnotation',
|
||||
'returnTypeAnnotation': {
|
||||
'type': 'VoidTypeAnnotation'
|
||||
},
|
||||
'params': [
|
||||
{
|
||||
'name': 'timeoutID',
|
||||
'optional': false,
|
||||
'typeAnnotation': {
|
||||
'type': 'NumberTypeAnnotation'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
'moduleName': 'SampleTurboModule'
|
||||
}
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`RN Codegen Flow Parser can generate fixture NATIVE_MODULE_WITH_PARTIALS 1`] = `
|
||||
"{
|
||||
'modules': {
|
||||
|
||||
@@ -296,6 +296,7 @@ class FlowParser implements Parser {
|
||||
if (
|
||||
node.declaration != null &&
|
||||
(node.declaration.type === 'TypeAlias' ||
|
||||
node.declaration.type === 'OpaqueType' ||
|
||||
node.declaration.type === 'InterfaceDeclaration')
|
||||
) {
|
||||
types[node.declaration.id.name] = node.declaration;
|
||||
@@ -309,6 +310,7 @@ class FlowParser implements Parser {
|
||||
types[node.declaration.id.name] = node.declaration;
|
||||
} else if (
|
||||
node.type === 'TypeAlias' ||
|
||||
node.type === 'OpaqueType' ||
|
||||
node.type === 'InterfaceDeclaration' ||
|
||||
node.type === 'EnumDeclaration'
|
||||
) {
|
||||
@@ -543,6 +545,10 @@ class FlowParser implements Parser {
|
||||
}
|
||||
|
||||
nextNodeForTypeAlias(typeAnnotation: $FlowFixMe): $FlowFixMe {
|
||||
if (typeAnnotation.type === 'OpaqueType') {
|
||||
return typeAnnotation.impltype;
|
||||
}
|
||||
|
||||
return typeAnnotation.right;
|
||||
}
|
||||
|
||||
|
||||
@@ -1233,7 +1233,8 @@ function handleGenericTypeAnnotation(
|
||||
let node;
|
||||
|
||||
switch (resolvedTypeAnnotation.type) {
|
||||
case parser.typeAlias: {
|
||||
case parser.typeAlias:
|
||||
case 'OpaqueType': {
|
||||
typeResolutionStatus = getTypeResolutionStatus(
|
||||
'alias',
|
||||
typeAnnotation,
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@react-native/codegen": "0.80.0-main"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react": "^19.1.0",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @fantom_flags cxxNativeAnimatedRemoveJsSync:*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
@@ -16,6 +17,7 @@ import ensureInstance from '../../../src/private/__tests__/utilities/ensureInsta
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import {createRef} from 'react';
|
||||
import {Animated, View, useAnimatedValue} from 'react-native';
|
||||
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
|
||||
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
|
||||
|
||||
test('moving box by 100 points', () => {
|
||||
@@ -32,11 +34,9 @@ test('moving box by 100 points', () => {
|
||||
{
|
||||
width: 100,
|
||||
height: 100,
|
||||
backgroundColor: 'red',
|
||||
},
|
||||
{transform: [{translateX}]},
|
||||
]}
|
||||
testID="box"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -75,11 +75,15 @@ test('moving box by 100 points', () => {
|
||||
Fantom.unstable_produceFramesForDuration(500);
|
||||
|
||||
// Animation is completed now. C++ Animated will commit the final position to the shadow tree.
|
||||
expect(viewElement.getBoundingClientRect().x).toBe(100);
|
||||
if (ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
|
||||
expect(viewElement.getBoundingClientRect().x).toBe(100);
|
||||
// TODO(T223344928): this shouldn't be neccessary
|
||||
Fantom.runWorkLoop();
|
||||
} else {
|
||||
expect(viewElement.getBoundingClientRect().x).toBe(0);
|
||||
Fantom.runWorkLoop(); // Animated still schedules a React state update for synchronisation to shadow tree
|
||||
}
|
||||
|
||||
// TODO: this shouldn't be needed but C++ Animated still schedules a React state update
|
||||
// for synchronisation, even though it doesn't need to.
|
||||
Fantom.runWorkLoop();
|
||||
expect(viewElement.getBoundingClientRect().x).toBe(100);
|
||||
});
|
||||
|
||||
@@ -143,6 +147,331 @@ test('animation driven by onScroll event', () => {
|
||||
|
||||
expect(transform.translateY).toBeCloseTo(100, 0.001);
|
||||
|
||||
// TODO(T226364699): this should `toBe(100)` but we are not syncing shadow tree yet.
|
||||
expect(viewElement.getBoundingClientRect().y).toBe(0);
|
||||
expect(viewElement.getBoundingClientRect().y).toBe(100);
|
||||
});
|
||||
|
||||
test('animated opacity', () => {
|
||||
let _opacity;
|
||||
const viewRef = createRef<HostInstance>();
|
||||
|
||||
function MyApp() {
|
||||
const opacity = useAnimatedValue(1);
|
||||
_opacity = opacity;
|
||||
return (
|
||||
<Animated.View
|
||||
ref={viewRef}
|
||||
style={[
|
||||
{
|
||||
width: 100,
|
||||
height: 100,
|
||||
opacity: opacity,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<MyApp />);
|
||||
});
|
||||
|
||||
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
|
||||
|
||||
expect(viewElement.getBoundingClientRect().x).toBe(0);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
Animated.timing(_opacity, {
|
||||
toValue: 0,
|
||||
duration: 30,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
});
|
||||
|
||||
Fantom.unstable_produceFramesForDuration(30);
|
||||
expect(Fantom.unstable_getDirectManipulationProps(viewElement).opacity).toBe(
|
||||
0,
|
||||
);
|
||||
|
||||
// TODO(T223344928): this shouldn't be neccessary with cxxNativeAnimatedRemoveJsSync:true
|
||||
Fantom.runWorkLoop();
|
||||
|
||||
expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
|
||||
<rn-view opacity="0" />,
|
||||
);
|
||||
});
|
||||
|
||||
test('moving box by 50 points with offset 10', () => {
|
||||
let _translateX;
|
||||
const viewRef = createRef<HostInstance>();
|
||||
|
||||
function MyApp() {
|
||||
const translateX = useAnimatedValue(0);
|
||||
_translateX = translateX;
|
||||
return (
|
||||
<Animated.View
|
||||
ref={viewRef}
|
||||
style={[
|
||||
{
|
||||
width: 100,
|
||||
height: 100,
|
||||
},
|
||||
{transform: [{translateX}]},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<MyApp />);
|
||||
});
|
||||
|
||||
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
|
||||
|
||||
expect(viewElement.getBoundingClientRect().x).toBe(0);
|
||||
|
||||
let finishValue = null;
|
||||
|
||||
Fantom.runTask(() => {
|
||||
Animated.timing(_translateX, {
|
||||
toValue: 50,
|
||||
duration: 1000, // 1 second
|
||||
useNativeDriver: true,
|
||||
}).start(result => {
|
||||
finishValue = result;
|
||||
});
|
||||
});
|
||||
|
||||
Fantom.runTask(() => {
|
||||
_translateX.setOffset(10);
|
||||
});
|
||||
|
||||
Fantom.unstable_produceFramesForDuration(500);
|
||||
|
||||
// shadow tree is not synchronised yet, position X is still 0.
|
||||
expect(viewElement.getBoundingClientRect().x).toBe(0);
|
||||
|
||||
expect(
|
||||
// $FlowFixMe[incompatible-use]
|
||||
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]
|
||||
.translateX,
|
||||
).toBeCloseTo(35, 0.001);
|
||||
|
||||
Fantom.unstable_produceFramesForDuration(500);
|
||||
|
||||
expect(
|
||||
// $FlowFixMe[incompatible-use]
|
||||
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0]
|
||||
.translateX,
|
||||
).toBeCloseTo(60, 0.001);
|
||||
|
||||
if (ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
|
||||
expect(root.getRenderedOutput({props: ['transform']}).toJSX()).toEqual(
|
||||
<rn-view transform='[{"translateX": 60.000000}]' />,
|
||||
);
|
||||
// TODO(T223344928): this shouldn't be neccessary
|
||||
Fantom.runWorkLoop();
|
||||
} else {
|
||||
expect(root.getRenderedOutput({props: ['transform']}).toJSX()).toEqual(
|
||||
<rn-view transform="[]" />,
|
||||
);
|
||||
Fantom.runWorkLoop(); // Animated still schedules a React state update for synchronisation to shadow tree
|
||||
}
|
||||
|
||||
expect(root.getRenderedOutput({props: ['transform']}).toJSX()).toEqual(
|
||||
<rn-view transform='[{"translateX": 60.000000}]' />, // // must include offset.
|
||||
);
|
||||
|
||||
expect(finishValue?.finished).toBe(true);
|
||||
expect(finishValue?.value).toBe(50); // must not include offset.
|
||||
expect(finishValue?.offset).toBe(10);
|
||||
});
|
||||
|
||||
describe('Value.flattenOffset', () => {
|
||||
it('accumulates offset with onScroll value', () => {
|
||||
const scrollViewRef = createRef<HostInstance>();
|
||||
const viewRef = createRef<HostInstance>();
|
||||
let _onScroll;
|
||||
|
||||
function PressableWithNativeDriver() {
|
||||
_onScroll = useAnimatedValue(0);
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Animated.View
|
||||
ref={viewRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: 10,
|
||||
height: 10,
|
||||
transform: [{translateY: _onScroll}],
|
||||
}}
|
||||
/>
|
||||
<Animated.ScrollView
|
||||
ref={scrollViewRef}
|
||||
onScroll={Animated.event(
|
||||
[
|
||||
{
|
||||
nativeEvent: {
|
||||
contentOffset: {
|
||||
y: _onScroll,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{useNativeDriver: true},
|
||||
)}>
|
||||
<View style={{height: 1000, width: 100}} />
|
||||
</Animated.ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
Fantom.runTask(() => {
|
||||
root.render(<PressableWithNativeDriver />);
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
Fantom.runTask(() => {
|
||||
_onScroll.addListener(fn);
|
||||
});
|
||||
|
||||
const scrollViewelement = ensureInstance(
|
||||
scrollViewRef.current,
|
||||
ReactNativeElement,
|
||||
);
|
||||
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
|
||||
|
||||
Fantom.scrollTo(scrollViewelement, {
|
||||
x: 0,
|
||||
y: 10,
|
||||
});
|
||||
|
||||
expect(fn).toBeCalledWith({value: 10});
|
||||
|
||||
Fantom.runTask(() => {
|
||||
_onScroll.setOffset(15);
|
||||
_onScroll.flattenOffset();
|
||||
});
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
_onScroll.setOffset(15);
|
||||
});
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
let transform =
|
||||
// $FlowFixMe[incompatible-use]
|
||||
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0];
|
||||
|
||||
expect(transform.translateY).toBeCloseTo(40, 0.001);
|
||||
|
||||
// TODO(T223344928): this shouldn't be neccessary with cxxNativeAnimatedRemoveJsSync:true
|
||||
Fantom.runWorkLoop();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Value.extractOffset', () => {
|
||||
it('sets the offset value to the base value and resets the base value to zero', () => {
|
||||
const scrollViewRef = createRef<HostInstance>();
|
||||
const viewRef = createRef<HostInstance>();
|
||||
let _onScroll;
|
||||
|
||||
function PressableWithNativeDriver() {
|
||||
_onScroll = useAnimatedValue(0);
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Animated.View
|
||||
ref={viewRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: 10,
|
||||
height: 10,
|
||||
transform: [{translateY: _onScroll}],
|
||||
}}
|
||||
/>
|
||||
<Animated.ScrollView
|
||||
ref={scrollViewRef}
|
||||
onScroll={Animated.event(
|
||||
[
|
||||
{
|
||||
nativeEvent: {
|
||||
contentOffset: {
|
||||
y: _onScroll,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
{useNativeDriver: true},
|
||||
)}>
|
||||
<View style={{height: 1000, width: 100}} />
|
||||
</Animated.ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
Fantom.runTask(() => {
|
||||
root.render(<PressableWithNativeDriver />);
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
Fantom.runTask(() => {
|
||||
_onScroll.addListener(fn);
|
||||
});
|
||||
|
||||
const scrollViewelement = ensureInstance(
|
||||
scrollViewRef.current,
|
||||
ReactNativeElement,
|
||||
);
|
||||
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
|
||||
|
||||
Fantom.scrollTo(scrollViewelement, {
|
||||
x: 0,
|
||||
y: 10,
|
||||
});
|
||||
|
||||
expect(fn).toBeCalledWith({value: 10});
|
||||
|
||||
Fantom.runTask(() => {
|
||||
_onScroll.setOffset(15);
|
||||
// Sets offset to be 15 + 10 = 25 (this is not observable from JS).
|
||||
_onScroll.extractOffset();
|
||||
});
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
let transform =
|
||||
// $FlowFixMe[incompatible-use]
|
||||
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0];
|
||||
|
||||
// Animated value is now 0 but offset is 25. The final value is 25.
|
||||
expect(transform.translateY).toBeCloseTo(25, 0.001);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
// Sets offset 35, overriding the previous `setOffset`.
|
||||
// Due to `extractOffset`, base value was restarted to 0.
|
||||
_onScroll.setOffset(35);
|
||||
});
|
||||
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
transform =
|
||||
// $FlowFixMe[incompatible-use]
|
||||
Fantom.unstable_getDirectManipulationProps(viewElement).transform[0];
|
||||
|
||||
// `extractOffset` resets value back to 0.
|
||||
// Previously we set offset to 35. The final value is 35.
|
||||
expect(transform.translateY).toBeCloseTo(35, 0.001);
|
||||
|
||||
// TODO(T223344928): this shouldn't be neccessary with cxxNativeAnimatedRemoveJsSync:true
|
||||
Fantom.runWorkLoop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import type AnimatedNode from '../nodes/AnimatedNode';
|
||||
import type AnimatedValue from '../nodes/AnimatedValue';
|
||||
|
||||
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
|
||||
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
import AnimatedProps from '../nodes/AnimatedProps';
|
||||
|
||||
export type EndResult = {
|
||||
@@ -149,8 +150,15 @@ export default class Animation {
|
||||
if (value != null) {
|
||||
animatedValue.__onAnimatedValueUpdateReceived(value, offset);
|
||||
|
||||
if (this.__isLooping === true) {
|
||||
return;
|
||||
if (
|
||||
!(
|
||||
ReactNativeFeatureFlags.cxxNativeAnimatedEnabled() &&
|
||||
ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()
|
||||
)
|
||||
) {
|
||||
if (this.__isLooping === true) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Once the JS side node is synced with the updated values, trigger an
|
||||
|
||||
@@ -13,15 +13,9 @@
|
||||
|
||||
#import <memory>
|
||||
|
||||
#if USE_HERMES
|
||||
#if __has_include(<jsireact/HermesExecutorFactory.h>)
|
||||
#import <jsireact/HermesExecutorFactory.h>
|
||||
#elif __has_include(<reacthermes/HermesExecutorFactory.h>)
|
||||
#if USE_THIRD_PARTY_JSC != 1
|
||||
#import <reacthermes/HermesExecutorFactory.h>
|
||||
#endif
|
||||
#elif USE_THIRD_PARTY_JSC != 1
|
||||
#import <React/JSCExecutorFactory.h>
|
||||
#endif // USE_HERMES
|
||||
|
||||
#import <ReactCommon/RCTTurboModuleManager.h>
|
||||
#import <jsireact/JSIExecutor.h>
|
||||
|
||||
@@ -145,16 +145,10 @@ std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupDefaultJsExecutor
|
||||
}
|
||||
[turboModuleManager installJSBindings:runtime];
|
||||
};
|
||||
#if USE_HERMES
|
||||
#if USE_THIRD_PARTY_JSC != 1
|
||||
return std::make_unique<facebook::react::HermesExecutorFactory>(
|
||||
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
|
||||
#elif USE_THIRD_PARTY_JSC != 1
|
||||
return std::make_unique<facebook::react::JSCExecutorFactory>(
|
||||
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
|
||||
#else
|
||||
throw std::runtime_error("No JSExecutorFactory specified.");
|
||||
return nullptr;
|
||||
#endif // USE_HERMES
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupJsExecutorFactoryForOldArch(
|
||||
@@ -169,14 +163,8 @@ std::unique_ptr<facebook::react::JSExecutorFactory> RCTAppSetupJsExecutorFactory
|
||||
facebook::react::RuntimeSchedulerBinding::createAndInstallIfNeeded(runtime, runtimeScheduler);
|
||||
}
|
||||
};
|
||||
#if USE_HERMES
|
||||
#if USE_THIRD_PARTY_JSC != 1
|
||||
return std::make_unique<facebook::react::HermesExecutorFactory>(
|
||||
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
|
||||
#elif USE_THIRD_PARTY_JSC != 1
|
||||
return std::make_unique<facebook::react::JSCExecutorFactory>(
|
||||
facebook::react::RCTJSIExecutorRuntimeInstaller(runtimeInstallerLambda));
|
||||
#else
|
||||
throw std::runtime_error("No JSExecutorFactory specified.");
|
||||
return nullptr;
|
||||
#endif // USE_HERMES
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
#import <ReactCommon/RCTHost.h>
|
||||
#import "RCTAppSetupUtils.h"
|
||||
#import "RCTDependencyProvider.h"
|
||||
#if USE_HERMES
|
||||
#if USE_THIRD_PARTY_JSC != 1
|
||||
#import <React/RCTHermesInstanceFactory.h>
|
||||
#elif USE_THIRD_PARTY_JSC != 1
|
||||
#import <React/RCTJscInstanceFactory.h>
|
||||
#endif
|
||||
|
||||
#import <react/nativemodule/defaults/DefaultTurboModules.h>
|
||||
@@ -45,12 +43,8 @@
|
||||
|
||||
- (JSRuntimeFactoryRef)createJSRuntimeFactory
|
||||
{
|
||||
#if USE_HERMES
|
||||
#if USE_THIRD_PARTY_JSC != 1
|
||||
return jsrt_create_hermes_factory();
|
||||
#elif USE_THIRD_PARTY_JSC != 1
|
||||
return jsrt_create_jsc_factory();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,8 @@ else
|
||||
end
|
||||
|
||||
is_new_arch_enabled = ENV["RCT_NEW_ARCH_ENABLED"] != "0"
|
||||
use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
|
||||
|
||||
new_arch_enabled_flag = (is_new_arch_enabled ? " -DRCT_NEW_ARCH_ENABLED=1" : "")
|
||||
hermes_flag = (use_hermes ? " -DUSE_HERMES=1" : "")
|
||||
use_third_party_jsc_flag = ENV['USE_THIRD_PARTY_JSC'] == '1' ? " -DUSE_THIRD_PARTY_JSC=1" : ""
|
||||
other_cflags = "$(inherited) " + new_arch_enabled_flag + hermes_flag + use_third_party_jsc_flag
|
||||
other_cflags = "$(inherited) " + new_arch_enabled_flag + js_engine_flags()
|
||||
|
||||
header_search_paths = [
|
||||
"$(PODS_TARGET_SRCROOT)/../../ReactCommon",
|
||||
@@ -31,7 +27,7 @@ header_search_paths = [
|
||||
"$(PODS_ROOT)/Headers/Public/ReactCommon",
|
||||
"$(PODS_ROOT)/Headers/Public/React-RCTFabric",
|
||||
"$(PODS_ROOT)/Headers/Private/Yoga",
|
||||
].concat(use_hermes ? [
|
||||
].concat(use_hermes() ? [
|
||||
"$(PODS_ROOT)/Headers/Public/React-hermes",
|
||||
"$(PODS_ROOT)/Headers/Public/hermes-engine"
|
||||
] : [])
|
||||
@@ -66,7 +62,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-CoreModules"
|
||||
s.dependency "React-RCTFBReactNativeSpec"
|
||||
s.dependency "React-defaultsnativemodule"
|
||||
if use_hermes
|
||||
if use_hermes()
|
||||
s.dependency 'React-hermes'
|
||||
end
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ Pod::Spec.new do |s|
|
||||
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
|
||||
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
|
||||
|
||||
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
|
||||
if use_hermes()
|
||||
s.dependency "hermes-engine"
|
||||
end
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ export type ButtonProps = $ReadOnly<{
|
||||
@platform tv
|
||||
|
||||
@default false
|
||||
@deprecated Use `focusable` instead
|
||||
*/
|
||||
hasTVPreferredFocus?: ?boolean,
|
||||
|
||||
|
||||
@@ -13,12 +13,21 @@ import {NativeMethods} from '../../../types/public/ReactNativeTypes';
|
||||
import {ViewProps} from '../View/ViewPropTypes';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Use `react-native-safe-area-context` instead. This component is deprecated and will be removed in a future release.
|
||||
*
|
||||
* Renders nested content and automatically applies paddings reflect the portion of the view
|
||||
* that is not covered by navigation bars, tab bars, toolbars, and other ancestor views.
|
||||
* Moreover, and most importantly, Safe Area's paddings reflect physical limitation of the screen,
|
||||
* such as rounded corners or camera notches (aka sensor housing area on iPhone X).
|
||||
*/
|
||||
declare class SafeAreaViewComponent extends React.Component<ViewProps> {}
|
||||
|
||||
declare const SafeAreaViewBase: Constructor<NativeMethods> &
|
||||
typeof SafeAreaViewComponent;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Use `react-native-safe-area-context` instead. This component is deprecated and will be removed in a future release.
|
||||
*/
|
||||
export class SafeAreaView extends SafeAreaViewBase {}
|
||||
|
||||
@@ -22,6 +22,7 @@ import * as React from 'react';
|
||||
* Moreover, and most importantly, Safe Area's paddings reflect physical
|
||||
* limitation of the screen, such as rounded corners or camera notches (aka
|
||||
* sensor housing area on iPhone X).
|
||||
* @deprecated Use `react-native-safe-area-context` instead. This component will be removed in a future release.
|
||||
*/
|
||||
const exported: component(
|
||||
ref?: React.RefSetter<React.ElementRef<typeof View>>,
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface StatusBarPropsAndroid {
|
||||
/**
|
||||
* The background color of the status bar.
|
||||
*
|
||||
* Please note that this prop has no effect on Android 15+
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
backgroundColor?: ColorValue | undefined;
|
||||
@@ -44,6 +46,8 @@ export interface StatusBarPropsAndroid {
|
||||
* the app will draw under the status bar. This is useful when using a
|
||||
* semi transparent status bar color.
|
||||
*
|
||||
* Please note that this prop has no effect on Android 15+
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
translucent?: boolean | undefined;
|
||||
|
||||
@@ -58,6 +58,9 @@ export type StatusBarAnimation = $Keys<{
|
||||
export type StatusBarPropsAndroid = $ReadOnly<{
|
||||
/**
|
||||
* The background color of the status bar.
|
||||
*
|
||||
* Please note that this prop has no effect on Android 15+
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
backgroundColor?: ?ColorValue,
|
||||
@@ -66,6 +69,8 @@ export type StatusBarPropsAndroid = $ReadOnly<{
|
||||
* When translucent is set to true, the app will draw under the status bar.
|
||||
* This is useful when using a semi transparent status bar color.
|
||||
*
|
||||
* Please note that this prop has no effect on Android 15+
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
translucent?: ?boolean,
|
||||
|
||||
@@ -30,6 +30,9 @@ type AndroidProps = $ReadOnly<{
|
||||
}>;
|
||||
|
||||
type IOSProps = $ReadOnly<{
|
||||
/**
|
||||
* @deprecated Use `focusable` instead
|
||||
*/
|
||||
hasTVPreferredFocus?: ?boolean,
|
||||
}>;
|
||||
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ type TVProps = {
|
||||
* *(Apple TV only)* TV preferred focus (see documentation for the View component).
|
||||
*
|
||||
* @platform ios
|
||||
* @deprecated Use `focusable` instead
|
||||
*/
|
||||
hasTVPreferredFocus?: ?boolean,
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface TVProps {
|
||||
* *(Apple TV only)* TV preferred focus (see documentation for the View component).
|
||||
*
|
||||
* @platform ios
|
||||
* @deprecated Use `focusable` instead
|
||||
*/
|
||||
hasTVPreferredFocus?: boolean | undefined;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ export type TVProps = $ReadOnly<{
|
||||
* *(Apple TV only)* TV preferred focus (see documentation for the View component).
|
||||
*
|
||||
* @platform ios
|
||||
* @deprecated Use `focusable` instead
|
||||
*/
|
||||
hasTVPreferredFocus?: ?boolean,
|
||||
|
||||
|
||||
+12
-13
@@ -16,11 +16,6 @@ import ViewNativeComponent from './ViewNativeComponent';
|
||||
import * as React from 'react';
|
||||
import {use} from 'react';
|
||||
|
||||
type PropsWithRef = $ReadOnly<{
|
||||
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
|
||||
...ViewProps,
|
||||
}>;
|
||||
|
||||
/**
|
||||
* The most fundamental component for building a UI, View is a container that
|
||||
* supports layout with flexbox, style, some touch handling, and accessibility
|
||||
@@ -28,7 +23,10 @@ type PropsWithRef = $ReadOnly<{
|
||||
*
|
||||
* @see https://reactnative.dev/docs/view
|
||||
*/
|
||||
function View(props: PropsWithRef): React.Node {
|
||||
export default component View(
|
||||
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
|
||||
...props: ViewProps
|
||||
) {
|
||||
const hasTextAncestor = use(TextAncestor);
|
||||
|
||||
let actualView;
|
||||
@@ -55,7 +53,7 @@ function View(props: PropsWithRef): React.Node {
|
||||
} = props;
|
||||
|
||||
// Since we destructured props, we can now treat it as mutable
|
||||
const processedProps = otherProps as {...PropsWithRef};
|
||||
const processedProps = otherProps as {...ViewProps};
|
||||
|
||||
const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g);
|
||||
if (parsedAriaLabelledBy !== undefined) {
|
||||
@@ -118,7 +116,12 @@ function View(props: PropsWithRef): React.Node {
|
||||
};
|
||||
}
|
||||
|
||||
actualView = <ViewNativeComponent {...processedProps} />;
|
||||
actualView =
|
||||
ref == null ? (
|
||||
<ViewNativeComponent {...processedProps} />
|
||||
) : (
|
||||
<ViewNativeComponent {...processedProps} ref={ref} />
|
||||
);
|
||||
} else {
|
||||
const {
|
||||
accessibilityElementsHidden,
|
||||
@@ -198,6 +201,7 @@ function View(props: PropsWithRef): React.Node {
|
||||
: importantForAccessibility
|
||||
}
|
||||
nativeID={id ?? nativeID}
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -207,8 +211,3 @@ function View(props: PropsWithRef): React.Node {
|
||||
}
|
||||
return actualView;
|
||||
}
|
||||
|
||||
export default View as component(
|
||||
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
|
||||
...props: ViewProps
|
||||
);
|
||||
|
||||
@@ -374,13 +374,6 @@ export type AccessibilityProps = $ReadOnly<{
|
||||
*/
|
||||
'aria-label'?: ?Stringish,
|
||||
|
||||
/**
|
||||
* Defines the order in which descendant elements receive accessibility focus.
|
||||
* The elements in the array represent nativeID values for the respective
|
||||
* descendant elements.
|
||||
*/
|
||||
experimental_accessibilityOrder?: ?Array<string>,
|
||||
|
||||
/**
|
||||
* Indicates to accessibility services to treat UI component like a specific role.
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,9 @@ import {
|
||||
import {Touchable} from '../Touchable/Touchable';
|
||||
import {AccessibilityProps} from './ViewAccessibility';
|
||||
|
||||
/**
|
||||
* @deprecated These properties are not implemented natively.
|
||||
*/
|
||||
export interface TVViewPropsIOS {
|
||||
/**
|
||||
* *(Apple TV only)* When set to true, this view will be focusable
|
||||
@@ -34,6 +37,7 @@ export interface TVViewPropsIOS {
|
||||
* *(Apple TV only)* May be set to true to force the Apple TV focus engine to move focus to this view.
|
||||
*
|
||||
* @platform ios
|
||||
* @deprecated Use `focusable` instead
|
||||
*/
|
||||
hasTVPreferredFocus?: boolean | undefined;
|
||||
|
||||
|
||||
@@ -280,6 +280,7 @@ export type ViewPropsAndroid = $ReadOnly<{
|
||||
* Whether to force the Android TV focus engine to move focus to this view.
|
||||
*
|
||||
* @platform android
|
||||
* @deprecated Use `focusable` instead
|
||||
*/
|
||||
hasTVPreferredFocus?: ?boolean,
|
||||
|
||||
@@ -489,6 +490,13 @@ type ViewBaseProps = $ReadOnly<{
|
||||
* See https://reactnative.dev/docs/view#removeclippedsubviews
|
||||
*/
|
||||
removeClippedSubviews?: ?boolean,
|
||||
|
||||
/**
|
||||
* Defines the order in which descendant elements receive accessibility focus.
|
||||
* The elements in the array represent nativeID values for the respective
|
||||
* descendant elements.
|
||||
*/
|
||||
experimental_accessibilityOrder?: ?Array<string>,
|
||||
}>;
|
||||
|
||||
export type ViewProps = $ReadOnly<{
|
||||
|
||||
@@ -169,6 +169,20 @@ describe('<View>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('transform style', () => {
|
||||
it('causes view to be unflattened', () => {
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<View style={{transform: [{translateX: 10}]}} />);
|
||||
});
|
||||
|
||||
expect(root.getRenderedOutput({props: ['transform']}).toJSX()).toEqual(
|
||||
<rn-view transform='[{"translateX": 10.000000}]' />,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('props', () => {
|
||||
describe('pointerEvents', () => {
|
||||
it('auto does not propagate to the mounting layer, it is the default', () => {
|
||||
|
||||
@@ -31,8 +31,8 @@ describe('<Button>', () => {
|
||||
.toJSX(),
|
||||
).toEqual(
|
||||
// Upper case on Android (also used by Fantom)
|
||||
<rn-view backgroundColor="rgba(33, 150, 243, 255)">
|
||||
<rn-paragraph foregroundColor="rgba(255, 255, 255, 255)">
|
||||
<rn-view backgroundColor="rgba(33, 150, 243, 1)">
|
||||
<rn-paragraph foregroundColor="rgba(255, 255, 255, 1)">
|
||||
HELLO
|
||||
</rn-paragraph>
|
||||
</rn-view>,
|
||||
@@ -53,8 +53,8 @@ describe('<Button>', () => {
|
||||
.getRenderedOutput({props: ['foregroundColor', 'backgroundColor']})
|
||||
.toJSX(),
|
||||
).toEqual(
|
||||
<rn-view backgroundColor="rgba(0, 0, 255, 255)">
|
||||
<rn-paragraph foregroundColor="rgba(255, 255, 255, 255)">
|
||||
<rn-view backgroundColor="rgba(0, 0, 255, 1)">
|
||||
<rn-paragraph foregroundColor="rgba(255, 255, 255, 1)">
|
||||
HELLO
|
||||
</rn-paragraph>
|
||||
</rn-view>,
|
||||
@@ -99,8 +99,8 @@ describe('<Button>', () => {
|
||||
.getRenderedOutput({props: ['foregroundColor', 'backgroundColor']})
|
||||
.toJSX(),
|
||||
).toEqual(
|
||||
<rn-view backgroundColor="rgba(223, 223, 223, 255)">
|
||||
<rn-paragraph foregroundColor="rgba(161, 161, 161, 255)">
|
||||
<rn-view backgroundColor="rgba(223, 223, 223, 1)">
|
||||
<rn-paragraph foregroundColor="rgba(161, 161, 161, 1)">
|
||||
HELLO
|
||||
</rn-paragraph>
|
||||
</rn-view>,
|
||||
@@ -181,8 +181,8 @@ describe('<Button>', () => {
|
||||
.getRenderedOutput({props: ['foregroundColor', 'backgroundColor']})
|
||||
.toJSX(),
|
||||
).toEqual(
|
||||
<rn-view backgroundColor="rgba(33, 150, 243, 255)">
|
||||
<rn-paragraph foregroundColor="rgba(255, 255, 255, 255)">
|
||||
<rn-view backgroundColor="rgba(33, 150, 243, 1)">
|
||||
<rn-paragraph foregroundColor="rgba(255, 255, 255, 1)">
|
||||
HELLO
|
||||
</rn-paragraph>
|
||||
</rn-view>,
|
||||
@@ -223,8 +223,8 @@ describe('<Button>', () => {
|
||||
.getRenderedOutput({props: ['foregroundColor', 'backgroundColor']})
|
||||
.toJSX(),
|
||||
).toEqual(
|
||||
<rn-view backgroundColor="rgba(223, 223, 223, 255)">
|
||||
<rn-paragraph foregroundColor="rgba(161, 161, 161, 255)">
|
||||
<rn-view backgroundColor="rgba(223, 223, 223, 1)">
|
||||
<rn-paragraph foregroundColor="rgba(161, 161, 161, 1)">
|
||||
HELLO
|
||||
</rn-paragraph>
|
||||
</rn-view>,
|
||||
|
||||
@@ -72,3 +72,8 @@ export interface ImageURISource {
|
||||
}
|
||||
|
||||
export type ImageRequireSource = number;
|
||||
|
||||
export type ImageSource =
|
||||
| ImageRequireSource
|
||||
| ImageURISource
|
||||
| ReadonlyArray<ImageURISource>;
|
||||
|
||||
+8
-2
@@ -35,9 +35,9 @@ export interface ModalBaseProps {
|
||||
*/
|
||||
visible?: boolean | undefined;
|
||||
/**
|
||||
* The `onRequestClose` callback is called when the user taps the hardware back button on Android or the menu button on Apple TV.
|
||||
* The `onRequestClose` callback is called when the user taps the hardware back button on Android, dismisses the sheet using a gesture on iOS (when `allowSwipeDismissal` is set to true) or the menu button on Apple TV.
|
||||
*
|
||||
* This is required on Apple TV and Android.
|
||||
* This is required on iOS and Android.
|
||||
*/
|
||||
onRequestClose?: ((event: NativeSyntheticEvent<any>) => void) | undefined;
|
||||
/**
|
||||
@@ -89,6 +89,12 @@ export interface ModalPropsIOS {
|
||||
onOrientationChange?:
|
||||
| ((event: NativeSyntheticEvent<any>) => void)
|
||||
| undefined;
|
||||
|
||||
/**
|
||||
* Controls whether the modal can be dismissed by swiping down on iOS.
|
||||
* This requires you to implement the `onRequestClose` prop to handle the dismissal.
|
||||
*/
|
||||
allowSwipeDismissal?: boolean | undefined;
|
||||
}
|
||||
|
||||
export interface ModalPropsAndroid {
|
||||
|
||||
+19
-2
@@ -86,9 +86,9 @@ export type ModalBaseProps = {
|
||||
*/
|
||||
visible?: ?boolean,
|
||||
/**
|
||||
* The `onRequestClose` callback is called when the user taps the hardware back button on Android or the menu button on Apple TV.
|
||||
* The `onRequestClose` callback is called when the user taps the hardware back button on Android, dismisses the sheet using a gesture on iOS (when `allowSwipeDismissal` is set to true) or the menu button on Apple TV.
|
||||
*
|
||||
* This is required on Apple TV and Android.
|
||||
* This is required on iOS and Android.
|
||||
*/
|
||||
// onRequestClose?: (event: NativeSyntheticEvent<any>) => void;
|
||||
onRequestClose?: ?DirectEventHandler<null>,
|
||||
@@ -147,6 +147,12 @@ export type ModalPropsIOS = {
|
||||
// | ((event: NativeSyntheticEvent<any>) => void)
|
||||
// | undefined;
|
||||
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
|
||||
|
||||
/**
|
||||
* Controls whether the modal can be dismissed by swiping down on iOS.
|
||||
* This requires you to implement the `onRequestClose` prop to handle the dismissal.
|
||||
*/
|
||||
allowSwipeDismissal?: ?boolean,
|
||||
};
|
||||
|
||||
export type ModalPropsAndroid = {
|
||||
@@ -192,6 +198,16 @@ function confirmProps(props: ModalProps) {
|
||||
'Modal with translucent navigation bar and without translucent status bar is not supported.',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Platform.OS === 'ios' &&
|
||||
props.allowSwipeDismissal === true &&
|
||||
!props.onRequestClose
|
||||
) {
|
||||
console.warn(
|
||||
'Modal requires the onRequestClose prop when used with `allowSwipeDismissal`. This is necessary to prevent state corruption.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +343,7 @@ class Modal extends React.Component<ModalProps, ModalState> {
|
||||
onStartShouldSetResponder={this._shouldSetResponder}
|
||||
supportedOrientations={this.props.supportedOrientations}
|
||||
onOrientationChange={this.props.onOrientationChange}
|
||||
allowSwipeDismissal={this.props.allowSwipeDismissal}
|
||||
testID={this.props.testID}>
|
||||
<VirtualizedListContextResetter>
|
||||
<ScrollView.Context.Provider value={null}>
|
||||
|
||||
packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload.js
Vendored
+81
-62
@@ -8,7 +8,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {AttributeConfiguration} from '../../Renderer/shims/ReactNativeTypes';
|
||||
import type {PartialAttributeConfiguration as AttributeConfiguration} from '../../Renderer/shims/ReactNativeTypes';
|
||||
|
||||
import flattenStyle from '../../StyleSheet/flattenStyle';
|
||||
import deepDiffer from '../../Utilities/differ/deepDiffer';
|
||||
@@ -61,7 +61,7 @@ function restoreDeletedValuesInNestedArray(
|
||||
} else if (node && removedKeyCount > 0) {
|
||||
const obj = node;
|
||||
for (const propKey in removedKeys) {
|
||||
// $FlowFixMe[incompatible-use] found when upgrading Flow
|
||||
// $FlowFixMe[incompatible-use] removedKeys is always non-null
|
||||
if (!removedKeys[propKey]) {
|
||||
continue;
|
||||
}
|
||||
@@ -133,12 +133,12 @@ function diffNestedArrayProperty(
|
||||
);
|
||||
}
|
||||
for (; i < nextArray.length; i++) {
|
||||
// Add all remaining properties.
|
||||
updatePayload = addNestedProperty(
|
||||
updatePayload,
|
||||
nextArray[i],
|
||||
validAttributes,
|
||||
);
|
||||
// Add all remaining properties
|
||||
const nextProp = nextArray[i];
|
||||
if (!nextProp) {
|
||||
continue;
|
||||
}
|
||||
updatePayload = addNestedProperty(updatePayload, nextProp, validAttributes);
|
||||
}
|
||||
return updatePayload;
|
||||
}
|
||||
@@ -183,9 +183,7 @@ function diffNestedProperty(
|
||||
if (Array.isArray(prevProp)) {
|
||||
return diffProperties(
|
||||
updatePayload,
|
||||
// $FlowFixMe - We know that this is always an object when the input is.
|
||||
flattenStyle(prevProp),
|
||||
// $FlowFixMe - We know that this isn't an array because of above flow.
|
||||
nextProp,
|
||||
validAttributes,
|
||||
);
|
||||
@@ -194,43 +192,11 @@ function diffNestedProperty(
|
||||
return diffProperties(
|
||||
updatePayload,
|
||||
prevProp,
|
||||
// $FlowFixMe - We know that this is always an object when the input is.
|
||||
flattenStyle(nextProp),
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* addNestedProperty takes a single set of props and valid attribute
|
||||
* attribute configurations. It processes each prop and adds it to the
|
||||
* updatePayload.
|
||||
*/
|
||||
function addNestedProperty(
|
||||
updatePayload: null | Object,
|
||||
nextProp: NestedNode,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): $FlowFixMe {
|
||||
if (!nextProp) {
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
if (!Array.isArray(nextProp)) {
|
||||
// Add each property of the leaf.
|
||||
return addProperties(updatePayload, nextProp, validAttributes);
|
||||
}
|
||||
|
||||
for (let i = 0; i < nextProp.length; i++) {
|
||||
// Add all the properties of the array.
|
||||
updatePayload = addNestedProperty(
|
||||
updatePayload,
|
||||
nextProp[i],
|
||||
validAttributes,
|
||||
);
|
||||
}
|
||||
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* clearNestedProperty takes a single set of props and valid attributes. It
|
||||
* adds a null sentinel to the updatePayload, for each prop key.
|
||||
@@ -285,14 +251,19 @@ function diffProperties(
|
||||
prevProp = prevProps[propKey];
|
||||
nextProp = nextProps[propKey];
|
||||
|
||||
// functions are converted to booleans as markers that the associated
|
||||
// events should be sent from native.
|
||||
if (typeof nextProp === 'function') {
|
||||
nextProp = (true: any);
|
||||
// If nextProp is not a function, then don't bother changing prevProp
|
||||
// since nextProp will win and go into the updatePayload regardless.
|
||||
if (typeof prevProp === 'function') {
|
||||
prevProp = (true: any);
|
||||
const attributeConfigHasProcess =
|
||||
typeof attributeConfig === 'object' &&
|
||||
typeof attributeConfig.process === 'function';
|
||||
if (!attributeConfigHasProcess) {
|
||||
// functions are converted to booleans as markers that the associated
|
||||
// events should be sent from native.
|
||||
nextProp = (true: any);
|
||||
// If nextProp is not a function, then don't bother changing prevProp
|
||||
// since nextProp will win and go into the updatePayload regardless.
|
||||
if (typeof prevProp === 'function') {
|
||||
prevProp = (true: any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,16 +413,69 @@ function diffProperties(
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* addProperties adds all the valid props to the payload after being processed.
|
||||
*/
|
||||
function addProperties(
|
||||
updatePayload: null | Object,
|
||||
function addNestedProperty(
|
||||
payload: null | Object,
|
||||
props: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): null | Object {
|
||||
// TODO: Fast path
|
||||
return diffProperties(updatePayload, emptyObject, props, validAttributes);
|
||||
// Flatten nested style props.
|
||||
if (Array.isArray(props)) {
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
payload = addNestedProperty(payload, props[i], validAttributes);
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
for (const propKey in props) {
|
||||
const prop = props[propKey];
|
||||
|
||||
const attributeConfig = ((validAttributes[
|
||||
propKey
|
||||
]: any): AttributeConfiguration);
|
||||
|
||||
if (attributeConfig == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let newValue;
|
||||
|
||||
if (prop === undefined) {
|
||||
// Discard the prop if it was previously defined.
|
||||
if (payload && payload[propKey] !== undefined) {
|
||||
newValue = null;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (typeof attributeConfig === 'object') {
|
||||
if (typeof attributeConfig.process === 'function') {
|
||||
// An atomic prop with custom processing.
|
||||
newValue = attributeConfig.process(prop);
|
||||
} else if (typeof attributeConfig.diff === 'function') {
|
||||
// An atomic prop with custom diffing. We don't need to do diffing when adding props.
|
||||
newValue = prop;
|
||||
}
|
||||
} else {
|
||||
if (typeof prop === 'function') {
|
||||
// A function prop. It represents an event handler. Pass it to native as 'true'.
|
||||
newValue = true;
|
||||
} else {
|
||||
// An atomic prop. Doesn't need to be flattened.
|
||||
newValue = prop;
|
||||
}
|
||||
}
|
||||
|
||||
if (newValue !== undefined) {
|
||||
if (!payload) {
|
||||
payload = ({}: {[string]: $FlowFixMe});
|
||||
}
|
||||
payload[propKey] = newValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
payload = addNestedProperty(payload, prop, attributeConfig);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,7 +487,6 @@ function clearProperties(
|
||||
prevProps: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): null | Object {
|
||||
// TODO: Fast path
|
||||
return diffProperties(updatePayload, prevProps, emptyObject, validAttributes);
|
||||
}
|
||||
|
||||
@@ -471,11 +494,7 @@ export function create(
|
||||
props: Object,
|
||||
validAttributes: AttributeConfiguration,
|
||||
): null | Object {
|
||||
return addProperties(
|
||||
null, // updatePayload
|
||||
props,
|
||||
validAttributes,
|
||||
);
|
||||
return addNestedProperty(null, props, validAttributes);
|
||||
}
|
||||
|
||||
export function diff(
|
||||
|
||||
+495
@@ -0,0 +1,495 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {PartialAttributeConfiguration as AttributeConfiguration} from '../../../Renderer/shims/ReactNativeTypes';
|
||||
|
||||
const {create, diff} = require('../ReactNativeAttributePayload');
|
||||
|
||||
describe('ReactNativeAttributePayload.create', () => {
|
||||
it('should work with simple example', () => {
|
||||
expect(create({b: 2, c: 3}, {a: true, b: true})).toEqual({
|
||||
b: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with complex example', () => {
|
||||
const validAttributes: AttributeConfiguration = {
|
||||
style: {
|
||||
position: true,
|
||||
zIndex: true,
|
||||
flexGrow: true,
|
||||
flexShrink: true,
|
||||
flexDirection: true,
|
||||
overflow: true,
|
||||
backgroundColor: true,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
create(
|
||||
{
|
||||
style: [
|
||||
{
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
flexDirection: 'row',
|
||||
overflow: 'scroll',
|
||||
},
|
||||
[
|
||||
{position: 'relative', zIndex: 2},
|
||||
{flexGrow: 0},
|
||||
{backgroundColor: 'red'},
|
||||
],
|
||||
],
|
||||
},
|
||||
validAttributes,
|
||||
),
|
||||
).toEqual({
|
||||
flexGrow: 0,
|
||||
flexShrink: 1,
|
||||
flexDirection: 'row',
|
||||
overflow: 'scroll',
|
||||
position: 'relative',
|
||||
zIndex: 2,
|
||||
backgroundColor: 'red',
|
||||
});
|
||||
});
|
||||
|
||||
it('should nullify previously defined style prop that is subsequently set to null or undefined', () => {
|
||||
expect(
|
||||
create({style: [{a: 0}, {a: undefined}]}, {style: {a: true}}),
|
||||
).toEqual({a: null});
|
||||
expect(create({style: [{a: 0}, {a: null}]}, {style: {a: true}})).toEqual({
|
||||
a: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore non-style fields that are set to undefined', () => {
|
||||
expect(create({}, {a: true})).toEqual(null);
|
||||
expect(create({a: undefined}, {a: true})).toEqual(null);
|
||||
expect(create({a: undefined, b: undefined}, {a: true, b: true})).toEqual(
|
||||
null,
|
||||
);
|
||||
expect(
|
||||
create({a: undefined, b: undefined, c: 1}, {a: true, b: true}),
|
||||
).toEqual(null);
|
||||
expect(
|
||||
create({a: undefined, b: undefined, c: 1}, {a: true, b: true, c: true}),
|
||||
).toEqual({c: 1});
|
||||
expect(
|
||||
create({a: 1, b: undefined, c: 2}, {a: true, b: true, c: true}),
|
||||
).toEqual({a: 1, c: 2});
|
||||
});
|
||||
|
||||
it('should ignore invalid fields', () => {
|
||||
expect(create({b: 2}, {})).toEqual(null);
|
||||
});
|
||||
|
||||
it('should not use the diff attribute', () => {
|
||||
const diffA = jest.fn();
|
||||
expect(create({a: [2]}, {a: {diff: diffA}})).toEqual({a: [2]});
|
||||
expect(diffA).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should use the process attribute', () => {
|
||||
const processA = jest.fn(a => a + 1);
|
||||
expect(create({a: 2}, {a: {process: processA}})).toEqual({a: 3});
|
||||
expect(processA).toBeCalledWith(2);
|
||||
});
|
||||
|
||||
it('should use the process attribute for functions as well', () => {
|
||||
const process = (x: Object) => x;
|
||||
const nextFunction = () => {};
|
||||
expect(create({a: nextFunction}, {a: {process}})).toEqual({
|
||||
a: nextFunction,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with undefined styles', () => {
|
||||
expect(create({style: undefined}, {style: {b: true}})).toEqual(null);
|
||||
expect(create({style: {a: '#ffffff', b: 1}}, {style: {b: true}})).toEqual({
|
||||
b: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should flatten nested styles and predefined styles', () => {
|
||||
const validStyleAttribute: AttributeConfiguration = {
|
||||
style: {foo: true, bar: true},
|
||||
};
|
||||
expect(create({style: [{foo: 1}, {bar: 2}]}, validStyleAttribute)).toEqual({
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
});
|
||||
expect(create({}, validStyleAttribute)).toEqual(null);
|
||||
const barStyle = {
|
||||
bar: 3,
|
||||
};
|
||||
expect(
|
||||
create({style: [[{foo: 1}, {foo: 2}], barStyle]}, validStyleAttribute),
|
||||
).toEqual({foo: 2, bar: 3});
|
||||
});
|
||||
|
||||
it('should not flatten nested props if attribute config is a primitive or only has diff/process', () => {
|
||||
expect(create({a: {foo: 1, bar: 2}}, {a: true})).toEqual({
|
||||
a: {foo: 1, bar: 2},
|
||||
});
|
||||
expect(create({a: [{foo: 1}, {bar: 2}]}, {a: true})).toEqual({
|
||||
a: [{foo: 1}, {bar: 2}],
|
||||
});
|
||||
expect(create({a: {foo: 1, bar: 2}}, {a: {diff: a => a}})).toEqual({
|
||||
a: {foo: 1, bar: 2},
|
||||
});
|
||||
expect(
|
||||
create({a: [{foo: 1}, {bar: 2}]}, {a: {diff: a => a, process: a => a}}),
|
||||
).toEqual({a: [{foo: 1}, {bar: 2}]});
|
||||
});
|
||||
|
||||
it('handles attributes defined multiple times', () => {
|
||||
const validAttributes: AttributeConfiguration = {
|
||||
foo: true,
|
||||
style: {foo: true},
|
||||
};
|
||||
expect(create({foo: 4, style: {foo: 2}}, validAttributes)).toEqual({
|
||||
foo: 2,
|
||||
});
|
||||
expect(create({style: {foo: 2}}, validAttributes)).toEqual({
|
||||
foo: 2,
|
||||
});
|
||||
expect(create({style: {foo: 2}, foo: 4}, validAttributes)).toEqual({
|
||||
foo: 4,
|
||||
});
|
||||
expect(create({foo: 4, style: {foo: null}}, validAttributes)).toEqual({
|
||||
foo: null, // this should ideally be null.
|
||||
});
|
||||
expect(
|
||||
create({foo: 4, style: [{foo: null}, {foo: 5}]}, validAttributes),
|
||||
).toEqual({
|
||||
foo: 5,
|
||||
});
|
||||
});
|
||||
|
||||
// Function properties are just markers to native that events should be sent.
|
||||
it('should convert functions to booleans', () => {
|
||||
expect(
|
||||
create(
|
||||
{
|
||||
a: function () {
|
||||
return 9;
|
||||
},
|
||||
b: function () {
|
||||
return 3;
|
||||
},
|
||||
},
|
||||
{a: true, b: true},
|
||||
),
|
||||
).toEqual({a: true, b: true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReactNativeAttributePayload.diff', () => {
|
||||
it('should work with simple example', () => {
|
||||
expect(diff({a: 1, c: 3}, {b: 2, c: 3}, {a: true, b: true})).toEqual({
|
||||
a: null,
|
||||
b: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should skip fields that are equal', () => {
|
||||
expect(
|
||||
diff(
|
||||
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
|
||||
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
|
||||
{a: true, b: true, c: true, d: true, e: true, f: true},
|
||||
),
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('should remove fields', () => {
|
||||
expect(diff({a: 1}, {}, {a: true})).toEqual({a: null});
|
||||
});
|
||||
|
||||
it('should remove fields that are set to undefined', () => {
|
||||
expect(diff({a: 1}, {a: undefined}, {a: true})).toEqual({a: null});
|
||||
});
|
||||
|
||||
it('should ignore invalid fields', () => {
|
||||
expect(diff({a: 1}, {b: 2}, {})).toEqual(null);
|
||||
});
|
||||
|
||||
it('should use the diff attribute', () => {
|
||||
const diffA = jest.fn((a, b) => true);
|
||||
const diffB = jest.fn((a, b) => false);
|
||||
expect(
|
||||
diff(
|
||||
{a: [1], b: [3]},
|
||||
{a: [2], b: [4]},
|
||||
{a: {diff: diffA}, b: {diff: diffB}},
|
||||
),
|
||||
).toEqual({a: [2]});
|
||||
expect(diffA).toBeCalledWith([1], [2]);
|
||||
expect(diffB).toBeCalledWith([3], [4]);
|
||||
});
|
||||
|
||||
it('should not use the diff attribute on addition/removal', () => {
|
||||
const diffA = jest.fn();
|
||||
const diffB = jest.fn();
|
||||
expect(
|
||||
diff({a: [1]}, {b: [2]}, {a: {diff: diffA}, b: {diff: diffB}}),
|
||||
).toEqual({a: null, b: [2]});
|
||||
expect(diffA).not.toBeCalled();
|
||||
expect(diffB).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should do deep diffs of Objects by default', () => {
|
||||
expect(
|
||||
diff(
|
||||
{a: [1], b: {k: [3, 4]}, c: {k: [4, 4]}},
|
||||
{a: [2], b: {k: [3, 4]}, c: {k: [4, 5]}},
|
||||
{a: true, b: true, c: true},
|
||||
),
|
||||
).toEqual({a: [2], c: {k: [4, 5]}});
|
||||
});
|
||||
|
||||
it('should work with undefined styles', () => {
|
||||
expect(
|
||||
diff(
|
||||
{style: {a: '#ffffff', b: 1}},
|
||||
{style: undefined},
|
||||
{style: {b: true}},
|
||||
),
|
||||
).toEqual({b: null});
|
||||
expect(
|
||||
diff(
|
||||
{style: undefined},
|
||||
{style: {a: '#ffffff', b: 1}},
|
||||
{style: {b: true}},
|
||||
),
|
||||
).toEqual({b: 1});
|
||||
expect(
|
||||
diff({style: undefined}, {style: undefined}, {style: {b: true}}),
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('should work with empty styles', () => {
|
||||
const validAttributes: AttributeConfiguration = {a: true, b: true};
|
||||
expect(diff({a: 1, c: 3}, {}, validAttributes)).toEqual({a: null});
|
||||
expect(diff({}, {a: 1, c: 3}, validAttributes)).toEqual({a: 1});
|
||||
expect(diff({}, {}, validAttributes)).toEqual(null);
|
||||
});
|
||||
|
||||
it('should flatten nested styles and predefined styles', () => {
|
||||
const validStyleAttribute: AttributeConfiguration = {
|
||||
style: {foo: true, bar: true},
|
||||
};
|
||||
|
||||
expect(
|
||||
diff({}, {style: [{foo: 1}, {bar: 2}]}, validStyleAttribute),
|
||||
).toEqual({foo: 1, bar: 2});
|
||||
|
||||
expect(
|
||||
diff({style: [{foo: 1}, {bar: 2}]}, {}, validStyleAttribute),
|
||||
).toEqual({foo: null, bar: null});
|
||||
|
||||
const barStyle = {
|
||||
bar: 3,
|
||||
};
|
||||
|
||||
expect(
|
||||
diff({}, {style: [[{foo: 1}, {foo: 2}], barStyle]}, validStyleAttribute),
|
||||
).toEqual({foo: 2, bar: 3});
|
||||
});
|
||||
|
||||
it('should reset a value to a previous if it is removed', () => {
|
||||
const validStyleAttribute: AttributeConfiguration = {
|
||||
style: {foo: true, bar: true},
|
||||
};
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{style: [{foo: 1}, {foo: 3}]},
|
||||
{style: [{foo: 1}, {bar: 2}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: 1, bar: 2});
|
||||
});
|
||||
|
||||
it('should not clear removed props if they are still in another slot', () => {
|
||||
const validStyleAttribute: AttributeConfiguration = {
|
||||
style: {foo: true, bar: true},
|
||||
};
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{style: [{}, {foo: 3, bar: 2}]},
|
||||
{style: [{foo: 3}, {bar: 2}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: 3}); // this should ideally be null. heuristic tradeoff.
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{style: [{}, {foo: 3, bar: 2}]},
|
||||
{style: [{foo: 1, bar: 1}, {bar: 2}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({bar: 2, foo: 1});
|
||||
});
|
||||
|
||||
it('should clear a prop if a later style is explicit null/undefined', () => {
|
||||
const validStyleAttribute: AttributeConfiguration = {
|
||||
style: {foo: true, bar: true},
|
||||
};
|
||||
expect(
|
||||
diff(
|
||||
{style: [{}, {foo: 3, bar: 2}]},
|
||||
{style: [{foo: 1}, {bar: 2, foo: null}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: null});
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{style: [{foo: 3}, {foo: null, bar: 2}]},
|
||||
{style: [{foo: null}, {bar: 2}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: null});
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{style: [{foo: 1}, {foo: null}]},
|
||||
{style: [{foo: 2}, {foo: null}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: null}); // this should ideally be null. heuristic.
|
||||
|
||||
// Test the same case with object equality because an early bailout doesn't
|
||||
// work in this case.
|
||||
const fooObj = {foo: 3};
|
||||
expect(
|
||||
diff(
|
||||
{style: [{foo: 1}, fooObj]},
|
||||
{style: [{foo: 2}, fooObj]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: 3}); // this should ideally be null. heuristic.
|
||||
|
||||
expect(
|
||||
diff(
|
||||
{style: [{foo: 1}, {foo: 3}]},
|
||||
{style: [{foo: 2}, {foo: undefined}]},
|
||||
validStyleAttribute,
|
||||
),
|
||||
).toEqual({foo: null}); // this should ideally be null. heuristic.
|
||||
});
|
||||
|
||||
it('handles attributes defined multiple times', () => {
|
||||
const validAttributes: AttributeConfiguration = {
|
||||
foo: true,
|
||||
style: {foo: true},
|
||||
};
|
||||
expect(diff({}, {foo: 4, style: {foo: 2}}, validAttributes)).toEqual({
|
||||
foo: 2,
|
||||
});
|
||||
expect(diff({foo: 4}, {style: {foo: 2}}, validAttributes)).toEqual({
|
||||
foo: 2,
|
||||
});
|
||||
expect(diff({style: {foo: 2}}, {foo: 4}, validAttributes)).toEqual({
|
||||
foo: 4,
|
||||
});
|
||||
});
|
||||
|
||||
// Function properties are just markers to native that events should be sent.
|
||||
it('should convert functions to booleans', () => {
|
||||
// Note that if the property changes from one function to another, we don't
|
||||
// need to send an update.
|
||||
expect(
|
||||
diff(
|
||||
{
|
||||
a: function () {
|
||||
return 1;
|
||||
},
|
||||
b: function () {
|
||||
return 2;
|
||||
},
|
||||
c: 3,
|
||||
},
|
||||
{
|
||||
b: function () {
|
||||
return 9;
|
||||
},
|
||||
c: function () {
|
||||
return 3;
|
||||
},
|
||||
},
|
||||
{a: true, b: true, c: true},
|
||||
),
|
||||
).toEqual({a: null, c: true});
|
||||
});
|
||||
|
||||
it('should skip changed functions', () => {
|
||||
expect(
|
||||
diff(
|
||||
{
|
||||
a: function () {
|
||||
return 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
a: function () {
|
||||
return 9;
|
||||
},
|
||||
},
|
||||
{a: true},
|
||||
),
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('should skip deeply-nested changed functions', () => {
|
||||
expect(
|
||||
diff(
|
||||
{
|
||||
wrapper: {
|
||||
a: function () {
|
||||
return 1;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
wrapper: {
|
||||
a: function () {
|
||||
return 9;
|
||||
},
|
||||
},
|
||||
},
|
||||
{wrapper: true},
|
||||
),
|
||||
).toEqual(null);
|
||||
});
|
||||
|
||||
it('should use the process function config when prop is a function', () => {
|
||||
const process = jest.fn(a => a);
|
||||
const nextFunction = function () {};
|
||||
expect(
|
||||
diff(
|
||||
{
|
||||
a: function () {},
|
||||
},
|
||||
{
|
||||
a: nextFunction,
|
||||
},
|
||||
{a: {process}},
|
||||
),
|
||||
).toEqual({a: nextFunction});
|
||||
expect(process).toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -3515,7 +3515,7 @@ declare export default typeof ReactNativeViewAttributes;
|
||||
`;
|
||||
|
||||
exports[`public API should not change unintentionally Libraries/Components/View/View.js 1`] = `
|
||||
"declare export default component(
|
||||
"declare export default component View(
|
||||
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
|
||||
...props: ViewProps
|
||||
);
|
||||
@@ -3685,7 +3685,6 @@ export type AccessibilityProps = $ReadOnly<{
|
||||
accessibilityLabel?: ?Stringish,
|
||||
accessibilityHint?: ?Stringish,
|
||||
\\"aria-label\\"?: ?Stringish,
|
||||
experimental_accessibilityOrder?: ?Array<string>,
|
||||
accessibilityRole?: ?AccessibilityRole,
|
||||
role?: ?Role,
|
||||
accessibilityState?: ?AccessibilityState,
|
||||
@@ -3833,6 +3832,7 @@ type ViewBaseProps = $ReadOnly<{
|
||||
hitSlop?: ?EdgeInsetsOrSizeProp,
|
||||
pointerEvents?: ?(\\"auto\\" | \\"box-none\\" | \\"box-only\\" | \\"none\\"),
|
||||
removeClippedSubviews?: ?boolean,
|
||||
experimental_accessibilityOrder?: ?Array<string>,
|
||||
}>;
|
||||
export type ViewProps = $ReadOnly<{
|
||||
...DirectEventProps,
|
||||
@@ -5689,6 +5689,7 @@ export type ModalPropsIOS = {
|
||||
>,
|
||||
onDismiss?: ?() => void,
|
||||
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
|
||||
allowSwipeDismissal?: ?boolean,
|
||||
};
|
||||
export type ModalPropsAndroid = {
|
||||
hardwareAccelerated?: ?boolean,
|
||||
|
||||
@@ -336,7 +336,10 @@ let reactRuntime = RNTarget(
|
||||
name: .reactRuntime,
|
||||
path: "ReactCommon/react/runtime",
|
||||
excludedPaths: ["tests", "iostests", "platform"],
|
||||
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt]
|
||||
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt],
|
||||
defines: [
|
||||
CXXSetting.define("HERMES_ENABLE_DEBUGGER", to: "1", .when(configuration: BuildConfiguration.debug))
|
||||
]
|
||||
)
|
||||
|
||||
/// React-runtimeApple.podspec
|
||||
|
||||
@@ -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.
|
||||
|
||||
require "json"
|
||||
|
||||
begin
|
||||
react_native_path = File.dirname(Pod::Executable.execute_command('node', ['-p',
|
||||
'require.resolve(
|
||||
"react-native",
|
||||
{paths: [process.argv[1]]},
|
||||
)', __dir__]).strip
|
||||
)
|
||||
rescue => e
|
||||
# Fallback to the parent directory if the above command fails (e.g when building locally in OOT Platform)
|
||||
react_native_path = File.join(__dir__, "..", "..")
|
||||
end
|
||||
|
||||
# package.json
|
||||
package = JSON.parse(File.read(File.join(react_native_path, "package.json")))
|
||||
version = package['version']
|
||||
|
||||
source = ReactNativeCoreUtils.resolve_podspec_source()
|
||||
|
||||
header_search_paths = [
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/RCTDeprecation",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/ReactCommon/yoga",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/ReactCommon",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/Libraries/AppDelegate",
|
||||
"${PODS_ROOT}/Headers/Private/React-Core-prebuilt/Libraries",
|
||||
|
||||
"$(REACT_NATIVE_PATH)/React/Base",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon",
|
||||
"$(REACT_NATIVE_PATH)/Libraries",
|
||||
"$(REACT_NATIVE_PATH)/ReactApple",
|
||||
"$(REACT_NATIVE_PATH)/ReactCxxPlatform",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon/react/runtime/platform/ios",
|
||||
"${REACT_NATIVE_PATH}/ReactCommon/jsi",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon/jsiexecutor/",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon/react/nativemodule/samples/platform/ios",
|
||||
"$(REACT_NATIVE_PATH)/ReactCommon/react/nativemodule/samples",
|
||||
]
|
||||
|
||||
Pod::Spec.new do |spec|
|
||||
spec.name = 'React-Core-prebuilt'
|
||||
spec.version = version
|
||||
spec.summary = "Prebuilt core of React Native."
|
||||
spec.homepage = "https://reactnative.dev/"
|
||||
spec.description = 'Prebuilt React Native Core libraries and headers'
|
||||
spec.homepage = 'https://github.com/facebook/react-native'
|
||||
spec.license = package['license']
|
||||
spec.authors = 'meta'
|
||||
spec.platforms = min_supported_versions
|
||||
spec.source = source
|
||||
|
||||
spec.vendored_frameworks = "React.xcframework"
|
||||
|
||||
spec.preserve_paths = '**/*.*'
|
||||
spec.header_mappings_dir = 'React.xcframework/Headers'
|
||||
spec.source_files = 'React.xcframework/Headers/**/*.{h,hpp}'
|
||||
|
||||
spec.module_name = 'React'
|
||||
spec.module_map = 'React.xcframework/Modules/module.modulemap'
|
||||
spec.public_header_files = 'React.xcframework/Headers/**/*.h'
|
||||
|
||||
# Setup the consuming project's search paths
|
||||
spec.user_target_xcconfig = {
|
||||
"HEADER_SEARCH_PATHS" => header_search_paths,
|
||||
"SWIFT_INCLUDE_PATHS" => "${PODS_ROOT}/Headers/Private/React-Core-prebuilt",
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
'CLANG_ENABLE_MODULES' => 'YES',
|
||||
}
|
||||
|
||||
spec.pod_target_xcconfig = {
|
||||
'WARNING_CFLAGS' => '-Wno-comma -Wno-shorten-64-to-32',
|
||||
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
|
||||
'DEFINES_MODULE' => 'YES',
|
||||
'CLANG_ENABLE_MODULES' => 'YES',
|
||||
}
|
||||
|
||||
# We need to make sure that the React.xcframework is copied correctly - in the downloaded tarball
|
||||
# the root directory is the framework, but when using it we need to have it in a subdirectory
|
||||
# called React.xcframework, so we need to move the contents of the tarball into that directory.
|
||||
# This is done in the prepare_command.
|
||||
spec.prepare_command = <<~'CMD'
|
||||
CURRENT_PATH=$(pwd)
|
||||
XCFRAMEWORK_PATH="${CURRENT_PATH}/React.xcframework"
|
||||
mkdir -p "${XCFRAMEWORK_PATH}"
|
||||
find "$CURRENT_PATH" -mindepth 1 -maxdepth 1 ! -name "$(basename "$XCFRAMEWORK_PATH")" -exec mv {} "$XCFRAMEWORK_PATH" \;
|
||||
CMD
|
||||
|
||||
# If we are passing a local tarball, we don't want to switch between Debug and Release
|
||||
if !ENV["RCT_USE_LOCAL_RN_CORE"]
|
||||
script_phase = {
|
||||
:name => "[RNDeps] Replace React Native Core for the right configuration, if needed",
|
||||
:execution_position => :before_compile,
|
||||
:script => <<-EOS
|
||||
. "$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh"
|
||||
|
||||
CONFIG="Release"
|
||||
if echo $GCC_PREPROCESSOR_DEFINITIONS | grep -q "DEBUG=1"; then
|
||||
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"
|
||||
EOS
|
||||
}
|
||||
|
||||
|
||||
# :always_out_of_date is only available in CocoaPods 1.13.0 and later
|
||||
if Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.13.0')
|
||||
# always run the script without warning
|
||||
script_phase[:always_out_of_date] = "1"
|
||||
end
|
||||
|
||||
spec.script_phase = script_phase
|
||||
end
|
||||
|
||||
end
|
||||
@@ -16,10 +16,6 @@ else
|
||||
source[:tag] = "v#{version}"
|
||||
end
|
||||
|
||||
use_hermes = ENV['USE_HERMES'] == nil || ENV['USE_HERMES'] == '1'
|
||||
use_hermes_flag = use_hermes ? "-DUSE_HERMES=1" : ""
|
||||
use_third_party_jsc_flag = ENV['USE_THIRD_PARTY_JSC'] == '1' ? "-DUSE_THIRD_PARTY_JSC=1" : ""
|
||||
|
||||
header_subspecs = {
|
||||
'CoreModulesHeaders' => 'React/CoreModules/**/*.h',
|
||||
'RCTActionSheetHeaders' => 'Libraries/ActionSheetIOS/*.h',
|
||||
@@ -35,7 +31,7 @@ header_subspecs = {
|
||||
}
|
||||
|
||||
frameworks_search_paths = []
|
||||
frameworks_search_paths << "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-hermes\"" if use_hermes
|
||||
frameworks_search_paths << "\"$(PODS_CONFIGURATION_BUILD_DIR)/React-hermes\"" if use_hermes()
|
||||
|
||||
header_search_paths = [
|
||||
"$(PODS_TARGET_SRCROOT)/ReactCommon",
|
||||
@@ -56,7 +52,7 @@ Pod::Spec.new do |s|
|
||||
s.platforms = min_supported_versions
|
||||
s.source = source
|
||||
s.resource_bundle = { "RCTI18nStrings" => ["React/I18n/strings/*.lproj"]}
|
||||
s.compiler_flags = use_hermes_flag + ' ' + use_third_party_jsc_flag
|
||||
s.compiler_flags = js_engine_flags()
|
||||
s.header_dir = "React"
|
||||
s.weak_framework = "JavaScriptCore"
|
||||
s.pod_target_xcconfig = {
|
||||
@@ -80,13 +76,9 @@ Pod::Spec.new do |s|
|
||||
"React/Inspector/**/*",
|
||||
"React/Runtime/**/*",
|
||||
]
|
||||
# If we are using Hermes (the default is use hermes, so USE_HERMES can be nil), we don't have jsc installed
|
||||
# So we have to exclude the JSCExecutorFactory
|
||||
if use_hermes
|
||||
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
|
||||
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
|
||||
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
|
||||
end
|
||||
|
||||
# The default is use hermes, we don't have jsc installed
|
||||
exclude_files = exclude_files.append("React/CxxBridge/JSCExecutorFactory.{h,mm}")
|
||||
|
||||
ss.exclude_files = exclude_files
|
||||
ss.private_header_files = "React/Cxx*/*.h"
|
||||
@@ -123,7 +115,7 @@ Pod::Spec.new do |s|
|
||||
s.dependency "React-runtimescheduler"
|
||||
s.dependency "Yoga"
|
||||
|
||||
if use_hermes
|
||||
if use_hermes()
|
||||
s.dependency "React-hermes"
|
||||
end
|
||||
|
||||
@@ -136,7 +128,6 @@ Pod::Spec.new do |s|
|
||||
add_dependency(s, "React-utils", :additional_framework_paths => ["react/utils/platform/ios"])
|
||||
add_dependency(s, "RCTDeprecation")
|
||||
|
||||
|
||||
depend_on_js_engine(s)
|
||||
add_rn_third_party_dependencies(s)
|
||||
end
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
|
||||
#import <React/RCTUtilsUIOverride.h>
|
||||
#import <ReactCommon/RuntimeExecutorSyncUIThreadUtils.h>
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
NSString *const RCTErrorUnspecified = @"EUNSPECIFIED";
|
||||
|
||||
// Returns the Path of Home directory
|
||||
@@ -314,7 +317,12 @@ void RCTUnsafeExecuteOnMainQueueSyncWithError(dispatch_block_t block, NSString *
|
||||
return;
|
||||
}
|
||||
|
||||
if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
|
||||
if (ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS()) {
|
||||
unsafeExecuteOnMainThreadSync(block);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
|
||||
RCTLogError(@"RCTUnsafeExecuteOnMainQueueSync: %@", context);
|
||||
}
|
||||
|
||||
@@ -341,7 +349,12 @@ static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, disp
|
||||
return;
|
||||
}
|
||||
|
||||
if (facebook::react::ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
|
||||
if (ReactNativeFeatureFlags::enableMainQueueCoordinatorOnIOS()) {
|
||||
unsafeExecuteOnMainThreadSync(block);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags::disableMainQueueSyncDispatchIOS()) {
|
||||
RCTLogError(@"RCTUnsafeExecuteOnMainQueueOnceSync: Sync dispatches to the main queue can deadlock React Native.");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef RCT_FIT_RM_OLD_RUNTIME
|
||||
|
||||
#include <jsireact/JSIExecutor.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class JSCExecutorFactory : public JSExecutorFactory {
|
||||
public:
|
||||
explicit JSCExecutorFactory(JSIExecutor::RuntimeInstaller runtimeInstaller)
|
||||
: runtimeInstaller_(std::move(runtimeInstaller)) {}
|
||||
|
||||
std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) override;
|
||||
|
||||
private:
|
||||
JSIExecutor::RuntimeInstaller runtimeInstaller_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
#endif // RCT_FIT_RM_OLD_RUNTIME
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "JSCExecutorFactory.h"
|
||||
|
||||
#ifndef RCT_FIT_RM_OLD_RUNTIME
|
||||
|
||||
#import <jsc/JSCRuntime.h>
|
||||
|
||||
#import <memory>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> __unused jsQueue)
|
||||
{
|
||||
return std::make_unique<JSIExecutor>(
|
||||
facebook::jsc::makeJSCRuntime(), delegate, JSIExecutor::defaultTimeoutInvoker, runtimeInstaller_);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
#endif // RCT_FIT_RM_OLD_RUNTIME
|
||||
@@ -45,10 +45,8 @@
|
||||
#import <react/utils/FollyConvert.h>
|
||||
#import <reactperflogger/BridgeNativeModulePerfLogger.h>
|
||||
|
||||
#if USE_HERMES
|
||||
#if !defined(USE_HERMES) || USE_HERMES == 1
|
||||
#import <reacthermes/HermesExecutorFactory.h>
|
||||
#elif USE_THIRD_PARTY_JSC != 1
|
||||
#import "JSCExecutorFactory.h"
|
||||
#endif
|
||||
#import "RCTJSIExecutorRuntimeInstaller.h"
|
||||
|
||||
@@ -471,12 +469,8 @@ struct RCTInstanceCallback : public InstanceCallback {
|
||||
}
|
||||
if (!executorFactory) {
|
||||
auto installBindings = RCTJSIExecutorRuntimeInstaller(nullptr);
|
||||
#if USE_HERMES
|
||||
#if !defined(USE_HERMES) || USE_HERMES == 1
|
||||
executorFactory = std::make_shared<HermesExecutorFactory>(installBindings);
|
||||
#elif USE_THIRD_PARTY_JSC != 1
|
||||
executorFactory = std::make_shared<JSCExecutorFactory>(installBindings);
|
||||
#else
|
||||
throw std::runtime_error("No JSExecutorFactory specified.");
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
@@ -1144,7 +1138,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithBundleURL
|
||||
/**
|
||||
* Prevent super from calling setUp (that'd create another batchedBridge)
|
||||
*/
|
||||
- (void)setUp {}
|
||||
- (void)setUp
|
||||
{
|
||||
}
|
||||
|
||||
- (Class)executorClass
|
||||
{
|
||||
|
||||
-2
@@ -22,8 +22,6 @@
|
||||
}
|
||||
_touchHandler = [RCTSurfaceTouchHandler new];
|
||||
|
||||
self.modalInPresentation = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
+16
@@ -124,6 +124,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
_viewController = [RCTFabricModalHostViewController new];
|
||||
_viewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
|
||||
_viewController.delegate = self;
|
||||
_viewController.modalInPresentation = YES;
|
||||
}
|
||||
return _viewController;
|
||||
}
|
||||
@@ -239,6 +240,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
|
||||
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
|
||||
{
|
||||
const auto &oldViewProps = static_cast<const ModalHostViewProps &>(*_props);
|
||||
const auto &newProps = static_cast<const ModalHostViewProps &>(*props);
|
||||
|
||||
#if !TARGET_OS_TV
|
||||
@@ -251,6 +253,10 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
|
||||
self.viewController.modalPresentationStyle = presentationConfiguration(newProps);
|
||||
|
||||
if (oldViewProps.allowSwipeDismissal != newProps.allowSwipeDismissal) {
|
||||
self.viewController.modalInPresentation = !newProps.allowSwipeDismissal;
|
||||
}
|
||||
|
||||
_shouldPresent = newProps.visible;
|
||||
[self ensurePresentedOnlyIfNeeded];
|
||||
|
||||
@@ -283,6 +289,16 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
auto eventEmitter = [self modalEventEmitter];
|
||||
const auto &props = static_cast<const ModalHostViewProps &>(*_props);
|
||||
|
||||
if (eventEmitter && props.allowSwipeDismissal) {
|
||||
eventEmitter->onRequestClose({});
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+4
-1
@@ -1230,7 +1230,10 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||
return label;
|
||||
}
|
||||
|
||||
return RCTRecursiveAccessibilityLabel(self.currentContainerView);
|
||||
if (self.isAccessibilityElement) {
|
||||
return RCTRecursiveAccessibilityLabel(self.currentContainerView);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabelForCoopting
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <react/runtime/JSRuntimeFactoryCAPI.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
JSRuntimeFactoryRef jsrt_create_jsc_factory(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTJscInstanceFactory.h"
|
||||
#import <ReactCommon/RCTJscInstance.h>
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
extern "C" {
|
||||
|
||||
JSRuntimeFactoryRef jsrt_create_jsc_factory(void)
|
||||
{
|
||||
return reinterpret_cast<JSRuntimeFactoryRef>(new RCTJscInstance());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -58,15 +58,14 @@ Pod::Spec.new do |s|
|
||||
add_dependency(s, "React-RuntimeCore")
|
||||
add_dependency(s, "React-RuntimeApple")
|
||||
|
||||
if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1"
|
||||
if use_third_party_jsc()
|
||||
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}", "RCTJscInstanceFactory.{mm,h}"]
|
||||
else
|
||||
s.dependency "hermes-engine"
|
||||
add_dependency(s, "React-RuntimeHermes")
|
||||
s.exclude_files = "RCTJscInstanceFactory.{h,mm}"
|
||||
elsif ENV['USE_THIRD_PARTY_JSC'] == '1'
|
||||
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}", "RCTJscInstanceFactory.{mm,h}"]
|
||||
else
|
||||
s.exclude_files = ["RCTHermesInstanceFactory.{mm,h}"]
|
||||
end
|
||||
|
||||
depend_on_js_engine(s)
|
||||
add_rn_third_party_dependencies(s)
|
||||
end
|
||||
|
||||
@@ -35,6 +35,11 @@ typedef struct {
|
||||
UIColor *right;
|
||||
} RCTBorderColors;
|
||||
|
||||
/**
|
||||
* Determine the largest border inset value.
|
||||
*/
|
||||
RCT_EXTERN CGFloat RCTMaxBorderInset(UIEdgeInsets borderInsets);
|
||||
|
||||
/**
|
||||
* Determine if the border widths, colors and radii are all equal.
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
|
||||
static const CGFloat RCTViewBorderThreshold = 0.001;
|
||||
|
||||
CGFloat RCTMaxBorderInset(UIEdgeInsets borderInsets)
|
||||
{
|
||||
return MAX(MAX(borderInsets.top, borderInsets.left), MAX(borderInsets.bottom, borderInsets.right));
|
||||
}
|
||||
|
||||
BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets)
|
||||
{
|
||||
return ABS(borderInsets.left - borderInsets.right) < RCTViewBorderThreshold &&
|
||||
@@ -415,8 +420,8 @@ static UIImage *RCTGetSolidBorderImage(
|
||||
return image;
|
||||
}
|
||||
|
||||
// Currently, the dashed / dotted implementation only supports a single colour +
|
||||
// single width, as that's currently required and supported on Android.
|
||||
// Currently, the dashed / dotted implementation only supports a single colour,
|
||||
// as that's currently required and supported on Android.
|
||||
//
|
||||
// Supporting individual widths + colours on each side is possible by modifying
|
||||
// the current implementation. The idea is that we will draw four different lines
|
||||
@@ -486,12 +491,12 @@ static UIImage *RCTGetDashedOrDottedBorderImage(
|
||||
{
|
||||
NSCParameterAssert(borderStyle == RCTBorderStyleDashed || borderStyle == RCTBorderStyleDotted);
|
||||
|
||||
if (!RCTBorderColorsAreEqual(borderColors) || !RCTBorderInsetsAreEqual(borderInsets)) {
|
||||
if (!RCTBorderColorsAreEqual(borderColors)) {
|
||||
RCTLogWarn(@"Unsupported dashed / dotted border style");
|
||||
return nil;
|
||||
}
|
||||
|
||||
const CGFloat lineWidth = borderInsets.top;
|
||||
const CGFloat lineWidth = RCTMaxBorderInset(borderInsets);
|
||||
if (lineWidth <= 0.0) {
|
||||
return nil;
|
||||
}
|
||||
@@ -519,6 +524,34 @@ static UIImage *RCTGetDashedOrDottedBorderImage(
|
||||
CGPathRef path =
|
||||
RCTPathCreateWithRoundedRect(pathRect, RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero), NULL, NO);
|
||||
|
||||
if (!RCTBorderInsetsAreEqual(borderInsets)) {
|
||||
CGContextSaveGState(context);
|
||||
{
|
||||
// Create a path representing the full rect
|
||||
CGMutablePathRef outerPath = CGPathCreateMutable();
|
||||
CGPathAddRect(outerPath, NULL, rect);
|
||||
|
||||
CGRect insetRect = CGRectMake(
|
||||
rect.origin.x + borderInsets.left,
|
||||
rect.origin.y + borderInsets.top,
|
||||
rect.size.width - borderInsets.left - borderInsets.right,
|
||||
rect.size.height - borderInsets.top - borderInsets.bottom);
|
||||
|
||||
// The padding edge (inner border) radius is the outer border radius minus the corresponding border thickness
|
||||
CGPathRef innerRoundedRect =
|
||||
RCTPathCreateWithRoundedRect(insetRect, RCTGetCornerInsets(cornerRadii, borderInsets), NULL, NO);
|
||||
|
||||
// Add both paths to outerPath
|
||||
CGPathAddPath(outerPath, NULL, innerRoundedRect);
|
||||
|
||||
// Clip using even-odd
|
||||
CGContextAddPath(context, outerPath);
|
||||
CGContextEOClip(context);
|
||||
CGPathRelease(outerPath);
|
||||
CGPathRelease(innerRoundedRect);
|
||||
}
|
||||
}
|
||||
|
||||
CGFloat dashLengths[2];
|
||||
dashLengths[0] = dashLengths[1] = (borderStyle == RCTBorderStyleDashed ? 3 : 1) * lineWidth;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onShow;
|
||||
@property (nonatomic, assign) BOOL visible;
|
||||
@property (nonatomic, assign) BOOL allowSwipeDismissal;
|
||||
|
||||
// Android only
|
||||
@property (nonatomic, assign) BOOL statusBarTranslucent;
|
||||
|
||||
@@ -35,6 +35,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
|
||||
if ((self = [super initWithFrame:CGRectZero])) {
|
||||
_bridge = bridge;
|
||||
_modalViewController = [RCTModalHostViewController new];
|
||||
_modalViewController.modalInPresentation = YES;
|
||||
UIView *containerView = [UIView new];
|
||||
containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||
_modalViewController.view = containerView;
|
||||
@@ -50,6 +51,14 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setAllowSwipeDismissal:(BOOL)allowSwipeDismissal
|
||||
{
|
||||
if (_allowSwipeDismissal != allowSwipeDismissal) {
|
||||
_allowSwipeDismissal = allowSwipeDismissal;
|
||||
_modalViewController.modalInPresentation = !allowSwipeDismissal;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyForBoundsChange:(CGRect)newBounds
|
||||
{
|
||||
if (_reactSubview && _isPresented) {
|
||||
@@ -70,6 +79,13 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
if (_onRequestClose != nil && _allowSwipeDismissal) {
|
||||
_onRequestClose(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyForOrientationChange
|
||||
{
|
||||
if (!_onOrientationChange) {
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.modalInPresentation = YES;
|
||||
|
||||
_preferredStatusBarStyle = [RCTUIStatusBarManager() statusBarStyle];
|
||||
_preferredStatusBarHidden = [RCTUIStatusBarManager() isStatusBarHidden];
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(visible, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onRequestClose, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowSwipeDismissal, BOOL)
|
||||
|
||||
// Fabric only
|
||||
RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock)
|
||||
|
||||
@@ -240,7 +240,10 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
|
||||
if (label) {
|
||||
return label;
|
||||
}
|
||||
return RCTRecursiveAccessibilityLabel(self);
|
||||
if (self.isAccessibilityElement) {
|
||||
return RCTRecursiveAccessibilityLabel(self);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
|
||||
|
||||
@@ -950,16 +950,16 @@ public abstract interface class com/facebook/react/bridge/MemoryPressureListener
|
||||
public abstract fun handleMemoryPressure (I)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/bridge/ModuleHolder {
|
||||
public final class com/facebook/react/bridge/ModuleHolder {
|
||||
public fun <init> (Lcom/facebook/react/bridge/NativeModule;)V
|
||||
public fun <init> (Lcom/facebook/react/module/model/ReactModuleInfo;Ljavax/inject/Provider;)V
|
||||
public fun destroy ()V
|
||||
public fun getCanOverrideExistingModule ()Z
|
||||
public fun getClassName ()Ljava/lang/String;
|
||||
public fun getModule ()Lcom/facebook/react/bridge/NativeModule;
|
||||
public fun getName ()Ljava/lang/String;
|
||||
public fun isCxxModule ()Z
|
||||
public fun isTurboModule ()Z
|
||||
public final fun destroy ()V
|
||||
public final fun getCanOverrideExistingModule ()Z
|
||||
public final fun getClassName ()Ljava/lang/String;
|
||||
public final fun getModule ()Lcom/facebook/react/bridge/NativeModule;
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
public final fun isCxxModule ()Z
|
||||
public final fun isTurboModule ()Z
|
||||
}
|
||||
|
||||
public final class com/facebook/react/bridge/ModuleSpec {
|
||||
@@ -2448,19 +2448,6 @@ public abstract interface class com/facebook/react/interfaces/fabric/ReactSurfac
|
||||
public abstract fun stop ()Lcom/facebook/react/interfaces/TaskInterface;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/jscexecutor/JSCExecutor : com/facebook/react/bridge/JavaScriptExecutor {
|
||||
public fun getName ()Ljava/lang/String;
|
||||
public static final fun loadLibrary ()V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/jscexecutor/JSCExecutorFactory : com/facebook/react/bridge/JavaScriptExecutorFactory {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
|
||||
public fun create ()Lcom/facebook/react/bridge/JavaScriptExecutor;
|
||||
public fun startSamplingProfiler ()V
|
||||
public fun stopSamplingProfiler (Ljava/lang/String;)V
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/jstasks/HeadlessJsTaskConfig {
|
||||
public fun <init> (Lcom/facebook/react/jstasks/HeadlessJsTaskConfig;)V
|
||||
public fun <init> (Ljava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
|
||||
@@ -2747,36 +2734,6 @@ public final class com/facebook/react/modules/debug/DevSettingsModule : com/face
|
||||
public final class com/facebook/react/modules/debug/DevSettingsModule$Companion {
|
||||
}
|
||||
|
||||
public final class com/facebook/react/modules/debug/FpsDebugFrameCallback : android/view/Choreographer$FrameCallback {
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
|
||||
public fun doFrame (J)V
|
||||
public final fun get4PlusFrameStutters ()I
|
||||
public final fun getExpectedNumFrames ()I
|
||||
public final fun getFps ()D
|
||||
public final fun getFpsInfo (J)Lcom/facebook/react/modules/debug/FpsDebugFrameCallback$FpsInfo;
|
||||
public final fun getJsFPS ()D
|
||||
public final fun getNumFrames ()I
|
||||
public final fun getNumJSFrames ()I
|
||||
public final fun getTotalTimeMS ()I
|
||||
public final fun reset ()V
|
||||
public final fun start ()V
|
||||
public final fun start (D)V
|
||||
public static synthetic fun start$default (Lcom/facebook/react/modules/debug/FpsDebugFrameCallback;DILjava/lang/Object;)V
|
||||
public final fun startAndRecordFpsAtEachFrame ()V
|
||||
public final fun stop ()V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/modules/debug/FpsDebugFrameCallback$FpsInfo {
|
||||
public fun <init> (IIIIDDI)V
|
||||
public final fun getFps ()D
|
||||
public final fun getJsFps ()D
|
||||
public final fun getTotal4PlusFrameStutters ()I
|
||||
public final fun getTotalExpectedFrames ()I
|
||||
public final fun getTotalFrames ()I
|
||||
public final fun getTotalJsFrames ()I
|
||||
public final fun getTotalTimeMs ()I
|
||||
}
|
||||
|
||||
public final class com/facebook/react/modules/debug/SourceCodeModule : com/facebook/fbreact/specs/NativeSourceCodeSpec {
|
||||
public static final field Companion Lcom/facebook/react/modules/debug/SourceCodeModule$Companion;
|
||||
public static final field NAME Ljava/lang/String;
|
||||
@@ -3165,10 +3122,6 @@ public class com/facebook/react/runtime/CoreReactPackage$$ReactModuleInfoProvide
|
||||
public fun getReactModuleInfos ()Ljava/util/Map;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/runtime/JSCInstance : com/facebook/react/runtime/JSRuntimeFactory {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public abstract class com/facebook/react/runtime/JSRuntimeFactory {
|
||||
public fun <init> (Lcom/facebook/jni/HybridData;)V
|
||||
}
|
||||
@@ -3283,10 +3236,6 @@ public final class com/facebook/react/soloader/OpenSourceMergedSoMapping : com/f
|
||||
public final fun libhermes_executor_so ()I
|
||||
public final fun libhermesinstancejni_so ()I
|
||||
public final fun libhermestooling_so ()I
|
||||
public final fun libjscexecutor_so ()I
|
||||
public final fun libjscinstance_so ()I
|
||||
public final fun libjscruntime_so ()I
|
||||
public final fun libjsctooling_so ()I
|
||||
public final fun libjsijniprofiler_so ()I
|
||||
public final fun libjsinspector_so ()I
|
||||
public final fun libmapbufferjni_so ()I
|
||||
@@ -5357,6 +5306,7 @@ public class com/facebook/react/viewmanagers/ModalHostViewManagerDelegate : com/
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/viewmanagers/ModalHostViewManagerInterface : com/facebook/react/uimanager/ViewManagerWithGeneratedInterface {
|
||||
public abstract fun setAllowSwipeDismissal (Landroid/view/View;Z)V
|
||||
public abstract fun setAnimated (Landroid/view/View;Z)V
|
||||
public abstract fun setAnimationType (Landroid/view/View;Ljava/lang/String;)V
|
||||
public abstract fun setHardwareAccelerated (Landroid/view/View;Z)V
|
||||
@@ -6447,37 +6397,6 @@ public final class com/facebook/react/views/text/TextAttributes {
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/text/TextLayoutManager {
|
||||
public static final field AS_KEY_BASE_ATTRIBUTES S
|
||||
public static final field AS_KEY_CACHE_ID S
|
||||
public static final field AS_KEY_FRAGMENTS S
|
||||
public static final field AS_KEY_HASH S
|
||||
public static final field AS_KEY_STRING S
|
||||
public static final field FR_KEY_HEIGHT S
|
||||
public static final field FR_KEY_IS_ATTACHMENT S
|
||||
public static final field FR_KEY_REACT_TAG S
|
||||
public static final field FR_KEY_STRING S
|
||||
public static final field FR_KEY_TEXT_ATTRIBUTES S
|
||||
public static final field FR_KEY_WIDTH S
|
||||
public static final field PA_KEY_ADJUST_FONT_SIZE_TO_FIT S
|
||||
public static final field PA_KEY_ELLIPSIZE_MODE S
|
||||
public static final field PA_KEY_HYPHENATION_FREQUENCY S
|
||||
public static final field PA_KEY_INCLUDE_FONT_PADDING S
|
||||
public static final field PA_KEY_MAXIMUM_FONT_SIZE S
|
||||
public static final field PA_KEY_MAX_NUMBER_OF_LINES S
|
||||
public static final field PA_KEY_MINIMUM_FONT_SIZE S
|
||||
public static final field PA_KEY_TEXT_ALIGN_VERTICAL S
|
||||
public static final field PA_KEY_TEXT_BREAK_STRATEGY S
|
||||
public fun <init> ()V
|
||||
public static fun deleteCachedSpannableForTag (I)V
|
||||
public static fun getOrCreateSpannableForText (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)Landroid/text/Spannable;
|
||||
public static fun getTextGravity (Lcom/facebook/react/common/mapbuffer/MapBuffer;Landroid/text/Spannable;I)I
|
||||
public static fun isRTL (Lcom/facebook/react/common/mapbuffer/MapBuffer;)Z
|
||||
public static fun measureLines (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/common/mapbuffer/MapBuffer;FF)Lcom/facebook/react/bridge/WritableArray;
|
||||
public static fun measureText (Landroid/content/Context;Lcom/facebook/react/common/mapbuffer/MapBuffer;Lcom/facebook/react/common/mapbuffer/MapBuffer;FLcom/facebook/yoga/YogaMeasureMode;FLcom/facebook/yoga/YogaMeasureMode;Lcom/facebook/react/views/text/ReactTextViewManagerCallback;[F)J
|
||||
public static fun setCachedSpannableForTag (ILandroid/text/Spannable;)V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/views/textinput/ContentSizeWatcher {
|
||||
public abstract fun onLayout ()V
|
||||
}
|
||||
|
||||
@@ -87,8 +87,6 @@ val preparePrefab by
|
||||
// hermes_executor
|
||||
// This prefab targets is used by Expo & Reanimated
|
||||
Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/"),
|
||||
// jscexecutor
|
||||
Pair("../ReactCommon/jsc/", "jsc/"),
|
||||
// fabricjni
|
||||
Pair("src/main/jni/react/fabric", "react/fabric/"),
|
||||
// glog
|
||||
@@ -236,10 +234,6 @@ val preparePrefab by
|
||||
"hermestooling",
|
||||
// hermes_executor
|
||||
Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/")),
|
||||
PrefabPreprocessingEntry(
|
||||
"jsctooling",
|
||||
// jsc
|
||||
Pair("../ReactCommon/jsc/", "jsc/")),
|
||||
))
|
||||
outputDir.set(prefabHeadersDir)
|
||||
}
|
||||
@@ -534,7 +528,6 @@ android {
|
||||
"reactnative",
|
||||
"jsi",
|
||||
"hermestooling",
|
||||
"jsctooling",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -583,10 +576,9 @@ android {
|
||||
resources.excludes.add("META-INF/LICENSE")
|
||||
// We intentionally don't want to bundle any JS Runtime inside the Android AAR
|
||||
// we produce. The reason behind this is that we want to allow users to pick the
|
||||
// JS engine by specifying a dependency on either `hermes-engine` or `android-jsc`
|
||||
// JS engine by specifying a dependency on either `hermes-engine` or other engines
|
||||
// that will include the necessary .so files to load.
|
||||
jniLibs.excludes.add("**/libhermes.so")
|
||||
jniLibs.excludes.add("**/libjsc.so")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
@@ -599,7 +591,6 @@ android {
|
||||
create("jsi") { headers = File(prefabHeadersDir, "jsi").absolutePath }
|
||||
create("reactnative") { headers = File(prefabHeadersDir, "reactnative").absolutePath }
|
||||
create("hermestooling") { headers = File(prefabHeadersDir, "hermestooling").absolutePath }
|
||||
create("jsctooling") { headers = File(prefabHeadersDir, "jsctooling").absolutePath }
|
||||
}
|
||||
|
||||
publishing {
|
||||
@@ -644,10 +635,9 @@ dependencies {
|
||||
compileOnly(libs.javax.annotation.api)
|
||||
api(libs.javax.inject)
|
||||
|
||||
// It's up to the consumer to decide if hermes/jsc should be included or not.
|
||||
// Therefore hermes-engine and jsc are compileOnly dependencies.
|
||||
// It's up to the consumer to decide if hermes or other engines should be included or not.
|
||||
// Therefore hermes-engine is a compileOnly dependencies.
|
||||
compileOnly(project(":packages:react-native:ReactAndroid:hermes-engine"))
|
||||
compileOnly(libs.jsc.android)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.assertj)
|
||||
|
||||
@@ -186,6 +186,24 @@ val prepareHeadersForPrefab by
|
||||
into(prefabHeadersDir)
|
||||
}
|
||||
|
||||
val buildHermesLib by
|
||||
tasks.registering(CustomExecTask::class) {
|
||||
dependsOn(buildHermesC)
|
||||
workingDir(hermesDir)
|
||||
inputs.files(hermesBuildOutputFileTree)
|
||||
commandLine(
|
||||
cmakeBinaryPath,
|
||||
"--build",
|
||||
hermesBuildDir.toString(),
|
||||
"--target",
|
||||
"libhermes",
|
||||
"-j",
|
||||
ndkBuildJobs,
|
||||
)
|
||||
standardOutputFile.set(project.file("$buildDir/build-hermes-lib.log"))
|
||||
errorOutputFile.set(project.file("$buildDir/build-hermes-lib.error.log"))
|
||||
}
|
||||
|
||||
fun windowsAwareCommandLine(vararg commands: String): List<String> {
|
||||
val result =
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
|
||||
-9
@@ -33,13 +33,4 @@ internal object ReactDebugOverlayTags {
|
||||
"UI Manager",
|
||||
"UI Manager View Operations (requires restart\nwarning: this is spammy)",
|
||||
Color.CYAN)
|
||||
@JvmField
|
||||
val FABRIC_UI_MANAGER: DebugOverlayTag =
|
||||
DebugOverlayTag("FabricUIManager", "Fabric UI Manager View Operations", Color.CYAN)
|
||||
@JvmField
|
||||
val FABRIC_RECONCILER: DebugOverlayTag =
|
||||
DebugOverlayTag("FabricReconciler", "Reconciler for Fabric", Color.CYAN)
|
||||
@JvmField
|
||||
val RELAY: DebugOverlayTag =
|
||||
DebugOverlayTag("Relay", "including prefetching", Color.rgb(0xFF, 0x99, 0x00))
|
||||
}
|
||||
|
||||
+4
@@ -1079,6 +1079,10 @@ public class ReactInstanceManager {
|
||||
if (reactPackage instanceof ViewManagerOnDemandReactPackage) {
|
||||
Collection<String> names =
|
||||
((ViewManagerOnDemandReactPackage) reactPackage).getViewManagerNames(context);
|
||||
// When converting this class to Kotlin, you need to retain this null check
|
||||
// or wrap around a try/catch otherwise this will cause a crash for OSS libraries
|
||||
// that are not migrated to Kotlin yet and are returning null for
|
||||
// `getViewManagerNames`
|
||||
if (names != null) {
|
||||
uniqueNames.addAll(names);
|
||||
}
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext?) :
|
||||
NativeAnimatedModuleSpec(reactContext), LifecycleEventListener, UIManagerListener {
|
||||
|
||||
// For `queueAndExecuteBatchedOperations`
|
||||
private enum class BatchExecutionOpCodes(val value: Int) {
|
||||
private enum class BatchExecutionOpCodes(value: Int) {
|
||||
OP_CODE_CREATE_ANIMATED_NODE(1),
|
||||
OP_CODE_UPDATE_ANIMATED_NODE_CONFIG(2),
|
||||
OP_CODE_GET_VALUE(3),
|
||||
@@ -111,7 +111,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext?) :
|
||||
private var valueMap: Array<BatchExecutionOpCodes>? = null
|
||||
|
||||
@JvmStatic
|
||||
public fun fromId(id: Int): BatchExecutionOpCodes {
|
||||
fun fromId(id: Int): BatchExecutionOpCodes {
|
||||
val valueMapNonnull: Array<BatchExecutionOpCodes> =
|
||||
valueMap ?: BatchExecutionOpCodes.values()
|
||||
if (valueMap == null) {
|
||||
|
||||
-413
@@ -1,413 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import static com.facebook.infer.annotation.Assertions.assertNotNull;
|
||||
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.debug.holder.PrinterHolder;
|
||||
import com.facebook.debug.tags.ReactDebugOverlayTags;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
|
||||
import com.facebook.systrace.SystraceMessage;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
class JavaMethodWrapper implements JavaModuleWrapper.NativeMethod {
|
||||
static {
|
||||
LegacyArchitectureLogger.assertLegacyArchitecture(
|
||||
"JavaMethodWrapper", LegacyArchitectureLogLevel.ERROR);
|
||||
}
|
||||
|
||||
private abstract static class ArgumentExtractor<T> {
|
||||
public int getJSArgumentsNeeded() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public abstract @Nullable T extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex);
|
||||
}
|
||||
|
||||
private static final ArgumentExtractor<Boolean> ARGUMENT_EXTRACTOR_BOOLEAN =
|
||||
new ArgumentExtractor<Boolean>() {
|
||||
@Override
|
||||
public Boolean extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
return jsArguments.getBoolean(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<Double> ARGUMENT_EXTRACTOR_DOUBLE =
|
||||
new ArgumentExtractor<Double>() {
|
||||
@Override
|
||||
public Double extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
return jsArguments.getDouble(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<Float> ARGUMENT_EXTRACTOR_FLOAT =
|
||||
new ArgumentExtractor<Float>() {
|
||||
@Override
|
||||
public Float extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
return (float) jsArguments.getDouble(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<Integer> ARGUMENT_EXTRACTOR_INTEGER =
|
||||
new ArgumentExtractor<Integer>() {
|
||||
@Override
|
||||
public Integer extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
return (int) jsArguments.getDouble(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<String> ARGUMENT_EXTRACTOR_STRING =
|
||||
new ArgumentExtractor<String>() {
|
||||
@Override
|
||||
public String extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
return jsArguments.getString(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<ReadableArray> ARGUMENT_EXTRACTOR_ARRAY =
|
||||
new ArgumentExtractor<ReadableArray>() {
|
||||
@Override
|
||||
public ReadableArray extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
return jsArguments.getArray(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<Dynamic> ARGUMENT_EXTRACTOR_DYNAMIC =
|
||||
new ArgumentExtractor<Dynamic>() {
|
||||
@Override
|
||||
public Dynamic extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
return DynamicFromArray.create(jsArguments, atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<ReadableMap> ARGUMENT_EXTRACTOR_MAP =
|
||||
new ArgumentExtractor<ReadableMap>() {
|
||||
@Override
|
||||
public ReadableMap extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
return jsArguments.getMap(atIndex);
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<Callback> ARGUMENT_EXTRACTOR_CALLBACK =
|
||||
new ArgumentExtractor<Callback>() {
|
||||
@Override
|
||||
public @Nullable Callback extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
if (jsArguments.isNull(atIndex)) {
|
||||
return null;
|
||||
} else {
|
||||
int id = (int) jsArguments.getDouble(atIndex);
|
||||
return new com.facebook.react.bridge.CallbackImpl(jsInstance, id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static final ArgumentExtractor<Promise> ARGUMENT_EXTRACTOR_PROMISE =
|
||||
new ArgumentExtractor<Promise>() {
|
||||
@Override
|
||||
public int getJSArgumentsNeeded() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Promise extractArgument(
|
||||
JSInstance jsInstance, ReadableArray jsArguments, int atIndex) {
|
||||
Callback resolve =
|
||||
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex);
|
||||
Callback reject =
|
||||
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex + 1);
|
||||
return new PromiseImpl(resolve, reject);
|
||||
}
|
||||
};
|
||||
|
||||
private static final boolean DEBUG =
|
||||
PrinterHolder.getPrinter().shouldDisplayLogMessage(ReactDebugOverlayTags.BRIDGE_CALLS);
|
||||
|
||||
private static char paramTypeToChar(Class paramClass) {
|
||||
char tryCommon = commonTypeToChar(paramClass);
|
||||
if (tryCommon != '\0') {
|
||||
return tryCommon;
|
||||
}
|
||||
if (paramClass == Callback.class) {
|
||||
return 'X';
|
||||
} else if (paramClass == Promise.class) {
|
||||
return 'P';
|
||||
} else if (paramClass == ReadableMap.class) {
|
||||
return 'M';
|
||||
} else if (paramClass == ReadableArray.class) {
|
||||
return 'A';
|
||||
} else if (paramClass == Dynamic.class) {
|
||||
return 'Y';
|
||||
} else {
|
||||
throw new RuntimeException("Got unknown param class: " + paramClass.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private static char returnTypeToChar(Class returnClass) {
|
||||
// Keep this in sync with MethodInvoker
|
||||
char tryCommon = commonTypeToChar(returnClass);
|
||||
if (tryCommon != '\0') {
|
||||
return tryCommon;
|
||||
}
|
||||
if (returnClass == void.class) {
|
||||
return 'v';
|
||||
} else if (returnClass == WritableMap.class) {
|
||||
return 'M';
|
||||
} else if (returnClass == WritableArray.class) {
|
||||
return 'A';
|
||||
} else {
|
||||
throw new RuntimeException("Got unknown return class: " + returnClass.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private static char commonTypeToChar(Class typeClass) {
|
||||
if (typeClass == boolean.class) {
|
||||
return 'z';
|
||||
} else if (typeClass == Boolean.class) {
|
||||
return 'Z';
|
||||
} else if (typeClass == int.class) {
|
||||
return 'i';
|
||||
} else if (typeClass == Integer.class) {
|
||||
return 'I';
|
||||
} else if (typeClass == double.class) {
|
||||
return 'd';
|
||||
} else if (typeClass == Double.class) {
|
||||
return 'D';
|
||||
} else if (typeClass == float.class) {
|
||||
return 'f';
|
||||
} else if (typeClass == Float.class) {
|
||||
return 'F';
|
||||
} else if (typeClass == String.class) {
|
||||
return 'S';
|
||||
} else {
|
||||
return '\0';
|
||||
}
|
||||
}
|
||||
|
||||
private final Method mMethod;
|
||||
private final Class[] mParameterTypes;
|
||||
private final int mParamLength;
|
||||
private final JavaModuleWrapper mModuleWrapper;
|
||||
private String mType = BaseJavaModule.METHOD_TYPE_ASYNC;
|
||||
private boolean mArgumentsProcessed = false;
|
||||
private @Nullable ArgumentExtractor[] mArgumentExtractors;
|
||||
private @Nullable String mSignature;
|
||||
private @Nullable Object[] mArguments;
|
||||
private @Nullable int mJSArgumentsNeeded;
|
||||
|
||||
public JavaMethodWrapper(JavaModuleWrapper module, Method method, boolean isSync) {
|
||||
mModuleWrapper = module;
|
||||
mMethod = method;
|
||||
mMethod.setAccessible(true);
|
||||
mParameterTypes = mMethod.getParameterTypes();
|
||||
mParamLength = mParameterTypes.length;
|
||||
|
||||
if (isSync) {
|
||||
mType = BaseJavaModule.METHOD_TYPE_SYNC;
|
||||
} else if (mParamLength > 0 && (mParameterTypes[mParamLength - 1] == Promise.class)) {
|
||||
mType = BaseJavaModule.METHOD_TYPE_PROMISE;
|
||||
}
|
||||
}
|
||||
|
||||
private void processArguments() {
|
||||
if (mArgumentsProcessed) {
|
||||
return;
|
||||
}
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT, "processArguments")
|
||||
.arg("method", mModuleWrapper.getName() + "." + mMethod.getName())
|
||||
.flush();
|
||||
try {
|
||||
mArgumentsProcessed = true;
|
||||
mArgumentExtractors = buildArgumentExtractors(mParameterTypes);
|
||||
mSignature =
|
||||
buildSignature(mMethod, mParameterTypes, (mType.equals(BaseJavaModule.METHOD_TYPE_SYNC)));
|
||||
// Since native methods are invoked from a message queue executed on a single thread, it is
|
||||
// safe to allocate only one arguments object per method that can be reused across calls
|
||||
mArguments = new Object[mParameterTypes.length];
|
||||
mJSArgumentsNeeded = calculateJSArgumentsNeeded();
|
||||
} finally {
|
||||
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
|
||||
}
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return mMethod;
|
||||
}
|
||||
|
||||
public String getSignature() {
|
||||
if (!mArgumentsProcessed) {
|
||||
processArguments();
|
||||
}
|
||||
return assertNotNull(mSignature);
|
||||
}
|
||||
|
||||
private String buildSignature(Method method, Class[] paramTypes, boolean isSync) {
|
||||
StringBuilder builder = new StringBuilder(paramTypes.length + 2);
|
||||
|
||||
if (isSync) {
|
||||
builder.append(returnTypeToChar(method.getReturnType()));
|
||||
builder.append('.');
|
||||
} else {
|
||||
builder.append("v.");
|
||||
}
|
||||
|
||||
for (int i = 0; i < paramTypes.length; i++) {
|
||||
Class paramClass = paramTypes[i];
|
||||
if (paramClass == Promise.class) {
|
||||
Assertions.assertCondition(
|
||||
i == paramTypes.length - 1, "Promise must be used as last parameter only");
|
||||
}
|
||||
builder.append(paramTypeToChar(paramClass));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) {
|
||||
ArgumentExtractor[] argumentExtractors = new ArgumentExtractor[paramTypes.length];
|
||||
for (int i = 0; i < paramTypes.length; i += argumentExtractors[i].getJSArgumentsNeeded()) {
|
||||
Class argumentClass = paramTypes[i];
|
||||
if (argumentClass == Boolean.class || argumentClass == boolean.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_BOOLEAN;
|
||||
} else if (argumentClass == Integer.class || argumentClass == int.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_INTEGER;
|
||||
} else if (argumentClass == Double.class || argumentClass == double.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_DOUBLE;
|
||||
} else if (argumentClass == Float.class || argumentClass == float.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_FLOAT;
|
||||
} else if (argumentClass == String.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_STRING;
|
||||
} else if (argumentClass == Callback.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_CALLBACK;
|
||||
} else if (argumentClass == Promise.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_PROMISE;
|
||||
Assertions.assertCondition(
|
||||
i == paramTypes.length - 1, "Promise must be used as last parameter only");
|
||||
} else if (argumentClass == ReadableMap.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_MAP;
|
||||
} else if (argumentClass == ReadableArray.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_ARRAY;
|
||||
} else if (argumentClass == Dynamic.class) {
|
||||
argumentExtractors[i] = ARGUMENT_EXTRACTOR_DYNAMIC;
|
||||
} else {
|
||||
throw new RuntimeException("Got unknown argument class: " + argumentClass.getSimpleName());
|
||||
}
|
||||
}
|
||||
return argumentExtractors;
|
||||
}
|
||||
|
||||
private int calculateJSArgumentsNeeded() {
|
||||
int n = 0;
|
||||
for (ArgumentExtractor extractor : assertNotNull(mArgumentExtractors)) {
|
||||
n += extractor.getJSArgumentsNeeded();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private String getAffectedRange(int startIndex, int jsArgumentsNeeded) {
|
||||
return jsArgumentsNeeded > 1
|
||||
? "" + startIndex + "-" + (startIndex + jsArgumentsNeeded - 1)
|
||||
: "" + startIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(JSInstance jsInstance, ReadableArray parameters) {
|
||||
String traceName = mModuleWrapper.getName() + "." + mMethod.getName();
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT, "callJavaModuleMethod")
|
||||
.arg("method", traceName)
|
||||
.flush();
|
||||
if (DEBUG) {
|
||||
PrinterHolder.getPrinter()
|
||||
.logMessage(
|
||||
ReactDebugOverlayTags.BRIDGE_CALLS,
|
||||
"JS->Java: %s.%s()",
|
||||
mModuleWrapper.getName(),
|
||||
mMethod.getName());
|
||||
}
|
||||
try {
|
||||
if (!mArgumentsProcessed) {
|
||||
processArguments();
|
||||
}
|
||||
if (mArguments == null || mArgumentExtractors == null) {
|
||||
throw new Error("processArguments failed");
|
||||
}
|
||||
if (mJSArgumentsNeeded != parameters.size()) {
|
||||
throw new NativeArgumentsParseException(
|
||||
traceName + " got " + parameters.size() + " arguments, expected " + mJSArgumentsNeeded);
|
||||
}
|
||||
|
||||
int i = 0, jsArgumentsConsumed = 0;
|
||||
try {
|
||||
for (; i < mArgumentExtractors.length; i++) {
|
||||
mArguments[i] =
|
||||
mArgumentExtractors[i].extractArgument(jsInstance, parameters, jsArgumentsConsumed);
|
||||
jsArgumentsConsumed += mArgumentExtractors[i].getJSArgumentsNeeded();
|
||||
}
|
||||
} catch (UnexpectedNativeTypeException | NullPointerException e) {
|
||||
throw new NativeArgumentsParseException(
|
||||
e.getMessage()
|
||||
+ " (constructing arguments for "
|
||||
+ traceName
|
||||
+ " at argument index "
|
||||
+ getAffectedRange(
|
||||
jsArgumentsConsumed, mArgumentExtractors[i].getJSArgumentsNeeded())
|
||||
+ ")",
|
||||
e);
|
||||
}
|
||||
|
||||
try {
|
||||
mMethod.invoke(mModuleWrapper.getModule(), mArguments);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
throw new RuntimeException(createInvokeExceptionMessage(traceName), e);
|
||||
} catch (InvocationTargetException ite) {
|
||||
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
|
||||
// which just make traces harder to read and bump out useful information
|
||||
if (ite.getCause() instanceof RuntimeException) {
|
||||
throw (RuntimeException) ite.getCause();
|
||||
}
|
||||
throw new RuntimeException(createInvokeExceptionMessage(traceName), ite);
|
||||
}
|
||||
} finally {
|
||||
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it easier to determine the cause of an error invoking a native method from Javascript
|
||||
* code by adding the function name.
|
||||
*/
|
||||
private static String createInvokeExceptionMessage(String traceName) {
|
||||
return "Could not invoke " + traceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how the method is exported in JavaScript: METHOD_TYPE_ASYNC for regular methods
|
||||
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller. METHOD_TYPE_SYNC
|
||||
* for sync methods
|
||||
*/
|
||||
@Override
|
||||
public String getType() {
|
||||
return mType;
|
||||
}
|
||||
}
|
||||
+399
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge
|
||||
|
||||
import com.facebook.debug.holder.PrinterHolder
|
||||
import com.facebook.debug.tags.ReactDebugOverlayTags
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
import com.facebook.systrace.Systrace.TRACE_TAG_REACT
|
||||
import com.facebook.systrace.SystraceMessage
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.lang.reflect.Method
|
||||
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
internal class JavaMethodWrapper(
|
||||
private val moduleWrapper: JavaModuleWrapper,
|
||||
val method: Method,
|
||||
isSync: Boolean
|
||||
) : JavaModuleWrapper.NativeMethod {
|
||||
private abstract class ArgumentExtractor<T> {
|
||||
open fun getJSArgumentsNeeded(): Int = 1
|
||||
|
||||
abstract fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): T?
|
||||
}
|
||||
|
||||
private val parameterTypes: Array<Class<*>>
|
||||
private val paramLength: Int
|
||||
|
||||
/**
|
||||
* Determines how the method is exported in JavaScript: METHOD_TYPE_ASYNC for regular methods
|
||||
* METHOD_TYPE_PROMISE for methods that return a promise object to the caller. METHOD_TYPE_SYNC
|
||||
* for sync methods
|
||||
*/
|
||||
override var type: String = BaseJavaModule.METHOD_TYPE_ASYNC
|
||||
private var argumentsProcessed = false
|
||||
private var argumentExtractors: Array<ArgumentExtractor<*>>? = null
|
||||
private var internalSignature: String? = null
|
||||
private var arguments: Array<Any?>? = null
|
||||
private var jsArgumentsNeeded = 0
|
||||
|
||||
init {
|
||||
method.isAccessible = true
|
||||
parameterTypes = method.parameterTypes
|
||||
paramLength = parameterTypes.size
|
||||
|
||||
if (isSync) {
|
||||
type = BaseJavaModule.METHOD_TYPE_SYNC
|
||||
} else if (paramLength > 0 && (parameterTypes[paramLength - 1] == Promise::class.java)) {
|
||||
type = BaseJavaModule.METHOD_TYPE_PROMISE
|
||||
}
|
||||
}
|
||||
|
||||
private fun processArguments() {
|
||||
if (argumentsProcessed) {
|
||||
return
|
||||
}
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT, "processArguments")
|
||||
.arg("method", moduleWrapper.name + "." + method.name)
|
||||
.flush()
|
||||
try {
|
||||
argumentsProcessed = true
|
||||
argumentExtractors = buildArgumentExtractors(parameterTypes)
|
||||
internalSignature =
|
||||
buildSignature(method, parameterTypes, (type == BaseJavaModule.METHOD_TYPE_SYNC))
|
||||
// Since native methods are invoked from a message queue executed on a single thread, it is
|
||||
// safe to allocate only one arguments object per method that can be reused across calls
|
||||
arguments = arrayOfNulls(parameterTypes.size)
|
||||
jsArgumentsNeeded = calculateJSArgumentsNeeded()
|
||||
} finally {
|
||||
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
|
||||
}
|
||||
}
|
||||
|
||||
val signature: String?
|
||||
get() {
|
||||
if (!argumentsProcessed) {
|
||||
processArguments()
|
||||
}
|
||||
return checkNotNull(internalSignature)
|
||||
}
|
||||
|
||||
private fun buildSignature(method: Method, paramTypes: Array<Class<*>>, isSync: Boolean): String =
|
||||
buildString(paramTypes.size + 2) {
|
||||
if (isSync) {
|
||||
append(returnTypeToChar(method.returnType))
|
||||
append('.')
|
||||
} else {
|
||||
append("v.")
|
||||
}
|
||||
|
||||
for (i in paramTypes.indices) {
|
||||
val paramClass = paramTypes[i]
|
||||
if (paramClass == Promise::class.java) {
|
||||
check(i == paramTypes.size - 1) { "Promise must be used as last parameter only" }
|
||||
}
|
||||
append(paramTypeToChar(paramClass))
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildArgumentExtractors(paramTypes: Array<Class<*>>): Array<ArgumentExtractor<*>> {
|
||||
val argumentExtractors = arrayOfNulls<ArgumentExtractor<*>>(paramTypes.size)
|
||||
var i = 0
|
||||
while (i < paramTypes.size) {
|
||||
val argumentClass = paramTypes[i]
|
||||
val extractor: ArgumentExtractor<*> =
|
||||
when (argumentClass) {
|
||||
Boolean::class.javaObjectType,
|
||||
Boolean::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_BOOLEAN
|
||||
Int::class.javaObjectType,
|
||||
Int::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_INTEGER
|
||||
Double::class.javaObjectType,
|
||||
Double::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_DOUBLE
|
||||
Float::class.javaObjectType,
|
||||
Float::class.javaPrimitiveType -> ARGUMENT_EXTRACTOR_FLOAT
|
||||
String::class.java -> ARGUMENT_EXTRACTOR_STRING
|
||||
Callback::class.java -> ARGUMENT_EXTRACTOR_CALLBACK
|
||||
Promise::class.java -> {
|
||||
check(i == paramTypes.size - 1) { "Promise must be used as last parameter only" }
|
||||
ARGUMENT_EXTRACTOR_PROMISE
|
||||
}
|
||||
ReadableMap::class.java -> ARGUMENT_EXTRACTOR_MAP
|
||||
ReadableArray::class.java -> ARGUMENT_EXTRACTOR_ARRAY
|
||||
Dynamic::class.java -> ARGUMENT_EXTRACTOR_DYNAMIC
|
||||
else ->
|
||||
throw RuntimeException("Got unknown argument class: ${argumentClass.simpleName}")
|
||||
}
|
||||
|
||||
argumentExtractors[i] = extractor
|
||||
i += extractor.getJSArgumentsNeeded()
|
||||
}
|
||||
|
||||
return argumentExtractors.requireNoNulls()
|
||||
}
|
||||
|
||||
private fun calculateJSArgumentsNeeded(): Int {
|
||||
var n = 0
|
||||
for (extractor in checkNotNull(argumentExtractors)) {
|
||||
n += extractor.getJSArgumentsNeeded()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
private fun getAffectedRange(startIndex: Int, jsArgumentsNeeded: Int): String =
|
||||
if (jsArgumentsNeeded > 1) {
|
||||
"$startIndex-${startIndex + jsArgumentsNeeded - 1}"
|
||||
} else {
|
||||
"$startIndex"
|
||||
}
|
||||
|
||||
override fun invoke(jsInstance: JSInstance, parameters: ReadableArray) {
|
||||
val traceName = moduleWrapper.name + "." + method.name
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT, "callJavaModuleMethod")
|
||||
.arg("method", traceName)
|
||||
.flush()
|
||||
if (DEBUG) {
|
||||
PrinterHolder.printer.logMessage(
|
||||
ReactDebugOverlayTags.BRIDGE_CALLS, "JS->Java: %s.%s()", moduleWrapper.name, method.name)
|
||||
}
|
||||
try {
|
||||
if (!argumentsProcessed) {
|
||||
processArguments()
|
||||
}
|
||||
|
||||
val validatedArguments =
|
||||
requireNotNull(arguments) { "processArguments failed: 'arguments' is null." }
|
||||
val validatedArgumentExtractors =
|
||||
requireNotNull(argumentExtractors) {
|
||||
"processArguments failed: 'argumentExtractors' is null."
|
||||
}
|
||||
|
||||
if (jsArgumentsNeeded != parameters.size()) {
|
||||
throw NativeArgumentsParseException(
|
||||
"$traceName got ${parameters.size()} arguments, expected $jsArgumentsNeeded")
|
||||
}
|
||||
|
||||
var i = 0
|
||||
var jsArgumentsConsumed = 0
|
||||
try {
|
||||
while (i < validatedArgumentExtractors.size) {
|
||||
validatedArguments[i] =
|
||||
validatedArgumentExtractors[i].extractArgument(
|
||||
jsInstance, parameters, jsArgumentsConsumed)
|
||||
jsArgumentsConsumed += validatedArgumentExtractors[i].getJSArgumentsNeeded()
|
||||
i++
|
||||
}
|
||||
} catch (e: UnexpectedNativeTypeException) {
|
||||
throw NativeArgumentsParseException(
|
||||
"${e.message} (constructing arguments for $traceName at argument index ${
|
||||
getAffectedRange(
|
||||
jsArgumentsConsumed,
|
||||
validatedArgumentExtractors[i].getJSArgumentsNeeded()
|
||||
)
|
||||
})",
|
||||
e)
|
||||
} catch (e: NullPointerException) {
|
||||
throw NativeArgumentsParseException(
|
||||
"${e.message} (constructing arguments for $traceName at argument index ${
|
||||
getAffectedRange(
|
||||
jsArgumentsConsumed,
|
||||
validatedArgumentExtractors[i].getJSArgumentsNeeded()
|
||||
)
|
||||
})",
|
||||
e)
|
||||
}
|
||||
|
||||
try {
|
||||
method.invoke(moduleWrapper.module, *validatedArguments)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
|
||||
} catch (e: IllegalAccessException) {
|
||||
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
|
||||
} catch (e: InvocationTargetException) {
|
||||
// Exceptions thrown from native module calls end up wrapped in InvocationTargetException
|
||||
// which just make traces harder to read and bump out useful information
|
||||
if (e.cause is RuntimeException) {
|
||||
throw (e.cause as RuntimeException)
|
||||
}
|
||||
throw RuntimeException(createInvokeExceptionMessage(traceName), e)
|
||||
}
|
||||
} finally {
|
||||
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
LegacyArchitectureLogger.assertLegacyArchitecture(
|
||||
"JavaMethodWrapper", LegacyArchitectureLogLevel.ERROR)
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_BOOLEAN: ArgumentExtractor<Boolean> =
|
||||
object : ArgumentExtractor<Boolean>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): Boolean = jsArguments.getBoolean(atIndex)
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_DOUBLE: ArgumentExtractor<Double> =
|
||||
object : ArgumentExtractor<Double>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): Double = jsArguments.getDouble(atIndex)
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_FLOAT: ArgumentExtractor<Float> =
|
||||
object : ArgumentExtractor<Float>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): Float = jsArguments.getDouble(atIndex).toFloat()
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_INTEGER: ArgumentExtractor<Int> =
|
||||
object : ArgumentExtractor<Int>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): Int = jsArguments.getDouble(atIndex).toInt()
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_STRING: ArgumentExtractor<String> =
|
||||
object : ArgumentExtractor<String>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): String? = jsArguments.getString(atIndex)
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_ARRAY: ArgumentExtractor<ReadableArray> =
|
||||
object : ArgumentExtractor<ReadableArray>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): ReadableArray? = jsArguments.getArray(atIndex)
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_DYNAMIC: ArgumentExtractor<Dynamic> =
|
||||
object : ArgumentExtractor<Dynamic>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): Dynamic = DynamicFromArray.create(jsArguments, atIndex)
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_MAP: ArgumentExtractor<ReadableMap> =
|
||||
object : ArgumentExtractor<ReadableMap>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): ReadableMap? = jsArguments.getMap(atIndex)
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_CALLBACK: ArgumentExtractor<Callback> =
|
||||
object : ArgumentExtractor<Callback>() {
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): Callback? =
|
||||
if (jsArguments.isNull(atIndex)) {
|
||||
null
|
||||
} else {
|
||||
val id = jsArguments.getDouble(atIndex).toInt()
|
||||
CallbackImpl(jsInstance, id)
|
||||
}
|
||||
}
|
||||
|
||||
private val ARGUMENT_EXTRACTOR_PROMISE: ArgumentExtractor<Promise> =
|
||||
object : ArgumentExtractor<Promise>() {
|
||||
override fun getJSArgumentsNeeded(): Int = 2
|
||||
|
||||
override fun extractArgument(
|
||||
jsInstance: JSInstance,
|
||||
jsArguments: ReadableArray,
|
||||
atIndex: Int
|
||||
): Promise {
|
||||
val resolve =
|
||||
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex)
|
||||
val reject =
|
||||
ARGUMENT_EXTRACTOR_CALLBACK.extractArgument(jsInstance, jsArguments, atIndex + 1)
|
||||
return PromiseImpl(resolve, reject)
|
||||
}
|
||||
}
|
||||
|
||||
private val DEBUG =
|
||||
PrinterHolder.printer.shouldDisplayLogMessage(ReactDebugOverlayTags.BRIDGE_CALLS)
|
||||
|
||||
private fun paramTypeToChar(paramClass: Class<*>): Char {
|
||||
val tryCommon = commonTypeToChar(paramClass)
|
||||
if (tryCommon != '\u0000') {
|
||||
return tryCommon
|
||||
}
|
||||
return when (paramClass) {
|
||||
Callback::class.java -> 'X'
|
||||
Promise::class.java -> 'P'
|
||||
ReadableMap::class.java -> 'M'
|
||||
ReadableArray::class.java -> 'A'
|
||||
Dynamic::class.java -> 'Y'
|
||||
else -> throw RuntimeException("Got unknown param class: ${paramClass.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun returnTypeToChar(returnClass: Class<*>): Char {
|
||||
// Keep this in sync with MethodInvoker
|
||||
val tryCommon = commonTypeToChar(returnClass)
|
||||
if (tryCommon != '\u0000') {
|
||||
return tryCommon
|
||||
}
|
||||
return when (returnClass) {
|
||||
Void.TYPE -> 'v'
|
||||
WritableMap::class.java -> 'M'
|
||||
WritableArray::class.java -> 'A'
|
||||
else -> throw RuntimeException("Got unknown return class: ${returnClass.simpleName}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun commonTypeToChar(typeClass: Class<*>): Char {
|
||||
return when (typeClass) {
|
||||
Boolean::class.javaPrimitiveType -> 'z'
|
||||
Boolean::class.javaObjectType -> 'Z'
|
||||
Int::class.javaPrimitiveType -> 'i'
|
||||
Int::class.javaObjectType -> 'I'
|
||||
Double::class.javaPrimitiveType -> 'd'
|
||||
Double::class.javaObjectType -> 'D'
|
||||
Float::class.javaPrimitiveType -> 'f'
|
||||
Float::class.javaObjectType -> 'F'
|
||||
String::class.java -> 'S'
|
||||
else -> '\u0000'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it easier to determine the cause of an error invoking a native method from Javascript
|
||||
* code by adding the function name.
|
||||
*/
|
||||
private fun createInvokeExceptionMessage(traceName: String): String =
|
||||
"Could not invoke $traceName"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class JavaScriptModuleRegistry {
|
||||
return name ?: getJSModuleName(moduleInterface).also { name = it }
|
||||
}
|
||||
|
||||
public override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
|
||||
override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
|
||||
val jsArgs = if (args != null) Arguments.fromJavaArgs(args) else WritableNativeArray()
|
||||
catalystInstance.callFunction(getJSModuleName(), method.name, jsArgs)
|
||||
return null
|
||||
|
||||
-247
@@ -1,247 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge;
|
||||
|
||||
import static com.facebook.infer.annotation.Assertions.assertNotNull;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_MODULE_END;
|
||||
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_MODULE_START;
|
||||
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.debug.holder.PrinterHolder;
|
||||
import com.facebook.debug.tags.ReactDebugOverlayTags;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.module.model.ReactModuleInfo;
|
||||
import com.facebook.systrace.SystraceMessage;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.inject.Provider;
|
||||
|
||||
/**
|
||||
* Holder to enable us to lazy create native modules.
|
||||
*
|
||||
* <p>This works by taking a provider instead of an instance, when it is first required we'll create
|
||||
* and initialize it. Initialization currently always happens on the UI thread but this is due to
|
||||
* change for performance reasons.
|
||||
*
|
||||
* <p>Lifecycle events via a {@link LifecycleEventListener} will still always happen on the UI
|
||||
* thread.
|
||||
*/
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
@DoNotStrip
|
||||
public class ModuleHolder {
|
||||
|
||||
private static final AtomicInteger sInstanceKeyCounter = new AtomicInteger(1);
|
||||
|
||||
private final int mInstanceKey = sInstanceKeyCounter.getAndIncrement();
|
||||
|
||||
private final String mName;
|
||||
private final ReactModuleInfo mReactModuleInfo;
|
||||
|
||||
private @Nullable Provider<? extends NativeModule> mProvider;
|
||||
// Outside of the constructor, these should only be checked or set when synchronized on this
|
||||
private @Nullable @GuardedBy("this") NativeModule mModule;
|
||||
|
||||
// These are used to communicate phases of creation and initialization across threads
|
||||
private @GuardedBy("this") boolean mInitializable;
|
||||
private @GuardedBy("this") boolean mIsCreating;
|
||||
private @GuardedBy("this") boolean mIsInitializing;
|
||||
|
||||
public ModuleHolder(ReactModuleInfo moduleInfo, Provider<? extends NativeModule> provider) {
|
||||
mName = moduleInfo.name();
|
||||
mProvider = provider;
|
||||
mReactModuleInfo = moduleInfo;
|
||||
if (moduleInfo.needsEagerInit()) {
|
||||
mModule = create();
|
||||
}
|
||||
}
|
||||
|
||||
public ModuleHolder(NativeModule nativeModule) {
|
||||
mName = nativeModule.getName();
|
||||
mReactModuleInfo =
|
||||
new ReactModuleInfo(
|
||||
nativeModule.getName(),
|
||||
nativeModule.getClass().getSimpleName(),
|
||||
nativeModule.canOverrideExistingModule(),
|
||||
true,
|
||||
CxxModuleWrapper.class.isAssignableFrom(nativeModule.getClass()),
|
||||
ReactModuleInfo.classIsTurboModule(nativeModule.getClass()));
|
||||
|
||||
mModule = nativeModule;
|
||||
PrinterHolder.getPrinter()
|
||||
.logMessage(ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", mName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if mModule has been created, and if so tries to initialize the module unless another
|
||||
* thread is already doing the initialization.
|
||||
* If mModule has not been created, records that initialization is needed
|
||||
*/
|
||||
/* package */ void markInitializable() {
|
||||
boolean shouldInitializeNow = false;
|
||||
NativeModule module = null;
|
||||
synchronized (this) {
|
||||
mInitializable = true;
|
||||
if (mModule != null) {
|
||||
Assertions.assertCondition(!mIsInitializing);
|
||||
shouldInitializeNow = true;
|
||||
module = mModule;
|
||||
}
|
||||
}
|
||||
if (shouldInitializeNow) {
|
||||
Assertions.assertNotNull(module);
|
||||
doInitialize(module);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ synchronized boolean hasInstance() {
|
||||
return mModule != null;
|
||||
}
|
||||
|
||||
public synchronized void destroy() {
|
||||
if (mModule != null) {
|
||||
mModule.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
public boolean getCanOverrideExistingModule() {
|
||||
return mReactModuleInfo.canOverrideExistingModule();
|
||||
}
|
||||
|
||||
public boolean isTurboModule() {
|
||||
return mReactModuleInfo.isTurboModule();
|
||||
}
|
||||
|
||||
public boolean isCxxModule() {
|
||||
return mReactModuleInfo.isCxxModule();
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return mReactModuleInfo.className();
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public NativeModule getModule() {
|
||||
NativeModule module;
|
||||
boolean shouldCreate = false;
|
||||
synchronized (this) {
|
||||
if (mModule != null) {
|
||||
return mModule;
|
||||
// if mModule has not been set, and no one is creating it. Then this thread should call
|
||||
// create
|
||||
} else if (!mIsCreating) {
|
||||
shouldCreate = true;
|
||||
mIsCreating = true;
|
||||
} else {
|
||||
// Wait for mModule to be created by another thread
|
||||
}
|
||||
}
|
||||
if (shouldCreate) {
|
||||
module = create();
|
||||
// Once module is built (and initialized if markInitializable has been called), modify mModule
|
||||
// And signal any waiting threads that it is acceptable to read the field now
|
||||
synchronized (this) {
|
||||
mIsCreating = false;
|
||||
this.notifyAll();
|
||||
}
|
||||
return module;
|
||||
} else {
|
||||
synchronized (this) {
|
||||
// Block waiting for another thread to build mModule instance
|
||||
// Since mIsCreating is true until after creation and instantiation (if needed), we wait
|
||||
// until the module is ready to use.
|
||||
while (mModule == null && mIsCreating) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return Assertions.assertNotNull(mModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private NativeModule create() {
|
||||
SoftAssertions.assertCondition(mModule == null, "Creating an already created module.");
|
||||
ReactMarker.logMarker(CREATE_MODULE_START, mName, mInstanceKey);
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.createModule")
|
||||
.arg("name", mName)
|
||||
.flush();
|
||||
PrinterHolder.getPrinter()
|
||||
.logMessage(ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", mName);
|
||||
NativeModule module;
|
||||
try {
|
||||
module = assertNotNull(mProvider).get();
|
||||
mProvider = null;
|
||||
boolean shouldInitializeNow = false;
|
||||
synchronized (this) {
|
||||
mModule = module;
|
||||
if (mInitializable && !mIsInitializing) {
|
||||
shouldInitializeNow = true;
|
||||
}
|
||||
}
|
||||
if (shouldInitializeNow) {
|
||||
doInitialize(module);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
/**
|
||||
* When NativeModules are created from JavaScript, any exception that occurs in the creation
|
||||
* process will have its stack trace swallowed before we display a RedBox to the user. Really,
|
||||
* we should have our HostObjects on Android understand JniExceptions and log the stack trace
|
||||
* to logcat. For now, logging to Logcat directly when creation fails is sufficient.
|
||||
*
|
||||
* @todo(T53311351)
|
||||
*/
|
||||
FLog.e(ReactConstants.TAG, ex, "Failed to create NativeModule '%s'", mName);
|
||||
throw ex;
|
||||
} finally {
|
||||
ReactMarker.logMarker(CREATE_MODULE_END, mName, mInstanceKey);
|
||||
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
private void doInitialize(NativeModule module) {
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.initialize")
|
||||
.arg("name", mName)
|
||||
.flush();
|
||||
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_START, mName, mInstanceKey);
|
||||
try {
|
||||
boolean shouldInitialize = false;
|
||||
// Check to see if another thread is initializing the object, if not claim the responsibility
|
||||
synchronized (this) {
|
||||
if (mInitializable && !mIsInitializing) {
|
||||
shouldInitialize = true;
|
||||
mIsInitializing = true;
|
||||
}
|
||||
}
|
||||
if (shouldInitialize) {
|
||||
module.initialize();
|
||||
// Once finished, set flags accordingly, but we don't expect anyone to wait for this to
|
||||
// finish
|
||||
// So no need to notify other threads
|
||||
synchronized (this) {
|
||||
mIsInitializing = false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_END, mName, mInstanceKey);
|
||||
SystraceMessage.endSection(TRACE_TAG_REACT).flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.bridge
|
||||
|
||||
import androidx.annotation.GuardedBy
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.debug.holder.PrinterHolder
|
||||
import com.facebook.debug.tags.ReactDebugOverlayTags
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.module.model.ReactModuleInfo
|
||||
import com.facebook.systrace.Systrace.TRACE_TAG_REACT
|
||||
import com.facebook.systrace.SystraceMessage
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import javax.inject.Provider
|
||||
|
||||
/**
|
||||
* Holder to enable us to lazy create native modules.
|
||||
*
|
||||
* This works by taking a provider instead of an instance, when it is first required we'll create
|
||||
* and initialize it. Initialization currently always happens on the UI thread but this is due to
|
||||
* change for performance reasons.
|
||||
*
|
||||
* Lifecycle events via a [LifecycleEventListener] will still always happen on the UI thread.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class ModuleHolder {
|
||||
private val instanceKey = instanceKeyCounter.getAndIncrement()
|
||||
@get:DoNotStrip public val name: String
|
||||
private val reactModuleInfo: ReactModuleInfo
|
||||
|
||||
private var provider: Provider<out NativeModule>? = null
|
||||
// Outside of the constructor, this should only be checked or set when synchronized on this
|
||||
@GuardedBy("this") private var internalModule: NativeModule? = null
|
||||
// This is used to communicate phases of creation and initialization across threads
|
||||
@GuardedBy("this") private var initializable = false
|
||||
@GuardedBy("this") private var isCreating = false
|
||||
@GuardedBy("this") private var isInitializing = false
|
||||
|
||||
public constructor(moduleInfo: ReactModuleInfo, provider: Provider<out NativeModule?>) {
|
||||
name = moduleInfo.name
|
||||
this.provider = provider
|
||||
reactModuleInfo = moduleInfo
|
||||
if (moduleInfo.needsEagerInit) {
|
||||
internalModule = create()
|
||||
}
|
||||
}
|
||||
|
||||
public constructor(nativeModule: NativeModule) {
|
||||
name = nativeModule.name
|
||||
reactModuleInfo =
|
||||
ReactModuleInfo(
|
||||
nativeModule.name,
|
||||
nativeModule.javaClass.simpleName,
|
||||
nativeModule.canOverrideExistingModule(),
|
||||
true,
|
||||
CxxModuleWrapper::class.java.isAssignableFrom(nativeModule.javaClass),
|
||||
ReactModuleInfo.classIsTurboModule(nativeModule.javaClass))
|
||||
|
||||
internalModule = nativeModule
|
||||
PrinterHolder.printer.logMessage(
|
||||
ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", name)
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if [internalModule] has been created, and if so tries to initialize the module unless another
|
||||
* thread is already doing the initialization.
|
||||
* If [internalModule] has not been created, records that initialization is needed.
|
||||
*/
|
||||
internal fun markInitializable() {
|
||||
var shouldInitializeNow = false
|
||||
var module: NativeModule? = null
|
||||
synchronized(this) {
|
||||
initializable = true
|
||||
if (internalModule != null) {
|
||||
check(!isInitializing)
|
||||
shouldInitializeNow = true
|
||||
module = internalModule
|
||||
}
|
||||
}
|
||||
if (shouldInitializeNow) {
|
||||
checkNotNull(module)
|
||||
doInitialize(module)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized internal fun hasInstance(): Boolean = internalModule != null
|
||||
|
||||
@Synchronized
|
||||
public fun destroy() {
|
||||
internalModule?.invalidate()
|
||||
}
|
||||
|
||||
public val canOverrideExistingModule: Boolean
|
||||
get() = reactModuleInfo.canOverrideExistingModule
|
||||
|
||||
public val isTurboModule: Boolean
|
||||
get() = reactModuleInfo.isTurboModule
|
||||
|
||||
public val isCxxModule: Boolean
|
||||
get() = reactModuleInfo.isCxxModule
|
||||
|
||||
public val className: String
|
||||
get() = reactModuleInfo.className
|
||||
|
||||
@get:DoNotStrip
|
||||
public val module: NativeModule
|
||||
get() {
|
||||
val module: NativeModule
|
||||
var shouldCreate = false
|
||||
synchronized(this) {
|
||||
val safeModule = internalModule
|
||||
if (safeModule != null) {
|
||||
return safeModule
|
||||
// if `internalModule` has not been set, and no one is creating it. Then this thread
|
||||
// should call
|
||||
// create
|
||||
} else if (!isCreating) {
|
||||
shouldCreate = true
|
||||
isCreating = true
|
||||
} else {
|
||||
// Wait for `internalModule` to be created by another thread
|
||||
}
|
||||
}
|
||||
if (shouldCreate) {
|
||||
module = create()
|
||||
// Once module is built (and initialized if markInitializable has been called), modify
|
||||
// `internalModule`
|
||||
// And signal any waiting threads that it is acceptable to read the field now
|
||||
synchronized(this) {
|
||||
isCreating = false
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (this as Object).notifyAll()
|
||||
}
|
||||
return module
|
||||
} else {
|
||||
synchronized(this) {
|
||||
// Block waiting for another thread to build `internalModule` instance
|
||||
// Since isCreating is true until after creation and instantiation (if needed), we wait
|
||||
// until the module is ready to use.
|
||||
while (internalModule == null && isCreating) {
|
||||
try {
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") (this as Object).wait()
|
||||
} catch (e: InterruptedException) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return checkNotNull(internalModule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun create(): NativeModule {
|
||||
SoftAssertions.assertCondition(internalModule == null, "Creating an already created module.")
|
||||
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_START, name, instanceKey)
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.createModule")
|
||||
.arg("name", name)
|
||||
.flush()
|
||||
PrinterHolder.printer.logMessage(
|
||||
ReactDebugOverlayTags.NATIVE_MODULE, "NativeModule init: %s", name)
|
||||
val module: NativeModule
|
||||
try {
|
||||
module = checkNotNull(provider).get()
|
||||
provider = null
|
||||
var shouldInitializeNow = false
|
||||
synchronized(this) {
|
||||
internalModule = module
|
||||
if (initializable && !isInitializing) {
|
||||
shouldInitializeNow = true
|
||||
}
|
||||
}
|
||||
if (shouldInitializeNow) {
|
||||
doInitialize(module)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
/**
|
||||
* When NativeModules are created from JavaScript, any exception that occurs in the creation
|
||||
* process will have its stack trace swallowed before we display a RedBox to the user. Really,
|
||||
* we should have our HostObjects on Android understand JniExceptions and log the stack trace
|
||||
* to logcat. For now, logging to Logcat directly when creation fails is sufficient.
|
||||
*
|
||||
* @todo(T53311351)
|
||||
*/
|
||||
FLog.e(ReactConstants.TAG, e, "Failed to create NativeModule '%s'", name)
|
||||
throw e
|
||||
} finally {
|
||||
ReactMarker.logMarker(ReactMarkerConstants.CREATE_MODULE_END, name, instanceKey)
|
||||
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
|
||||
}
|
||||
return module
|
||||
}
|
||||
|
||||
private fun doInitialize(module: NativeModule?) {
|
||||
SystraceMessage.beginSection(TRACE_TAG_REACT, "ModuleHolder.initialize")
|
||||
.arg("name", name)
|
||||
.flush()
|
||||
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_START, name, instanceKey)
|
||||
try {
|
||||
var shouldInitialize = false
|
||||
// Check to see if another thread is initializing the object, if not claim the responsibility
|
||||
synchronized(this) {
|
||||
if (initializable && !isInitializing) {
|
||||
shouldInitialize = true
|
||||
isInitializing = true
|
||||
}
|
||||
}
|
||||
if (shouldInitialize) {
|
||||
module?.initialize()
|
||||
// Once finished, set flags accordingly, but we don't expect anyone to wait for this to
|
||||
// finish, so no need to notify other threads.
|
||||
synchronized(this) { isInitializing = false }
|
||||
}
|
||||
} finally {
|
||||
ReactMarker.logMarker(ReactMarkerConstants.INITIALIZE_MODULE_END, name, instanceKey)
|
||||
SystraceMessage.endSection(TRACE_TAG_REACT).flush()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private val instanceKeyCounter = AtomicInteger(1)
|
||||
}
|
||||
}
|
||||
-5
@@ -55,11 +55,6 @@ internal object ReactSoftExceptionLogger {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun clearListeners(): Unit {
|
||||
listeners.clear()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun logSoftExceptionVerbose(@CategoryMode category: String, cause: Throwable): Unit {
|
||||
logSoftException("${category}|${cause.javaClass.simpleName}:${cause.message}", cause)
|
||||
|
||||
+2
-3
@@ -17,7 +17,6 @@ import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.common.build.ReactBuildConfig
|
||||
import com.facebook.react.fabric.ComponentFactory
|
||||
import com.facebook.react.runtime.BindingsInstaller
|
||||
import com.facebook.react.runtime.JSCInstance
|
||||
import com.facebook.react.runtime.JSRuntimeFactory
|
||||
import com.facebook.react.runtime.ReactHostImpl
|
||||
import com.facebook.react.runtime.cxxreactpackage.CxxReactPackage
|
||||
@@ -208,7 +207,7 @@ public object DefaultReactHost {
|
||||
jsMainModulePath,
|
||||
jsBundleAssetPath,
|
||||
jsBundleFilePath,
|
||||
if (isHermesEnabled) HermesInstance() else JSCInstance(),
|
||||
HermesInstance(),
|
||||
useDevSupport,
|
||||
cxxReactPackageProviders,
|
||||
exceptionHandler,
|
||||
@@ -264,7 +263,7 @@ public object DefaultReactHost {
|
||||
jsMainModulePath,
|
||||
jsBundleAssetPath,
|
||||
jsBundleFilePath,
|
||||
if (isHermesEnabled) HermesInstance() else JSCInstance(),
|
||||
HermesInstance(),
|
||||
useDevSupport,
|
||||
cxxReactPackageProviders,
|
||||
)
|
||||
|
||||
+4
-3
@@ -17,7 +17,6 @@ import com.facebook.react.bridge.UIManagerProvider
|
||||
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
||||
import com.facebook.react.fabric.ComponentFactory
|
||||
import com.facebook.react.fabric.FabricUIManagerProviderImpl
|
||||
import com.facebook.react.runtime.JSCInstance
|
||||
import com.facebook.react.runtime.JSRuntimeFactory
|
||||
import com.facebook.react.runtime.hermes.HermesInstance
|
||||
import com.facebook.react.uimanager.ViewManagerRegistry
|
||||
@@ -93,6 +92,9 @@ protected constructor(
|
||||
* If true, the app will load the Hermes engine, and fail if not found. If false, the app will
|
||||
* load the JSC engine, and fail if not found.
|
||||
*/
|
||||
@Deprecated(
|
||||
"Setting isHermesEnabled inside `ReactNativeHost` is deprecated and this field will be ignored. If this field is set to true, you can safely remove it. If this field is set to false, please follow the setup on https://github.com/react-native-community/javascriptcore to continue using JSC",
|
||||
ReplaceWith(""))
|
||||
protected open val isHermesEnabled: Boolean
|
||||
get() = true
|
||||
|
||||
@@ -106,8 +108,7 @@ protected constructor(
|
||||
context: Context,
|
||||
jsRuntimeFactory: JSRuntimeFactory? = null
|
||||
): ReactHost {
|
||||
val concreteJSRuntimeFactory =
|
||||
jsRuntimeFactory ?: if (isHermesEnabled) HermesInstance() else JSCInstance()
|
||||
val concreteJSRuntimeFactory = jsRuntimeFactory ?: HermesInstance()
|
||||
return DefaultReactHost.getDefaultReactHost(
|
||||
context,
|
||||
packages,
|
||||
|
||||
-3
@@ -107,9 +107,6 @@ public class DefaultDevLoadingViewImplementation(
|
||||
}
|
||||
}
|
||||
|
||||
private val context: Context?
|
||||
get() = reactInstanceDevHelper.currentActivity
|
||||
|
||||
public companion object {
|
||||
private var isEnabled = true
|
||||
|
||||
|
||||
+16
-7
@@ -33,7 +33,7 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
|
||||
textView = findViewById<View>(R.id.fps_text) as TextView
|
||||
frameCallback = FpsDebugFrameCallback(reactContext!!)
|
||||
fpsMonitorRunnable = FPSMonitorRunnable()
|
||||
setCurrentFPS(0.0, 0.0, 0, 0)
|
||||
setCurrentFPS(0.0, 0.0, 0, 0, frameCallback.isRunningOnFabric)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
@@ -53,16 +53,21 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
|
||||
currentFPS: Double,
|
||||
currentJSFPS: Double,
|
||||
droppedUIFrames: Int,
|
||||
total4PlusFrameStutters: Int
|
||||
total4PlusFrameStutters: Int,
|
||||
runningOnFabric: Boolean
|
||||
) {
|
||||
val fpsString =
|
||||
var fpsString =
|
||||
String.format(
|
||||
Locale.US,
|
||||
"UI: %.1f fps\n%d dropped so far\n%d stutters (4+) so far\nJS: %.1f fps",
|
||||
"UI: %.1f fps\n%d dropped so far\n%d stutters (4+) so far",
|
||||
currentFPS,
|
||||
droppedUIFrames,
|
||||
total4PlusFrameStutters,
|
||||
currentJSFPS)
|
||||
total4PlusFrameStutters)
|
||||
if (!runningOnFabric) {
|
||||
// The JS FPS is only relevant for the legacy architecture, as Fabric we don't use
|
||||
// BridgeIdleDebugListener to track JS frame drops.
|
||||
fpsString += String.format(Locale.US, "\nJS: %.1f fps", currentJSFPS)
|
||||
}
|
||||
textView.text = fpsString
|
||||
FLog.d(ReactConstants.TAG, fpsString)
|
||||
}
|
||||
@@ -80,7 +85,11 @@ internal class FpsView(reactContext: ReactContext?) : FrameLayout(reactContext!!
|
||||
totalFramesDropped += frameCallback.expectedNumFrames - frameCallback.numFrames
|
||||
total4PlusFrameStutters += frameCallback.get4PlusFrameStutters()
|
||||
setCurrentFPS(
|
||||
frameCallback.fps, frameCallback.jsFPS, totalFramesDropped, total4PlusFrameStutters)
|
||||
frameCallback.fps,
|
||||
frameCallback.jsFPS,
|
||||
totalFramesDropped,
|
||||
total4PlusFrameStutters,
|
||||
frameCallback.isRunningOnFabric)
|
||||
frameCallback.reset()
|
||||
postDelayed(this, UPDATE_INTERVAL_MS.toLong())
|
||||
}
|
||||
|
||||
+7
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<4c40e490481c50d2f19830366f50aad5>>
|
||||
* @generated SignedSource<<d8013c4b4ec45f241908e7f8f23f7dd1>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -48,6 +48,12 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun cxxNativeAnimatedEnabled(): Boolean = accessor.cxxNativeAnimatedEnabled()
|
||||
|
||||
/**
|
||||
* Removes JS sync at end of native animation
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun cxxNativeAnimatedRemoveJsSync(): Boolean = accessor.cxxNativeAnimatedRemoveJsSync()
|
||||
|
||||
/**
|
||||
* Disable sync dispatch on the main queue on iOS
|
||||
*/
|
||||
|
||||
+11
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<8cb9eb082005be480c39272ffc7dde03>>
|
||||
* @generated SignedSource<<15b001b69d9ea8bedbce2ce0f70bfa8e>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -23,6 +23,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
private var commonTestFlagCache: Boolean? = null
|
||||
private var animatedShouldSignalBatchCache: Boolean? = null
|
||||
private var cxxNativeAnimatedEnabledCache: Boolean? = null
|
||||
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
|
||||
private var disableMainQueueSyncDispatchIOSCache: Boolean? = null
|
||||
private var disableMountItemReorderingAndroidCache: Boolean? = null
|
||||
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
|
||||
@@ -100,6 +101,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun cxxNativeAnimatedRemoveJsSync(): Boolean {
|
||||
var cached = cxxNativeAnimatedRemoveJsSyncCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.cxxNativeAnimatedRemoveJsSync()
|
||||
cxxNativeAnimatedRemoveJsSyncCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun disableMainQueueSyncDispatchIOS(): Boolean {
|
||||
var cached = disableMainQueueSyncDispatchIOSCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<b6ddd7c2e829c427e7d6b07bc588cd8c>>
|
||||
* @generated SignedSource<<dc8ac9241675deb1324d8ff0572f2fad>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -34,6 +34,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun cxxNativeAnimatedEnabled(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun cxxNativeAnimatedRemoveJsSync(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun disableMainQueueSyncDispatchIOS(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun disableMountItemReorderingAndroid(): Boolean
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<76edde93369be682e837189f82871dfe>>
|
||||
* @generated SignedSource<<56835f26710665a11a05879a0c90e577>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -29,6 +29,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun cxxNativeAnimatedEnabled(): Boolean = false
|
||||
|
||||
override fun cxxNativeAnimatedRemoveJsSync(): Boolean = false
|
||||
|
||||
override fun disableMainQueueSyncDispatchIOS(): Boolean = false
|
||||
|
||||
override fun disableMountItemReorderingAndroid(): Boolean = false
|
||||
|
||||
+12
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<e718763435de15f788b1f510e4799a67>>
|
||||
* @generated SignedSource<<a6444cccfff813fe0e96c20855496560>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -27,6 +27,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
private var commonTestFlagCache: Boolean? = null
|
||||
private var animatedShouldSignalBatchCache: Boolean? = null
|
||||
private var cxxNativeAnimatedEnabledCache: Boolean? = null
|
||||
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
|
||||
private var disableMainQueueSyncDispatchIOSCache: Boolean? = null
|
||||
private var disableMountItemReorderingAndroidCache: Boolean? = null
|
||||
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
|
||||
@@ -107,6 +108,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun cxxNativeAnimatedRemoveJsSync(): Boolean {
|
||||
var cached = cxxNativeAnimatedRemoveJsSyncCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.cxxNativeAnimatedRemoveJsSync()
|
||||
accessedFeatureFlags.add("cxxNativeAnimatedRemoveJsSync")
|
||||
cxxNativeAnimatedRemoveJsSyncCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun disableMainQueueSyncDispatchIOS(): Boolean {
|
||||
var cached = disableMainQueueSyncDispatchIOSCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<892db232156970e8241d1f1ddde3a745>>
|
||||
* @generated SignedSource<<e671a1bb72c4b5afcd6f364f05666e89>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -29,6 +29,8 @@ public interface ReactNativeFeatureFlagsProvider {
|
||||
|
||||
@DoNotStrip public fun cxxNativeAnimatedEnabled(): Boolean
|
||||
|
||||
@DoNotStrip public fun cxxNativeAnimatedRemoveJsSync(): Boolean
|
||||
|
||||
@DoNotStrip public fun disableMainQueueSyncDispatchIOS(): Boolean
|
||||
|
||||
@DoNotStrip public fun disableMountItemReorderingAndroid(): Boolean
|
||||
|
||||
+2
-2
@@ -233,8 +233,8 @@ internal object TurboModuleInteropUtils {
|
||||
i += 1
|
||||
}
|
||||
|
||||
if (returnClass == java.lang.Boolean::class.javaPrimitiveType ||
|
||||
returnClass == java.lang.Boolean::class.java) {
|
||||
if (returnClass == Boolean::class.javaPrimitiveType ||
|
||||
returnClass == Boolean::class.javaObjectType) {
|
||||
return "BooleanKind"
|
||||
}
|
||||
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.jscexecutor
|
||||
|
||||
import com.facebook.jni.HybridData
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
import com.facebook.react.bridge.JavaScriptExecutor
|
||||
import com.facebook.react.bridge.ReadableNativeMap
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
@DoNotStrip
|
||||
@LegacyArchitecture
|
||||
public class JSCExecutor internal constructor(jscConfig: ReadableNativeMap) :
|
||||
JavaScriptExecutor(initHybrid(jscConfig)) {
|
||||
|
||||
override fun getName(): String {
|
||||
return "JSCExecutor"
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
init {
|
||||
loadLibrary()
|
||||
LegacyArchitectureLogger.assertLegacyArchitecture("JSCExecutor")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(UnsatisfiedLinkError::class)
|
||||
fun loadLibrary() {
|
||||
SoLoader.loadLibrary("jscexecutor")
|
||||
}
|
||||
|
||||
@JvmStatic private external fun initHybrid(jscConfig: ReadableNativeMap): HybridData
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.jscexecutor
|
||||
|
||||
import com.facebook.react.bridge.JavaScriptExecutor
|
||||
import com.facebook.react.bridge.JavaScriptExecutorFactory
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
public class JSCExecutorFactory(private val appName: String, private val deviceName: String) :
|
||||
JavaScriptExecutorFactory {
|
||||
|
||||
init {
|
||||
LegacyArchitectureLogger.assertLegacyArchitecture(
|
||||
"JSCExecutorFactory", LegacyArchitectureLogLevel.ERROR)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun create(): JavaScriptExecutor {
|
||||
val jscConfig =
|
||||
WritableNativeMap().apply {
|
||||
putString("OwnerIdentity", "ReactNative")
|
||||
putString("AppIdentity", appName)
|
||||
putString("DeviceIdentity", deviceName)
|
||||
}
|
||||
return JSCExecutor(jscConfig)
|
||||
}
|
||||
|
||||
override fun startSamplingProfiler() {
|
||||
throw UnsupportedOperationException("Starting sampling profiler not supported on ${toString()}")
|
||||
}
|
||||
|
||||
override fun stopSamplingProfiler(filename: String) {
|
||||
throw UnsupportedOperationException("Stopping sampling profiler not supported on ${toString()}")
|
||||
}
|
||||
|
||||
override fun toString(): String = "JSIExecutor+JSCRuntime"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user