mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
90
Commits
@@ -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=$?
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
|
||||
@@ -14,8 +14,8 @@ jobs:
|
||||
with:
|
||||
repo-token: ${{ secrets.REACT_NATIVE_BOT_GITHUB_TOKEN }}
|
||||
days-before-stale: 180
|
||||
stale-issue-message: 'This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
stale-issue-message: 'This issue is stale because it has been open for 180 days with no activity. It will be closed in 7 days unless you comment on it or remove the "Stale" label.'
|
||||
stale-pr-message: 'This PR is stale because it has been open for 180 days with no activity. It will be closed in 7 days unless you comment on it or remove the "Stale" label.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.'
|
||||
exempt-issue-labels: 'Help Wanted :octocat:, Good first issue, Never gets stale, Issue: Author Provided Repro'
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -124,7 +124,7 @@ if (project.findProperty("react.internal.useHermesNightly")?.toString()?.toBoole
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(project(":packages:react-native:ReactAndroid:hermes-engine"))
|
||||
.using(module("com.facebook.react:hermes-android:0.0.0-+"))
|
||||
.using(module("com.facebook.react:hermes-android:0.+"))
|
||||
.because("Users opted to use hermes from nightly")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -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",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@generated SignedSource<<9ada78dff12dcfc7937c8934261f47f4>>
|
||||
Git revision: 68cfd0ae84acb0ed8e47b421afd64ae3b0b5b727
|
||||
@generated SignedSource<<cf5be4596d1f07e220b710fc286d02fa>>
|
||||
Git revision: d95ac13bf0ab64a5e6c2eb18eb138587063b9c34
|
||||
Built with --nohooks: false
|
||||
Is local checkout: false
|
||||
Remote URL: https://github.com/facebook/react-native-devtools-frontend
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+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")}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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")
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -73,9 +73,9 @@ const ALL_GENERATORS = {
|
||||
generateViewConfigJs: generateViewConfigJs.generate,
|
||||
};
|
||||
|
||||
type FilesOutput = Map<string, string>;
|
||||
export type FilesOutput = Map<string, string>;
|
||||
|
||||
type GenerateFunction = (
|
||||
export type GenerateFunction = (
|
||||
libraryName: string,
|
||||
schema: SchemaType,
|
||||
packageName?: string,
|
||||
@@ -83,11 +83,11 @@ type GenerateFunction = (
|
||||
headerPrefix?: string,
|
||||
) => FilesOutput;
|
||||
|
||||
type LibraryGeneratorsFunctions = $ReadOnly<{
|
||||
export type LibraryGeneratorsFunctions = $ReadOnly<{
|
||||
[string]: Array<GenerateFunction>,
|
||||
}>;
|
||||
|
||||
type LibraryOptions = $ReadOnly<{
|
||||
export type LibraryOptions = $ReadOnly<{
|
||||
libraryName: string,
|
||||
schema: SchemaType,
|
||||
outputDirectory: string,
|
||||
@@ -97,13 +97,13 @@ type LibraryOptions = $ReadOnly<{
|
||||
libraryGenerators?: LibraryGeneratorsFunctions,
|
||||
}>;
|
||||
|
||||
type SchemasOptions = $ReadOnly<{
|
||||
export type SchemasOptions = $ReadOnly<{
|
||||
schemas: {[string]: SchemaType},
|
||||
outputDirectory: string,
|
||||
supportedApplePlatforms?: {[string]: {[string]: boolean}},
|
||||
}>;
|
||||
|
||||
type LibraryGenerators =
|
||||
export type LibraryGenerators =
|
||||
| 'componentsAndroid'
|
||||
| 'componentsIOS'
|
||||
| 'descriptors'
|
||||
@@ -116,14 +116,14 @@ type LibraryGenerators =
|
||||
| 'modulesCxx'
|
||||
| 'modulesIOS';
|
||||
|
||||
type SchemasGenerators = 'providerIOS';
|
||||
export type SchemasGenerators = 'providerIOS';
|
||||
|
||||
type LibraryConfig = $ReadOnly<{
|
||||
export type LibraryConfig = $ReadOnly<{
|
||||
generators: Array<LibraryGenerators>,
|
||||
test?: boolean,
|
||||
}>;
|
||||
|
||||
type SchemasConfig = $ReadOnly<{
|
||||
export type SchemasConfig = $ReadOnly<{
|
||||
generators: Array<SchemasGenerators>,
|
||||
test?: boolean,
|
||||
}>;
|
||||
|
||||
+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
|
||||
|
||||
@@ -14,12 +14,8 @@
|
||||
#import <memory>
|
||||
|
||||
#if USE_THIRD_PARTY_JSC != 1
|
||||
#if __has_include(<jsireact/HermesExecutorFactory.h>)
|
||||
#import <jsireact/HermesExecutorFactory.h>
|
||||
#elif __has_include(<reacthermes/HermesExecutorFactory.h>)
|
||||
#import <reacthermes/HermesExecutorFactory.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#import <ReactCommon/RCTTurboModuleManager.h>
|
||||
#import <jsireact/JSIExecutor.h>
|
||||
|
||||
@@ -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>,
|
||||
|
||||
+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,
|
||||
@@ -9348,6 +9349,11 @@ export type {
|
||||
PublicRootInstance,
|
||||
PublicTextInstance,
|
||||
} from \\"./Libraries/ReactPrivate/ReactNativePrivateInterface\\";
|
||||
export {
|
||||
default as unstable_VirtualView,
|
||||
VirtualViewMode,
|
||||
} from \\"./src/private/components/virtualview/VirtualView\\";
|
||||
export type { ModeChangeEvent } from \\"./src/private/components/virtualview/VirtualView\\";
|
||||
"
|
||||
`;
|
||||
|
||||
|
||||
@@ -336,7 +336,10 @@ let reactRuntime = RNTarget(
|
||||
name: .reactRuntime,
|
||||
path: "ReactCommon/react/runtime",
|
||||
excludedPaths: ["tests", "iostests", "platform"],
|
||||
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt]
|
||||
dependencies: [.reactNativeDependencies, .jsi, .reactJsiExecutor, .reactCxxReact, .reactJsErrorHandler, .reactPerformanceTimeline, .reactUtils, .reactFeatureFlags, .reactJsInspector, .reactJsiTooling, .reactHermes, .reactRuntimeScheduler, .hermesPrebuilt],
|
||||
defines: [
|
||||
CXXSetting.define("HERMES_ENABLE_DEBUGGER", to: "1", .when(configuration: BuildConfiguration.debug))
|
||||
]
|
||||
)
|
||||
|
||||
/// React-runtimeApple.podspec
|
||||
@@ -394,6 +397,7 @@ let reactFabric = RNTarget(
|
||||
"components/textinput",
|
||||
"components/textinput/platform/ios/",
|
||||
"components/unimplementedview",
|
||||
"components/virtualview",
|
||||
"components/root/tests",
|
||||
],
|
||||
dependencies: [.reactNativeDependencies, .reactJsiExecutor, .rctTypesafety, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .reactRendererDebug, .reactGraphics, .yoga],
|
||||
@@ -412,6 +416,8 @@ let reactFabricComponents = RNTarget(
|
||||
name: .reactFabricComponents,
|
||||
path: "ReactCommon/react/renderer",
|
||||
excludedPaths: [
|
||||
"components/modal/platform/android",
|
||||
"components/modal/platform/cxx",
|
||||
"components/view/platform/android",
|
||||
"components/view/platform/windows",
|
||||
"components/view/platform/macos",
|
||||
@@ -427,7 +433,7 @@ let reactFabricComponents = RNTarget(
|
||||
"conponents/rncore", // this was the old folder where RN Core Components were generated. If you ran codegen in the past, you might have some files in it that might make the build fail.
|
||||
],
|
||||
dependencies: [.reactNativeDependencies, .reactCore, .reactJsiExecutor, .reactTurboModuleCore, .jsi, .logger, .reactDebug, .reactFeatureFlags, .reactUtils, .reactRuntimeScheduler, .reactCxxReact, .yoga, .reactRendererDebug, .reactGraphics, .reactFabric, .reactTurboModuleBridging],
|
||||
sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "textlayoutmanager", "textlayoutmanager/platform/ios"]
|
||||
sources: ["components/inputaccessory", "components/modal", "components/safeareaview", "components/text", "components/text/platform/cxx", "components/textinput", "components/textinput/platform/ios/", "components/unimplementedview", "components/virtualview", "textlayoutmanager", "textlayoutmanager/platform/ios"]
|
||||
)
|
||||
|
||||
/// React-FabricImage.podspec
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef RCT_FIT_RM_OLD_RUNTIME
|
||||
|
||||
#include <jsireact/JSIExecutor.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class JSCExecutorFactory : public JSExecutorFactory {
|
||||
public:
|
||||
explicit JSCExecutorFactory(JSIExecutor::RuntimeInstaller runtimeInstaller)
|
||||
: runtimeInstaller_(std::move(runtimeInstaller)) {}
|
||||
|
||||
std::unique_ptr<JSExecutor> createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> jsQueue) override;
|
||||
|
||||
private:
|
||||
JSIExecutor::RuntimeInstaller runtimeInstaller_;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
#endif // RCT_FIT_RM_OLD_RUNTIME
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "JSCExecutorFactory.h"
|
||||
|
||||
#ifndef RCT_FIT_RM_OLD_RUNTIME
|
||||
|
||||
#import <jsc/JSCRuntime.h>
|
||||
|
||||
#import <memory>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
std::unique_ptr<JSExecutor> JSCExecutorFactory::createJSExecutor(
|
||||
std::shared_ptr<ExecutorDelegate> delegate,
|
||||
std::shared_ptr<MessageQueueThread> __unused jsQueue)
|
||||
{
|
||||
return std::make_unique<JSIExecutor>(
|
||||
facebook::jsc::makeJSCRuntime(), delegate, JSIExecutor::defaultTimeoutInvoker, runtimeInstaller_);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
#endif // RCT_FIT_RM_OLD_RUNTIME
|
||||
-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
|
||||
|
||||
+5
-2
@@ -1215,7 +1215,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||
result = [NSMutableString string];
|
||||
}
|
||||
if (result.length > 0) {
|
||||
[result appendString:@" "];
|
||||
[result appendString:@", "];
|
||||
}
|
||||
[result appendString:label];
|
||||
}
|
||||
@@ -1230,7 +1230,10 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||
return label;
|
||||
}
|
||||
|
||||
return RCTRecursiveAccessibilityLabel(self.currentContainerView);
|
||||
if (self.isAccessibilityElement) {
|
||||
return RCTRecursiveAccessibilityLabel(self.currentContainerView);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabelForCoopting
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import <React/RCTViewComponentView.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTVirtualViewComponentView : RCTViewComponentView
|
||||
|
||||
+ (instancetype)new NS_UNAVAILABLE;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTVirtualViewComponentView.h"
|
||||
|
||||
#import <React/RCTAssert.h>
|
||||
#import <React/RCTConversions.h>
|
||||
#import <React/RCTScrollViewComponentView.h>
|
||||
#import <React/RCTScrollableProtocol.h>
|
||||
#import <React/UIView+React.h>
|
||||
#import <jsi/jsi.h>
|
||||
|
||||
#import <react/featureflags/ReactNativeFeatureFlags.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/ComponentDescriptors.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/EventEmitters.h>
|
||||
#import <react/renderer/components/FBReactNativeSpec/Props.h>
|
||||
#import <react/renderer/components/virtualview/VirtualViewComponentDescriptor.h>
|
||||
#import <react/renderer/components/virtualview/VirtualViewShadowNode.h>
|
||||
|
||||
#import "RCTFabricComponentsPlugins.h"
|
||||
|
||||
using namespace facebook;
|
||||
using namespace facebook::react;
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTVirtualViewMode) {
|
||||
RCTVirtualViewModeVisible = 0,
|
||||
RCTVirtualViewModePrerender = 1,
|
||||
RCTVirtualViewModeHidden = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether one CGRect overlaps with another CGRect.
|
||||
*
|
||||
* This is different from CGRectIntersectsRect because a CGRect representing
|
||||
* a line or a point is considered to overlap with another CGRect if the line
|
||||
* or point is within the rect bounds. However, two CGRects are not considered
|
||||
* to overlap if they only share a boundary.
|
||||
*/
|
||||
static BOOL CGRectOverlaps(CGRect rect1, CGRect rect2)
|
||||
{
|
||||
CGFloat minY1 = CGRectGetMinY(rect1);
|
||||
CGFloat maxY1 = CGRectGetMaxY(rect1);
|
||||
CGFloat minY2 = CGRectGetMinY(rect2);
|
||||
CGFloat maxY2 = CGRectGetMaxY(rect2);
|
||||
if (minY1 >= maxY2 || minY2 >= maxY1) {
|
||||
// No overlap on the y-axis.
|
||||
return NO;
|
||||
}
|
||||
CGFloat minX1 = CGRectGetMinX(rect1);
|
||||
CGFloat maxX1 = CGRectGetMaxX(rect1);
|
||||
CGFloat minX2 = CGRectGetMinX(rect2);
|
||||
CGFloat maxX2 = CGRectGetMaxX(rect2);
|
||||
if (minX1 >= maxX2 || minX2 >= maxX1) {
|
||||
// No overlap on the x-axis.
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
@interface RCTVirtualViewComponentView () <UIScrollViewDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTVirtualViewComponentView {
|
||||
RCTScrollViewComponentView *_lastParentScrollViewComponentView;
|
||||
std::optional<enum RCTVirtualViewMode> _mode;
|
||||
std::optional<CGRect> _targetRect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
_props = VirtualViewShadowNode::defaultSharedProps();
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
|
||||
{
|
||||
if (!_mode.has_value()) {
|
||||
const auto &newViewProps = static_cast<const VirtualViewProps &>(*props);
|
||||
_mode = newViewProps.initialHidden ? RCTVirtualViewModeHidden : RCTVirtualViewModeVisible;
|
||||
}
|
||||
|
||||
[super updateProps:props oldProps:oldProps];
|
||||
}
|
||||
|
||||
- (RCTScrollViewComponentView *)getParentScrollViewComponentView
|
||||
{
|
||||
UIView *view = self.superview;
|
||||
while (view != nil) {
|
||||
if ([view isKindOfClass:[RCTScrollViewComponentView class]]) {
|
||||
return (RCTScrollViewComponentView *)view;
|
||||
}
|
||||
view = view.superview;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)prepareForRecycle
|
||||
{
|
||||
[super prepareForRecycle];
|
||||
|
||||
// No need to remove the scroll listener here since the view is always removed from window before being recycled and
|
||||
// we do that in didMoveToWindow, which gets called when the view is removed from window.
|
||||
RCTAssert(
|
||||
_lastParentScrollViewComponentView == nil,
|
||||
@"_lastParentScrollViewComponentView should already have been cleared in didMoveToWindow.");
|
||||
|
||||
_mode.reset();
|
||||
_targetRect.reset();
|
||||
}
|
||||
|
||||
// Handles case when sibling changes size.
|
||||
// TODO(T202601695): This doesn't yet handle the case of elements in the ScrollView outside a VirtualColumn changing
|
||||
// size.
|
||||
- (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
|
||||
oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics
|
||||
{
|
||||
[super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:_layoutMetrics];
|
||||
|
||||
[self dispatchOnModeChangeIfNeeded:YES];
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
|
||||
if (_lastParentScrollViewComponentView) {
|
||||
[_lastParentScrollViewComponentView removeScrollListener:self];
|
||||
_lastParentScrollViewComponentView = nil;
|
||||
}
|
||||
|
||||
if (RCTScrollViewComponentView *parentScrollViewComponentView = [self getParentScrollViewComponentView]) {
|
||||
if (self.window) {
|
||||
// TODO(T202601695): We also want the ScrollView to emit layout changes from didLayoutSubviews so that any event
|
||||
// that may affect visibily of this view notifies the listeners.
|
||||
[parentScrollViewComponentView addScrollListener:self];
|
||||
_lastParentScrollViewComponentView = parentScrollViewComponentView;
|
||||
|
||||
// We want to dispatch the event immediately when the view is added to the window before any scrolling occurs.
|
||||
[self dispatchOnModeChangeIfNeeded:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
|
||||
{
|
||||
[self dispatchOnModeChangeIfNeeded:NO];
|
||||
}
|
||||
|
||||
- (void)dispatchOnModeChangeIfNeeded:(BOOL)checkForTargetRectChange
|
||||
{
|
||||
if (!_lastParentScrollViewComponentView) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIScrollView *scrollView = _lastParentScrollViewComponentView.scrollView;
|
||||
CGRect targetRect = [self convertRect:self.bounds toView:scrollView];
|
||||
|
||||
// While scrolling, the `targetRect` does not change, so we don't check for changed `targetRect` in that case.
|
||||
if (checkForTargetRectChange) {
|
||||
if (_targetRect.has_value() && CGRectEqualToRect(targetRect, _targetRect.value())) {
|
||||
return;
|
||||
}
|
||||
_targetRect = targetRect;
|
||||
}
|
||||
|
||||
enum RCTVirtualViewMode newMode;
|
||||
CGRect thresholdRect = CGRectMake(
|
||||
scrollView.contentOffset.x,
|
||||
scrollView.contentOffset.y,
|
||||
scrollView.frame.size.width,
|
||||
scrollView.frame.size.height);
|
||||
if (CGRectOverlaps(targetRect, thresholdRect)) {
|
||||
newMode = RCTVirtualViewModeVisible;
|
||||
} else {
|
||||
auto prerender = false;
|
||||
const CGFloat prerenderRatio = ReactNativeFeatureFlags::virtualViewPrerenderRatio();
|
||||
if (prerenderRatio > 0) {
|
||||
thresholdRect = CGRectInset(
|
||||
thresholdRect, -thresholdRect.size.width * prerenderRatio, -thresholdRect.size.height * prerenderRatio);
|
||||
prerender = CGRectOverlaps(targetRect, thresholdRect);
|
||||
}
|
||||
if (prerender) {
|
||||
newMode = RCTVirtualViewModePrerender;
|
||||
} else {
|
||||
newMode = RCTVirtualViewModeHidden;
|
||||
thresholdRect = CGRectZero;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mode.has_value() && newMode == _mode.value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: Make sure to keep these props in sync with dispatchSyncModeChange below where we have to explicitly copy all
|
||||
// props.
|
||||
VirtualViewEventEmitter::OnModeChange event = {
|
||||
.mode = (int)newMode,
|
||||
.targetRect =
|
||||
{.x = targetRect.origin.x,
|
||||
.y = targetRect.origin.y,
|
||||
.width = targetRect.size.width,
|
||||
.height = targetRect.size.height},
|
||||
.thresholdRect =
|
||||
{.x = thresholdRect.origin.x,
|
||||
.y = thresholdRect.origin.y,
|
||||
.width = thresholdRect.size.width,
|
||||
.height = thresholdRect.size.height},
|
||||
};
|
||||
|
||||
const std::optional<enum RCTVirtualViewMode> oldMode = _mode;
|
||||
_mode = newMode;
|
||||
|
||||
switch (newMode) {
|
||||
case RCTVirtualViewModeVisible:
|
||||
[self dispatchSyncModeChange:event];
|
||||
break;
|
||||
case RCTVirtualViewModePrerender:
|
||||
if (!oldMode.has_value() || oldMode != RCTVirtualViewModeVisible) {
|
||||
[self dispatchAsyncModeChange:event];
|
||||
}
|
||||
break;
|
||||
case RCTVirtualViewModeHidden:
|
||||
[self dispatchAsyncModeChange:event];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dispatchAsyncModeChange:(VirtualViewEventEmitter::OnModeChange &)event
|
||||
{
|
||||
if (!_eventEmitter) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<const VirtualViewEventEmitter> emitter =
|
||||
std::static_pointer_cast<const VirtualViewEventEmitter>(_eventEmitter);
|
||||
emitter->onModeChange(event);
|
||||
}
|
||||
|
||||
- (void)dispatchSyncModeChange:(VirtualViewEventEmitter::OnModeChange &)event
|
||||
{
|
||||
if (!_eventEmitter) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<const VirtualViewEventEmitter> emitter =
|
||||
std::static_pointer_cast<const VirtualViewEventEmitter>(_eventEmitter);
|
||||
|
||||
// TODO: Move this into a custom event emitter. We had to duplicate the codegen code here from onModeChange in order
|
||||
// to dispatch synchronously and discrete.
|
||||
emitter->experimental_flushSync([&emitter, &event]() {
|
||||
emitter->dispatchEvent(
|
||||
"modeChange",
|
||||
[event](jsi::Runtime &runtime) {
|
||||
auto payload = jsi::Object(runtime);
|
||||
payload.setProperty(runtime, "mode", event.mode);
|
||||
{
|
||||
auto targetRect = jsi::Object(runtime);
|
||||
targetRect.setProperty(runtime, "x", event.targetRect.x);
|
||||
targetRect.setProperty(runtime, "y", event.targetRect.y);
|
||||
targetRect.setProperty(runtime, "width", event.targetRect.width);
|
||||
targetRect.setProperty(runtime, "height", event.targetRect.height);
|
||||
payload.setProperty(runtime, "targetRect", targetRect);
|
||||
}
|
||||
{
|
||||
auto thresholdRect = jsi::Object(runtime);
|
||||
thresholdRect.setProperty(runtime, "x", event.thresholdRect.x);
|
||||
thresholdRect.setProperty(runtime, "y", event.thresholdRect.y);
|
||||
thresholdRect.setProperty(runtime, "width", event.thresholdRect.width);
|
||||
thresholdRect.setProperty(runtime, "height", event.thresholdRect.height);
|
||||
payload.setProperty(runtime, "thresholdRect", thresholdRect);
|
||||
}
|
||||
return payload;
|
||||
},
|
||||
RawEvent::Category::Discrete);
|
||||
});
|
||||
}
|
||||
|
||||
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
||||
{
|
||||
return concreteComponentDescriptorProvider<VirtualViewComponentDescriptor>();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
Class<RCTComponentViewProtocol> VirtualViewCls(void)
|
||||
{
|
||||
return RCTVirtualViewComponentView.class;
|
||||
}
|
||||
@@ -16,6 +16,12 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
+ (std::vector<facebook::react::ProcessedColorStop>)getFixedColorStops:
|
||||
(const std::vector<facebook::react::ColorStop> &)colorStops
|
||||
gradientLineLength:(CGFloat)gradientLineLength;
|
||||
// CAGradientLayer linear gradient squishes the non-square gradient to square gradient.
|
||||
// This function fixes the "squished" effect.
|
||||
// See https://stackoverflow.com/a/43176174 for more information.
|
||||
+ (std::pair<CGPoint, CGPoint>)pointsForCAGradientLayerLinearGradient:(CGPoint)startPoint
|
||||
endPoint:(CGPoint)endPoint
|
||||
bounds:(CGSize)bounds;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -9,10 +9,140 @@
|
||||
#import <React/RCTAnimationUtils.h>
|
||||
#import <React/RCTConversions.h>
|
||||
#import <react/utils/FloatComparison.h>
|
||||
#include <optional>
|
||||
#import <vector>
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
namespace {
|
||||
|
||||
struct Line;
|
||||
|
||||
struct LineSegment {
|
||||
CGPoint p1;
|
||||
CGPoint p2;
|
||||
|
||||
LineSegment(CGPoint p1, CGPoint p2) : p1(p1), p2(p2) {}
|
||||
|
||||
LineSegment(CGPoint p1, CGFloat m, CGFloat distance);
|
||||
|
||||
CGFloat getLength() const
|
||||
{
|
||||
CGFloat dx = p2.x - p1.x;
|
||||
CGFloat dy = p2.y - p1.y;
|
||||
return sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
CGFloat getDistance() const
|
||||
{
|
||||
return p1.x <= p2.x ? getLength() : -getLength();
|
||||
}
|
||||
|
||||
CGPoint getMidpoint() const
|
||||
{
|
||||
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
|
||||
}
|
||||
|
||||
CGFloat getSlope() const
|
||||
{
|
||||
CGFloat dx = p2.x - p1.x;
|
||||
if (floatEquality(dx, 0.0)) {
|
||||
return std::numeric_limits<CGFloat>::infinity();
|
||||
}
|
||||
return (p2.y - p1.y) / dx;
|
||||
}
|
||||
|
||||
CGFloat getPerpendicularSlope() const
|
||||
{
|
||||
CGFloat slope = getSlope();
|
||||
if (std::isinf(slope)) {
|
||||
return 0.0;
|
||||
}
|
||||
if (floatEquality(slope, 0.0)) {
|
||||
return -std::numeric_limits<CGFloat>::infinity();
|
||||
}
|
||||
return -1 / slope;
|
||||
}
|
||||
|
||||
Line toLine() const;
|
||||
|
||||
LineSegment perpendicularBisector() const
|
||||
{
|
||||
CGPoint midpoint = getMidpoint();
|
||||
CGFloat perpSlope = getPerpendicularSlope();
|
||||
CGFloat dist = getDistance();
|
||||
return {LineSegment(midpoint, perpSlope, -dist / 2).p2, LineSegment(midpoint, perpSlope, dist / 2).p2};
|
||||
}
|
||||
|
||||
LineSegment multiplied(CGSize multipliers) const
|
||||
{
|
||||
return {
|
||||
CGPointMake(p1.x * multipliers.width, p1.y * multipliers.height),
|
||||
CGPointMake(p2.x * multipliers.width, p2.y * multipliers.height)};
|
||||
}
|
||||
|
||||
LineSegment divided(CGSize divisors) const
|
||||
{
|
||||
return multiplied(CGSizeMake(1 / divisors.width, 1 / divisors.height));
|
||||
}
|
||||
};
|
||||
|
||||
struct Line {
|
||||
CGFloat m;
|
||||
CGFloat b;
|
||||
|
||||
Line(CGFloat m, CGFloat b) : m(m), b(b) {}
|
||||
Line(CGFloat m, CGPoint p) : m(m), b(p.y - m * p.x) {}
|
||||
Line(CGPoint p1, CGPoint p2) : m(LineSegment(p1, p2).getSlope()), b(p1.y - m * p1.x) {}
|
||||
|
||||
CGFloat y(CGFloat x) const
|
||||
{
|
||||
return m * x + b;
|
||||
}
|
||||
|
||||
CGPoint point(CGFloat x) const
|
||||
{
|
||||
return CGPointMake(x, y(x));
|
||||
}
|
||||
|
||||
std::optional<CGPoint> intersection(const Line &other) const
|
||||
{
|
||||
CGFloat n = other.m;
|
||||
CGFloat c = other.b;
|
||||
if (floatEquality(m, n)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
CGFloat x = (c - b) / (m - n);
|
||||
return point(x);
|
||||
}
|
||||
};
|
||||
|
||||
LineSegment::LineSegment(CGPoint p1, CGFloat m, CGFloat distance) : p1(p1)
|
||||
{
|
||||
Line line(m, p1);
|
||||
CGPoint measuringPoint = line.point(p1.x + 1);
|
||||
LineSegment measuringSegment(p1, measuringPoint);
|
||||
CGFloat measuringDeltaH = measuringSegment.getDistance();
|
||||
CGFloat deltaX = !floatEquality(measuringDeltaH, 0.0) ? distance / measuringDeltaH : 0.0;
|
||||
p2 = line.point(p1.x + deltaX);
|
||||
}
|
||||
|
||||
Line LineSegment::toLine() const
|
||||
{
|
||||
return {p1, p2};
|
||||
}
|
||||
|
||||
CGSize calculateMultipliers(CGSize bounds)
|
||||
{
|
||||
if (bounds.height <= bounds.width) {
|
||||
return CGSizeMake(1, bounds.width / bounds.height);
|
||||
} else {
|
||||
return CGSizeMake(bounds.height / bounds.width, 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static std::optional<Float> resolveColorStopPosition(ValueUnit position, CGFloat gradientLineLength)
|
||||
{
|
||||
if (position.unit == UnitType::Point) {
|
||||
@@ -52,21 +182,21 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
|
||||
auto leftDist = offset - offsetLeft;
|
||||
auto rightDist = offsetRight - offset;
|
||||
auto totalDist = offsetRight - offsetLeft;
|
||||
SharedColor leftSharedColor = colorStops[x - 1].color;
|
||||
SharedColor rightSharedColor = colorStops[x + 1].color;
|
||||
const SharedColor &leftSharedColor = colorStops[x - 1].color;
|
||||
const SharedColor &rightSharedColor = colorStops[x + 1].color;
|
||||
|
||||
if (facebook::react::floatEquality(leftDist, rightDist)) {
|
||||
if (floatEquality(leftDist, rightDist)) {
|
||||
colorStops.erase(colorStops.begin() + x);
|
||||
--indexOffset;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (facebook::react::floatEquality(leftDist, .0f)) {
|
||||
if (floatEquality(leftDist, 0.0)) {
|
||||
colorStops[x].color = rightSharedColor;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (facebook::react::floatEquality(rightDist, .0f)) {
|
||||
if (floatEquality(rightDist, 0.0)) {
|
||||
colorStops[x].color = leftSharedColor;
|
||||
continue;
|
||||
}
|
||||
@@ -120,7 +250,7 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
|
||||
auto green = (interpolatedColor >> 8) & 0xFF;
|
||||
auto blue = interpolatedColor & 0xFF;
|
||||
|
||||
newStop.color = facebook::react::colorFromRGBA(red, green, blue, alpha);
|
||||
newStop.color = colorFromRGBA(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
// Replace the color hint with new color stops
|
||||
@@ -201,4 +331,47 @@ static std::vector<ProcessedColorStop> processColorTransitionHints(const std::ve
|
||||
}
|
||||
return processColorTransitionHints(fixedColorStops);
|
||||
}
|
||||
|
||||
// CAGradientLayer linear gradient squishes the non-square gradient to square gradient.
|
||||
// This function fixes the "squished" effect.
|
||||
// See https://stackoverflow.com/a/43176174 for more information.
|
||||
+ (std::pair<CGPoint, CGPoint>)pointsForCAGradientLayerLinearGradient:(CGPoint)startPoint
|
||||
endPoint:(CGPoint)endPoint
|
||||
bounds:(CGSize)bounds
|
||||
{
|
||||
if (floatEquality(startPoint.x, endPoint.x) || floatEquality(startPoint.y, endPoint.y)) {
|
||||
// Apple's implementation of horizontal and vertical gradients works just fine
|
||||
return {startPoint, endPoint};
|
||||
}
|
||||
|
||||
LineSegment startEnd(startPoint, endPoint);
|
||||
LineSegment ab = startEnd.multiplied({bounds.width, bounds.height});
|
||||
const CGPoint a = ab.p1;
|
||||
const CGPoint b = ab.p2;
|
||||
|
||||
LineSegment cd = ab.perpendicularBisector();
|
||||
|
||||
CGSize multipliers = calculateMultipliers(bounds);
|
||||
LineSegment lineSegmentCD = cd.multiplied(multipliers);
|
||||
|
||||
LineSegment lineSegmentEF = lineSegmentCD.perpendicularBisector();
|
||||
|
||||
LineSegment ef = lineSegmentEF.divided(multipliers);
|
||||
|
||||
Line efLine = ef.toLine();
|
||||
|
||||
Line aParallelLine(cd.getSlope(), a);
|
||||
Line bParallelLine(cd.getSlope(), b);
|
||||
|
||||
std::optional<CGPoint> g_opt = efLine.intersection(aParallelLine);
|
||||
std::optional<CGPoint> h_opt = efLine.intersection(bParallelLine);
|
||||
|
||||
if (g_opt && h_opt) {
|
||||
LineSegment gh(*g_opt, *h_opt);
|
||||
LineSegment result = gh.divided({bounds.width, bounds.height});
|
||||
return {result.p1, result.p2};
|
||||
}
|
||||
|
||||
return {startPoint, endPoint};
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -19,54 +19,52 @@ using namespace facebook::react;
|
||||
|
||||
+ (CALayer *)gradientLayerWithSize:(CGSize)size gradient:(const LinearGradient &)gradient
|
||||
{
|
||||
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size];
|
||||
const auto &direction = gradient.direction;
|
||||
UIImage *gradientImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *_Nonnull rendererContext) {
|
||||
CGPoint startPoint;
|
||||
CGPoint endPoint;
|
||||
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
|
||||
CGPoint startPoint;
|
||||
CGPoint endPoint;
|
||||
|
||||
if (direction.type == GradientDirectionType::Angle) {
|
||||
CGFloat angle = std::get<Float>(direction.value);
|
||||
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
|
||||
} else if (direction.type == GradientDirectionType::Keyword) {
|
||||
auto keyword = std::get<GradientKeyword>(direction.value);
|
||||
CGFloat angle = getAngleForKeyword(keyword, size);
|
||||
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
|
||||
} else {
|
||||
// Default to top-to-bottom gradient
|
||||
startPoint = CGPointMake(0.0, 0.0);
|
||||
endPoint = CGPointMake(0.0, size.height);
|
||||
}
|
||||
if (direction.type == GradientDirectionType::Angle) {
|
||||
CGFloat angle = std::get<Float>(direction.value);
|
||||
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
|
||||
} else if (direction.type == GradientDirectionType::Keyword) {
|
||||
auto keyword = std::get<GradientKeyword>(direction.value);
|
||||
CGFloat angle = getAngleForKeyword(keyword, size);
|
||||
std::tie(startPoint, endPoint) = getPointsFromAngle(angle, size);
|
||||
} else {
|
||||
// Default to top-to-bottom gradient
|
||||
CGFloat centerX = size.width / 2;
|
||||
startPoint = CGPointMake(centerX, 0.0);
|
||||
endPoint = CGPointMake(centerX, size.height);
|
||||
}
|
||||
|
||||
CGFloat dx = endPoint.x - startPoint.x;
|
||||
CGFloat dy = endPoint.y - startPoint.y;
|
||||
CGFloat gradientLineLength = sqrt(dx * dx + dy * dy);
|
||||
const auto colorStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops
|
||||
gradientLineLength:gradientLineLength];
|
||||
CGFloat dx = endPoint.x - startPoint.x;
|
||||
CGFloat dy = endPoint.y - startPoint.y;
|
||||
CGFloat gradientLineLength = sqrt(dx * dx + dy * dy);
|
||||
const auto colorStops = [RCTGradientUtils getFixedColorStops:gradient.colorStops
|
||||
gradientLineLength:gradientLineLength];
|
||||
NSMutableArray<id> *colors = [NSMutableArray array];
|
||||
NSMutableArray<NSNumber *> *locations = [NSMutableArray array];
|
||||
CGPoint relativeStartPoint = CGPointMake(startPoint.x / size.width, startPoint.y / size.height);
|
||||
CGPoint relativeEndPoint = CGPointMake(endPoint.x / size.width, endPoint.y / size.height);
|
||||
|
||||
CGContextRef context = rendererContext.CGContext;
|
||||
NSMutableArray *colors = [NSMutableArray array];
|
||||
CGFloat locations[colorStops.size()];
|
||||
CGPoint fixedStartPoint;
|
||||
CGPoint fixedEndPoint;
|
||||
|
||||
for (size_t i = 0; i < colorStops.size(); ++i) {
|
||||
const auto &colorStop = colorStops[i];
|
||||
CGColorRef cgColor = RCTCreateCGColorRefFromSharedColor(colorStop.color);
|
||||
[colors addObject:(__bridge id)cgColor];
|
||||
locations[i] = std::max(std::min(colorStop.position.value(), 1.0), 0.0);
|
||||
}
|
||||
std::tie(fixedStartPoint, fixedEndPoint) = [RCTGradientUtils pointsForCAGradientLayerLinearGradient:relativeStartPoint
|
||||
endPoint:relativeEndPoint
|
||||
bounds:size];
|
||||
|
||||
CGGradientRef cgGradient = CGGradientCreateWithColors(NULL, (__bridge CFArrayRef)colors, locations);
|
||||
gradientLayer.startPoint = fixedStartPoint;
|
||||
gradientLayer.endPoint = fixedEndPoint;
|
||||
|
||||
CGContextDrawLinearGradient(context, cgGradient, startPoint, endPoint, 0);
|
||||
for (const auto &colorStop : colorStops) {
|
||||
[colors addObject:(id)RCTUIColorFromSharedColor(colorStop.color).CGColor];
|
||||
[locations addObject:@(std::max(std::min(colorStop.position.value(), 1.0), 0.0))];
|
||||
}
|
||||
|
||||
for (id color in colors) {
|
||||
CGColorRelease((__bridge CGColorRef)color);
|
||||
}
|
||||
CGGradientRelease(cgGradient);
|
||||
}];
|
||||
|
||||
CALayer *gradientLayer = [CALayer layer];
|
||||
gradientLayer.contents = (__bridge id)gradientImage.CGImage;
|
||||
gradientLayer.colors = colors;
|
||||
gradientLayer.locations = locations;
|
||||
|
||||
return gradientLayer;
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "RCTJscInstanceFactory.h"
|
||||
#import <ReactCommon/RCTJscInstance.h>
|
||||
|
||||
using namespace facebook::react;
|
||||
|
||||
extern "C" {
|
||||
|
||||
JSRuntimeFactoryRef jsrt_create_jsc_factory(void)
|
||||
{
|
||||
return reinterpret_cast<JSRuntimeFactoryRef>(new RCTJscInstance());
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
@property (nonatomic, copy) RCTDirectEventBlock onShow;
|
||||
@property (nonatomic, assign) BOOL visible;
|
||||
@property (nonatomic, assign) BOOL allowSwipeDismissal;
|
||||
|
||||
// Android only
|
||||
@property (nonatomic, assign) BOOL statusBarTranslucent;
|
||||
|
||||
@@ -35,6 +35,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
|
||||
if ((self = [super initWithFrame:CGRectZero])) {
|
||||
_bridge = bridge;
|
||||
_modalViewController = [RCTModalHostViewController new];
|
||||
_modalViewController.modalInPresentation = YES;
|
||||
UIView *containerView = [UIView new];
|
||||
containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
|
||||
_modalViewController.view = containerView;
|
||||
@@ -50,6 +51,14 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setAllowSwipeDismissal:(BOOL)allowSwipeDismissal
|
||||
{
|
||||
if (_allowSwipeDismissal != allowSwipeDismissal) {
|
||||
_allowSwipeDismissal = allowSwipeDismissal;
|
||||
_modalViewController.modalInPresentation = !allowSwipeDismissal;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyForBoundsChange:(CGRect)newBounds
|
||||
{
|
||||
if (_reactSubview && _isPresented) {
|
||||
@@ -70,6 +79,13 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
|
||||
{
|
||||
if (_onRequestClose != nil && _allowSwipeDismissal) {
|
||||
_onRequestClose(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)notifyForOrientationChange
|
||||
{
|
||||
if (!_onOrientationChange) {
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
self.modalInPresentation = YES;
|
||||
|
||||
_preferredStatusBarStyle = [RCTUIStatusBarManager() statusBarStyle];
|
||||
_preferredStatusBarHidden = [RCTUIStatusBarManager() isStatusBarHidden];
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(visible, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onRequestClose, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowSwipeDismissal, BOOL)
|
||||
|
||||
// Fabric only
|
||||
RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock)
|
||||
|
||||
@@ -240,7 +240,10 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
|
||||
if (label) {
|
||||
return label;
|
||||
}
|
||||
return RCTRecursiveAccessibilityLabel(self);
|
||||
if (self.isAccessibilityElement) {
|
||||
return RCTRecursiveAccessibilityLabel(self);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
|
||||
|
||||
@@ -148,29 +148,29 @@ public class com/facebook/react/ReactDelegate {
|
||||
public fun <init> (Landroid/app/Activity;Lcom/facebook/react/ReactNativeHost;Ljava/lang/String;Landroid/os/Bundle;)V
|
||||
public fun <init> (Landroid/app/Activity;Lcom/facebook/react/ReactNativeHost;Ljava/lang/String;Landroid/os/Bundle;Z)V
|
||||
protected fun createRootView ()Lcom/facebook/react/ReactRootView;
|
||||
public fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
public fun getReactHost ()Lcom/facebook/react/ReactHost;
|
||||
public fun getReactInstanceManager ()Lcom/facebook/react/ReactInstanceManager;
|
||||
public fun getReactRootView ()Lcom/facebook/react/ReactRootView;
|
||||
protected fun isFabricEnabled ()Z
|
||||
public fun loadApp ()V
|
||||
public fun loadApp (Ljava/lang/String;)V
|
||||
public fun onActivityResult (IILandroid/content/Intent;Z)V
|
||||
public fun onBackPressed ()Z
|
||||
public fun onConfigurationChanged (Landroid/content/res/Configuration;)V
|
||||
public fun onHostDestroy ()V
|
||||
public fun onHostPause ()V
|
||||
public fun onHostResume ()V
|
||||
public fun onKeyDown (ILandroid/view/KeyEvent;)Z
|
||||
public fun onKeyLongPress (I)Z
|
||||
public fun onNewIntent (Landroid/content/Intent;)Z
|
||||
public fun onUserLeaveHint ()V
|
||||
public fun onWindowFocusChanged (Z)V
|
||||
public fun reload ()V
|
||||
public fun setReactRootView (Lcom/facebook/react/ReactRootView;)V
|
||||
public fun setReactSurface (Lcom/facebook/react/interfaces/fabric/ReactSurface;)V
|
||||
public fun shouldShowDevMenuOrReload (ILandroid/view/KeyEvent;)Z
|
||||
public fun unloadApp ()V
|
||||
public final fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
public final fun getReactHost ()Lcom/facebook/react/ReactHost;
|
||||
public final fun getReactInstanceManager ()Lcom/facebook/react/ReactInstanceManager;
|
||||
public final fun getReactRootView ()Lcom/facebook/react/ReactRootView;
|
||||
protected final fun isFabricEnabled ()Z
|
||||
public final fun loadApp ()V
|
||||
public final fun loadApp (Ljava/lang/String;)V
|
||||
public final fun onActivityResult (IILandroid/content/Intent;Z)V
|
||||
public final fun onBackPressed ()Z
|
||||
public final fun onConfigurationChanged (Landroid/content/res/Configuration;)V
|
||||
public final fun onHostDestroy ()V
|
||||
public final fun onHostPause ()V
|
||||
public final fun onHostResume ()V
|
||||
public final fun onKeyDown (ILandroid/view/KeyEvent;)Z
|
||||
public final fun onKeyLongPress (I)Z
|
||||
public final fun onNewIntent (Landroid/content/Intent;)Z
|
||||
public final fun onUserLeaveHint ()V
|
||||
public final fun onWindowFocusChanged (Z)V
|
||||
public final fun reload ()V
|
||||
public final fun setReactRootView (Lcom/facebook/react/ReactRootView;)V
|
||||
public final fun setReactSurface (Lcom/facebook/react/interfaces/fabric/ReactSurface;)V
|
||||
public final fun shouldShowDevMenuOrReload (ILandroid/view/KeyEvent;)Z
|
||||
public final fun unloadApp ()V
|
||||
}
|
||||
|
||||
public class com/facebook/react/ReactFragment : androidx/fragment/app/Fragment, com/facebook/react/modules/core/PermissionAwareActivity {
|
||||
@@ -259,6 +259,7 @@ public class com/facebook/react/ReactInstanceManager {
|
||||
public fun createViewManager (Ljava/lang/String;)Lcom/facebook/react/uimanager/ViewManager;
|
||||
public fun destroy ()V
|
||||
public fun detachRootView (Lcom/facebook/react/uimanager/ReactRoot;)V
|
||||
public fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
public fun getDevSupportManager ()Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
|
||||
public fun getJsExecutorName ()Ljava/lang/String;
|
||||
public fun getLifecycleState ()Lcom/facebook/react/common/LifecycleState;
|
||||
@@ -600,12 +601,14 @@ public abstract interface class com/facebook/react/bridge/CatalystInstance : com
|
||||
public abstract fun getSourceURL ()Ljava/lang/String;
|
||||
public abstract fun hasNativeModule (Ljava/lang/Class;)Z
|
||||
public abstract fun hasRunJSBundle ()Z
|
||||
public abstract fun initialize ()V
|
||||
public abstract fun invokeCallback (ILcom/facebook/react/bridge/NativeArrayInterface;)V
|
||||
public abstract fun isDestroyed ()Z
|
||||
public abstract fun registerSegment (ILjava/lang/String;)V
|
||||
public abstract fun removeBridgeIdleDebugListener (Lcom/facebook/react/bridge/NotThreadSafeBridgeIdleDebugListener;)V
|
||||
public abstract fun runJSBundle ()V
|
||||
public abstract fun setFabricUIManager (Lcom/facebook/react/bridge/UIManager;)V
|
||||
public abstract fun setGlobalVariable (Ljava/lang/String;Ljava/lang/String;)V
|
||||
public abstract fun setTurboModuleRegistry (Lcom/facebook/react/internal/turbomodule/core/interfaces/TurboModuleRegistry;)V
|
||||
}
|
||||
|
||||
@@ -2372,24 +2375,6 @@ public final class com/facebook/react/fabric/FabricUIManagerProviderImpl : com/f
|
||||
public fun createUIManager (Lcom/facebook/react/bridge/ReactApplicationContext;)Lcom/facebook/react/bridge/UIManager;
|
||||
}
|
||||
|
||||
public class com/facebook/react/fabric/mounting/MountItemDispatcher {
|
||||
public fun <init> (Lcom/facebook/react/fabric/mounting/MountingManager;Lcom/facebook/react/fabric/mounting/MountItemDispatcher$ItemDispatchListener;)V
|
||||
public fun addMountItem (Lcom/facebook/react/fabric/mounting/mountitems/MountItem;)V
|
||||
public fun addPreAllocateMountItem (Lcom/facebook/react/fabric/mounting/mountitems/MountItem;)V
|
||||
public fun addViewCommandMountItem (Lcom/facebook/react/fabric/mounting/mountitems/DispatchCommandMountItem;)V
|
||||
public fun dispatchMountItems (Ljava/util/Queue;)V
|
||||
public fun dispatchPreMountItems (J)V
|
||||
public fun getBatchedExecutionTime ()J
|
||||
public fun getRunStartTime ()J
|
||||
public fun tryDispatchMountItems ()V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/fabric/mounting/MountItemDispatcher$ItemDispatchListener {
|
||||
public abstract fun didDispatchMountItems ()V
|
||||
public abstract fun didMountItems (Ljava/util/List;)V
|
||||
public abstract fun willMountItems (Ljava/util/List;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/fabric/mounting/SurfaceMountingManager {
|
||||
public static final field TAG Ljava/lang/String;
|
||||
public fun <init> (ILcom/facebook/react/touch/JSResponderHandler;Lcom/facebook/react/uimanager/ViewManagerRegistry;Lcom/facebook/react/uimanager/RootViewManager;Lcom/facebook/react/fabric/mounting/MountingManager$MountItemExecutor;Lcom/facebook/react/uimanager/ThemedReactContext;)V
|
||||
@@ -2448,19 +2433,6 @@ public abstract interface class com/facebook/react/interfaces/fabric/ReactSurfac
|
||||
public abstract fun stop ()Lcom/facebook/react/interfaces/TaskInterface;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/jscexecutor/JSCExecutor : com/facebook/react/bridge/JavaScriptExecutor {
|
||||
public fun getName ()Ljava/lang/String;
|
||||
public static final fun loadLibrary ()V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/jscexecutor/JSCExecutorFactory : com/facebook/react/bridge/JavaScriptExecutorFactory {
|
||||
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
|
||||
public fun create ()Lcom/facebook/react/bridge/JavaScriptExecutor;
|
||||
public fun startSamplingProfiler ()V
|
||||
public fun stopSamplingProfiler (Ljava/lang/String;)V
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/jstasks/HeadlessJsTaskConfig {
|
||||
public fun <init> (Lcom/facebook/react/jstasks/HeadlessJsTaskConfig;)V
|
||||
public fun <init> (Ljava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
|
||||
@@ -3135,10 +3107,6 @@ public class com/facebook/react/runtime/CoreReactPackage$$ReactModuleInfoProvide
|
||||
public fun getReactModuleInfos ()Ljava/util/Map;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/runtime/JSCInstance : com/facebook/react/runtime/JSRuntimeFactory {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public abstract class com/facebook/react/runtime/JSRuntimeFactory {
|
||||
public fun <init> (Lcom/facebook/jni/HybridData;)V
|
||||
}
|
||||
@@ -3253,10 +3221,6 @@ public final class com/facebook/react/soloader/OpenSourceMergedSoMapping : com/f
|
||||
public final fun libhermes_executor_so ()I
|
||||
public final fun libhermesinstancejni_so ()I
|
||||
public final fun libhermestooling_so ()I
|
||||
public final fun libjscexecutor_so ()I
|
||||
public final fun libjscinstance_so ()I
|
||||
public final fun libjscruntime_so ()I
|
||||
public final fun libjsctooling_so ()I
|
||||
public final fun libjsijniprofiler_so ()I
|
||||
public final fun libjsinspector_so ()I
|
||||
public final fun libmapbufferjni_so ()I
|
||||
@@ -3336,6 +3300,7 @@ public abstract class com/facebook/react/uimanager/BaseViewManager : com/faceboo
|
||||
public fun getExportedCustomBubblingEventTypeConstants ()Ljava/util/Map;
|
||||
public fun getExportedCustomDirectEventTypeConstants ()Ljava/util/Map;
|
||||
protected fun onAfterUpdateTransaction (Landroid/view/View;)V
|
||||
public fun onDropViewInstance (Landroid/view/View;)V
|
||||
public fun onLayoutChange (Landroid/view/View;IIIIIIII)V
|
||||
protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
|
||||
public fun setAccessibilityActions (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
@@ -4925,32 +4890,6 @@ public final class com/facebook/react/uimanager/events/NativeGestureUtil {
|
||||
public static final fun notifyNativeGestureStarted (Landroid/view/View;Landroid/view/MotionEvent;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/uimanager/events/PointerEvent : com/facebook/react/uimanager/events/Event {
|
||||
public fun dispatch (Lcom/facebook/react/uimanager/events/RCTEventEmitter;)V
|
||||
public fun dispatchModern (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;)V
|
||||
public fun getCoalescingKey ()S
|
||||
public fun getEventAnimationDriverMatchSpec ()Lcom/facebook/react/uimanager/events/Event$EventAnimationDriverMatchSpec;
|
||||
public fun getEventName ()Ljava/lang/String;
|
||||
public static fun obtain (Ljava/lang/String;ILcom/facebook/react/uimanager/events/PointerEvent$PointerEventState;Landroid/view/MotionEvent;)Lcom/facebook/react/uimanager/events/PointerEvent;
|
||||
public static fun obtain (Ljava/lang/String;ILcom/facebook/react/uimanager/events/PointerEvent$PointerEventState;Landroid/view/MotionEvent;S)Lcom/facebook/react/uimanager/events/PointerEvent;
|
||||
public fun onDispose ()V
|
||||
}
|
||||
|
||||
public class com/facebook/react/uimanager/events/PointerEvent$PointerEventState {
|
||||
public fun <init> (IIIILjava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Set;)V
|
||||
public fun getActivePointerId ()I
|
||||
public final fun getEventCoordinatesByPointerId ()Ljava/util/Map;
|
||||
public final fun getHitPathByPointerId ()Ljava/util/Map;
|
||||
public final fun getHitPathForActivePointer ()Ljava/util/List;
|
||||
public fun getHoveringPointerIds ()Ljava/util/Set;
|
||||
public fun getLastButtonState ()I
|
||||
public final fun getOffsetByPointerId ()Ljava/util/Map;
|
||||
public fun getPrimaryPointerId ()I
|
||||
public final fun getScreenCoordinatesByPointerId ()Ljava/util/Map;
|
||||
public fun getSurfaceId ()I
|
||||
public fun supportsHover (I)Z
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/uimanager/events/RCTEventEmitter : com/facebook/react/bridge/JavaScriptModule {
|
||||
public abstract fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
|
||||
public abstract fun receiveTouches (Ljava/lang/String;Lcom/facebook/react/bridge/WritableArray;Lcom/facebook/react/bridge/WritableArray;)V
|
||||
@@ -5327,6 +5266,7 @@ public class com/facebook/react/viewmanagers/ModalHostViewManagerDelegate : com/
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/viewmanagers/ModalHostViewManagerInterface : com/facebook/react/uimanager/ViewManagerWithGeneratedInterface {
|
||||
public abstract fun setAllowSwipeDismissal (Landroid/view/View;Z)V
|
||||
public abstract fun setAnimated (Landroid/view/View;Z)V
|
||||
public abstract fun setAnimationType (Landroid/view/View;Ljava/lang/String;)V
|
||||
public abstract fun setHardwareAccelerated (Landroid/view/View;Z)V
|
||||
@@ -5356,6 +5296,15 @@ public abstract interface class com/facebook/react/viewmanagers/UnimplementedNat
|
||||
public abstract fun setName (Landroid/view/View;Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public class com/facebook/react/viewmanagers/VirtualViewManagerDelegate : com/facebook/react/uimanager/BaseViewManagerDelegate {
|
||||
public fun <init> (Lcom/facebook/react/uimanager/BaseViewManager;)V
|
||||
public fun setProperty (Landroid/view/View;Ljava/lang/String;Ljava/lang/Object;)V
|
||||
}
|
||||
|
||||
public abstract interface class com/facebook/react/viewmanagers/VirtualViewManagerInterface : com/facebook/react/uimanager/ViewManagerWithGeneratedInterface {
|
||||
public abstract fun setInitialHidden (Landroid/view/View;Z)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/views/drawer/ReactDrawerLayout : androidx/drawerlayout/widget/DrawerLayout {
|
||||
public fun <init> (Lcom/facebook/react/bridge/ReactContext;)V
|
||||
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
|
||||
@@ -6183,16 +6132,17 @@ public final class com/facebook/react/views/text/ReactFontManager$Companion {
|
||||
public final fun getInstance ()Lcom/facebook/react/views/text/ReactFontManager;
|
||||
}
|
||||
|
||||
public class com/facebook/react/views/text/ReactTextShadowNode : com/facebook/react/views/text/ReactBaseTextShadowNode {
|
||||
public final class com/facebook/react/views/text/ReactTextShadowNode : com/facebook/react/views/text/ReactBaseTextShadowNode {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;)V
|
||||
public synthetic fun <init> (Lcom/facebook/react/views/text/ReactTextViewManagerCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun calculateLayoutOnChildren ()Ljava/lang/Iterable;
|
||||
public fun hoistNativeChildren ()Z
|
||||
public fun isVirtualAnchor ()Z
|
||||
public fun markUpdated ()V
|
||||
public fun onBeforeLayout (Lcom/facebook/react/uimanager/NativeViewHierarchyOptimizer;)V
|
||||
public fun onCollectExtraUpdates (Lcom/facebook/react/uimanager/UIViewOperationQueue;)V
|
||||
public fun setShouldNotifyOnTextLayout (Z)V
|
||||
public final fun setShouldNotifyOnTextLayout (Z)V
|
||||
}
|
||||
|
||||
public final class com/facebook/react/views/text/ReactTextUpdate {
|
||||
|
||||
@@ -87,8 +87,6 @@ val preparePrefab by
|
||||
// hermes_executor
|
||||
// This prefab targets is used by Expo & Reanimated
|
||||
Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/"),
|
||||
// jscexecutor
|
||||
Pair("../ReactCommon/jsc/", "jsc/"),
|
||||
// fabricjni
|
||||
Pair("src/main/jni/react/fabric", "react/fabric/"),
|
||||
// glog
|
||||
@@ -236,10 +234,6 @@ val preparePrefab by
|
||||
"hermestooling",
|
||||
// hermes_executor
|
||||
Pair("../ReactCommon/hermes/inspector-modern/", "hermes/inspector-modern/")),
|
||||
PrefabPreprocessingEntry(
|
||||
"jsctooling",
|
||||
// jsc
|
||||
Pair("../ReactCommon/jsc/", "jsc/")),
|
||||
))
|
||||
outputDir.set(prefabHeadersDir)
|
||||
}
|
||||
@@ -534,7 +528,6 @@ android {
|
||||
"reactnative",
|
||||
"jsi",
|
||||
"hermestooling",
|
||||
"jsctooling",
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -583,10 +576,9 @@ android {
|
||||
resources.excludes.add("META-INF/LICENSE")
|
||||
// We intentionally don't want to bundle any JS Runtime inside the Android AAR
|
||||
// we produce. The reason behind this is that we want to allow users to pick the
|
||||
// JS engine by specifying a dependency on either `hermes-engine` or `android-jsc`
|
||||
// JS engine by specifying a dependency on either `hermes-engine` or other engines
|
||||
// that will include the necessary .so files to load.
|
||||
jniLibs.excludes.add("**/libhermes.so")
|
||||
jniLibs.excludes.add("**/libjsc.so")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
@@ -599,7 +591,6 @@ android {
|
||||
create("jsi") { headers = File(prefabHeadersDir, "jsi").absolutePath }
|
||||
create("reactnative") { headers = File(prefabHeadersDir, "reactnative").absolutePath }
|
||||
create("hermestooling") { headers = File(prefabHeadersDir, "hermestooling").absolutePath }
|
||||
create("jsctooling") { headers = File(prefabHeadersDir, "jsctooling").absolutePath }
|
||||
}
|
||||
|
||||
publishing {
|
||||
@@ -644,10 +635,9 @@ dependencies {
|
||||
compileOnly(libs.javax.annotation.api)
|
||||
api(libs.javax.inject)
|
||||
|
||||
// It's up to the consumer to decide if hermes/jsc should be included or not.
|
||||
// Therefore hermes-engine and jsc are compileOnly dependencies.
|
||||
// It's up to the consumer to decide if hermes or other engines should be included or not.
|
||||
// Therefore hermes-engine is a compileOnly dependencies.
|
||||
compileOnly(project(":packages:react-native:ReactAndroid:hermes-engine"))
|
||||
compileOnly(libs.jsc.android)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.assertj)
|
||||
|
||||
@@ -186,6 +186,24 @@ val prepareHeadersForPrefab by
|
||||
into(prefabHeadersDir)
|
||||
}
|
||||
|
||||
val buildHermesLib by
|
||||
tasks.registering(CustomExecTask::class) {
|
||||
dependsOn(buildHermesC)
|
||||
workingDir(hermesDir)
|
||||
inputs.files(hermesBuildOutputFileTree)
|
||||
commandLine(
|
||||
cmakeBinaryPath,
|
||||
"--build",
|
||||
hermesBuildDir.toString(),
|
||||
"--target",
|
||||
"libhermes",
|
||||
"-j",
|
||||
ndkBuildJobs,
|
||||
)
|
||||
standardOutputFile.set(project.file("$buildDir/build-hermes-lib.log"))
|
||||
errorOutputFile.set(project.file("$buildDir/build-hermes-lib.error.log"))
|
||||
}
|
||||
|
||||
fun windowsAwareCommandLine(vararg commands: String): List<String> {
|
||||
val result =
|
||||
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
|
||||
-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))
|
||||
}
|
||||
|
||||
-460
@@ -1,460 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture;
|
||||
import com.facebook.react.devsupport.DoubleTapReloadRecognizer;
|
||||
import com.facebook.react.devsupport.ReleaseDevSupportManager;
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager;
|
||||
import com.facebook.react.interfaces.fabric.ReactSurface;
|
||||
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags;
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A delegate for handling React Application support. This delegate is unaware whether it is used in
|
||||
* an {@link Activity} or a {@link android.app.Fragment}.
|
||||
*/
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class ReactDelegate {
|
||||
|
||||
private final Activity mActivity;
|
||||
|
||||
@Nullable private ReactRootView mReactRootView;
|
||||
|
||||
@Nullable private final String mMainComponentName;
|
||||
|
||||
@Nullable private Bundle mLaunchOptions;
|
||||
|
||||
@Nullable private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
|
||||
|
||||
@Nullable private ReactNativeHost mReactNativeHost;
|
||||
|
||||
@Nullable private ReactHost mReactHost;
|
||||
|
||||
@Nullable private ReactSurface mReactSurface;
|
||||
|
||||
private boolean mFabricEnabled = ReactNativeNewArchitectureFeatureFlags.enableFabricRenderer();
|
||||
|
||||
/**
|
||||
* Do not use this constructor as it's not accounting for New Architecture at all. You should use
|
||||
* {@link ReactDelegate#ReactDelegate(Activity, ReactNativeHost, String, Bundle, boolean)} as it's
|
||||
* the constructor used for New Architecture.
|
||||
*
|
||||
* @deprecated Use one of the other constructors instead to account for New Architecture.
|
||||
*/
|
||||
@Deprecated(since = "0.75.0")
|
||||
public ReactDelegate(
|
||||
Activity activity,
|
||||
@Nullable ReactNativeHost reactNativeHost,
|
||||
@Nullable String appKey,
|
||||
@Nullable Bundle launchOptions) {
|
||||
mActivity = activity;
|
||||
mMainComponentName = appKey;
|
||||
mLaunchOptions = launchOptions;
|
||||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
|
||||
mReactNativeHost = reactNativeHost;
|
||||
}
|
||||
|
||||
public ReactDelegate(
|
||||
Activity activity,
|
||||
@Nullable ReactHost reactHost,
|
||||
@Nullable String appKey,
|
||||
@Nullable Bundle launchOptions) {
|
||||
mActivity = activity;
|
||||
mMainComponentName = appKey;
|
||||
mLaunchOptions = launchOptions;
|
||||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
|
||||
mReactHost = reactHost;
|
||||
}
|
||||
|
||||
@Deprecated(since = "0.81.0")
|
||||
public ReactDelegate(
|
||||
Activity activity,
|
||||
@Nullable ReactNativeHost reactNativeHost,
|
||||
@Nullable String appKey,
|
||||
@Nullable Bundle launchOptions,
|
||||
boolean fabricEnabled) {
|
||||
mFabricEnabled = fabricEnabled;
|
||||
mActivity = activity;
|
||||
mMainComponentName = appKey;
|
||||
mLaunchOptions = launchOptions;
|
||||
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
|
||||
mReactNativeHost = reactNativeHost;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DevSupportManager getDevSupportManager() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null
|
||||
&& mReactHost.getDevSupportManager() != null) {
|
||||
return mReactHost.getDevSupportManager();
|
||||
} else if (getReactNativeHost() != null
|
||||
&& getReactNativeHost().hasInstance()
|
||||
&& getReactNativeHost().getReactInstanceManager() != null) {
|
||||
return getReactNativeHost().getReactInstanceManager().getDevSupportManager();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void onHostResume() {
|
||||
if (!(mActivity instanceof DefaultHardwareBackBtnHandler)) {
|
||||
throw new ClassCastException(
|
||||
"Host Activity does not implement DefaultHardwareBackBtnHandler");
|
||||
}
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost
|
||||
.getReactInstanceManager()
|
||||
.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onUserLeaveHint() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onHostLeaveHint(mActivity);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onUserLeaveHint(mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onHostPause() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onHostPause(mActivity);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onHostPause(mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onHostDestroy() {
|
||||
unloadApp();
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onHostDestroy(mActivity);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onHostDestroy(mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onBackPressed();
|
||||
return true;
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onBackPressed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onNewIntent(Intent intent) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onNewIntent(intent);
|
||||
return true;
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null && reactNativeHost.hasInstance()) {
|
||||
reactNativeHost.getReactInstanceManager().onNewIntent(intent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onActivityResult(
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
@Nullable Intent data,
|
||||
boolean shouldForwardToReactInstance) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onActivityResult(mActivity, requestCode, resultCode, data);
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null
|
||||
&& reactNativeHost.hasInstance()
|
||||
&& shouldForwardToReactInstance) {
|
||||
reactNativeHost
|
||||
.getReactInstanceManager()
|
||||
.onActivityResult(mActivity, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onWindowFocusChange(hasFocus);
|
||||
} else {
|
||||
if (getReactNativeHost() != null && getReactNativeHost().hasInstance()) {
|
||||
getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
mReactHost.onConfigurationChanged(Assertions.assertNotNull(mActivity));
|
||||
} else {
|
||||
if (getReactNativeHost() != null && getReactNativeHost().hasInstance()) {
|
||||
getReactInstanceManager()
|
||||
.onConfigurationChanged(Assertions.assertNotNull(mActivity), newConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
|
||||
&& ((ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null
|
||||
&& mReactHost.getDevSupportManager() != null)
|
||||
|| (getReactNativeHost() != null
|
||||
&& getReactNativeHost().hasInstance()
|
||||
&& getReactNativeHost().getUseDeveloperSupport()))) {
|
||||
event.startTracking();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onKeyLongPress(int keyCode) {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()
|
||||
&& mReactHost != null) {
|
||||
DevSupportManager devSupportManager = mReactHost.getDevSupportManager();
|
||||
// onKeyLongPress is a Dev API and not supported in RELEASE mode.
|
||||
if (devSupportManager != null && !(devSupportManager instanceof ReleaseDevSupportManager)) {
|
||||
devSupportManager.showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
ReactNativeHost reactNativeHost = getReactNativeHost();
|
||||
if (reactNativeHost != null
|
||||
&& reactNativeHost.hasInstance()
|
||||
&& reactNativeHost.getUseDeveloperSupport()) {
|
||||
reactNativeHost.getReactInstanceManager().showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
DevSupportManager devSupportManager = getDevSupportManager();
|
||||
if (devSupportManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload in RELEASE mode
|
||||
if (devSupportManager instanceof ReleaseDevSupportManager) {
|
||||
// Do not reload the bundle from JS as there is no bundler running in release mode.
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactHost != null) {
|
||||
mReactHost.reload("ReactDelegate.reload()");
|
||||
}
|
||||
} else {
|
||||
UiThreadUtil.runOnUiThread(
|
||||
() -> {
|
||||
if (mReactNativeHost != null
|
||||
&& mReactNativeHost.hasInstance()
|
||||
&& mReactNativeHost.getReactInstanceManager() != null) {
|
||||
mReactNativeHost.getReactInstanceManager().recreateReactContextInBackground();
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload in DEBUG mode
|
||||
devSupportManager.handleReloadJS();
|
||||
}
|
||||
|
||||
/** Start the React surface with the app key supplied in the {@link ReactDelegate} constructor. */
|
||||
public void loadApp() {
|
||||
if (mMainComponentName == null) {
|
||||
throw new IllegalStateException("Cannot loadApp without a main component name.");
|
||||
} else {
|
||||
loadApp(mMainComponentName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the React surface for the given app key.
|
||||
*
|
||||
* @param appKey The ID of the app to load into the surface.
|
||||
*/
|
||||
public void loadApp(String appKey) {
|
||||
// With Bridgeless enabled, create and start the surface
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactSurface == null && mReactHost != null) {
|
||||
mReactSurface = mReactHost.createSurface(mActivity, appKey, mLaunchOptions);
|
||||
}
|
||||
Objects.requireNonNull(mReactSurface).start();
|
||||
} else {
|
||||
if (mReactRootView != null) {
|
||||
throw new IllegalStateException("Cannot loadApp while app is already running.");
|
||||
}
|
||||
mReactRootView = createRootView();
|
||||
if (getReactNativeHost() != null) {
|
||||
mReactRootView.startReactApplication(
|
||||
getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop the React surface started with {@link ReactDelegate#loadApp()}. */
|
||||
public void unloadApp() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactSurface != null) {
|
||||
mReactSurface.stop();
|
||||
mReactSurface = null;
|
||||
}
|
||||
} else {
|
||||
if (mReactRootView != null) {
|
||||
mReactRootView.unmountReactApplication();
|
||||
mReactRootView = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setReactSurface(ReactSurface reactSurface) {
|
||||
mReactSurface = reactSurface;
|
||||
}
|
||||
|
||||
public void setReactRootView(ReactRootView reactRootView) {
|
||||
mReactRootView = reactRootView;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ReactRootView getReactRootView() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactSurface != null) {
|
||||
return (ReactRootView) mReactSurface.getView();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return mReactRootView;
|
||||
}
|
||||
}
|
||||
|
||||
// Not used in bridgeless
|
||||
protected ReactRootView createRootView() {
|
||||
ReactRootView reactRootView = new ReactRootView(mActivity);
|
||||
reactRootView.setIsFabric(isFabricEnabled());
|
||||
return reactRootView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles delegating the {@link Activity#onKeyUp(int, KeyEvent)} method to determine whether the
|
||||
* application should show the developer menu or should reload the React Application.
|
||||
*
|
||||
* @return true if we consume the event and either shoed the develop menu or reloaded the
|
||||
* application.
|
||||
*/
|
||||
public boolean shouldShowDevMenuOrReload(int keyCode, KeyEvent event) {
|
||||
DevSupportManager devSupportManager = getDevSupportManager();
|
||||
// shouldShowDevMenuOrReload is a Dev API and not supported in RELEASE mode.
|
||||
if (devSupportManager == null || devSupportManager instanceof ReleaseDevSupportManager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
devSupportManager.showDevOptionsDialog();
|
||||
return true;
|
||||
}
|
||||
boolean didDoubleTapR =
|
||||
Objects.requireNonNull(mDoubleTapReloadRecognizer)
|
||||
.didDoubleTapR(keyCode, mActivity.getCurrentFocus());
|
||||
if (didDoubleTapR) {
|
||||
devSupportManager.handleReloadJS();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Get the {@link ReactNativeHost} used by this app. */
|
||||
@DeprecatedInNewArchitecture(message = "Use getReactHost()")
|
||||
private @Nullable ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@DeprecatedInNewArchitecture(message = "Use getReactHost()")
|
||||
public ReactInstanceManager getReactInstanceManager() {
|
||||
if (getReactNativeHost() == null) {
|
||||
throw new IllegalStateException("Cannot get ReactInstanceManager without a ReactNativeHost.");
|
||||
}
|
||||
return getReactNativeHost().getReactInstanceManager();
|
||||
}
|
||||
|
||||
public @Nullable ReactHost getReactHost() {
|
||||
return mReactHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current {@link ReactContext} from ReactHost or ReactInstanceManager
|
||||
*
|
||||
* <p>Do not store a reference to this, if the React instance is reloaded or destroyed, this
|
||||
* context will no longer be valid.
|
||||
*/
|
||||
public @Nullable ReactContext getCurrentReactContext() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (mReactHost != null) {
|
||||
return mReactHost.getCurrentReactContext();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return getReactInstanceManager().getCurrentReactContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
|
||||
* also control if Concurrent Root (React 18) should be enabled or not.
|
||||
*
|
||||
* @return true if Fabric is enabled for this Activity, false otherwise.
|
||||
*/
|
||||
protected boolean isFabricEnabled() {
|
||||
return mFabricEnabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
|
||||
import com.facebook.react.common.annotations.DeprecatedInNewArchitecture
|
||||
import com.facebook.react.devsupport.DoubleTapReloadRecognizer
|
||||
import com.facebook.react.devsupport.ReleaseDevSupportManager
|
||||
import com.facebook.react.devsupport.interfaces.DevSupportManager
|
||||
import com.facebook.react.interfaces.fabric.ReactSurface
|
||||
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags
|
||||
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
|
||||
|
||||
/**
|
||||
* A delegate for handling React Application support. This delegate is unaware whether it is used in
|
||||
* an [Activity] or a [android.app.Fragment].
|
||||
*/
|
||||
public open class ReactDelegate {
|
||||
private val activity: Activity
|
||||
private var internalReactRootView: ReactRootView? = null
|
||||
private val mainComponentName: String?
|
||||
private var launchOptions: Bundle?
|
||||
private var doubleTapReloadRecognizer: DoubleTapReloadRecognizer?
|
||||
private var reactNativeHost: ReactNativeHost? = null
|
||||
public var reactHost: ReactHost? = null
|
||||
private set
|
||||
|
||||
private var reactSurface: ReactSurface? = null
|
||||
|
||||
/**
|
||||
* Override this method if you wish to selectively toggle Fabric for a specific surface. This will
|
||||
* also control if Concurrent Root (React 18) should be enabled or not.
|
||||
*
|
||||
* @return true if Fabric is enabled for this Activity, false otherwise.
|
||||
*/
|
||||
protected var isFabricEnabled: Boolean =
|
||||
ReactNativeNewArchitectureFeatureFlags.enableFabricRenderer()
|
||||
private set
|
||||
|
||||
/**
|
||||
* Do not use this constructor as it's not accounting for New Architecture at all. You should use
|
||||
* [ReactDelegate(Activity, ReactNativeHost, String, Bundle, boolean)] as it's the constructor
|
||||
* used for New Architecture.
|
||||
*/
|
||||
@Deprecated(
|
||||
"Use one of the other constructors instead to account for New Architecture. Deprecated since 0.75.0")
|
||||
public constructor(
|
||||
activity: Activity,
|
||||
reactNativeHost: ReactNativeHost?,
|
||||
appKey: String?,
|
||||
launchOptions: Bundle?
|
||||
) {
|
||||
this.activity = activity
|
||||
mainComponentName = appKey
|
||||
this.launchOptions = launchOptions
|
||||
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
|
||||
this.reactNativeHost = reactNativeHost
|
||||
}
|
||||
|
||||
public constructor(
|
||||
activity: Activity,
|
||||
reactHost: ReactHost?,
|
||||
appKey: String?,
|
||||
launchOptions: Bundle?
|
||||
) {
|
||||
this.activity = activity
|
||||
mainComponentName = appKey
|
||||
this.launchOptions = launchOptions
|
||||
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
|
||||
this.reactHost = reactHost
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated since 0.81.0, use one of the other constructors instead.")
|
||||
public constructor(
|
||||
activity: Activity,
|
||||
reactNativeHost: ReactNativeHost?,
|
||||
appKey: String?,
|
||||
launchOptions: Bundle?,
|
||||
fabricEnabled: Boolean
|
||||
) {
|
||||
isFabricEnabled = fabricEnabled
|
||||
this.activity = activity
|
||||
mainComponentName = appKey
|
||||
this.launchOptions = launchOptions
|
||||
doubleTapReloadRecognizer = DoubleTapReloadRecognizer()
|
||||
this.reactNativeHost = reactNativeHost
|
||||
}
|
||||
|
||||
private val devSupportManager: DevSupportManager?
|
||||
get() =
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost?.devSupportManager != null) {
|
||||
reactHost?.devSupportManager
|
||||
} else if (reactNativeHost?.hasInstance() == true &&
|
||||
reactNativeHost?.reactInstanceManager != null) {
|
||||
reactNativeHost?.reactInstanceManager?.devSupportManager
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
public fun onHostResume() {
|
||||
if (activity !is DefaultHardwareBackBtnHandler) {
|
||||
throw ClassCastException("Host Activity does not implement DefaultHardwareBackBtnHandler")
|
||||
}
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onHostResume(activity, activity as DefaultHardwareBackBtnHandler)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost
|
||||
?.reactInstanceManager
|
||||
?.onHostResume(activity, activity as DefaultHardwareBackBtnHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onUserLeaveHint() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onHostLeaveHint(activity)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onUserLeaveHint(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onHostPause() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onHostPause(activity)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onHostPause(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onHostDestroy() {
|
||||
unloadApp()
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onHostDestroy(activity)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onHostDestroy(activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onBackPressed(): Boolean {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onBackPressed()
|
||||
return true
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onBackPressed()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public fun onNewIntent(intent: Intent): Boolean {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onNewIntent(intent)
|
||||
return true
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onNewIntent(intent)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public fun onActivityResult(
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
data: Intent?,
|
||||
shouldForwardToReactInstance: Boolean
|
||||
) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null &&
|
||||
shouldForwardToReactInstance) {
|
||||
reactHost?.onActivityResult(activity, requestCode, resultCode, data)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true && shouldForwardToReactInstance) {
|
||||
reactNativeHost
|
||||
?.reactInstanceManager
|
||||
?.onActivityResult(activity, requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onWindowFocusChange(hasFocus)
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
reactNativeHost?.reactInstanceManager?.onWindowFocusChange(hasFocus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
reactHost?.onConfigurationChanged(checkNotNull(activity))
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true) {
|
||||
getReactInstanceManager().onConfigurationChanged(checkNotNull(activity), newConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD &&
|
||||
((ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost?.devSupportManager != null) ||
|
||||
(reactNativeHost?.hasInstance() == true &&
|
||||
reactNativeHost?.useDeveloperSupport == true))) {
|
||||
event.startTracking()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public fun onKeyLongPress(keyCode: Int): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
|
||||
reactHost != null) {
|
||||
val devSupportManager = reactHost?.devSupportManager
|
||||
// onKeyLongPress is a Dev API and not supported in RELEASE mode.
|
||||
if (devSupportManager != null && devSupportManager !is ReleaseDevSupportManager) {
|
||||
devSupportManager.showDevOptionsDialog()
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (reactNativeHost?.hasInstance() == true &&
|
||||
reactNativeHost?.useDeveloperSupport == true) {
|
||||
reactNativeHost?.reactInstanceManager?.showDevOptionsDialog()
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public fun reload() {
|
||||
val devSupportManager = devSupportManager ?: return
|
||||
|
||||
// Reload in RELEASE mode
|
||||
if (devSupportManager is ReleaseDevSupportManager) {
|
||||
// Do not reload the bundle from JS as there is no bundler running in release mode.
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
reactHost?.reload("ReactDelegate.reload()")
|
||||
} else {
|
||||
runOnUiThread {
|
||||
if (reactNativeHost?.hasInstance() == true &&
|
||||
reactNativeHost?.reactInstanceManager != null) {
|
||||
reactNativeHost?.reactInstanceManager?.recreateReactContextInBackground()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Reload in DEBUG mode
|
||||
devSupportManager.handleReloadJS()
|
||||
}
|
||||
|
||||
/** Start the React surface with the app key supplied in the [ReactDelegate] constructor. */
|
||||
public fun loadApp() {
|
||||
val name = requireNotNull(mainComponentName) { "Cannot loadApp without a main component name." }
|
||||
loadApp(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the React surface for the given app key.
|
||||
*
|
||||
* @param appKey The ID of the app to load into the surface.
|
||||
*/
|
||||
public fun loadApp(appKey: String) {
|
||||
// With Bridgeless enabled, create and start the surface
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
val reactHost = reactHost
|
||||
if (reactSurface == null && reactHost != null) {
|
||||
reactSurface = reactHost.createSurface(activity, appKey, launchOptions)
|
||||
}
|
||||
reactSurface?.start()
|
||||
} else {
|
||||
check(internalReactRootView == null) { "Cannot loadApp while app is already running." }
|
||||
internalReactRootView = createRootView()
|
||||
if (reactNativeHost != null) {
|
||||
internalReactRootView?.startReactApplication(
|
||||
reactNativeHost?.reactInstanceManager, appKey, launchOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop the React surface started with [ReactDelegate.loadApp]. */
|
||||
public fun unloadApp() {
|
||||
if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
reactSurface?.stop()
|
||||
reactSurface = null
|
||||
} else {
|
||||
if (internalReactRootView != null) {
|
||||
internalReactRootView?.unmountReactApplication()
|
||||
internalReactRootView = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun setReactSurface(reactSurface: ReactSurface?) {
|
||||
this.reactSurface = reactSurface
|
||||
}
|
||||
|
||||
public var reactRootView: ReactRootView?
|
||||
get() {
|
||||
return if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (reactSurface != null) {
|
||||
reactSurface?.view as ReactRootView?
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
internalReactRootView
|
||||
}
|
||||
}
|
||||
set(reactRootView) {
|
||||
internalReactRootView = reactRootView
|
||||
}
|
||||
|
||||
// Not used in bridgeless
|
||||
protected open fun createRootView(): ReactRootView? {
|
||||
val reactRootView = ReactRootView(activity)
|
||||
reactRootView.setIsFabric(isFabricEnabled)
|
||||
return reactRootView
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles delegating the [Activity.onKeyUp] method to determine whether the application should
|
||||
* show the developer menu or should reload the React Application.
|
||||
*
|
||||
* @return true if we consume the event and either show the develop menu or reloaded the
|
||||
* application.
|
||||
*/
|
||||
public fun shouldShowDevMenuOrReload(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
val devSupportManager = devSupportManager
|
||||
// shouldShowDevMenuOrReload is a Dev API and not supported in RELEASE mode.
|
||||
if (devSupportManager == null || devSupportManager is ReleaseDevSupportManager) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_MENU) {
|
||||
devSupportManager.showDevOptionsDialog()
|
||||
return true
|
||||
}
|
||||
val didDoubleTapR = doubleTapReloadRecognizer?.didDoubleTapR(keyCode, activity.currentFocus)
|
||||
if (didDoubleTapR == true) {
|
||||
devSupportManager.handleReloadJS()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@DeprecatedInNewArchitecture(message = "Use reactHost")
|
||||
public fun getReactInstanceManager(): ReactInstanceManager {
|
||||
val nonNullReactNativeHost =
|
||||
checkNotNull(reactNativeHost) {
|
||||
"Cannot get ReactInstanceManager without a ReactNativeHost."
|
||||
}
|
||||
return nonNullReactNativeHost.reactInstanceManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current [ReactContext] from [ReactHost] or [ReactInstanceManager]
|
||||
*
|
||||
* Do not store a reference to this, if the React instance is reloaded or destroyed, this context
|
||||
* will no longer be valid.
|
||||
*/
|
||||
public val currentReactContext: ReactContext?
|
||||
get() {
|
||||
return if (ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture()) {
|
||||
if (reactHost != null) {
|
||||
reactHost?.currentReactContext
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
getReactInstanceManager().currentReactContext
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -74,7 +74,6 @@ import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.common.SurfaceDelegateFactory;
|
||||
import com.facebook.react.common.annotations.VisibleForTesting;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
|
||||
@@ -1079,6 +1078,10 @@ public class ReactInstanceManager {
|
||||
if (reactPackage instanceof ViewManagerOnDemandReactPackage) {
|
||||
Collection<String> names =
|
||||
((ViewManagerOnDemandReactPackage) reactPackage).getViewManagerNames(context);
|
||||
// When converting this class to Kotlin, you need to retain this null check
|
||||
// or wrap around a try/catch otherwise this will cause a crash for OSS libraries
|
||||
// that are not migrated to Kotlin yet and are returning null for
|
||||
// `getViewManagerNames`
|
||||
if (names != null) {
|
||||
uniqueNames.addAll(names);
|
||||
}
|
||||
@@ -1115,7 +1118,6 @@ public class ReactInstanceManager {
|
||||
/**
|
||||
* @return current ReactApplicationContext
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public @Nullable ReactContext getCurrentReactContext() {
|
||||
synchronized (mReactContextLock) {
|
||||
return mCurrentReactContext;
|
||||
|
||||
+1
-1
@@ -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),
|
||||
|
||||
+2
-3
@@ -9,7 +9,6 @@ package com.facebook.react.bridge
|
||||
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
import com.facebook.react.bridge.queue.ReactQueueConfiguration
|
||||
import com.facebook.react.common.annotations.VisibleForTesting
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry
|
||||
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder
|
||||
@@ -53,7 +52,7 @@ public interface CatalystInstance : MemoryPressureListener, JSInstance, JSBundle
|
||||
public val isDestroyed: Boolean
|
||||
|
||||
/** Initialize all the native modules */
|
||||
@VisibleForTesting public fun initialize()
|
||||
public fun initialize()
|
||||
|
||||
public val reactQueueConfiguration: ReactQueueConfiguration
|
||||
|
||||
@@ -90,7 +89,7 @@ public interface CatalystInstance : MemoryPressureListener, JSInstance, JSBundle
|
||||
/** This method registers the file path of an additional JS segment by its ID. */
|
||||
public fun registerSegment(segmentId: Int, path: String)
|
||||
|
||||
@VisibleForTesting public fun setGlobalVariable(propName: String, jsonValue: String)
|
||||
public fun setGlobalVariable(propName: String, jsonValue: String)
|
||||
|
||||
/**
|
||||
* Do not use this anymore. Use [runtimeExecutor] instead. Get the C pointer (as a long) to the
|
||||
|
||||
-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
-2
@@ -36,9 +36,9 @@ private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
public fun create(
|
||||
fun create(
|
||||
spec: ReactQueueConfigurationSpec,
|
||||
exceptionHandler: QueueThreadExceptionHandler
|
||||
): ReactQueueConfigurationImpl {
|
||||
|
||||
+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
|
||||
|
||||
|
||||
+2
-2
@@ -1317,7 +1317,7 @@ public class FabricUIManager
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
@Override
|
||||
public void willMountItems(@Nullable List<MountItem> mountItems) {
|
||||
public void willMountItems(@Nullable List<? extends MountItem> mountItems) {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.willMountItems(FabricUIManager.this);
|
||||
}
|
||||
@@ -1326,7 +1326,7 @@ public class FabricUIManager
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
@Override
|
||||
public void didMountItems(@Nullable List<MountItem> mountItems) {
|
||||
public void didMountItems(@Nullable List<? extends MountItem> mountItems) {
|
||||
for (UIManagerListener listener : mListeners) {
|
||||
listener.didMountItems(FabricUIManager.this);
|
||||
}
|
||||
|
||||
-410
@@ -1,410 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.mounting;
|
||||
|
||||
import static com.facebook.infer.annotation.ThreadConfined.UI;
|
||||
import static com.facebook.react.fabric.FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT;
|
||||
|
||||
import android.os.SystemClock;
|
||||
import android.view.View;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.infer.annotation.ThreadConfined;
|
||||
import com.facebook.react.bridge.ReactIgnorableMountingException;
|
||||
import com.facebook.react.bridge.ReactNoCrashSoftException;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException;
|
||||
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem;
|
||||
import com.facebook.react.fabric.mounting.mountitems.MountItem;
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
public class MountItemDispatcher {
|
||||
|
||||
private static final String TAG = "MountItemDispatcher";
|
||||
|
||||
private static final long FRAME_TIME_NS = 1_000_000_000 / 60;
|
||||
|
||||
private final MountingManager mMountingManager;
|
||||
private final ItemDispatchListener mItemDispatchListener;
|
||||
|
||||
private final ConcurrentLinkedQueue<DispatchCommandMountItem> mViewCommandMountItems =
|
||||
new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ConcurrentLinkedQueue<MountItem> mMountItems = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private final ConcurrentLinkedQueue<MountItem> mPreMountItems = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private boolean mInDispatch = false;
|
||||
private long mBatchedExecutionTime = 0L;
|
||||
private long mRunStartTime = 0L;
|
||||
|
||||
private long mLastFrameTimeNanos = 0L;
|
||||
|
||||
public MountItemDispatcher(MountingManager mountingManager, ItemDispatchListener listener) {
|
||||
mMountingManager = mountingManager;
|
||||
mItemDispatchListener = listener;
|
||||
}
|
||||
|
||||
public void addViewCommandMountItem(DispatchCommandMountItem mountItem) {
|
||||
mViewCommandMountItems.add(mountItem);
|
||||
}
|
||||
|
||||
public void addMountItem(MountItem mountItem) {
|
||||
mMountItems.add(mountItem);
|
||||
}
|
||||
|
||||
public void addPreAllocateMountItem(MountItem mountItem) {
|
||||
// We do this check only for PreAllocateViewMountItem - and not DispatchMountItem or regular
|
||||
// MountItem - because PreAllocateViewMountItems are not batched, and is relatively more
|
||||
// expensive
|
||||
// both to queue, to drain, and to execute.
|
||||
if (!mMountingManager.surfaceIsStopped(mountItem.getSurfaceId())) {
|
||||
mPreMountItems.add(mountItem);
|
||||
} else if (IS_DEVELOPMENT_ENVIRONMENT) {
|
||||
FLog.e(
|
||||
TAG,
|
||||
"Not queueing PreAllocateMountItem: surfaceId stopped: [%d] - %s",
|
||||
mountItem.getSurfaceId(),
|
||||
mountItem.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to dispatch MountItems. In case of the exception, we will retry 10 times before giving up.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
public void tryDispatchMountItems() {
|
||||
// If we're already dispatching, don't reenter.
|
||||
// Reentrance can potentially happen a lot on Android in Fabric because
|
||||
// `updateState` from the
|
||||
// mounting layer causes mount items to be dispatched synchronously. We want to 1) make sure
|
||||
// we don't reenter in those cases, but 2) still execute those queued instructions
|
||||
// synchronously.
|
||||
// This is a pretty blunt tool, but we might not have better options since we really don't want
|
||||
// to execute anything out-of-order.
|
||||
if (mInDispatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
mInDispatch = true;
|
||||
|
||||
try {
|
||||
dispatchMountItems();
|
||||
} finally {
|
||||
// Clean up after running dispatchMountItems - even if an exception was thrown
|
||||
mInDispatch = false;
|
||||
}
|
||||
|
||||
// We call didDispatchMountItems regardless of whether we actually dispatched anything, since
|
||||
// NativeAnimatedModule relies on this for executing any animations that may have been
|
||||
// scheduled
|
||||
mItemDispatchListener.didDispatchMountItems();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
public void dispatchMountItems(Queue<MountItem> mountItems) {
|
||||
while (!mountItems.isEmpty()) {
|
||||
MountItem item = mountItems.poll();
|
||||
Assertions.assertNotNull(item);
|
||||
try {
|
||||
item.execute(mMountingManager);
|
||||
} catch (RetryableMountingLayerException e) {
|
||||
if (item instanceof DispatchCommandMountItem) {
|
||||
// Only DispatchCommandMountItem supports retries
|
||||
DispatchCommandMountItem mountItem = (DispatchCommandMountItem) item;
|
||||
// Retrying exactly once
|
||||
if (mountItem.getRetries() == 0) {
|
||||
mountItem.incrementRetries();
|
||||
// In case we haven't retried executing this item yet, execute in the next batch of
|
||||
// items
|
||||
addViewCommandMountItem(mountItem);
|
||||
}
|
||||
} else {
|
||||
printMountItem(
|
||||
item, "dispatchExternalMountItems: mounting failed with " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes view commands, pre mount items and mount items in the respective order:
|
||||
* 1. View commands.
|
||||
* 2. Pre mount items.
|
||||
* 3. Regular mount items.
|
||||
*
|
||||
* Does nothing if `viewCommandMountItemsToDispatch` and `mountItemsToDispatch` are empty.
|
||||
* Nothing should call this directly except for `tryDispatchMountItems`.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private void dispatchMountItems() {
|
||||
mBatchedExecutionTime = 0;
|
||||
|
||||
mRunStartTime = SystemClock.uptimeMillis();
|
||||
|
||||
List<DispatchCommandMountItem> viewCommandMountItemsToDispatch =
|
||||
getAndResetViewCommandMountItems();
|
||||
List<MountItem> mountItemsToDispatch = getAndResetMountItems();
|
||||
|
||||
if (mountItemsToDispatch == null && viewCommandMountItemsToDispatch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mItemDispatchListener.willMountItems(mountItemsToDispatch);
|
||||
|
||||
// As an optimization, execute all ViewCommands first
|
||||
// This should be:
|
||||
// 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always
|
||||
// wanted to be as "synchronous" as possible.
|
||||
// 2) Safer: ViewCommands are inherently disconnected from the tree commit/diff/mount process.
|
||||
// JS imperatively queues these commands.
|
||||
// If JS has queued a command, it's reasonable to assume that the more time passes, the more
|
||||
// likely it is that the view disappears.
|
||||
// Thus, by executing ViewCommands early, we should actually avoid a category of
|
||||
// errors/glitches.
|
||||
if (viewCommandMountItemsToDispatch != null) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews viewCommandMountItems");
|
||||
for (DispatchCommandMountItem command : viewCommandMountItemsToDispatch) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem");
|
||||
}
|
||||
try {
|
||||
executeOrEnqueue(command);
|
||||
} catch (RetryableMountingLayerException e) {
|
||||
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
|
||||
// the current batch of mount items has finished executing.
|
||||
if (command.getRetries() == 0) {
|
||||
command.incrementRetries();
|
||||
addViewCommandMountItem(command);
|
||||
} else {
|
||||
// It's very common for commands to be executed on views that no longer exist - for
|
||||
// example, a blur event on TextInput being fired because of a navigation event away
|
||||
// from the current screen. If the exception is marked as Retryable, we log a soft
|
||||
// exception but never crash in debug.
|
||||
// It's not clear that logging this is even useful, because these events are very
|
||||
// common, mundane, and there's not much we can do about them currently.
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
new ReactNoCrashSoftException(
|
||||
"Caught exception executing ViewCommand: " + command.toString(), e));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
// Non-Retryable exceptions are logged as soft exceptions in prod, but crash in Debug.
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
new RuntimeException(
|
||||
"Caught exception executing ViewCommand: " + command.toString(), e));
|
||||
}
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
|
||||
// If there are MountItems to dispatch, we make sure all the "pre mount items" are executed
|
||||
// first
|
||||
List<MountItem> preMountItemsToDispatch = getAndResetPreMountItems();
|
||||
if (preMountItemsToDispatch != null) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews preMountItems");
|
||||
for (MountItem preMountItem : preMountItemsToDispatch) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem");
|
||||
}
|
||||
executeOrEnqueue(preMountItem);
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
|
||||
if (mountItemsToDispatch != null) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews mountItems to execute");
|
||||
|
||||
long batchedExecutionStartTime = SystemClock.uptimeMillis();
|
||||
|
||||
for (MountItem mountItem : mountItemsToDispatch) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(mountItem, "dispatchMountItems: Executing mountItem");
|
||||
}
|
||||
|
||||
try {
|
||||
executeOrEnqueue(mountItem);
|
||||
} catch (Throwable e) {
|
||||
// If there's an exception, we want to log diagnostics in prod and rethrow.
|
||||
FLog.e(TAG, "dispatchMountItems: caught exception, displaying mount state", e);
|
||||
for (MountItem m : mountItemsToDispatch) {
|
||||
if (m == mountItem) {
|
||||
// We want to mark the mount item that caused exception
|
||||
FLog.e(TAG, "dispatchMountItems: mountItem: next mountItem triggered exception!");
|
||||
}
|
||||
printMountItem(m, "dispatchMountItems: mountItem");
|
||||
}
|
||||
if (mountItem.getSurfaceId() != View.NO_ID) {
|
||||
SurfaceMountingManager surfaceManager =
|
||||
mMountingManager.getSurfaceManager(mountItem.getSurfaceId());
|
||||
if (surfaceManager != null) {
|
||||
surfaceManager.printSurfaceState();
|
||||
}
|
||||
}
|
||||
|
||||
if (ReactIgnorableMountingException.isIgnorable(e)) {
|
||||
ReactSoftExceptionLogger.logSoftException(TAG, e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
mBatchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime;
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
|
||||
mItemDispatchListener.didMountItems(mountItemsToDispatch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes pre mount items. Pre mount items are operations that can be executed before the mount items come. For example view preallocation.
|
||||
* This is a performance optimisation to do as much work ahead of time as possible.
|
||||
*
|
||||
* `tryDispatchMountItems` will also execute pre mount items, but only if there are mount items to be executed.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
public void dispatchPreMountItems(long frameTimeNanos) {
|
||||
mLastFrameTimeNanos = frameTimeNanos;
|
||||
|
||||
if (mPreMountItems.isEmpty()) {
|
||||
// Avoid starting systrace if there are no pre mount items.
|
||||
return;
|
||||
}
|
||||
|
||||
long deadline = mLastFrameTimeNanos + FRAME_TIME_NS / 2;
|
||||
dispatchPreMountItemsImpl(deadline);
|
||||
}
|
||||
|
||||
private void dispatchPreMountItemsImpl(long deadline) {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews");
|
||||
|
||||
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems from
|
||||
// reentering during dispatchPreMountItems
|
||||
mInDispatch = true;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (System.nanoTime() > deadline) {
|
||||
break;
|
||||
}
|
||||
|
||||
MountItem preMountItemToDispatch = mPreMountItems.poll();
|
||||
// If list is empty, `poll` will return null, or var will never be set
|
||||
if (preMountItemToDispatch == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(preMountItemToDispatch, "dispatchPreMountItems");
|
||||
}
|
||||
executeOrEnqueue(preMountItemToDispatch);
|
||||
}
|
||||
} finally {
|
||||
mInDispatch = false;
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
}
|
||||
|
||||
private void executeOrEnqueue(MountItem item) {
|
||||
if (mMountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
FLog.e(
|
||||
TAG,
|
||||
"executeOrEnqueue: Item execution delayed, surface %s is not ready yet",
|
||||
item.getSurfaceId());
|
||||
}
|
||||
SurfaceMountingManager surfaceMountingManager =
|
||||
mMountingManager.getSurfaceManagerEnforced(
|
||||
item.getSurfaceId(), "MountItemDispatcher::executeOrEnqueue");
|
||||
surfaceMountingManager.scheduleMountItemOnViewAttach(item);
|
||||
} else {
|
||||
item.execute(mMountingManager);
|
||||
}
|
||||
}
|
||||
|
||||
private static <E> @Nullable List<E> drainConcurrentItemQueue(ConcurrentLinkedQueue<E> queue) {
|
||||
if (queue.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<E> result = new ArrayList<>();
|
||||
do {
|
||||
E item = queue.poll();
|
||||
if (item != null) {
|
||||
result.add(item);
|
||||
}
|
||||
} while (!queue.isEmpty());
|
||||
if (result.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private @Nullable List<DispatchCommandMountItem> getAndResetViewCommandMountItems() {
|
||||
return drainConcurrentItemQueue(mViewCommandMountItems);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private @Nullable List<MountItem> getAndResetMountItems() {
|
||||
return drainConcurrentItemQueue(mMountItems);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private @Nullable List<MountItem> getAndResetPreMountItems() {
|
||||
return drainConcurrentItemQueue(mPreMountItems);
|
||||
}
|
||||
|
||||
public long getBatchedExecutionTime() {
|
||||
return mBatchedExecutionTime;
|
||||
}
|
||||
|
||||
public long getRunStartTime() {
|
||||
return mRunStartTime;
|
||||
}
|
||||
|
||||
private static void printMountItem(MountItem mountItem, String prefix) {
|
||||
// If a MountItem description is split across multiple lines, it's because it's a
|
||||
// compound MountItem. Log each line separately.
|
||||
String[] mountItemLines = mountItem.toString().split("\n");
|
||||
for (String m : mountItemLines) {
|
||||
FLog.e(TAG, prefix + ": " + m);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ItemDispatchListener {
|
||||
void willMountItems(@Nullable List<MountItem> mountItems);
|
||||
|
||||
void didMountItems(@Nullable List<MountItem> mountItems);
|
||||
|
||||
void didDispatchMountItems();
|
||||
}
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.fabric.mounting
|
||||
|
||||
import android.os.SystemClock
|
||||
import android.view.View
|
||||
import androidx.annotation.UiThread
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.infer.annotation.ThreadConfined
|
||||
import com.facebook.infer.annotation.ThreadConfined.UI
|
||||
import com.facebook.react.bridge.ReactIgnorableMountingException
|
||||
import com.facebook.react.bridge.ReactNoCrashSoftException
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger
|
||||
import com.facebook.react.bridge.RetryableMountingLayerException
|
||||
import com.facebook.react.fabric.FabricUIManager
|
||||
import com.facebook.react.fabric.mounting.mountitems.DispatchCommandMountItem
|
||||
import com.facebook.react.fabric.mounting.mountitems.MountItem
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
||||
import com.facebook.systrace.Systrace
|
||||
import java.util.Queue
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
internal class MountItemDispatcher(
|
||||
private val mountingManager: MountingManager,
|
||||
private val itemDispatchListener: ItemDispatchListener
|
||||
) {
|
||||
private val viewCommandMountItems: Queue<DispatchCommandMountItem> = ConcurrentLinkedQueue()
|
||||
private val mountItems: Queue<MountItem> = ConcurrentLinkedQueue()
|
||||
private val preMountItems: Queue<MountItem> = ConcurrentLinkedQueue()
|
||||
|
||||
private var inDispatch: Boolean = false
|
||||
var batchedExecutionTime: Long = 0L
|
||||
private set
|
||||
|
||||
var runStartTime: Long = 0L
|
||||
private set
|
||||
|
||||
private var lastFrameTimeNanos: Long = 0L
|
||||
|
||||
fun addViewCommandMountItem(mountItem: DispatchCommandMountItem) {
|
||||
viewCommandMountItems.add(mountItem)
|
||||
}
|
||||
|
||||
fun addMountItem(mountItem: MountItem) {
|
||||
mountItems.add(mountItem)
|
||||
}
|
||||
|
||||
fun addPreAllocateMountItem(mountItem: MountItem) {
|
||||
// We do this check only for PreAllocateViewMountItem - and not DispatchMountItem or regular
|
||||
// MountItem - because PreAllocateViewMountItems are not batched, and is relatively more
|
||||
// expensive
|
||||
// both to queue, to drain, and to execute.
|
||||
if (!mountingManager.surfaceIsStopped(mountItem.getSurfaceId())) {
|
||||
preMountItems.add(mountItem)
|
||||
} else if (FabricUIManager.IS_DEVELOPMENT_ENVIRONMENT) {
|
||||
FLog.e(
|
||||
TAG,
|
||||
"Not queueing PreAllocateMountItem: surfaceId stopped: [%d] - %s",
|
||||
mountItem.getSurfaceId(),
|
||||
mountItem.toString())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to dispatch MountItems. In case of the exception, we will retry 10 times before giving up.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
fun tryDispatchMountItems() {
|
||||
// If we're already dispatching, don't reenter.
|
||||
// Reentrance can potentially happen a lot on Android in Fabric because `updateState` from the
|
||||
// mounting layer causes mount items to be dispatched synchronously. We want to 1) make sure we
|
||||
// don't reenter in those cases, but 2) still execute those queued instructions synchronously.
|
||||
// This is a pretty blunt tool, but we might not have better options since we really don't want
|
||||
// to execute anything out-of-order.
|
||||
if (inDispatch) {
|
||||
return
|
||||
}
|
||||
|
||||
inDispatch = true
|
||||
|
||||
try {
|
||||
dispatchMountItems()
|
||||
} finally {
|
||||
// Clean up after running dispatchMountItems - even if an exception was thrown
|
||||
inDispatch = false
|
||||
}
|
||||
|
||||
// We call didDispatchMountItems regardless of whether we actually dispatched anything, since
|
||||
// NativeAnimatedModule relies on this for executing any animations that may have been
|
||||
// scheduled
|
||||
itemDispatchListener.didDispatchMountItems()
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
fun dispatchMountItems(mountItems: Queue<MountItem?>) {
|
||||
while (!mountItems.isEmpty()) {
|
||||
val item = requireNotNull(mountItems.poll()) { "MountItem should not be null" }
|
||||
try {
|
||||
item.execute(mountingManager)
|
||||
} catch (e: RetryableMountingLayerException) {
|
||||
if (item is DispatchCommandMountItem) {
|
||||
// Only DispatchCommandMountItem supports retries
|
||||
val mountItem: DispatchCommandMountItem = item as DispatchCommandMountItem
|
||||
// Retrying exactly once
|
||||
if (mountItem.getRetries() == 0) {
|
||||
mountItem.incrementRetries()
|
||||
// In case we haven't retried executing this item yet, execute in the next batch of
|
||||
// items
|
||||
addViewCommandMountItem(mountItem)
|
||||
}
|
||||
} else {
|
||||
printMountItem(item, "dispatchExternalMountItems: mounting failed with ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes view commands, pre mount items and mount items in the respective order:
|
||||
* 1. View commands.
|
||||
* 2. Pre mount items.
|
||||
* 3. Regular mount items.
|
||||
*
|
||||
* Does nothing if `viewCommandMountItemsToDispatch` and `mountItemsToDispatch` are empty.
|
||||
* Nothing should call this directly except for `tryDispatchMountItems`.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
private fun dispatchMountItems() {
|
||||
batchedExecutionTime = 0
|
||||
runStartTime = SystemClock.uptimeMillis()
|
||||
|
||||
val viewCommandMountItemsToDispatch = getAndResetViewCommandMountItems()
|
||||
val mountItemsToDispatch = getAndResetMountItems()
|
||||
|
||||
if (mountItemsToDispatch == null && viewCommandMountItemsToDispatch == null) {
|
||||
return
|
||||
}
|
||||
|
||||
itemDispatchListener.willMountItems(mountItemsToDispatch)
|
||||
|
||||
// As an optimization, execute all ViewCommands first
|
||||
// This should be:
|
||||
// 1) Performant: ViewCommands are often a replacement for SetNativeProps, which we've always
|
||||
// wanted to be as "synchronous" as possible.
|
||||
// 2) Safer: ViewCommands are inherently disconnected from the tree commit/diff/mount process.
|
||||
// JS imperatively queues these commands.
|
||||
// If JS has queued a command, it's reasonable to assume that the more time passes, the more
|
||||
// likely it is that the view disappears.
|
||||
// Thus, by executing ViewCommands early, we should actually avoid a category of
|
||||
// errors/glitches.
|
||||
viewCommandMountItemsToDispatch?.let { commands ->
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews viewCommandMountItems")
|
||||
|
||||
for (command in commands) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(command, "dispatchMountItems: Executing viewCommandMountItem")
|
||||
}
|
||||
try {
|
||||
executeOrEnqueue(command)
|
||||
} catch (e: RetryableMountingLayerException) {
|
||||
// If the exception is marked as Retryable, we retry the viewcommand exactly once, after
|
||||
// the current batch of mount items has finished executing.
|
||||
if (command.getRetries() == 0) {
|
||||
command.incrementRetries()
|
||||
addViewCommandMountItem(command)
|
||||
} else {
|
||||
// It's very common for commands to be executed on views that no longer exist - for
|
||||
// example, a blur event on TextInput being fired because of a navigation event away
|
||||
// from the current screen. If the exception is marked as Retryable, we log a soft
|
||||
// exception but never crash in debug.
|
||||
// It's not clear that logging this is even useful, because these events are very
|
||||
// common, mundane, and there's not much we can do about them currently.
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
ReactNoCrashSoftException("Caught exception executing ViewCommand: $command", e))
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// Non-retryable exceptions are logged as soft exceptions in prod, but crash in Debug.
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG, RuntimeException("Caught exception executing ViewCommand: $command", e))
|
||||
}
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
// If there are MountItems to dispatch, we make sure all the "pre mount items" are executed
|
||||
// first
|
||||
getAndResetPreMountItems()?.let { preMountItems ->
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews preMountItems")
|
||||
for (preMountItem in preMountItems) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(preMountItem, "dispatchMountItems: Executing preMountItem")
|
||||
}
|
||||
executeOrEnqueue(preMountItem)
|
||||
}
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
mountItemsToDispatch?.let { items ->
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT, "MountItemDispatcher::mountViews mountItems to execute")
|
||||
val batchedExecutionStartTime = SystemClock.uptimeMillis()
|
||||
|
||||
for (mountItem in items) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(mountItem, "dispatchMountItems: Executing mountItem")
|
||||
}
|
||||
|
||||
try {
|
||||
executeOrEnqueue(mountItem)
|
||||
} catch (e: Throwable) {
|
||||
// If there's an exception, we want to log diagnostics in prod and rethrow.
|
||||
FLog.e(TAG, "dispatchMountItems: caught exception, displaying mount state", e)
|
||||
for (m in items) {
|
||||
if (m === mountItem) {
|
||||
// We want to mark the mount item that caused exception
|
||||
FLog.e(TAG, "dispatchMountItems: mountItem: next mountItem triggered exception!")
|
||||
}
|
||||
printMountItem(m, "dispatchMountItems: mountItem")
|
||||
}
|
||||
|
||||
if (mountItem.getSurfaceId() != View.NO_ID) {
|
||||
mountingManager.getSurfaceManager(mountItem.getSurfaceId())?.printSurfaceState()
|
||||
}
|
||||
|
||||
if (ReactIgnorableMountingException.isIgnorable(e)) {
|
||||
ReactSoftExceptionLogger.logSoftException(TAG, e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
batchedExecutionTime += SystemClock.uptimeMillis() - batchedExecutionStartTime
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
itemDispatchListener.didMountItems(mountItemsToDispatch)
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes pre-mount items. Pre-mount items are operations that can be executed before the mount
|
||||
* items come, for example view pre-allocation. This is a performance optimisation to do as much
|
||||
* work ahead of time as possible.
|
||||
*
|
||||
* `tryDispatchMountItems` will also execute pre mount items, but only if there are mount items to be executed.
|
||||
*/
|
||||
@UiThread
|
||||
@ThreadConfined(UI)
|
||||
fun dispatchPreMountItems(frameTimeNanos: Long) {
|
||||
lastFrameTimeNanos = frameTimeNanos
|
||||
|
||||
if (preMountItems.isEmpty()) {
|
||||
// Avoid starting systrace if there are no pre mount items.
|
||||
return
|
||||
}
|
||||
|
||||
val deadline = lastFrameTimeNanos + FRAME_TIME_NS / 2
|
||||
dispatchPreMountItemsImpl(deadline)
|
||||
}
|
||||
|
||||
private fun dispatchPreMountItemsImpl(deadline: Long) {
|
||||
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "MountItemDispatcher::premountViews")
|
||||
|
||||
// dispatchPreMountItems cannot be reentrant, but we want to prevent dispatchMountItems from
|
||||
// reentering during dispatchPreMountItems
|
||||
inDispatch = true
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (System.nanoTime() > deadline) {
|
||||
break
|
||||
}
|
||||
|
||||
// If list is empty, `poll` will return null, or var will never be set
|
||||
val preMountItemToDispatch = preMountItems.poll() ?: break
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
printMountItem(preMountItemToDispatch, "dispatchPreMountItems")
|
||||
}
|
||||
executeOrEnqueue(preMountItemToDispatch)
|
||||
}
|
||||
} finally {
|
||||
inDispatch = false
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT)
|
||||
}
|
||||
|
||||
private fun executeOrEnqueue(item: MountItem) {
|
||||
if (mountingManager.isWaitingForViewAttach(item.getSurfaceId())) {
|
||||
if (ReactNativeFeatureFlags.enableFabricLogs()) {
|
||||
FLog.e(
|
||||
TAG,
|
||||
"executeOrEnqueue: Item execution delayed, surface %s is not ready yet",
|
||||
item.getSurfaceId())
|
||||
}
|
||||
val surfaceMountingManager: SurfaceMountingManager =
|
||||
mountingManager.getSurfaceManagerEnforced(
|
||||
item.getSurfaceId(), "MountItemDispatcher::executeOrEnqueue")
|
||||
surfaceMountingManager.scheduleMountItemOnViewAttach(item)
|
||||
} else {
|
||||
item.execute(mountingManager)
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadConfined(UI)
|
||||
@UiThread
|
||||
private fun getAndResetViewCommandMountItems(): List<DispatchCommandMountItem>? =
|
||||
drainConcurrentItemQueue(viewCommandMountItems)
|
||||
|
||||
@ThreadConfined(UI)
|
||||
@UiThread
|
||||
private fun getAndResetMountItems(): List<MountItem>? = drainConcurrentItemQueue(mountItems)
|
||||
|
||||
@ThreadConfined(UI)
|
||||
@UiThread
|
||||
private fun getAndResetPreMountItems(): List<MountItem>? = drainConcurrentItemQueue(preMountItems)
|
||||
|
||||
interface ItemDispatchListener {
|
||||
fun willMountItems(mountItems: List<MountItem>?)
|
||||
|
||||
fun didMountItems(mountItems: List<MountItem>?)
|
||||
|
||||
fun didDispatchMountItems()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TAG: String = "MountItemDispatcher"
|
||||
|
||||
private const val FRAME_TIME_NS: Long = (1000000000 / 60).toLong()
|
||||
|
||||
private fun <E> drainConcurrentItemQueue(queue: Queue<E>): List<E>? {
|
||||
if (queue.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return buildList {
|
||||
do {
|
||||
queue.poll()?.let { add(it) }
|
||||
} while (queue.isNotEmpty())
|
||||
}
|
||||
.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
private fun printMountItem(mountItem: MountItem, prefix: String) {
|
||||
// If a MountItem description is split across multiple lines, it's because it's a
|
||||
// compound MountItem. Log each line separately.
|
||||
val mountItemLines = mountItem.toString().split("\n").dropLastWhile { it.isEmpty() }
|
||||
|
||||
for (line in mountItemLines) {
|
||||
FLog.e(TAG, "$prefix: $line")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<4c40e490481c50d2f19830366f50aad5>>
|
||||
* @generated SignedSource<<dee97293bfeab942c9cd0ba8622a5f86>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -48,6 +48,12 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun cxxNativeAnimatedEnabled(): Boolean = accessor.cxxNativeAnimatedEnabled()
|
||||
|
||||
/**
|
||||
* Removes JS sync at end of native animation
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun cxxNativeAnimatedRemoveJsSync(): Boolean = accessor.cxxNativeAnimatedRemoveJsSync()
|
||||
|
||||
/**
|
||||
* Disable sync dispatch on the main queue on iOS
|
||||
*/
|
||||
@@ -150,12 +156,6 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun enableIOSViewClipToPaddingBox(): Boolean = accessor.enableIOSViewClipToPaddingBox()
|
||||
|
||||
/**
|
||||
* Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun enableIntersectionObserverEventLoopIntegration(): Boolean = accessor.enableIntersectionObserverEventLoopIntegration()
|
||||
|
||||
/**
|
||||
* When enabled, LayoutAnimations API will animate state changes on Android.
|
||||
*/
|
||||
|
||||
+11
-11
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<8cb9eb082005be480c39272ffc7dde03>>
|
||||
* @generated SignedSource<<c46464d2d10ac0c8a6b7869ae5b3cd2b>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -23,6 +23,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
private var commonTestFlagCache: Boolean? = null
|
||||
private var animatedShouldSignalBatchCache: Boolean? = null
|
||||
private var cxxNativeAnimatedEnabledCache: Boolean? = null
|
||||
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
|
||||
private var disableMainQueueSyncDispatchIOSCache: Boolean? = null
|
||||
private var disableMountItemReorderingAndroidCache: Boolean? = null
|
||||
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
|
||||
@@ -40,7 +41,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
|
||||
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
|
||||
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
|
||||
private var enableIntersectionObserverEventLoopIntegrationCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
|
||||
private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null
|
||||
@@ -100,6 +100,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun cxxNativeAnimatedRemoveJsSync(): Boolean {
|
||||
var cached = cxxNativeAnimatedRemoveJsSyncCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.cxxNativeAnimatedRemoveJsSync()
|
||||
cxxNativeAnimatedRemoveJsSyncCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun disableMainQueueSyncDispatchIOS(): Boolean {
|
||||
var cached = disableMainQueueSyncDispatchIOSCache
|
||||
if (cached == null) {
|
||||
@@ -253,15 +262,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean {
|
||||
var cached = enableIntersectionObserverEventLoopIntegrationCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.enableIntersectionObserverEventLoopIntegration()
|
||||
enableIntersectionObserverEventLoopIntegrationCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean {
|
||||
var cached = enableLayoutAnimationsOnAndroidCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<b6ddd7c2e829c427e7d6b07bc588cd8c>>
|
||||
* @generated SignedSource<<62c548a9bfbf777a40b1b9e9d52b1de2>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -34,6 +34,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun cxxNativeAnimatedEnabled(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun cxxNativeAnimatedRemoveJsSync(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun disableMainQueueSyncDispatchIOS(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun disableMountItemReorderingAndroid(): Boolean
|
||||
@@ -68,8 +70,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableIOSViewClipToPaddingBox(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableIntersectionObserverEventLoopIntegration(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnAndroid(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnIOS(): Boolean
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<76edde93369be682e837189f82871dfe>>
|
||||
* @generated SignedSource<<de8775978e15479830fb215c6f15b144>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -29,6 +29,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun cxxNativeAnimatedEnabled(): Boolean = false
|
||||
|
||||
override fun cxxNativeAnimatedRemoveJsSync(): Boolean = false
|
||||
|
||||
override fun disableMainQueueSyncDispatchIOS(): Boolean = false
|
||||
|
||||
override fun disableMountItemReorderingAndroid(): Boolean = false
|
||||
@@ -63,8 +65,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun enableIOSViewClipToPaddingBox(): Boolean = false
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean = true
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean = false
|
||||
|
||||
override fun enableLayoutAnimationsOnIOS(): Boolean = true
|
||||
|
||||
+12
-12
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<e718763435de15f788b1f510e4799a67>>
|
||||
* @generated SignedSource<<220b0cff47d2eadddf8a36646852d619>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -27,6 +27,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
private var commonTestFlagCache: Boolean? = null
|
||||
private var animatedShouldSignalBatchCache: Boolean? = null
|
||||
private var cxxNativeAnimatedEnabledCache: Boolean? = null
|
||||
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
|
||||
private var disableMainQueueSyncDispatchIOSCache: Boolean? = null
|
||||
private var disableMountItemReorderingAndroidCache: Boolean? = null
|
||||
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
|
||||
@@ -44,7 +45,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
|
||||
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
|
||||
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
|
||||
private var enableIntersectionObserverEventLoopIntegrationCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
|
||||
private var enableMainQueueCoordinatorOnIOSCache: Boolean? = null
|
||||
@@ -107,6 +107,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun cxxNativeAnimatedRemoveJsSync(): Boolean {
|
||||
var cached = cxxNativeAnimatedRemoveJsSyncCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.cxxNativeAnimatedRemoveJsSync()
|
||||
accessedFeatureFlags.add("cxxNativeAnimatedRemoveJsSync")
|
||||
cxxNativeAnimatedRemoveJsSyncCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun disableMainQueueSyncDispatchIOS(): Boolean {
|
||||
var cached = disableMainQueueSyncDispatchIOSCache
|
||||
if (cached == null) {
|
||||
@@ -277,16 +287,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean {
|
||||
var cached = enableIntersectionObserverEventLoopIntegrationCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.enableIntersectionObserverEventLoopIntegration()
|
||||
accessedFeatureFlags.add("enableIntersectionObserverEventLoopIntegration")
|
||||
enableIntersectionObserverEventLoopIntegrationCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean {
|
||||
var cached = enableLayoutAnimationsOnAndroidCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<892db232156970e8241d1f1ddde3a745>>
|
||||
* @generated SignedSource<<d2ab95a5f7bf0dfc52428a90bee2fc10>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -29,6 +29,8 @@ public interface ReactNativeFeatureFlagsProvider {
|
||||
|
||||
@DoNotStrip public fun cxxNativeAnimatedEnabled(): Boolean
|
||||
|
||||
@DoNotStrip public fun cxxNativeAnimatedRemoveJsSync(): Boolean
|
||||
|
||||
@DoNotStrip public fun disableMainQueueSyncDispatchIOS(): Boolean
|
||||
|
||||
@DoNotStrip public fun disableMountItemReorderingAndroid(): Boolean
|
||||
@@ -63,8 +65,6 @@ public interface ReactNativeFeatureFlagsProvider {
|
||||
|
||||
@DoNotStrip public fun enableIOSViewClipToPaddingBox(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableIntersectionObserverEventLoopIntegration(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableLayoutAnimationsOnAndroid(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableLayoutAnimationsOnIOS(): Boolean
|
||||
|
||||
+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"
|
||||
}
|
||||
+2
-8
@@ -8,13 +8,13 @@
|
||||
package com.facebook.react.modules.appearance
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.facebook.fbreact.specs.NativeAppearanceSpec
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.UiThreadUtil
|
||||
import com.facebook.react.bridge.buildReadableMap
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
import com.facebook.react.views.common.UiModeUtils
|
||||
|
||||
/** Module that exposes the user's preferred color scheme. */
|
||||
@ReactModule(name = NativeAppearanceSpec.NAME)
|
||||
@@ -41,13 +41,7 @@ constructor(
|
||||
return overrideColorScheme.getScheme()
|
||||
}
|
||||
|
||||
val currentNightMode =
|
||||
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
||||
return when (currentNightMode) {
|
||||
Configuration.UI_MODE_NIGHT_NO -> "light"
|
||||
Configuration.UI_MODE_NIGHT_YES -> "dark"
|
||||
else -> "light"
|
||||
}
|
||||
return if (UiModeUtils.isDarkMode(context)) "dark" else "light"
|
||||
}
|
||||
|
||||
public override fun getColorScheme(): String {
|
||||
|
||||
+4
-20
@@ -13,8 +13,6 @@ import android.os.Build
|
||||
import android.view.View
|
||||
import android.view.WindowInsetsController
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.fbreact.specs.NativeStatusBarManagerAndroidSpec
|
||||
import com.facebook.react.bridge.GuardedRunnable
|
||||
@@ -23,6 +21,7 @@ import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.UiThreadUtil
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.module.annotations.ReactModule
|
||||
import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx
|
||||
import com.facebook.react.uimanager.PixelUtil
|
||||
import com.facebook.react.views.view.setStatusBarTranslucency
|
||||
import com.facebook.react.views.view.setStatusBarVisibility
|
||||
@@ -34,32 +33,17 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) :
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun getTypedExportedConstants(): Map<String, Any> {
|
||||
val currentActivity = reactApplicationContext.currentActivity
|
||||
val statusBarColor =
|
||||
reactApplicationContext.getCurrentActivity()?.window?.statusBarColor?.let { color ->
|
||||
currentActivity?.window?.statusBarColor?.let { color ->
|
||||
String.format("#%06X", 0xFFFFFF and color)
|
||||
} ?: "black"
|
||||
return mapOf(
|
||||
HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx()),
|
||||
HEIGHT_KEY to PixelUtil.toDIPFromPixel(getStatusBarHeightPx(currentActivity).toFloat()),
|
||||
DEFAULT_BACKGROUND_COLOR_KEY to statusBarColor,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStatusBarHeightPx(): Float {
|
||||
val windowInsets =
|
||||
reactApplicationContext
|
||||
.getCurrentActivity()
|
||||
?.window
|
||||
?.decorView
|
||||
?.let(ViewCompat::getRootWindowInsets) ?: return 0f
|
||||
return windowInsets
|
||||
.getInsets(
|
||||
WindowInsetsCompat.Type.statusBars() or
|
||||
WindowInsetsCompat.Type.navigationBars() or
|
||||
WindowInsetsCompat.Type.displayCutout())
|
||||
.top
|
||||
.toFloat()
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun setColor(colorDouble: Double, animated: Boolean) {
|
||||
val color = colorDouble.toInt()
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.runtime
|
||||
|
||||
import com.facebook.jni.HybridData
|
||||
import com.facebook.jni.annotations.DoNotStrip
|
||||
import com.facebook.jni.annotations.DoNotStripAny
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
@DoNotStripAny
|
||||
public class JSCInstance : JSRuntimeFactory(initHybrid()) {
|
||||
private companion object {
|
||||
init {
|
||||
SoLoader.loadLibrary("jscinstance")
|
||||
}
|
||||
|
||||
@DoNotStrip @JvmStatic private external fun initHybrid(): HybridData
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -8,9 +8,14 @@
|
||||
package com.facebook.react.runtime
|
||||
|
||||
import com.facebook.jni.HybridData
|
||||
import com.facebook.proguard.annotations.DoNotStrip
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
public abstract class JSRuntimeFactory(private val mHybridData: HybridData) {
|
||||
public abstract class JSRuntimeFactory(
|
||||
@Suppress("unused", "NoHungarianNotation", "NotAccessedPrivateField")
|
||||
@DoNotStrip
|
||||
private val mHybridData: HybridData
|
||||
) {
|
||||
private companion object {
|
||||
init {
|
||||
SoLoader.loadLibrary("rninstance")
|
||||
|
||||
+13
-1
@@ -67,6 +67,7 @@ import com.facebook.react.uimanager.ViewManager
|
||||
import com.facebook.react.uimanager.ViewManagerRegistry
|
||||
import com.facebook.react.uimanager.ViewManagerResolver
|
||||
import com.facebook.react.uimanager.events.EventDispatcher
|
||||
import com.facebook.react.util.RNLog
|
||||
import com.facebook.soloader.SoLoader
|
||||
import com.facebook.systrace.Systrace
|
||||
import com.facebook.systrace.SystraceMessage
|
||||
@@ -542,7 +543,18 @@ internal class ReactInstance(
|
||||
for (reactPackage in reactPackages) {
|
||||
if (reactPackage is ViewManagerOnDemandReactPackage) {
|
||||
val names = reactPackage.getViewManagerNames(context)
|
||||
uniqueNames.addAll(names)
|
||||
// We need to null check here because some Java implementation of the
|
||||
// `ViewManagerOnDemandReactPackage` interface could still return null even
|
||||
// if the method is marked as returning a non-nullable collection in Kotlin.
|
||||
// See https://github.com/facebook/react-native/issues/52014
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
if (names == null) {
|
||||
RNLog.w(
|
||||
context,
|
||||
"The ReactPackage called: `${reactPackage.javaClass.simpleName}` is returning null for getViewManagerNames(). This is violating the signature of the method. That method should be updated to return an empty collection.")
|
||||
} else {
|
||||
uniqueNames.addAll(names)
|
||||
}
|
||||
}
|
||||
}
|
||||
return uniqueNames
|
||||
|
||||
-1
@@ -188,7 +188,6 @@ internal constructor(
|
||||
internal val eventDispatcher: EventDispatcher?
|
||||
get() = reactHost?.eventDispatcher
|
||||
|
||||
@get:VisibleForTesting
|
||||
internal val isAttached: Boolean
|
||||
get() = reactHost != null
|
||||
|
||||
|
||||
-17
@@ -42,11 +42,6 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
|
||||
"jsijniprofiler" -> {
|
||||
"hermestooling"
|
||||
}
|
||||
"jscexecutor",
|
||||
"jscruntime",
|
||||
"jscinstance" -> {
|
||||
"jsctooling"
|
||||
}
|
||||
else -> input
|
||||
}
|
||||
|
||||
@@ -56,10 +51,6 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
|
||||
"hermes_executor" -> libhermes_executor_so()
|
||||
"hermesinstancejni" -> libhermesinstancejni_so()
|
||||
"hermestooling" -> libhermestooling_so()
|
||||
"jscexecutor" -> libjscexecutor_so()
|
||||
"jscinstance" -> libjscinstance_so()
|
||||
"jscruntime" -> libjscruntime_so()
|
||||
"jsctooling" -> libjsctooling_so()
|
||||
"jsijniprofiler" -> libjsijniprofiler_so()
|
||||
"jsinspector" -> libjsinspector_so()
|
||||
"mapbufferjni" -> libmapbufferjni_so()
|
||||
@@ -85,14 +76,6 @@ public object OpenSourceMergedSoMapping : ExternalSoMapping {
|
||||
|
||||
public external fun libhermestooling_so(): Int
|
||||
|
||||
public external fun libjscexecutor_so(): Int
|
||||
|
||||
public external fun libjscinstance_so(): Int
|
||||
|
||||
public external fun libjscruntime_so(): Int
|
||||
|
||||
public external fun libjsctooling_so(): Int
|
||||
|
||||
public external fun libjsijniprofiler_so(): Int
|
||||
|
||||
public external fun libjsinspector_so(): Int
|
||||
|
||||
+58
-24
@@ -173,30 +173,19 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
protected void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull T view) {
|
||||
super.addEventEmitters(reactContext, view);
|
||||
|
||||
@Nullable OnFocusChangeListener originalFocusChangeListener = view.getOnFocusChangeListener();
|
||||
view.setOnFocusChangeListener(
|
||||
(v, hasFocus) -> {
|
||||
if (originalFocusChangeListener != null) {
|
||||
originalFocusChangeListener.onFocusChange(v, hasFocus);
|
||||
}
|
||||
int surfaceId = UIManagerHelper.getSurfaceId(v.getContext());
|
||||
if (surfaceId == View.NO_ID) {
|
||||
return;
|
||||
}
|
||||
if (view.getContext() instanceof ThemedReactContext) {
|
||||
ThemedReactContext themedReactContext = (ThemedReactContext) v.getContext();
|
||||
@Nullable
|
||||
EventDispatcher eventDispatcher =
|
||||
UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, view.getId());
|
||||
if (eventDispatcher != null) {
|
||||
if (hasFocus) {
|
||||
eventDispatcher.dispatchEvent(new FocusEvent(surfaceId, view.getId()));
|
||||
} else {
|
||||
eventDispatcher.dispatchEvent(new BlurEvent(surfaceId, view.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
BaseVMFocusChangeListener focusChangeListener =
|
||||
new BaseVMFocusChangeListener(view.getOnFocusChangeListener());
|
||||
focusChangeListener.attach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDropViewInstance(@NonNull T view) {
|
||||
super.onDropViewInstance(view);
|
||||
|
||||
@Nullable OnFocusChangeListener focusChangeListener = view.getOnFocusChangeListener();
|
||||
if (focusChangeListener instanceof BaseVMFocusChangeListener) {
|
||||
((BaseVMFocusChangeListener) focusChangeListener).detach(view);
|
||||
}
|
||||
}
|
||||
|
||||
// Currently, layout listener is only attached when transform or transformOrigin is set.
|
||||
@@ -1052,4 +1041,49 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
||||
}
|
||||
|
||||
// Please add new props to BaseViewManagerDelegate as well!
|
||||
|
||||
/**
|
||||
* A helper class to keep track of the original focus change listener if one is set. This is
|
||||
* especially helpful for views that are recycled so we can retain and restore the original
|
||||
* listener upon recycling (onDropViewInstance).
|
||||
*/
|
||||
private class BaseVMFocusChangeListener<V extends View> implements OnFocusChangeListener {
|
||||
private @Nullable OnFocusChangeListener mOriginalFocusChangeListener;
|
||||
|
||||
public BaseVMFocusChangeListener(@Nullable OnFocusChangeListener originalFocusChangeListener) {
|
||||
mOriginalFocusChangeListener = originalFocusChangeListener;
|
||||
}
|
||||
|
||||
public void attach(T view) {
|
||||
view.setOnFocusChangeListener(this);
|
||||
}
|
||||
|
||||
public void detach(T view) {
|
||||
view.setOnFocusChangeListener(mOriginalFocusChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChange(View view, boolean hasFocus) {
|
||||
if (mOriginalFocusChangeListener != null) {
|
||||
mOriginalFocusChangeListener.onFocusChange(view, hasFocus);
|
||||
}
|
||||
int surfaceId = UIManagerHelper.getSurfaceId(view.getContext());
|
||||
if (surfaceId == View.NO_ID) {
|
||||
return;
|
||||
}
|
||||
if (view.getContext() instanceof ThemedReactContext) {
|
||||
ThemedReactContext themedReactContext = (ThemedReactContext) view.getContext();
|
||||
@Nullable
|
||||
EventDispatcher eventDispatcher =
|
||||
UIManagerHelper.getEventDispatcherForReactTag(themedReactContext, view.getId());
|
||||
if (eventDispatcher != null) {
|
||||
if (hasFocus) {
|
||||
eventDispatcher.dispatchEvent(new FocusEvent(surfaceId, view.getId()));
|
||||
} else {
|
||||
eventDispatcher.dispatchEvent(new BlurEvent(surfaceId, view.getId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -7,9 +7,12 @@
|
||||
|
||||
package com.facebook.react.uimanager
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.WindowManager
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
|
||||
@@ -98,4 +101,14 @@ public object DisplayMetricsHolder {
|
||||
putDouble("fontScale", fontScale)
|
||||
putDouble("densityDpi", displayMetrics.densityDpi.toDouble())
|
||||
}
|
||||
|
||||
internal fun getStatusBarHeightPx(activity: Activity?): Int {
|
||||
val windowInsets = activity?.window?.decorView?.let(ViewCompat::getRootWindowInsets) ?: return 0
|
||||
return windowInsets
|
||||
.getInsets(
|
||||
WindowInsetsCompat.Type.statusBars() or
|
||||
WindowInsetsCompat.Type.navigationBars() or
|
||||
WindowInsetsCompat.Type.displayCutout())
|
||||
.top
|
||||
}
|
||||
}
|
||||
|
||||
-8
@@ -27,12 +27,4 @@ internal object LayoutDirectionUtil {
|
||||
YogaDirection.RTL -> View.LAYOUT_DIRECTION_RTL
|
||||
else -> View.LAYOUT_DIRECTION_INHERIT
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun toYogaFromAndroid(direction: Int): YogaDirection =
|
||||
when (direction) {
|
||||
View.LAYOUT_DIRECTION_LTR -> YogaDirection.LTR
|
||||
View.LAYOUT_DIRECTION_RTL -> YogaDirection.RTL
|
||||
else -> YogaDirection.INHERIT
|
||||
}
|
||||
}
|
||||
|
||||
-5
@@ -17,7 +17,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
@@ -501,10 +500,6 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
|
||||
node.setContentDescription(mView.getContentDescription());
|
||||
}
|
||||
|
||||
if (mView instanceof TextView && ((TextView) mView).getText() != null) {
|
||||
node.setText(((TextView) mView).getText());
|
||||
}
|
||||
|
||||
populateAccessibilityNodeInfo(mView, node);
|
||||
node.setBoundsInParent(new Rect(0, 0, mView.getWidth(), mView.getHeight()));
|
||||
} else {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user