Compare commits

..
Author SHA1 Message Date
Rob HoganandFacebook GitHub Bot 7f38795c8a Bump Metro to 0.83.0 (#52576)
Summary:
Bump Metro to 0.83.0.

This release contains some breaking changes for integrators, and a minimum Node.js version of 22.14.

Full release notes: https://github.com/facebook/metro/releases/tag/v0.83.0

Changelog: [General][Changed] Bump Metro to ^0.83.0

Test Plan:
Imported from GitHub, without a `Test Plan:` line.

Rollback Plan:

Differential Revision: D78171925

Pulled By: robhogan
2025-07-13 14:34:09 -07:00
269 changed files with 3751 additions and 4121 deletions
+9
View File
@@ -9,3 +9,12 @@ end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
[*.gradle]
indent_size = 4
[*.kts]
indent_size = 4
[BUCK]
indent_size = 4
@@ -14,23 +14,23 @@ runs:
uses: actions/download-artifact@v4
with:
name: hermes-workspace
path: 'C:\tmp\hermes'
path: 'D:\tmp\hermes'
- name: Set up workspace
shell: powershell
run: |
mkdir -p C:\tmp\hermes\osx-bin
mkdir -p D:\tmp\hermes\osx-bin
mkdir -p .\packages\react-native\sdks\hermes
cp -r -Force C:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
cp -r -Force D:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
cp -r -Force .\packages\react-native\sdks\hermes-engine\utils\* .\packages\react-native\sdks\hermes\.
- name: Windows cache
uses: actions/cache@v4
with:
key: v3-hermes-${{ github.job }}-windows-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}
path: |
C:\tmp\hermes\win64-bin\
C:\tmp\hermes\hermes\icu\
C:\tmp\hermes\hermes\deps\
C:\tmp\hermes\hermes\build_release\
D:\tmp\hermes\win64-bin\
D:\tmp\hermes\hermes\icu\
D:\tmp\hermes\hermes\deps\
D:\tmp\hermes\hermes\build_release\
- name: setup-msbuild
uses: microsoft/setup-msbuild@v1.3.2
- name: Set up workspace
@@ -83,4 +83,4 @@ runs:
uses: actions/upload-artifact@v4.3.4
with:
name: hermes-win64-bin
path: C:\tmp\hermes\win64-bin\
path: D:\tmp\hermes\win64-bin\
+3 -3
View File
@@ -128,9 +128,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
+3 -3
View File
@@ -125,9 +125,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
+4 -4
View File
@@ -382,9 +382,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
@@ -583,7 +583,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: ["24", "22", "20"]
node-version: ["24", "22"]
steps:
- name: Checkout
uses: actions/checkout@v4
-30
View File
@@ -1,35 +1,5 @@
# Changelog
## v0.81.0-rc.1
### Added
#### iOS specific
- Add release/debug switch script for React-Core-prebuilt ([42d1a7934c](https://github.com/facebook/react-native/commit/42d1a7934cad4b2c92653e3fa7781c2af8f44df4) by [@chrfalch](https://github.com/chrfalch))
- Added support for using USE_FRAMEWORKS with prebuilt React Native Core ([40e45f5366](https://github.com/facebook/react-native/commit/40e45f53661ce80c3a6fbbf07f52dc900afcad52) by [@chrfalch](https://github.com/chrfalch))
- Add the `ENTERPRISE_REPOSITORY` env variable to cocopaods infra ([23f3bf9239](https://github.com/facebook/react-native/commit/23f3bf9239a849590f1c72b25732d0090780128c) by [@cipolleschi](https://github.com/cipolleschi))
### Changed
- Bump Metro to 0.83.0 ([6b9f5d622f](https://github.com/facebook/react-native/commit/6b9f5d622ffbe79da8f4e7b7d8094504a480425e) by [@robhogan](https://github.com/robhogan))
#### Android specific
- Gradle to 8.14.3 ([6892dde363](https://github.com/facebook/react-native/commit/6892dde36373bbef2d0afe535ae818b1a7164f08) by [@cortinico](https://github.com/cortinico))
- Expose `react_renderer_bridging` headers via prefab ([d1730ff960](https://github.com/facebook/react-native/commit/d1730ff960fcb9a01ee94b9e46e5a9fbb7d73f4a) by [@tomekzaw](https://github.com/tomekzaw))
- Introduce more deprecation warnings for Legacy Arch classes ([625f69f284](https://github.com/facebook/react-native/commit/625f69f284ddfd9c6beecaa4052a871d092053ef) by [@cortinico](https://github.com/cortinico))
### Fixed
- Fixed nodes with `display: contents` set being cloned with the wrong owner ([d4b36b0300](https://github.com/facebook/react-native/commit/d4b36b03003eb2de9eaf5b57bb639bae8cc12f20) by [@j-piasecki](https://github.com/j-piasecki))
#### iOS specific
- Fixed premature return in header file generation from podspec globs ([f2b064c2d4](https://github.com/facebook/react-native/commit/f2b064c2d40c39017ac2a31bf3caf8acef23038c) by [@chrfalch](https://github.com/chrfalch))
## v0.81.0-rc.0
### Breaking
-28
View File
@@ -13,7 +13,6 @@ plugins {
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.binary.compatibility.validator) apply true
alias(libs.plugins.android.test) apply false
alias(libs.plugins.ktfmt) apply true
}
val reactAndroidProperties = java.util.Properties()
@@ -131,30 +130,3 @@ if (project.findProperty("react.internal.useHermesNightly")?.toString()?.toBoole
}
}
}
ktfmt {
blockIndent.set(2)
continuationIndent.set(4)
maxWidth.set(100)
removeUnusedImports.set(false)
manageTrailingCommas.set(false)
}
// Configure ktfmt tasks to include gradle-plugin
listOf("ktfmtCheck", "ktfmtFormat").forEach { taskName ->
tasks.named(taskName) { dependsOn(gradle.includedBuild("gradle-plugin").task(":$taskName")) }
}
allprojects {
// Apply exclusions for specific files that should not be formatted
val excludePatterns =
listOf(
"**/build/**",
"**/hermes-engine/**",
"**/internal/featureflags/**",
"**/systeminfo/ReactNativeVersion.kt")
listOf(
com.ncorti.ktfmt.gradle.tasks.KtfmtCheckTask::class,
com.ncorti.ktfmt.gradle.tasks.KtfmtFormatTask::class)
.forEach { tasks.withType(it) { exclude(excludePatterns) } }
}
-2
View File
@@ -19,8 +19,6 @@
"featureflags": "yarn --cwd packages/react-native featureflags",
"js-api-diff": "node ./scripts/js-api/diff-api-snapshot",
"lint-ci": "./.github/workflow-scripts/analyze_code.sh && yarn shellcheck",
"lint-kotlin-check": "./gradlew ktfmtCheck",
"lint-kotlin": "./gradlew ktfmtFormat",
"lint-markdown": "markdownlint-cli2 2>&1",
"lint": "eslint --max-warnings 0 .",
"prettier": "prettier --write \"./**/*.{js,md,yml,ts,tsx}\"",
+1 -1
View File
@@ -17,7 +17,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"path-support.js",
+1 -1
View File
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"index.js"
+1 -1
View File
@@ -43,6 +43,6 @@
}
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"dist"
+2 -2
View File
@@ -1,5 +1,5 @@
@generated SignedSource<<e2d97b04634bf3b566f33d455f38658f>>
Git revision: 8dc0d5b365e6b4600b78e5a7381aa861d8c1c81e
@generated SignedSource<<1ce3fb7dd23581737fedaf58528fe575>>
Git revision: 844f225009e6445d088ed0e431d1fc430325ed95
Built with --nohooks: false
Is local checkout: false
Remote URL: https://github.com/facebook/react-native-devtools-frontend
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -20,6 +20,6 @@
"BUILD_INFO"
],
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -26,7 +26,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 20.19.4",
"node": ">= 22.14.0",
"electron": ">=36.3.0"
},
"dependencies": {
@@ -10,25 +10,9 @@
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
const {app} = require('electron') as any;
const util = require('util');
// Handle global command line arguments which don't require a window
// or the single instance lock to be held.
const {
values: {version = false},
} = util.parseArgs({
options: {version: {type: 'boolean'}},
args: process.argv.slice(app.isPackaged ? 1 : 2),
strict: false,
});
if (version) {
console.log(`${app.getName()} v${app.getVersion()}`);
// Not app.quit() - we want to exit immediately without initialising the graphical subsystem.
app.exit(0);
}
const gotTheLock = app.requestSingleInstanceLock({
argv: process.argv.slice(app.isPackaged ? 1 : 2),
argv: process.argv.slice(2),
});
if (!gotTheLock) {
+1 -1
View File
@@ -35,7 +35,7 @@
"ws": "^6.2.3"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"devDependencies": {
"selfsigned": "^2.4.1",
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "index.js",
"dependencies": {
@@ -22,6 +22,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -36,6 +36,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -22
View File
@@ -5,10 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
plugins {
alias(libs.plugins.kotlin.jvm).apply(false)
alias(libs.plugins.ktfmt).apply(true)
}
plugins { alias(libs.plugins.kotlin.jvm).apply(false) }
tasks.register("build") {
dependsOn(
@@ -27,21 +24,3 @@ tasks.register("clean") {
":shared:clean",
)
}
tasks.named("ktfmtCheck") {
dependsOn(
":react-native-gradle-plugin:ktfmtCheck",
":settings-plugin:ktfmtCheck",
":shared-testutil:ktfmtCheck",
":shared:ktfmtCheck",
)
}
tasks.named("ktfmtFormat") {
dependsOn(
":react-native-gradle-plugin:ktfmtFormat",
":settings-plugin:ktfmtFormat",
":shared-testutil:ktfmtFormat",
":shared:ktfmtFormat",
)
}
@@ -6,7 +6,6 @@ javapoet = "1.13.0"
junit = "4.13.2"
kotlin = "2.1.20"
assertj = "3.25.1"
ktfmt = "0.22.0"
[libraries]
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
@@ -19,4 +18,3 @@ assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"scripts": {
"build": "./gradlew build",
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
id("java-gradle-plugin")
}
@@ -18,7 +18,6 @@ import com.facebook.react.tasks.GenerateEntryPointTask
import com.facebook.react.tasks.GeneratePackageListTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildTypesForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevServerLocation
import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
@@ -85,7 +84,6 @@ class ReactPlugin : Plugin<Project> {
configureAutolinking(project, extension)
configureCodegen(project, extension, rootExtension, isLibrary = false)
configureResources(project, extension)
configureBuildTypesForApp(project)
}
// Library Only Configuration
@@ -19,7 +19,6 @@ import java.net.Inet4Address
import java.net.NetworkInterface
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.plus
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
@@ -28,26 +27,6 @@ import org.w3c.dom.Element
@Suppress("UnstableApiUsage")
internal object AgpConfiguratorUtils {
fun configureBuildTypesForApp(project: Project) {
val action =
Action<AppliedPlugin> {
project.extensions
.getByType(ApplicationAndroidComponentsExtension::class.java)
.finalizeDsl { ext ->
ext.buildTypes {
val debug =
getByName("debug").apply {
manifestPlaceholders["usesCleartextTraffic"] = "true"
}
getByName("release").apply {
manifestPlaceholders["usesCleartextTraffic"] = "false"
}
}
}
}
project.pluginManager.withPlugin("com.android.application", action)
}
fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
val action =
Action<AppliedPlugin> {
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
id("java-gradle-plugin")
}
@@ -10,10 +10,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
}
plugins { alias(libs.plugins.kotlin.jvm) }
repositories { mavenCentral() }
@@ -10,10 +10,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.ktfmt)
}
plugins { alias(libs.plugins.kotlin.jvm) }
repositories { mavenCentral() }
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"exports": {
".": "./src/index.js",
+1 -1
View File
@@ -30,6 +30,6 @@
}
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
@@ -16,7 +16,6 @@ describe('console.timeStamp()', () => {
});
it("doesn't throw when label is not specified", () => {
// $FlowExpectedError[incompatible-call] not passing label intentionally
expect(() => console.timeStamp()).not.toThrow();
});
@@ -26,6 +25,7 @@ describe('console.timeStamp()', () => {
it("doesn't throw when additional arguments are specified", () => {
expect(() =>
// $FlowExpectedError[extra-arg]
console.timeStamp('label', 100, 500, 'Track', 'Group', 'error'),
).not.toThrow();
});
@@ -34,7 +34,7 @@ describe('console.timeStamp()', () => {
// $FlowExpectedError[incompatible-call]
expect(() => console.timeStamp({})).not.toThrow();
expect(() =>
// $FlowExpectedError[incompatible-call]
// $FlowExpectedError[extra-arg]
console.timeStamp('label', true, null, {}, [], () => {}),
).not.toThrow();
});
+6 -10
View File
@@ -569,7 +569,10 @@ function consoleAssertPolyfill(expression, label) {
}
}
function stub() {}
// https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp_static.
// Non-standard API for recording markers on a timeline of the Performance instrumentation.
// The actual logging is not provided by definition.
function consoleTimeStampPolyfill() {}
if (global.nativeLoggingHook) {
const originalConsole = global.console;
@@ -582,11 +585,7 @@ if (global.nativeLoggingHook) {
}
global.console = {
time: stub,
timeEnd: stub,
timeStamp: stub,
count: stub,
countReset: stub,
timeStamp: consoleTimeStampPolyfill,
...(originalConsole ?? {}),
error: getNativeLogFunction(LOG_LEVELS.error),
info: getNativeLogFunction(LOG_LEVELS.info),
@@ -677,6 +676,7 @@ if (global.nativeLoggingHook) {
});
}
} else if (!global.console) {
function stub() {}
const log = global.print || stub;
global.console = {
@@ -692,8 +692,6 @@ if (global.nativeLoggingHook) {
}
},
clear: stub,
count: stub,
countReset: stub,
dir: stub,
dirxml: stub,
group: stub,
@@ -702,8 +700,6 @@ if (global.nativeLoggingHook) {
profile: stub,
profileEnd: stub,
table: stub,
time: stub,
timeEnd: stub,
timeStamp: stub,
};
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"console.js",
@@ -13,7 +13,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "src/index.js",
"files": [
+14 -22
View File
@@ -38,6 +38,19 @@ function isFirstParty(fileName) {
// use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
const loose = true;
const defaultPlugins = [
[require('babel-plugin-syntax-hermes-parser'), {parseLangTypes: 'flow'}],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-transform-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[require('@babel/plugin-transform-private-property-in-object'), {loose}],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
];
// For Static Hermes testing (experimental), the hermes-canary transformProfile
// is used to enable regenerator (and some related lowering passes) because SH
// requires more Babel lowering than Hermes temporarily.
@@ -221,28 +234,7 @@ const getPreset = (src, options) => {
plugins: [require('@babel/plugin-transform-flow-strip-types')],
},
{
plugins: [
[
require('babel-plugin-syntax-hermes-parser'),
{
parseLangTypes: 'flow',
reactRuntimeTarget: '19',
...options.hermesParserOptions,
},
],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-transform-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[
require('@babel/plugin-transform-private-property-in-object'),
{loose},
],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
],
plugins: defaultPlugins,
},
{
test: isTypeScriptSource,
@@ -14,7 +14,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "src/index.js",
"files": [
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"scripts": {
"build": "yarn clean && node scripts/build.js --verbose",
@@ -51,13 +51,12 @@ if (!schemaQuery.startsWith('@')) {
const schemaQueryOutputFile = schemaQuery.replace(/^@/, '');
const schemaQueryOutput = fs.readFileSync(schemaQueryOutputFile, 'utf8');
const schemaFiles = schemaQueryOutput.split(' ');
const modules: {
[hasteModuleName: string]: NativeModuleSchema | ComponentSchema,
} = {};
const specNameToFile: {[hasteModuleName: string]: string} = {};
const schemaFiles =
schemaQueryOutput.length > 0 ? schemaQueryOutput.split(' ') : [];
for (const file of schemaFiles) {
const schema: SchemaType = JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -65,15 +65,15 @@ const TestTemplate = ({
propValue: string,
}) => `
TEST(${componentName}_${testName}, etc) {
RawPropsParser propParser{};
auto propParser = RawPropsParser();
propParser.prepare<${componentName}>();
${componentName} sourceProps{};
RawProps rawProps(folly::dynamic::object("${propName}", ${propValue}));
auto const &sourceProps = ${componentName}();
auto const &rawProps = RawProps(folly::dynamic::object("${propName}", ${propValue}));
ContextContainer contextContainer{};
PropsParserContext parserContext{-1, contextContainer};
rawProps.parse(propParser);
rawProps.parse(propParser, parserContext);
${componentName}(parserContext, sourceProps, rawProps);
}
`;
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"exports": {
".": "./src/index.js",
@@ -31,8 +31,12 @@ function mockQueueMicrotask() {
}
describe('Animated', () => {
let ReactNativeFeatureFlags;
beforeEach(() => {
jest.resetModules();
ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
});
mockQueueMicrotask();
@@ -109,7 +113,50 @@ describe('Animated', () => {
expect(callback.mock.calls.length).toBe(1);
});
it('does not detach on updates', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => false,
});
const opacity = new Animated.Value(0);
jest.spyOn(opacity, '__detach');
const root = await create(<Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
await update(root, <Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
await unmount(root);
expect(opacity.__detach).toBeCalled();
});
it('stops animation when detached', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => false,
});
const opacity = new Animated.Value(0);
const callback = jest.fn();
const root = await create(<Animated.View style={{opacity}} />);
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: false,
}).start(callback);
await unmount(root);
expect(callback).toBeCalledWith({finished: false});
});
it('detaches only on unmount (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const opacity = new Animated.Value(0);
jest.spyOn(opacity, '__detach');
@@ -128,6 +175,10 @@ describe('Animated', () => {
});
it('restores default values only on update (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const __restoreDefaultValues = jest.spyOn(
AnimatedProps.prototype,
'__restoreDefaultValues',
@@ -162,6 +213,10 @@ describe('Animated', () => {
});
it('stops animation when detached (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const opacity = new Animated.Value(0);
const callback = jest.fn();
@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*
* @fantom_flags enableFixForParentTagDuringReparenting:true
* @fantom_flags enableSynchronousStateUpdates:true
* @fantom_flags enableViewCulling:true
* @flow strict-local
* @format
@@ -2555,56 +2556,3 @@ describe('culling inside ScrollView with overflow visible', () => {
);
});
});
describe('horizontal ScrollView in RTL script', () => {
it('renders item 1', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
style={{direction: 'rtl', height: 100, width: 100}}
horizontal={true}>
<View nativeID={'item1'} style={{height: 90, width: 90, margin: 5}} />
<View nativeID={'item2'} style={{height: 90, width: 90, margin: 5}} />
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "AndroidHorizontalScrollContentView", nativeID: (N/A)}',
'Create {type: "View", nativeID: "item1"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "item1"}',
'Insert {type: "AndroidHorizontalScrollContentView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
});
it('takes contentOffset into account', () => {
const root = Fantom.createRoot({viewportWidth: 100, viewportHeight: 100});
Fantom.runTask(() => {
root.render(
<ScrollView
style={{direction: 'rtl', height: 100, width: 100}}
horizontal={true}
contentOffset={{x: 100, y: 0}}>
<View nativeID={'item1'} style={{height: 90, width: 90, margin: 5}} />
<View nativeID={'item2'} style={{height: 90, width: 90, margin: 5}} />
</ScrollView>,
);
});
expect(root.takeMountingManagerLogs()).toEqual([
'Update {type: "RootView", nativeID: (root)}',
'Create {type: "ScrollView", nativeID: (N/A)}',
'Create {type: "AndroidHorizontalScrollContentView", nativeID: (N/A)}',
'Create {type: "View", nativeID: "item2"}',
'Insert {type: "View", parentNativeID: (N/A), index: 0, nativeID: "item2"}',
'Insert {type: "AndroidHorizontalScrollContentView", parentNativeID: (N/A), index: 0, nativeID: (N/A)}',
'Insert {type: "ScrollView", parentNativeID: (root), index: 0, nativeID: (N/A)}',
]);
});
});
@@ -10,14 +10,9 @@
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
import ensureInstance from '../../../../src/private/__tests__/utilities/ensureInstance';
import * as Fantom from '@react-native/fantom';
import * as React from 'react';
import {createRef} from 'react';
import {View} from 'react-native';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
describe('<View>', () => {
describe('width and height style', () => {
@@ -186,40 +181,6 @@ describe('<View>', () => {
<rn-view transform='[{"translateX": 10.000000}]' />,
);
});
[
[undefined, {x: -5, y: 0, width: 20, height: 10}],
['50% 50%', {x: -5, y: 0, width: 20, height: 10}],
['top left', {x: 0, y: 0, width: 20, height: 10}],
['right bottom', {x: -10, y: 0, width: 20, height: 10}],
].forEach(([transformOrigin, expectedBounds]) => {
it(`applies transformOrigin correctly for ${String(transformOrigin)}`, () => {
const root = Fantom.createRoot();
const viewRef = createRef<HostInstance>();
Fantom.runTask(() => {
root.render(
<View
ref={viewRef}
style={{
width: 10,
height: 10,
transform: [{scaleX: 2}],
transformOrigin,
}}
/>,
);
});
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
const viewBounds = viewElement.getBoundingClientRect();
expect(viewBounds.x).toBe(expectedBounds.x);
expect(viewBounds.y).toBe(expectedBounds.y);
expect(viewBounds.width).toBe(expectedBounds.width);
expect(viewBounds.height).toBe(expectedBounds.height);
});
});
});
describe('props', () => {
@@ -49,7 +49,6 @@ function _defineCheckVersionTests() {
// $FlowFixMe[cannot-write]
console.error = jest.fn();
// $FlowFixMe[cannot-write]
// $FlowFixMe[unsafe-addition]
global.console = {error: jest.fn(error => (consoleOutput += error))};
spyOnConsoleError = jest.spyOn(global.console, 'error');
});
+1 -2
View File
@@ -8,13 +8,12 @@
* @format
*/
import Performance from '../../src/private/webapis/performance/Performance';
import NativePerformance from '../../src/private/webapis/performance/specs/NativePerformance';
// In case if the native implementation of the Performance API is available, use it,
// otherwise fall back to the legacy/default one, which only defines 'Performance.now()'
if (NativePerformance) {
const Performance =
require('../../src/private/webapis/performance/Performance').default;
// $FlowExpectedError[cannot-write]
global.performance = new Performance();
} else {
@@ -4,32 +4,180 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @flow strict-local
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import type {Task} from './TaskQueue';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import EventEmitter from '../vendor/emitter/EventEmitter';
const BatchedBridge = require('../BatchedBridge/BatchedBridge').default;
const TaskQueue = require('./TaskQueue').default;
const invariant = require('invariant');
export type SimpleTask = {
name: string,
run: () => void,
};
export type PromiseTask = {
name: string,
gen: () => Promise<void>,
};
export type Task = SimpleTask | PromiseTask | (() => void);
export type {Task, SimpleTask, PromiseTask} from './TaskQueue';
export type Handle = number;
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
const _emitter = new EventEmitter<{
interactionComplete: [],
interactionStart: [],
}>();
const DEBUG_DELAY: 0 = 0;
const DEBUG: false = false;
const InteractionManagerImpl = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
const tasks: Array<Task> = [];
const promise = new Promise((resolve: () => void) => {
_scheduleUpdate();
if (task) {
tasks.push(task);
}
tasks.push({
run: resolve,
name: 'resolve ' + ((task && task.name) || '?'),
});
_taskQueue.enqueueTasks(tasks);
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel: function () {
_taskQueue.cancelTasks(tasks);
},
};
},
/**
* Notify manager that an interaction has started.
*/
createInteractionHandle(): Handle {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('InteractionManager: create interaction handle');
_scheduleUpdate();
const handle = ++_inc;
_addInteractionSet.add(handle);
return handle;
},
/**
* Notify manager that an interaction has completed.
*/
clearInteractionHandle(handle: Handle) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('InteractionManager: clear interaction handle');
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
_scheduleUpdate();
_addInteractionSet.delete(handle);
_deleteInteractionSet.add(handle);
},
// $FlowFixMe[unclear-type] unclear type of _emitter
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
addListener: _emitter.addListener.bind(_emitter) as (
eventType: string,
// $FlowFixMe[unclear-type] unclear type of arguments
listener: (...args: any) => mixed,
context: mixed,
) => EventSubscription,
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*/
setDeadline(deadline: number) {
_deadline = deadline;
},
};
const _interactionSet = new Set<number | Handle>();
const _addInteractionSet = new Set<number | Handle>();
const _deleteInteractionSet = new Set<Handle>();
const _taskQueue = new TaskQueue({onMoreTasks: _scheduleUpdate});
let _nextUpdateHandle: $FlowFixMe | TimeoutID = 0;
let _inc = 0;
let _deadline = -1;
/**
* Schedule an asynchronous update to the interaction state.
*/
function _scheduleUpdate() {
if (!_nextUpdateHandle) {
if (_deadline > 0) {
_nextUpdateHandle = setTimeout(_processUpdate, 0 + DEBUG_DELAY);
} else {
_nextUpdateHandle = setImmediate(_processUpdate);
}
}
}
/**
* Notify listeners, process queue, etc
*/
function _processUpdate() {
_nextUpdateHandle = 0;
const interactionCount = _interactionSet.size;
_addInteractionSet.forEach(handle => _interactionSet.add(handle));
_deleteInteractionSet.forEach(handle => _interactionSet.delete(handle));
const nextInteractionCount = _interactionSet.size;
if (interactionCount !== 0 && nextInteractionCount === 0) {
// transition from 1+ --> 0 interactions
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
_emitter.emit(InteractionManager.Events.interactionComplete);
} else if (interactionCount === 0 && nextInteractionCount !== 0) {
// transition from 0 --> 1+ interactions
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
_emitter.emit(InteractionManager.Events.interactionStart);
}
// process the queue regardless of a transition
if (nextInteractionCount === 0) {
while (_taskQueue.hasTasksToProcess()) {
_taskQueue.processNext();
if (
_deadline > 0 &&
BatchedBridge.getEventLoopRunningTime() >= _deadline
) {
// Hit deadline before processing all tasks, so process more later.
_scheduleUpdate();
break;
}
}
}
_addInteractionSet.clear();
_deleteInteractionSet.clear();
}
/**
@@ -83,106 +231,11 @@ function reject(error: Error): void {
*
* @deprecated
*/
const InteractionManagerStub = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
const InteractionManager = (
ReactNativeFeatureFlags.disableInteractionManager()
? // $FlowFixMe[incompatible-variance]
require('./InteractionManagerStub').default
: InteractionManagerImpl
) as typeof InteractionManagerImpl;
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*
* @deprecated
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
task.gen().then(resolve, reject);
} else if (typeof task.run === 'function') {
try {
task.run();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
}
} else if (typeof task === 'function') {
try {
task();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError('Invalid task of type: ' + typeof task));
}
});
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel() {
clearImmediate(immediateID);
},
};
},
/**
* Notify manager that an interaction has started.
*
* @deprecated
*/
createInteractionHandle(): Handle {
return -1;
},
/**
* Notify manager that an interaction has completed.
*
* @deprecated
*/
clearInteractionHandle(handle: Handle) {
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
},
/**
* @deprecated
*/
addListener(
eventType: string,
// $FlowIgnore[unclear-type]
listener: (...args: any) => mixed,
context: mixed,
): EventSubscription {
return {
remove() {},
};
},
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*
* @deprecated
*/
setDeadline(deadline: number) {
// Do nothing.
},
};
export default InteractionManagerStub;
export default InteractionManager;
@@ -0,0 +1,184 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
const invariant = require('invariant');
export type Handle = number;
type Task =
| {
name: string,
run: () => void,
}
| {
name: string,
gen: () => Promise<void>,
}
| (() => void);
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
}
/**
* InteractionManager allows long-running work to be scheduled after any
* interactions/animations have completed. In particular, this allows JavaScript
* animations to run smoothly.
*
* Applications can schedule tasks to run after interactions with the following:
*
* ```
* InteractionManager.runAfterInteractions(() => {
* // ...long-running synchronous task...
* });
* ```
*
* Compare this to other scheduling alternatives:
*
* - requestAnimationFrame(): for code that animates a view over time.
* - setImmediate/setTimeout(): run code later, note this may delay animations.
* - runAfterInteractions(): run code later, without delaying active animations.
*
* The touch handling system considers one or more active touches to be an
* 'interaction' and will delay `runAfterInteractions()` callbacks until all
* touches have ended or been cancelled.
*
* InteractionManager also allows applications to register animations by
* creating an interaction 'handle' on animation start, and clearing it upon
* completion:
*
* ```
* var handle = InteractionManager.createInteractionHandle();
* // run animation... (`runAfterInteractions` tasks are queued)
* // later, on animation completion:
* InteractionManager.clearInteractionHandle(handle);
* // queued tasks run if all handles were cleared
* ```
*
* `runAfterInteractions` takes either a plain callback function, or a
* `PromiseTask` object with a `gen` method that returns a `Promise`. If a
* `PromiseTask` is supplied, then it is fully resolved (including asynchronous
* dependencies that also schedule more tasks via `runAfterInteractions`) before
* starting on the next task that might have been queued up synchronously
* earlier.
*
* By default, queued tasks are executed together in a loop in one
* `setImmediate` batch. If `setDeadline` is called with a positive number, then
* tasks will only be executed until the deadline (in terms of js event loop run
* time) approaches, at which point execution will yield via setTimeout,
* allowing events such as touches to start interactions and block queued tasks
* from executing, making apps more responsive.
*
* @deprecated
*/
const InteractionManagerStub = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*
* @deprecated
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
task.gen().then(resolve, reject);
} else if (typeof task.run === 'function') {
try {
task.run();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
}
} else if (typeof task === 'function') {
try {
task();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError('Invalid task of type: ' + typeof task));
}
});
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel() {
clearImmediate(immediateID);
},
};
},
/**
* Notify manager that an interaction has started.
*
* @deprecated
*/
createInteractionHandle(): Handle {
return -1;
},
/**
* Notify manager that an interaction has completed.
*
* @deprecated
*/
clearInteractionHandle(handle: Handle) {
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
},
/**
* @deprecated
*/
addListener(): EventSubscription {
return {
remove() {},
};
},
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*
* @deprecated
*/
setDeadline(deadline: number) {
// Do nothing.
},
};
export default InteractionManagerStub;
+197
View File
@@ -0,0 +1,197 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
const invariant = require('invariant');
export type SimpleTask = {
name: string,
run: () => void,
};
export type PromiseTask = {
name: string,
gen: () => Promise<void>,
};
export type Task = SimpleTask | PromiseTask | (() => void);
const DEBUG: false = false;
/**
* TaskQueue - A system for queueing and executing a mix of simple callbacks and
* trees of dependent tasks based on Promises. No tasks are executed unless
* `processNext` is called.
*
* `enqueue` takes a Task object with either a simple `run` callback, or a
* `gen` function that returns a `Promise` and puts it in the queue. If a gen
* function is supplied, then the promise it returns will block execution of
* tasks already in the queue until it resolves. This can be used to make sure
* the first task is fully resolved (including asynchronous dependencies that
* also schedule more tasks via `enqueue`) before starting on the next task.
* The `onMoreTasks` constructor argument is used to inform the owner that an
* async task has resolved and that the queue should be processed again.
*
* Note: Tasks are only actually executed with explicit calls to `processNext`.
*/
class TaskQueue {
/**
* TaskQueue instances are self contained and independent, so multiple tasks
* of varying semantics and priority can operate together.
*
* `onMoreTasks` is invoked when `PromiseTask`s resolve if there are more
* tasks to process.
*/
constructor({onMoreTasks}: {onMoreTasks: () => void, ...}) {
this._onMoreTasks = onMoreTasks;
this._queueStack = [{tasks: [], popable: false}];
}
/**
* Add a task to the queue. It is recommended to name your tasks for easier
* async debugging. Tasks will not be executed until `processNext` is called
* explicitly.
*/
enqueue(task: Task): void {
this._getCurrentQueue().push(task);
}
enqueueTasks(tasks: Array<Task>): void {
tasks.forEach(task => this.enqueue(task));
}
cancelTasks(tasksToCancel: Array<Task>): void {
// search through all tasks and remove them.
this._queueStack = this._queueStack
.map(queue => ({
...queue,
tasks: queue.tasks.filter(task => tasksToCancel.indexOf(task) === -1),
}))
.filter((queue, idx) => queue.tasks.length > 0 || idx === 0);
}
/**
* Check to see if `processNext` should be called.
*
* @returns {boolean} Returns true if there are tasks that are ready to be
* processed with `processNext`, or returns false if there are no more tasks
* to be processed right now, although there may be tasks in the queue that
* are blocked by earlier `PromiseTask`s that haven't resolved yet.
* `onMoreTasks` will be called after each `PromiseTask` resolves if there are
* tasks ready to run at that point.
*/
hasTasksToProcess(): boolean {
return this._getCurrentQueue().length > 0;
}
/**
* Executes the next task in the queue.
*/
processNext(): void {
const queue = this._getCurrentQueue();
if (queue.length) {
const task = queue.shift();
try {
if (typeof task === 'object' && task.gen) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: genPromise for task ' + task.name);
this._genPromise(task);
} else if (typeof task === 'object' && task.run) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: run task ' + task.name);
task.run();
} else {
invariant(
typeof task === 'function',
'Expected Function, SimpleTask, or PromiseTask, but got:\n' +
JSON.stringify(task, null, 2),
);
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: run anonymous task');
task();
}
} catch (e) {
e.message =
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
'TaskQueue: Error with task ' + (task.name || '') + ': ' + e.message;
throw e;
}
}
}
_queueStack: Array<{
tasks: Array<Task>,
popable: boolean,
...
}>;
_onMoreTasks: () => void;
_getCurrentQueue(): Array<Task> {
const stackIdx = this._queueStack.length - 1;
const queue = this._queueStack[stackIdx];
if (
queue.popable &&
queue.tasks.length === 0 &&
this._queueStack.length > 1
) {
this._queueStack.pop();
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG &&
console.log('TaskQueue: popped queue: ', {
stackIdx,
queueStackSize: this._queueStack.length,
});
return this._getCurrentQueue();
} else {
return queue.tasks;
}
}
_genPromise(task: PromiseTask) {
// Each async task pushes it's own queue onto the queue stack. This
// effectively defers execution of previously queued tasks until the promise
// resolves, at which point we allow the new queue to be popped, which
// happens once it is fully processed.
this._queueStack.push({tasks: [], popable: false});
const stackIdx = this._queueStack.length - 1;
const stackItem = this._queueStack[stackIdx];
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: push new queue: ', {stackIdx});
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: exec gen task ' + task.name);
task
.gen()
.then(() => {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG &&
console.log('TaskQueue: onThen for gen task ' + task.name, {
stackIdx,
queueStackSize: this._queueStack.length,
});
stackItem.popable = true;
this.hasTasksToProcess() && this._onMoreTasks();
})
.catch(ex => {
setTimeout(() => {
ex.message = `TaskQueue: Error resolving Promise in task ${task.name}: ${ex.message}`;
throw ex;
}, 0);
});
}
}
export default TaskQueue;
@@ -0,0 +1,342 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
* @format
*/
'use strict';
import type {ReactNativeFeatureFlagsJsOnlyOverrides} from '../../../src/private/featureflags/ReactNativeFeatureFlags';
function importModules(overrides: ReactNativeFeatureFlagsJsOnlyOverrides) {
const ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
// Make sure to setup overrides before importing any modules.
ReactNativeFeatureFlags.override(overrides);
const BatchedBridge = require('../../BatchedBridge/BatchedBridge').default;
const InteractionManager = require('../InteractionManager').default;
jest.mock('../../vendor/core/ErrorUtils');
jest.mock('../../BatchedBridge/BatchedBridge');
return {
BatchedBridge,
InteractionManager,
};
}
const isWindows = process.platform === 'win32';
const itif = (condition: boolean) => (condition ? it : it.skip);
function expectToBeCalledOnce(
fn: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
): void {
expect(fn.mock.calls.length).toBe(1);
}
describe('InteractionManager', () => {
let InteractionManager;
let interactionStart;
let interactionComplete;
beforeEach(() => {
jest.resetModules();
({InteractionManager} = importModules({
disableInteractionManager: () => false,
}));
interactionStart = jest.fn();
interactionComplete = jest.fn();
InteractionManager.addListener(
InteractionManager.Events.interactionStart,
interactionStart,
);
InteractionManager.addListener(
InteractionManager.Events.interactionComplete,
interactionComplete,
);
});
it('throws when clearing an undefined handle', () => {
// $FlowExpectedError[incompatible-call]
expect(() => InteractionManager.clearInteractionHandle()).toThrow();
});
it('notifies asynchronously when interaction starts', () => {
InteractionManager.createInteractionHandle();
expect(interactionStart).not.toBeCalled();
jest.runAllTimers();
expect(interactionStart).toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('notifies asynchronously when interaction stops', () => {
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
interactionStart.mockClear();
InteractionManager.clearInteractionHandle(handle);
expect(interactionComplete).not.toBeCalled();
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).toBeCalled();
});
it('does not notify when started & stopped in same event loop', () => {
const handle = InteractionManager.createInteractionHandle();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('does not notify when going from two -> one active interactions', () => {
InteractionManager.createInteractionHandle();
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
interactionStart.mockClear();
interactionComplete.mockClear();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('runs tasks asynchronously when there are interactions', () => {
const task = jest.fn();
InteractionManager.runAfterInteractions(task);
expect(task).not.toBeCalled();
jest.runAllTimers();
expect(task).toBeCalled();
});
it('runs tasks when interactions complete', () => {
const task = jest.fn();
const handle = InteractionManager.createInteractionHandle();
InteractionManager.runAfterInteractions(task);
jest.runAllTimers();
InteractionManager.clearInteractionHandle(handle);
expect(task).not.toBeCalled();
jest.runAllTimers();
expect(task).toBeCalled();
});
it('does not run tasks twice', () => {
const task1 = jest.fn();
const task2 = jest.fn();
InteractionManager.runAfterInteractions(task1);
jest.runAllTimers();
InteractionManager.runAfterInteractions(task2);
jest.runAllTimers();
expectToBeCalledOnce(task1);
});
it('runs tasks added while processing previous tasks', () => {
const task1 = jest.fn(() => {
InteractionManager.runAfterInteractions(task2);
});
const task2 = jest.fn();
InteractionManager.runAfterInteractions(task1);
expect(task2).not.toBeCalled();
jest.runAllTimers();
expect(task1).toBeCalled();
expect(task2).toBeCalled();
});
it('allows tasks to be cancelled', () => {
const task1 = jest.fn();
const task2 = jest.fn();
const promise1 = InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
expect(task1).not.toBeCalled();
expect(task2).not.toBeCalled();
promise1.cancel();
jest.runAllTimers();
expect(task1).not.toBeCalled();
expect(task2).toBeCalled();
});
});
describe('promise tasks', () => {
let BatchedBridge;
let InteractionManager;
let sequenceId;
function createSequenceTask(expectedSequenceId: number) {
return jest.fn(() => {
expect(++sequenceId).toBe(expectedSequenceId);
});
}
beforeEach(() => {
jest.resetModules();
({BatchedBridge, InteractionManager} = importModules({
disableInteractionManager: () => false,
}));
sequenceId = 0;
});
it('should run a basic promise task', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
return new Promise(resolve => resolve());
});
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
jest.runAllTimers();
expectToBeCalledOnce(task1);
});
it('should handle nested promises', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
return new Promise(resolve => {
InteractionManager.runAfterInteractions({
gen: task2,
name: 'gen2',
}).then(resolve);
});
});
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => resolve());
});
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
jest.runAllTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
});
it('should pause promise tasks during interactions then resume', () => {
const task1 = createSequenceTask(1);
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => {
setTimeout(() => {
InteractionManager.runAfterInteractions(task3).then(resolve);
}, 1);
});
});
const task3 = createSequenceTask(3);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
jest.runAllTimers(); // Just to be sure...
expect(task3).not.toBeCalled();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expectToBeCalledOnce(task3);
});
it('should execute tasks in loop within deadline', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(10);
const task1 = createSequenceTask(1);
const task2 = createSequenceTask(2);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
});
it('should execute tasks one at a time if deadline exceeded', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
const task1 = createSequenceTask(1);
const task2 = createSequenceTask(2);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expect(task2).not.toBeCalled();
jest.runOnlyPendingTimers(); // resolve1
jest.runOnlyPendingTimers(); // task2
expectToBeCalledOnce(task2);
});
const bigAsyncTest = resolveTest => {
jest.useRealTimers();
const task1 = createSequenceTask(1);
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => {
InteractionManager.runAfterInteractions(task3);
setTimeout(() => {
InteractionManager.runAfterInteractions({
gen: task4,
name: 'gen4',
}).then(resolve);
}, 1);
});
});
const task3 = createSequenceTask(3);
const task4 = jest.fn(() => {
expect(++sequenceId).toBe(4);
return new Promise(resolve => {
InteractionManager.runAfterInteractions(task5).then(resolve);
});
});
const task5 = createSequenceTask(5);
const task6 = createSequenceTask(6);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
InteractionManager.runAfterInteractions(task6);
setTimeout(() => {
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
expectToBeCalledOnce(task4);
expectToBeCalledOnce(task5);
expectToBeCalledOnce(task6);
resolveTest();
}, 100);
};
itif(!isWindows)(
'resolves async tasks recursively before other queued tasks',
() => {
return new Promise(bigAsyncTest);
},
);
itif(!isWindows)('should also work with a deadline', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
return new Promise(bigAsyncTest);
});
});
@@ -0,0 +1,169 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
* @format
*/
'use strict';
const Promise = require('promise');
function expectToBeCalledOnce(fn) {
expect(fn.mock.calls.length).toBe(1);
}
function clearTaskQueue(taskQueue) {
do {
jest.runAllTimers();
taskQueue.processNext();
jest.runAllTimers();
} while (taskQueue.hasTasksToProcess());
}
describe('TaskQueue', () => {
let taskQueue;
let onMoreTasks;
let sequenceId;
function createSequenceTask(expectedSequenceId) {
return jest.fn(() => {
expect(++sequenceId).toBe(expectedSequenceId);
});
}
beforeEach(() => {
jest.resetModules();
onMoreTasks = jest.fn();
const TaskQueue = require('../TaskQueue').default;
taskQueue = new TaskQueue({onMoreTasks});
sequenceId = 0;
});
it('should run a basic task', () => {
const task1 = createSequenceTask(1);
taskQueue.enqueue({run: task1, name: 'run1'});
expect(taskQueue.hasTasksToProcess()).toBe(true);
taskQueue.processNext();
expectToBeCalledOnce(task1);
});
it('should handle blocking promise task', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(1);
resolve();
}, 1);
});
});
const task2 = createSequenceTask(2);
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.enqueue({run: task2, name: 'run2'});
taskQueue.processNext();
expectToBeCalledOnce(task1);
expect(task2).not.toBeCalled();
expect(onMoreTasks).not.toBeCalled();
expect(taskQueue.hasTasksToProcess()).toBe(false);
clearTaskQueue(taskQueue);
expectToBeCalledOnce(onMoreTasks);
expectToBeCalledOnce(task2);
});
it('should handle nested simple tasks', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
taskQueue.enqueue({run: task3, name: 'run3'});
});
const task2 = createSequenceTask(2);
const task3 = createSequenceTask(3);
taskQueue.enqueue({run: task1, name: 'run1'});
taskQueue.enqueue({run: task2, name: 'run2'}); // not blocked by task 1
clearTaskQueue(taskQueue);
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
});
it('should handle nested promises', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(1);
taskQueue.enqueue({gen: task2, name: 'gen2'});
taskQueue.enqueue({run: resolve, name: 'resolve1'});
}, 1);
});
});
const task2 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(2);
taskQueue.enqueue({run: task3, name: 'run3'});
taskQueue.enqueue({run: resolve, name: 'resolve2'});
}, 1);
});
});
const task3 = createSequenceTask(3);
const task4 = createSequenceTask(4);
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.enqueue({run: task4, name: 'run4'}); // blocked by task 1 promise
clearTaskQueue(taskQueue);
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
expectToBeCalledOnce(task4);
});
it('should be able to cancel tasks', () => {
const task1 = jest.fn();
const task2 = createSequenceTask(1);
const task3 = jest.fn();
const task4 = createSequenceTask(2);
taskQueue.enqueue(task1);
taskQueue.enqueue(task2);
taskQueue.enqueue(task3);
taskQueue.enqueue(task4);
taskQueue.cancelTasks([task1, task3]);
clearTaskQueue(taskQueue);
expect(task1).not.toBeCalled();
expect(task3).not.toBeCalled();
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task4);
expect(taskQueue.hasTasksToProcess()).toBe(false);
});
it('should not crash when last task is cancelled', () => {
const task1 = jest.fn();
taskQueue.enqueue(task1);
taskQueue.cancelTasks([task1]);
clearTaskQueue(taskQueue);
expect(task1).not.toBeCalled();
expect(taskQueue.hasTasksToProcess()).toBe(false);
});
it('should not crash when task is cancelled between being started and resolved', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1);
});
});
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.processNext();
taskQueue.cancelTasks([task1]);
jest.runAllTimers();
});
});
@@ -400,6 +400,7 @@ class XMLHttpRequest extends EventTarget {
if (XMLHttpRequest._profiling) {
console.timeStamp(
'Incremental Data: ' + this._getMeasureURL(),
// $FlowFixMe[extra-arg] Add correct typing for `console.timeStamp`
start,
undefined,
PERFORMANCE_TRACK_NAME,
@@ -454,6 +455,7 @@ class XMLHttpRequest extends EventTarget {
const start = this._startTime;
console.timeStamp(
this._getMeasureURL(),
// $FlowFixMe[extra-arg] Add correct typing for `console.timeStamp`
start,
undefined,
PERFORMANCE_TRACK_NAME,
@@ -10,21 +10,21 @@
import typeof {enable} from 'promise/setimmediate/rejection-tracking';
import ExceptionsManager from './Core/ExceptionsManager';
import LogBox from './LogBox/LogBox';
let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
allRejections: true,
onUnhandled: (id, rejection) => {
onUnhandled: (id, rejection = {}) => {
let message: string;
let stack: ?string;
if (rejection === undefined) {
message = '';
} else if (
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
Object.prototype.toString.call(rejection) === '[object Error]'
) {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
const stringValue = Object.prototype.toString.call(rejection);
if (stringValue === '[object Error]') {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
message = Error.prototype.toString.call(rejection);
const error: Error = (rejection: $FlowFixMe);
stack = error.stack;
} else {
try {
message = require('pretty-format').format(rejection);
@@ -34,23 +34,37 @@ let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
? rejection
: JSON.stringify((rejection: $FlowFixMe));
}
// It could although this object is not a standard error, it still has stack information to unwind
// $FlowFixMe ignore types just check if stack is there
if (rejection?.stack && typeof rejection.stack === 'string') {
stack = rejection.stack;
}
}
ExceptionsManager.handleException(
new Error(
`Uncaught (in promise, id: ${id})${message ? `: "${message}"` : ''}`,
{
cause: rejection,
const warning = `Possible unhandled promise rejection (id: ${id}):\n${
message ?? ''
}`;
if (__DEV__) {
LogBox.addLog({
level: 'warn',
message: {
content: warning,
substitutions: [],
},
),
false /* isFatal */,
);
componentStack: [],
componentStackType: null,
stack,
category: 'possible_unhandled_promise_rejection',
});
} else {
console.warn(warning);
}
},
onHandled: id => {
const warning =
`Promise rejection handled (id: ${id})\n` +
'This means you can ignore any previous messages of the form ' +
`"Uncaught (in promise, id: ${id})"`;
`"Possible unhandled promise rejection (id: ${id}):"`;
console.warn(warning);
},
};
@@ -24,13 +24,9 @@ using namespace facebook::react;
@end
@implementation RCTPullToRefreshViewComponentView {
BOOL _isBeforeInitialLayout;
UIRefreshControl *_refreshControl;
RCTScrollViewComponentView *__weak _scrollViewComponentView;
// This variable keeps track of whether the view is recycled or not. Once the view is recycled, the component
// creates a new instance of UIRefreshControl, resetting the native props to the default values.
// However, when recycling, we are keeping around the old _props. The flag is used to force the application
// of the current props to the newly created UIRefreshControl the first time that updateProps is called.
BOOL _recycled;
}
- (instancetype)initWithFrame:(CGRect)frame
@@ -40,7 +36,8 @@ using namespace facebook::react;
// attaching and detaching of a pull-to-refresh view to a scroll view.
// The pull-to-refresh view is not a subview of this view.
self.hidden = YES;
_recycled = NO;
_isBeforeInitialLayout = YES;
[self _initializeUIRefreshControl];
}
@@ -66,53 +63,54 @@ using namespace facebook::react;
{
[super prepareForRecycle];
_scrollViewComponentView = nil;
_props = nil;
_isBeforeInitialLayout = YES;
[self _initializeUIRefreshControl];
_recycled = YES;
}
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
{
// Prop updates are ignored by _refreshControl until after the initial layout, so just store them in _props until then
if (_isBeforeInitialLayout) {
_props = std::static_pointer_cast<const PullToRefreshViewProps>(props);
return;
}
const auto &oldConcreteProps = static_cast<const PullToRefreshViewProps &>(*_props);
const auto &newConcreteProps = static_cast<const PullToRefreshViewProps &>(*props);
if (_recycled || newConcreteProps.tintColor != oldConcreteProps.tintColor) {
if (newConcreteProps.tintColor != oldConcreteProps.tintColor) {
_refreshControl.tintColor = RCTUIColorFromSharedColor(newConcreteProps.tintColor);
}
if (_recycled || newConcreteProps.progressViewOffset != oldConcreteProps.progressViewOffset) {
if (newConcreteProps.progressViewOffset != oldConcreteProps.progressViewOffset) {
[self _updateProgressViewOffset:newConcreteProps.progressViewOffset];
}
BOOL needsUpdateTitle = NO;
if (_recycled || newConcreteProps.title != oldConcreteProps.title) {
if (newConcreteProps.title != oldConcreteProps.title) {
needsUpdateTitle = YES;
}
if (_recycled || newConcreteProps.titleColor != oldConcreteProps.titleColor) {
if (newConcreteProps.titleColor != oldConcreteProps.titleColor) {
needsUpdateTitle = YES;
}
[super updateProps:props oldProps:oldProps];
if (_recycled || needsUpdateTitle) {
if (needsUpdateTitle) {
[self _updateTitle];
}
// All prop updates must happen above the call to begin refreshing, or else _refreshControl will ignore the updates
if (_recycled || newConcreteProps.refreshing != oldConcreteProps.refreshing) {
if (newConcreteProps.refreshing != oldConcreteProps.refreshing) {
if (newConcreteProps.refreshing) {
[self beginRefreshingProgrammatically];
} else {
[_refreshControl endRefreshing];
}
}
if (_recycled || newConcreteProps.zIndex != oldConcreteProps.zIndex) {
_refreshControl.layer.zPosition = newConcreteProps.zIndex.value_or(0);
}
_recycled = NO;
}
#pragma mark -
@@ -157,12 +155,10 @@ using namespace facebook::react;
// Attempts to begin refreshing before the initial layout are ignored by _refreshControl. So if the control is
// refreshing when mounted, we need to call beginRefreshing in layoutSubviews or it won't work.
if (self.window) {
const auto &concreteProps = static_cast<const PullToRefreshViewProps &>(*_props);
if (_isBeforeInitialLayout) {
_isBeforeInitialLayout = NO;
if (concreteProps.refreshing) {
[self beginRefreshingProgrammatically];
}
[self updateProps:_props oldProps:PullToRefreshViewShadowNode::defaultSharedProps()];
}
}
@@ -218,12 +214,11 @@ using namespace facebook::react;
// When refreshing programmatically (i.e. without pulling down), we must explicitly adjust the ScrollView content
// offset, or else the _refreshControl won't be visible
if (!_refreshControl.isRefreshing) {
UIScrollView *scrollView = _scrollViewComponentView.scrollView;
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - _refreshControl.frame.size.height};
[scrollView setContentOffset:offset];
[_refreshControl beginRefreshing];
}
UIScrollView *scrollView = _scrollViewComponentView.scrollView;
CGPoint offset = {scrollView.contentOffset.x, scrollView.contentOffset.y - _refreshControl.frame.size.height};
[scrollView setContentOffset:offset];
[_refreshControl beginRefreshing];
}
#pragma mark - Native commands
@@ -112,11 +112,6 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
__weak UIView *_firstVisibleView;
CGFloat _endDraggingSensitivityMultiplier;
// A flag indicating that accessibility API is used.
// It is not restored to the default value in prepareForRecycle.
// Once an accessibility API is used, view culling will be disabled for the entire session.
BOOL _isAccessibilityAPIUsed;
}
+ (RCTScrollViewComponentView *_Nullable)findScrollViewComponentViewForView:(UIView *)view
@@ -139,7 +134,6 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
_isUserTriggeredScrolling = NO;
_shouldUpdateContentInsetAdjustmentBehavior = YES;
_automaticallyAdjustKeyboardInsets = NO;
_isAccessibilityAPIUsed = NO;
[self addSubview:_scrollView];
_containerView = [[UIView alloc] initWithFrame:CGRectZero];
@@ -599,38 +593,6 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
return metrics;
}
/**
* When use of accessibility APIs is detected, view culling is disabled to make accessibility work correctly.
*/
- (void)_disableViewCullingIfNecessary
{
if (!_isAccessibilityAPIUsed) {
_isAccessibilityAPIUsed = YES;
[self _updateStateWithContentOffset];
}
}
- (NSInteger)accessibilityElementCount
{
// From empirical testing, method `accessibilityElementCount` is called lazily only
// when accessibility is used.
// Why we don't use UIAccessibilitySwitchControlStatusDidChangeNotification and
// UIAccessibilityVoiceOverStatusDidChangeNotification? The notifications are not called when using Accessibility
// Inspector. We anticipate developers will want to debug accessbility with Accessibility Inspector and don't want to
// break that developer workflow with view culling. Therefore, we are using API use detection to disable view culling
// instead of the notifications.
[self _disableViewCullingIfNecessary];
return [super accessibilityElementCount];
}
- (NSArray<id<UIFocusItem>> *)focusItemsInRect:(CGRect)rect
{
// From empirical testing, method `focusItemsInRect:` is called lazily only
// when keyboard navigation is used.
[self _disableViewCullingIfNecessary];
return [super focusItemsInRect:rect];
}
- (void)_updateStateWithContentOffset
{
if (!_state) {
@@ -638,23 +600,17 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
}
auto contentOffset = RCTPointFromCGPoint(_scrollView.contentOffset);
BOOL isAccessibilityAPIUsed = _isAccessibilityAPIUsed;
_state->updateState(
[contentOffset, isAccessibilityAPIUsed](
[contentOffset](
const ScrollViewShadowNode::ConcreteState::Data &oldData) -> ScrollViewShadowNode::ConcreteState::SharedData {
if (oldData.contentOffset == contentOffset && oldData.disableViewCulling == isAccessibilityAPIUsed) {
// avoid doing a state update if content offset and use of accessibility didn't change.
if (oldData.contentOffset == contentOffset) {
// avoid doing a state update if content offset didn't change.
return nullptr;
}
auto newData = oldData;
newData.contentOffset = contentOffset;
newData.disableViewCulling =
UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning() || isAccessibilityAPIUsed;
return std::make_shared<const ScrollViewShadowNode::ConcreteState::Data>(newData);
},
ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges()
? EventQueue::UpdateMode::unstable_Immediate
: EventQueue::UpdateMode::Asynchronous);
});
}
- (void)prepareForRecycle
@@ -9,6 +9,7 @@
#import "RCTParagraphComponentAccessibilityProvider.h"
#import <MobileCoreServices/UTCoreTypes.h>
#import <React/RCTViewAccessibilityElement.h>
#import <react/renderer/components/text/ParagraphComponentDescriptor.h>
#import <react/renderer/components/text/ParagraphProps.h>
#import <react/renderer/components/text/ParagraphState.h>
@@ -226,6 +227,10 @@ using namespace facebook::react;
for (NSObject *element in elements) {
if ([element isKindOfClass:[UIView class]] && [cooptingCandidates containsObject:((UIView *)element)]) {
return YES;
} else if (
[element isKindOfClass:[RCTViewAccessibilityElement class]] &&
[cooptingCandidates containsObject:((RCTViewAccessibilityElement *)element).view]) {
return YES;
}
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTViewComponentView.h"
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/*
* A UIAcccessibilityElement representing a RCTViewComponentView from an
* accessibility standpoint. This enables RCTViewComponentView's to reference
* themselves in `accessibilityElements` without actually being an accessibility
* element. If it were, then iOS would not call into `accessibilityElements`.
*/
@interface RCTViewAccessibilityElement : UIAccessibilityElement
@property (readonly) RCTViewComponentView *view;
- (instancetype)initWithView:(RCTViewComponentView *)view;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,83 @@
/*
* 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 "RCTViewAccessibilityElement.h"
@implementation RCTViewAccessibilityElement
- (instancetype)initWithView:(RCTViewComponentView *)view
{
if (self = [super initWithAccessibilityContainer:view]) {
_view = view;
}
return self;
}
- (CGRect)accessibilityFrame
{
return UIAccessibilityConvertFrameToScreenCoordinates(_view.bounds, _view);
}
#pragma mark - Forwarding to _view
- (NSString *)accessibilityLabel
{
return _view.accessibilityLabel;
}
- (NSString *)accessibilityValue
{
return _view.accessibilityValue;
}
- (UIAccessibilityTraits)accessibilityTraits
{
return _view.accessibilityTraits;
}
- (NSString *)accessibilityHint
{
return _view.accessibilityHint;
}
- (BOOL)accessibilityIgnoresInvertColors
{
return _view.accessibilityIgnoresInvertColors;
}
- (BOOL)shouldGroupAccessibilityChildren
{
return _view.shouldGroupAccessibilityChildren;
}
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
{
return _view.accessibilityCustomActions;
}
- (NSString *)accessibilityLanguage
{
return _view.accessibilityLanguage;
}
- (BOOL)accessibilityViewIsModal
{
return _view.accessibilityViewIsModal;
}
- (BOOL)accessibilityElementsHidden
{
return _view.accessibilityElementsHidden;
}
- (BOOL)accessibilityRespondsToUserInteraction
{
return _view.accessibilityRespondsToUserInteraction;
}
@end
@@ -6,6 +6,7 @@
*/
#import "RCTViewComponentView.h"
#import "RCTViewAccessibilityElement.h"
#import <CoreGraphics/CoreGraphics.h>
#import <QuartzCore/QuartzCore.h>
@@ -50,6 +51,8 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
UIView *_containerView;
BOOL _useCustomContainerView;
NSMutableSet<NSString *> *_accessibilityOrderNativeIDs;
NSMutableArray<NSObject *> *_accessibilityElements;
RCTViewAccessibilityElement *_axElementDescribingSelf;
}
#ifdef RCT_DYNAMIC_FRAMEWORKS
@@ -402,11 +405,7 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
[_accessibilityOrderNativeIDs addObject:RCTNSStringFromString(childId)];
}
// If we are prop updating and have children we can go ahead and assign this prop.
// Otherwise, we might not have children attached yet and need to wait before then.
if (self.currentContainerView.subviews.count > 0) {
[self updateAccessibilityElements];
}
_accessibilityElements = [NSMutableArray new];
}
// `accessibilityTraits`
@@ -618,6 +617,7 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
_isJSResponder = NO;
_removeClippedSubviews = NO;
_reactSubviews = [NSMutableArray new];
_accessibilityElements = [NSMutableArray new];
}
- (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(NSSet<NSString *> *_Nullable)props
@@ -1149,37 +1149,43 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
return self;
}
- (void)didMoveToSuperview
- (NSArray<NSObject *> *)accessibilityElements
{
// At this point we are guaranteed to have subviews, if we are going to have them
if (ReactNativeFeatureFlags::enableAccessibilityOrder()) {
[self updateAccessibilityElements];
if ([_accessibilityOrderNativeIDs count] <= 0) {
return super.accessibilityElements;
}
}
- (void)updateAccessibilityElements
{
if ([_accessibilityOrderNativeIDs count] == 0) {
self.accessibilityElements = nil;
return;
// TODO: Currently this ignores changes to descendant nativeID's. While that should rarely, if ever happen, it's an
// edge case we should address. Currently this fixes some app deaths so landing this without addressing that edge case
// for now.
if ([_accessibilityElements count] > 0) {
return _accessibilityElements;
}
NSMutableDictionary<NSString *, UIView *> *nativeIdToView = [NSMutableDictionary new];
[RCTViewComponentView collectAccessibilityElements:self
intoDictionary:nativeIdToView
nativeIds:_accessibilityOrderNativeIDs];
NSMutableArray *accessibilityElements = [NSMutableArray new];
for (const auto &childId : _props->accessibilityOrder) {
for (auto childId : _props->accessibilityOrder) {
NSString *nsStringChildId = RCTNSStringFromString(childId);
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:nsStringChildId];
if (viewWithMatchingNativeId != nil) {
[accessibilityElements addObject:viewWithMatchingNativeId];
// Special case to allow for self-referencing with accessibilityOrder
if ([nsStringChildId isEqualToString:self.nativeId]) {
if (!_axElementDescribingSelf) {
_axElementDescribingSelf = [[RCTViewAccessibilityElement alloc] initWithView:self];
}
_axElementDescribingSelf.isAccessibilityElement = [super isAccessibilityElement];
[_accessibilityElements addObject:_axElementDescribingSelf];
} else {
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:nsStringChildId];
if (viewWithMatchingNativeId) {
[_accessibilityElements addObject:viewWithMatchingNativeId];
}
}
}
self.accessibilityElements = accessibilityElements;
return _accessibilityElements;
}
+ (void)collectAccessibilityElements:(UIView *)view
@@ -1246,6 +1252,13 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
return self.contentView.isAccessibilityElement;
}
// If we reference ourselves in accessibilityOrder then we will make a
// UIAccessibilityElement object to represent ourselves since returning YES
// here would mean iOS would not call into accessibilityElements
if ([_accessibilityOrderNativeIDs containsObject:self.nativeId]) {
return NO;
}
return [super isAccessibilityElement];
}
@@ -31,8 +31,8 @@ public abstract class com/facebook/react/HeadlessJsTaskService : android/app/Ser
public fun <init> ()V
public static final fun acquireWakeLockNow (Landroid/content/Context;)V
protected final fun getReactContext ()Lcom/facebook/react/bridge/ReactContext;
protected fun getReactHost ()Lcom/facebook/react/ReactHost;
protected fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
protected final fun getReactHost ()Lcom/facebook/react/ReactHost;
protected final fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
protected fun getTaskConfig (Landroid/content/Intent;)Lcom/facebook/react/jstasks/HeadlessJsTaskConfig;
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
public fun onDestroy ()V
@@ -19,7 +19,6 @@ plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.download)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.ktfmt)
}
version = project.findProperty("VERSION_NAME")?.toString()!!
@@ -621,7 +620,6 @@ dependencies {
api(libs.androidx.autofill)
api(libs.androidx.swiperefreshlayout)
api(libs.androidx.tracing)
api(libs.androidx.window)
api(libs.fbjni)
api(libs.fresco)
@@ -113,7 +113,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
* somewhere.
*/
@Suppress("DEPRECATION")
protected open val reactNativeHost: ReactNativeHost
protected val reactNativeHost: ReactNativeHost
get() = (application as ReactApplication).reactNativeHost
/**
@@ -121,7 +121,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
* [ReactApplication] and calls [ReactApplication.reactHost]. This method assumes it is called in
* new architecture and returns null if not.
*/
protected open val reactHost: ReactHost?
protected val reactHost: ReactHost?
get() = (application as ReactApplication).reactHost
protected val reactContext: ReactContext?
@@ -135,10 +135,6 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private void init() {
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
setClipChildren(false);
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
}
}
@Override
@@ -869,7 +865,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private int mDeviceRotation = 0;
/* package */ CustomGlobalLayoutListener() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext());
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
mVisibleViewArea = new Rect();
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
}
@@ -992,7 +988,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
return;
}
mDeviceRotation = rotation;
DisplayMetricsHolder.initDisplayMetrics(getContext());
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
emitOrientationChanged(rotation);
}
@@ -8,7 +8,6 @@
package com.facebook.react.bridge
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import java.util.ArrayList
import java.util.Arrays
import kotlin.jvm.JvmStatic
@@ -66,16 +65,9 @@ public open class ReadableNativeArray protected constructor() : NativeArray(), R
if (other !is ReadableNativeArray) {
return false
}
return if (ReactNativeFeatureFlags.useNativeEqualsInNativeReadableArrayAndroid()) {
nativeEquals(other)
} else {
localArray.contentDeepEquals(other.localArray)
}
return localArray.contentDeepEquals(other.localArray)
}
private external fun nativeEquals(other: ReadableNativeArray): Boolean
override fun toArrayList(): ArrayList<Any?> {
val arrayList = ArrayList<Any?>()
repeat(size()) { i ->
@@ -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<<a90efac589511beb130c499e51150de8>>
* @generated SignedSource<<f45b66a7bbafef525b79c657522d062e>>
*/
/**
@@ -54,12 +54,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun disableMountItemReorderingAndroid(): Boolean = accessor.disableMountItemReorderingAndroid()
/**
* Disable some workarounds for old Android versions in TextLayoutManager logic for retrieving attachment metrics
*/
@JvmStatic
public fun disableOldAndroidAttachmentMetricsWorkarounds(): Boolean = accessor.disableOldAndroidAttachmentMetricsWorkarounds()
/**
* Turns off the global measurement cache used by TextLayoutManager on Android.
*/
@@ -162,12 +156,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableIOSViewClipToPaddingBox(): Boolean = accessor.enableIOSViewClipToPaddingBox()
/**
* Dispatches state updates for content offset changes synchronously on the main thread.
*/
@JvmStatic
public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = accessor.enableImmediateUpdateModeForContentOffsetChanges()
/**
* This is to fix the issue with interop view manager where component descriptor lookup is causing ViewManager to preload.
*/
@@ -234,6 +222,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableResourceTimingAPI(): Boolean = accessor.enableResourceTimingAPI()
/**
* Dispatches state updates synchronously in Fabric (e.g.: updates the scroll position in the shadow tree synchronously from the main thread).
*/
@JvmStatic
public fun enableSynchronousStateUpdates(): Boolean = accessor.enableSynchronousStateUpdates()
/**
* Enables View Culling: as soon as a view goes off screen, it can be reused anywhere in the UI and pieced together with other items to create new UI elements.
*/
@@ -306,24 +300,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun preparedTextCacheSize(): Double = accessor.preparedTextCacheSize()
/**
* Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again.
*/
@JvmStatic
public fun preventShadowTreeCommitExhaustionWithLocking(): Boolean = accessor.preventShadowTreeCommitExhaustionWithLocking()
/**
* Releases the cached image data when it is consumed by the observers.
*/
@JvmStatic
public fun releaseImageDataWhenConsumed(): Boolean = accessor.releaseImageDataWhenConsumed()
/**
* Skip activity identity assertion in ReactHostImpl::onHostPause()
*/
@JvmStatic
public fun skipActivityIdentityAssertionOnHostPause(): Boolean = accessor.skipActivityIdentityAssertionOnHostPause()
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
@@ -348,18 +324,6 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun useFabricInterop(): Boolean = accessor.useFabricInterop()
/**
* Use a native implementation of equals in NativeReadableArray.
*/
@JvmStatic
public fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = accessor.useNativeEqualsInNativeReadableArrayAndroid()
/**
* Use a native implementation of TransformHelper
*/
@JvmStatic
public fun useNativeTransformHelperAndroid(): Boolean = accessor.useNativeTransformHelperAndroid()
/**
* When enabled, the native view configs are used in bridgeless mode.
*/
@@ -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<<b32f66fb09971e786dd1380bbf417720>>
* @generated SignedSource<<455793458c7d1f7c3efb33a3ae2c017c>>
*/
/**
@@ -24,7 +24,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var cxxNativeAnimatedEnabledCache: Boolean? = null
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
private var disableMountItemReorderingAndroidCache: Boolean? = null
private var disableOldAndroidAttachmentMetricsWorkaroundsCache: Boolean? = null
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
private var enableAccessibilityOrderCache: Boolean? = null
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
@@ -42,7 +41,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null
private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
@@ -54,6 +52,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enablePreparedTextLayoutCache: Boolean? = null
private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null
private var enableResourceTimingAPICache: Boolean? = null
private var enableSynchronousStateUpdatesCache: Boolean? = null
private var enableViewCullingCache: Boolean? = null
private var enableViewRecyclingCache: Boolean? = null
private var enableViewRecyclingForTextCache: Boolean? = null
@@ -66,15 +65,10 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var fuseboxNetworkInspectionEnabledCache: Boolean? = null
private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null
private var preparedTextCacheSizeCache: Double? = null
private var preventShadowTreeCommitExhaustionWithLockingCache: Boolean? = null
private var releaseImageDataWhenConsumedCache: Boolean? = null
private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
private var useFabricInteropCache: Boolean? = null
private var useNativeEqualsInNativeReadableArrayAndroidCache: Boolean? = null
private var useNativeTransformHelperAndroidCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
private var useRawPropsJsiValueCache: Boolean? = null
@@ -119,15 +113,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun disableOldAndroidAttachmentMetricsWorkarounds(): Boolean {
var cached = disableOldAndroidAttachmentMetricsWorkaroundsCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.disableOldAndroidAttachmentMetricsWorkarounds()
disableOldAndroidAttachmentMetricsWorkaroundsCache = cached
}
return cached
}
override fun disableTextLayoutManagerCacheAndroid(): Boolean {
var cached = disableTextLayoutManagerCacheAndroidCache
if (cached == null) {
@@ -281,15 +266,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean {
var cached = enableImmediateUpdateModeForContentOffsetChangesCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableImmediateUpdateModeForContentOffsetChanges()
enableImmediateUpdateModeForContentOffsetChangesCache = cached
}
return cached
}
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean {
var cached = enableInteropViewManagerClassLookUpOptimizationIOSCache
if (cached == null) {
@@ -389,6 +365,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableSynchronousStateUpdates(): Boolean {
var cached = enableSynchronousStateUpdatesCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableSynchronousStateUpdates()
enableSynchronousStateUpdatesCache = cached
}
return cached
}
override fun enableViewCulling(): Boolean {
var cached = enableViewCullingCache
if (cached == null) {
@@ -497,33 +482,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean {
var cached = preventShadowTreeCommitExhaustionWithLockingCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.preventShadowTreeCommitExhaustionWithLocking()
preventShadowTreeCommitExhaustionWithLockingCache = cached
}
return cached
}
override fun releaseImageDataWhenConsumed(): Boolean {
var cached = releaseImageDataWhenConsumedCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.releaseImageDataWhenConsumed()
releaseImageDataWhenConsumedCache = cached
}
return cached
}
override fun skipActivityIdentityAssertionOnHostPause(): Boolean {
var cached = skipActivityIdentityAssertionOnHostPauseCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.skipActivityIdentityAssertionOnHostPause()
skipActivityIdentityAssertionOnHostPauseCache = cached
}
return cached
}
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean {
var cached = traceTurboModulePromiseRejectionsOnAndroidCache
if (cached == null) {
@@ -560,24 +518,6 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean {
var cached = useNativeEqualsInNativeReadableArrayAndroidCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.useNativeEqualsInNativeReadableArrayAndroid()
useNativeEqualsInNativeReadableArrayAndroidCache = cached
}
return cached
}
override fun useNativeTransformHelperAndroid(): Boolean {
var cached = useNativeTransformHelperAndroidCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.useNativeTransformHelperAndroid()
useNativeTransformHelperAndroidCache = cached
}
return cached
}
override fun useNativeViewConfigsInBridgelessMode(): Boolean {
var cached = useNativeViewConfigsInBridgelessModeCache
if (cached == null) {
@@ -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<<d1da48f826bc6a1793d1630cb89cb5c1>>
* @generated SignedSource<<9588b972841baa23bd3bb3b7326b4710>>
*/
/**
@@ -36,8 +36,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun disableMountItemReorderingAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun disableOldAndroidAttachmentMetricsWorkarounds(): Boolean
@DoNotStrip @JvmStatic public external fun disableTextLayoutManagerCacheAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun enableAccessibilityOrder(): Boolean
@@ -72,8 +70,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableIOSViewClipToPaddingBox(): Boolean
@DoNotStrip @JvmStatic public external fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean
@DoNotStrip @JvmStatic public external fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnAndroid(): Boolean
@@ -96,6 +92,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableResourceTimingAPI(): Boolean
@DoNotStrip @JvmStatic public external fun enableSynchronousStateUpdates(): Boolean
@DoNotStrip @JvmStatic public external fun enableViewCulling(): Boolean
@DoNotStrip @JvmStatic public external fun enableViewRecycling(): Boolean
@@ -120,12 +118,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun preparedTextCacheSize(): Double
@DoNotStrip @JvmStatic public external fun preventShadowTreeCommitExhaustionWithLocking(): Boolean
@DoNotStrip @JvmStatic public external fun releaseImageDataWhenConsumed(): Boolean
@DoNotStrip @JvmStatic public external fun skipActivityIdentityAssertionOnHostPause(): Boolean
@DoNotStrip @JvmStatic public external fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean
@@ -134,10 +126,6 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun useFabricInterop(): Boolean
@DoNotStrip @JvmStatic public external fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun useNativeTransformHelperAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): Boolean
@DoNotStrip @JvmStatic public external fun useOptimizedEventBatchingOnAndroid(): Boolean
@@ -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<<c5ed8a904dcda7ade87527233b879b92>>
* @generated SignedSource<<7b6a844d99839c72647d6685d72f6896>>
*/
/**
@@ -31,8 +31,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun disableMountItemReorderingAndroid(): Boolean = false
override fun disableOldAndroidAttachmentMetricsWorkarounds(): Boolean = true
override fun disableTextLayoutManagerCacheAndroid(): Boolean = false
override fun enableAccessibilityOrder(): Boolean = false
@@ -67,8 +65,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableIOSViewClipToPaddingBox(): Boolean = false
override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = false
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean = false
override fun enableLayoutAnimationsOnAndroid(): Boolean = false
@@ -91,6 +87,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableResourceTimingAPI(): Boolean = false
override fun enableSynchronousStateUpdates(): Boolean = false
override fun enableViewCulling(): Boolean = false
override fun enableViewRecycling(): Boolean = false
@@ -101,7 +99,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableVirtualViewDebugFeatures(): Boolean = false
override fun enableVirtualViewRenderState(): Boolean = true
override fun enableVirtualViewRenderState(): Boolean = false
override fun enableVirtualViewWindowFocusDetection(): Boolean = false
@@ -115,12 +113,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun preparedTextCacheSize(): Double = 200.0
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean = false
override fun releaseImageDataWhenConsumed(): Boolean = false
override fun skipActivityIdentityAssertionOnHostPause(): Boolean = false
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean = false
override fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean = false
@@ -129,10 +121,6 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun useFabricInterop(): Boolean = true
override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = false
override fun useNativeTransformHelperAndroid(): Boolean = false
override fun useNativeViewConfigsInBridgelessMode(): Boolean = false
override fun useOptimizedEventBatchingOnAndroid(): Boolean = false
@@ -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<<23605f090bfbebe911caa9d3d834d3e8>>
* @generated SignedSource<<d6024891cd1852741976e94cd6d637c6>>
*/
/**
@@ -28,7 +28,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var cxxNativeAnimatedEnabledCache: Boolean? = null
private var cxxNativeAnimatedRemoveJsSyncCache: Boolean? = null
private var disableMountItemReorderingAndroidCache: Boolean? = null
private var disableOldAndroidAttachmentMetricsWorkaroundsCache: Boolean? = null
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
private var enableAccessibilityOrderCache: Boolean? = null
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
@@ -46,7 +45,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null
private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
@@ -58,6 +56,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enablePreparedTextLayoutCache: Boolean? = null
private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null
private var enableResourceTimingAPICache: Boolean? = null
private var enableSynchronousStateUpdatesCache: Boolean? = null
private var enableViewCullingCache: Boolean? = null
private var enableViewRecyclingCache: Boolean? = null
private var enableViewRecyclingForTextCache: Boolean? = null
@@ -70,15 +69,10 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var fuseboxNetworkInspectionEnabledCache: Boolean? = null
private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null
private var preparedTextCacheSizeCache: Double? = null
private var preventShadowTreeCommitExhaustionWithLockingCache: Boolean? = null
private var releaseImageDataWhenConsumedCache: Boolean? = null
private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
private var useFabricInteropCache: Boolean? = null
private var useNativeEqualsInNativeReadableArrayAndroidCache: Boolean? = null
private var useNativeTransformHelperAndroidCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
private var useRawPropsJsiValueCache: Boolean? = null
@@ -127,16 +121,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun disableOldAndroidAttachmentMetricsWorkarounds(): Boolean {
var cached = disableOldAndroidAttachmentMetricsWorkaroundsCache
if (cached == null) {
cached = currentProvider.disableOldAndroidAttachmentMetricsWorkarounds()
accessedFeatureFlags.add("disableOldAndroidAttachmentMetricsWorkarounds")
disableOldAndroidAttachmentMetricsWorkaroundsCache = cached
}
return cached
}
override fun disableTextLayoutManagerCacheAndroid(): Boolean {
var cached = disableTextLayoutManagerCacheAndroidCache
if (cached == null) {
@@ -307,16 +291,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean {
var cached = enableImmediateUpdateModeForContentOffsetChangesCache
if (cached == null) {
cached = currentProvider.enableImmediateUpdateModeForContentOffsetChanges()
accessedFeatureFlags.add("enableImmediateUpdateModeForContentOffsetChanges")
enableImmediateUpdateModeForContentOffsetChangesCache = cached
}
return cached
}
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean {
var cached = enableInteropViewManagerClassLookUpOptimizationIOSCache
if (cached == null) {
@@ -427,6 +401,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableSynchronousStateUpdates(): Boolean {
var cached = enableSynchronousStateUpdatesCache
if (cached == null) {
cached = currentProvider.enableSynchronousStateUpdates()
accessedFeatureFlags.add("enableSynchronousStateUpdates")
enableSynchronousStateUpdatesCache = cached
}
return cached
}
override fun enableViewCulling(): Boolean {
var cached = enableViewCullingCache
if (cached == null) {
@@ -547,36 +531,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean {
var cached = preventShadowTreeCommitExhaustionWithLockingCache
if (cached == null) {
cached = currentProvider.preventShadowTreeCommitExhaustionWithLocking()
accessedFeatureFlags.add("preventShadowTreeCommitExhaustionWithLocking")
preventShadowTreeCommitExhaustionWithLockingCache = cached
}
return cached
}
override fun releaseImageDataWhenConsumed(): Boolean {
var cached = releaseImageDataWhenConsumedCache
if (cached == null) {
cached = currentProvider.releaseImageDataWhenConsumed()
accessedFeatureFlags.add("releaseImageDataWhenConsumed")
releaseImageDataWhenConsumedCache = cached
}
return cached
}
override fun skipActivityIdentityAssertionOnHostPause(): Boolean {
var cached = skipActivityIdentityAssertionOnHostPauseCache
if (cached == null) {
cached = currentProvider.skipActivityIdentityAssertionOnHostPause()
accessedFeatureFlags.add("skipActivityIdentityAssertionOnHostPause")
skipActivityIdentityAssertionOnHostPauseCache = cached
}
return cached
}
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean {
var cached = traceTurboModulePromiseRejectionsOnAndroidCache
if (cached == null) {
@@ -617,26 +571,6 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean {
var cached = useNativeEqualsInNativeReadableArrayAndroidCache
if (cached == null) {
cached = currentProvider.useNativeEqualsInNativeReadableArrayAndroid()
accessedFeatureFlags.add("useNativeEqualsInNativeReadableArrayAndroid")
useNativeEqualsInNativeReadableArrayAndroidCache = cached
}
return cached
}
override fun useNativeTransformHelperAndroid(): Boolean {
var cached = useNativeTransformHelperAndroidCache
if (cached == null) {
cached = currentProvider.useNativeTransformHelperAndroid()
accessedFeatureFlags.add("useNativeTransformHelperAndroid")
useNativeTransformHelperAndroidCache = cached
}
return cached
}
override fun useNativeViewConfigsInBridgelessMode(): Boolean {
var cached = useNativeViewConfigsInBridgelessModeCache
if (cached == null) {
@@ -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<<780793412b76f101be1569d7a866c435>>
* @generated SignedSource<<391d35ccb9b7004fd8d9643df9a08ab5>>
*/
/**
@@ -31,8 +31,6 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun disableMountItemReorderingAndroid(): Boolean
@DoNotStrip public fun disableOldAndroidAttachmentMetricsWorkarounds(): Boolean
@DoNotStrip public fun disableTextLayoutManagerCacheAndroid(): Boolean
@DoNotStrip public fun enableAccessibilityOrder(): Boolean
@@ -67,8 +65,6 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableIOSViewClipToPaddingBox(): Boolean
@DoNotStrip public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean
@DoNotStrip public fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean
@DoNotStrip public fun enableLayoutAnimationsOnAndroid(): Boolean
@@ -91,6 +87,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableResourceTimingAPI(): Boolean
@DoNotStrip public fun enableSynchronousStateUpdates(): Boolean
@DoNotStrip public fun enableViewCulling(): Boolean
@DoNotStrip public fun enableViewRecycling(): Boolean
@@ -115,12 +113,6 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun preparedTextCacheSize(): Double
@DoNotStrip public fun preventShadowTreeCommitExhaustionWithLocking(): Boolean
@DoNotStrip public fun releaseImageDataWhenConsumed(): Boolean
@DoNotStrip public fun skipActivityIdentityAssertionOnHostPause(): Boolean
@DoNotStrip public fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean
@DoNotStrip public fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean
@@ -129,10 +121,6 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun useFabricInterop(): Boolean
@DoNotStrip public fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean
@DoNotStrip public fun useNativeTransformHelperAndroid(): Boolean
@DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): Boolean
@DoNotStrip public fun useOptimizedEventBatchingOnAndroid(): Boolean
@@ -23,6 +23,10 @@ import com.facebook.react.common.build.ReactBuildConfig
@SuppressLint("UseReactNativeNewArchitectureFeatureFlagDetector")
public object ReactNativeNewArchitectureFeatureFlags {
@JvmStatic
public fun isNewArchitectureStrictModeEnabled(): Boolean =
ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE
@JvmStatic
public fun enableBridgelessArchitecture(): Boolean {
if (ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE) {
@@ -35,6 +35,7 @@ import java.util.HashMap
import java.util.UUID
import okhttp3.MediaType
import okhttp3.RequestBody
import okhttp3.ResponseBody
import okio.ByteString
@ReactModule(name = NativeBlobModuleSpec.NAME)
@@ -71,7 +72,7 @@ public class BlobModule(reactContext: ReactApplicationContext) :
return !isRemote && responseType == "blob"
}
override fun fetch(uri: Uri): Pair<WritableMap, ByteArray> {
override fun fetch(uri: Uri): WritableMap {
val data = getBytesFromUri(uri)
val blob = Arguments.createMap()
@@ -84,7 +85,7 @@ public class BlobModule(reactContext: ReactApplicationContext) :
blob.putString("name", getNameFromUri(uri))
blob.putDouble("lastModified", getLastModifiedFromUri(uri))
return blob to data
return blob
}
}
@@ -118,7 +119,8 @@ public class BlobModule(reactContext: ReactApplicationContext) :
return responseType == "blob"
}
override fun toResponseData(data: ByteArray): WritableMap {
override fun toResponseData(body: ResponseBody): WritableMap {
val data = body.bytes()
val blob = Arguments.createMap()
blob.putString("blobId", store(data))
blob.putInt("offset", 0)
@@ -1,90 +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.modules.network
import com.facebook.proguard.annotations.DoNotStripAny
/**
* [Experimental] An interface for reporting network events to the modern debugger server and Web
* Performance APIs.
*
* In a production (non dev or profiling) build, CDP reporting is disabled.
*
* This is a helper class wrapping `facebook::react::jsinspector_modern::NetworkReporter`.
*/
@DoNotStripAny
internal object InspectorNetworkReporter {
/**
* Report a network request that is about to be sent.
* - Corresponds to `Network.requestWillBeSent` in CDP.
* - Corresponds to `PerformanceResourceTiming.requestStart` (specifically, marking when the
* native request was initiated).
*/
@JvmStatic
external fun reportRequestStart(
requestId: Int,
requestUrl: String,
requestMethod: String,
requestHeaders: Map<String, String>,
requestBody: String,
encodedDataLength: Long
)
/**
* Report detailed timing info, such as DNS lookup, when a request has started.
* - Corresponds to `Network.requestWillBeSentExtraInfo` in CDP.
* - Corresponds to `PerformanceResourceTiming.domainLookupStart`,
* `PerformanceResourceTiming.connectStart`.
*/
@JvmStatic external fun reportConnectionTiming(requestId: Int, headers: Map<String, String>)
/**
* Report when HTTP response headers have been received, corresponding to when the first byte of
* the response is available.
* - Corresponds to `Network.responseReceived` in CDP.
* - Corresponds to `PerformanceResourceTiming.responseStart`.
*/
@JvmStatic
external fun reportResponseStart(
requestId: Int,
requestUrl: String,
responseStatus: Int,
responseHeaders: Map<String, String>,
expectedDataLength: Long
)
/**
* Report when additional chunks of the response body have been received.
*
* Corresponds to `Network.dataReceived` in CDP.
*/
@JvmStatic external fun reportDataReceived(requestId: Int, dataLength: Int)
/**
* Report when a network request is complete and we are no longer receiving response data.
* - Corresponds to `Network.loadingFinished` in CDP.
* - Corresponds to `PerformanceResourceTiming.responseEnd`.
*/
@JvmStatic external fun reportResponseEnd(requestId: Int, encodedDataLength: Long)
/**
* Store response body preview. This is an optional reporting method, and is a no-op if CDP
* debugging is disabled.
*/
@JvmStatic
external fun maybeStoreResponseBody(requestId: Int, body: String, base64Encoded: Boolean)
/**
* Incrementally store a response body preview, when a string response is received in chunks.
* Buffered contents will be flushed to `NetworkReporter` with `reportResponseEnd`.
*
* As with `maybeStoreResponseBody`, calling this method is optional and a no-op if CDP debugging
* is disabled.
*/
@JvmStatic external fun maybeStoreResponseBodyIncremental(requestId: Int, data: String)
}
@@ -1,250 +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.
*/
@file:Suppress("DEPRECATION_ERROR") // Conflicting okhttp versions
package com.facebook.react.modules.network
import android.os.Bundle
import android.util.Base64
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.buildReadableArray
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import java.net.SocketTimeoutException
import okhttp3.Headers
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
/**
* Utility class for reporting network lifecycle events to JavaScript and InspectorNetworkReporter.
*/
internal object NetworkEventUtil {
@JvmStatic
fun onCreateRequest(requestId: Int, request: Request) {
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
val headersMap = okHttpHeadersToMap(request.headers())
InspectorNetworkReporter.reportRequestStart(
requestId,
request.url().toString(),
request.method(),
headersMap,
request.body()?.toString().orEmpty(),
request.body()?.contentLength() ?: 0,
)
InspectorNetworkReporter.reportConnectionTiming(requestId, headersMap)
}
}
@JvmStatic
fun onDataSend(
reactContext: ReactApplicationContext?,
requestId: Int,
progress: Long,
total: Long
) {
reactContext?.emitDeviceEvent(
"didSendNetworkData",
buildReadableArray {
add(requestId)
add(progress.toInt())
add(total.toInt())
})
}
@JvmStatic
fun onIncrementalDataReceived(
reactContext: ReactApplicationContext?,
requestId: Int,
data: String?,
progress: Long,
total: Long
) {
if (ReactNativeFeatureFlags.enableNetworkEventReporting() && data != null) {
InspectorNetworkReporter.reportDataReceived(requestId, data.encodeToByteArray().size)
InspectorNetworkReporter.maybeStoreResponseBodyIncremental(requestId, data)
}
reactContext?.emitDeviceEvent(
"didReceiveNetworkIncrementalData",
buildReadableArray {
add(requestId)
add(data)
add(progress.toInt())
add(total.toInt())
})
}
@JvmStatic
fun onDataReceivedProgress(
reactContext: ReactApplicationContext?,
requestId: Int,
progress: Long,
total: Long
) {
reactContext?.emitDeviceEvent(
"didReceiveNetworkDataProgress",
buildReadableArray {
add(requestId)
add(progress.toInt())
add(total.toInt())
})
}
@JvmStatic
fun onDataReceived(
reactContext: ReactApplicationContext?,
requestId: Int,
data: String?,
responseType: String
) {
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
InspectorNetworkReporter.maybeStoreResponseBody(
requestId, data.orEmpty(), responseType == "base64")
}
reactContext?.emitDeviceEvent(
"didReceiveNetworkData",
buildReadableArray {
add(requestId)
add(data)
})
}
@JvmStatic
fun onDataReceived(
reactContext: ReactApplicationContext?,
requestId: Int,
data: WritableMap,
rawData: ByteArray
) {
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
InspectorNetworkReporter.maybeStoreResponseBody(
requestId, Base64.encodeToString(rawData, Base64.NO_WRAP), true)
}
reactContext?.emitDeviceEvent(
"didReceiveNetworkData",
Arguments.createArray().apply {
pushInt(requestId)
pushMap(data)
})
}
@JvmStatic
fun onRequestError(
reactContext: ReactApplicationContext?,
requestId: Int,
error: String?,
e: Throwable?
) {
reactContext?.emitDeviceEvent(
"didCompleteNetworkResponse",
buildReadableArray {
add(requestId)
add(error)
if (e?.javaClass == SocketTimeoutException::class.java) {
add(true) // last argument is a time out boolean
}
})
}
@JvmStatic
fun onRequestSuccess(
reactContext: ReactApplicationContext?,
requestId: Int,
encodedDataLength: Long
) {
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
InspectorNetworkReporter.reportResponseEnd(requestId, encodedDataLength)
}
reactContext?.emitDeviceEvent(
"didCompleteNetworkResponse",
buildReadableArray {
add(requestId)
addNull()
})
}
@JvmStatic
fun onResponseReceived(
reactContext: ReactApplicationContext?,
requestId: Int,
requestUrl: String?,
response: Response,
) {
val headersMap = okHttpHeadersToMap(response.headers())
val headersBundle = Bundle()
for ((headerName, headerValue) in headersMap) {
headersBundle.putString(headerName, headerValue)
}
if (ReactNativeFeatureFlags.enableNetworkEventReporting()) {
InspectorNetworkReporter.reportResponseStart(
requestId,
requestUrl.orEmpty(),
response.code(),
headersMap,
response.body()?.contentLength() ?: 0,
)
}
reactContext?.emitDeviceEvent(
"didReceiveNetworkResponse",
Arguments.createArray().apply {
pushInt(requestId)
pushInt(response.code())
pushMap(Arguments.fromBundle(headersBundle))
pushString(requestUrl)
})
}
@Deprecated("Compatibility overload")
@JvmStatic
fun onResponseReceived(
reactContext: ReactApplicationContext?,
requestId: Int,
statusCode: Int,
headers: WritableMap?,
url: String?
) {
val headersBuilder = Headers.Builder()
headers?.let { map ->
val iterator = map.keySetIterator()
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
val value = map.getString(key)
if (value != null) {
headersBuilder.add(key, value)
}
}
}
onResponseReceived(
reactContext,
requestId,
url,
Response.Builder()
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url(url.orEmpty()).build())
.headers(headersBuilder.build())
.code(statusCode)
.message("")
.build())
}
private fun okHttpHeadersToMap(headers: Headers): Map<String, String> {
val responseHeaders = mutableMapOf<String, String>()
for (i in 0 until headers.size()) {
val headerName = headers.name(i)
// multiple values for the same header
if (responseHeaders.containsKey(headerName)) {
responseHeaders[headerName] = "${responseHeaders[headerName]}, ${headers.value(i)}"
} else {
responseHeaders[headerName] = headers.value(i)
}
}
return responseHeaders
}
}
@@ -11,9 +11,11 @@
package com.facebook.react.modules.network
import android.net.Uri
import android.os.Bundle
import android.util.Base64
import com.facebook.common.logging.FLog
import com.facebook.fbreact.specs.NativeNetworkingAndroidSpec
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableArray
@@ -34,7 +36,6 @@ import okhttp3.JavaNetCookieJar
import okhttp3.MediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response
@@ -60,10 +61,8 @@ public class NetworkingModule(
/** Returns if the handler should be used for an URI. */
public fun supports(uri: Uri, responseType: String): Boolean
/**
* Fetch the URI and return a tuple containing the JS body payload and the raw response body.
*/
@Throws(IOException::class) public fun fetch(uri: Uri): Pair<WritableMap, ByteArray>
/** Fetch the URI and return the JS body payload. */
@Throws(IOException::class) public fun fetch(uri: Uri): WritableMap
}
/** Allows adding custom handling to build the [RequestBody] from the JS body payload. */
@@ -81,7 +80,7 @@ public class NetworkingModule(
public fun supports(responseType: String): Boolean
/** Returns the JS body payload for the [ResponseBody]. */
@Throws(IOException::class) public fun toResponseData(data: ByteArray): WritableMap
@Throws(IOException::class) public fun toResponseData(body: ResponseBody): WritableMap
}
private val client: OkHttpClient
@@ -232,7 +231,7 @@ public class NetworkingModule(
} catch (th: Throwable) {
FLog.e(TAG, "Failed to send url request: $url", th)
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
getReactApplicationContextIfActiveOrWarn(), requestId, th.message, th)
}
}
@@ -256,26 +255,18 @@ public class NetworkingModule(
// Check if a handler is registered
for (handler in uriHandlers) {
if (handler.supports(uri, responseType)) {
val (res, rawBody) = handler.fetch(uri)
val encodedDataLength = res.toString().toByteArray().size
val res = handler.fetch(uri)
// fix: UriHandlers which are not using file:// scheme fail in whatwg-fetch at this line
// https://github.com/JakeChampion/fetch/blob/main/fetch.js#L547
val response =
Response.Builder()
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url(url.orEmpty()).build())
.code(200)
.message("OK")
.build()
NetworkEventUtil.onResponseReceived(reactApplicationContext, requestId, url, response)
NetworkEventUtil.onDataReceived(reactApplicationContext, requestId, res, rawBody)
NetworkEventUtil.onRequestSuccess(
reactApplicationContext, requestId, encodedDataLength.toLong())
ResponseUtil.onResponseReceived(
reactApplicationContext, requestId, 200, Arguments.createMap(), url)
ResponseUtil.onDataReceived(reactApplicationContext, requestId, res)
ResponseUtil.onRequestSuccess(reactApplicationContext, requestId)
return
}
}
} catch (e: IOException) {
NetworkEventUtil.onRequestError(reactApplicationContext, requestId, e.message, e)
ResponseUtil.onRequestError(reactApplicationContext, requestId, e.message, e)
return
}
@@ -283,7 +274,7 @@ public class NetworkingModule(
try {
requestBuilder = Request.Builder().url(url.orEmpty())
} catch (e: Exception) {
NetworkEventUtil.onRequestError(reactApplicationContext, requestId, e.message, e)
ResponseUtil.onRequestError(reactApplicationContext, requestId, e.message, null)
return
}
@@ -322,7 +313,7 @@ public class NetworkingModule(
// JS below, so no need to do anything here.
return
}
NetworkEventUtil.onDataReceivedProgress(
ResponseUtil.onDataReceivedProgress(
reactApplicationContext, requestId, bytesWritten, contentLength)
last = now
}
@@ -342,7 +333,7 @@ public class NetworkingModule(
val requestHeaders = extractHeaders(headers, data)
if (requestHeaders == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Unrecognized headers format", null)
return
}
@@ -368,7 +359,7 @@ public class NetworkingModule(
handler != null -> requestBody = handler.toRequestBody(data, contentType)
data.hasKey(REQUEST_BODY_KEY_STRING) -> {
if (contentType == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext,
requestId,
"Payload is set but no content-type header specified",
@@ -383,7 +374,7 @@ public class NetworkingModule(
requestBody = RequestBodyUtil.createGzip(contentMediaType, body)
}
if (requestBody == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Failed to gzip request body", null)
return
}
@@ -398,7 +389,7 @@ public class NetworkingModule(
checkNotNull(contentMediaType.charset(StandardCharsets.UTF_8))
}
if (body == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Received request but body was empty", null)
return
}
@@ -408,7 +399,7 @@ public class NetworkingModule(
}
data.hasKey(REQUEST_BODY_KEY_BASE64) -> {
if (contentType == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext,
requestId,
"Payload is set but no content-type header specified",
@@ -420,7 +411,7 @@ public class NetworkingModule(
val contentMediaType = MediaType.parse(contentType)
if (contentMediaType == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext,
requestId,
"Invalid content type specified: $contentType",
@@ -429,7 +420,7 @@ public class NetworkingModule(
}
val base64DecodedString = ByteString.decodeBase64(base64String)
if (base64DecodedString == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Request body base64 string was invalid", null)
return
}
@@ -438,7 +429,7 @@ public class NetworkingModule(
}
data.hasKey(REQUEST_BODY_KEY_URI) -> {
if (contentType == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext,
requestId,
"Payload is set but no content-type header specified",
@@ -447,13 +438,13 @@ public class NetworkingModule(
}
val uri = data.getString(REQUEST_BODY_KEY_URI)
if (uri == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Request body URI field was set but null", null)
return
}
val fileInputStream = RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri)
if (fileInputStream == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Could not retrieve file for uri $uri", null)
return
}
@@ -465,7 +456,7 @@ public class NetworkingModule(
}
val parts = data.getArray(REQUEST_BODY_KEY_FORMDATA)
if (parts == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Received request but form data was empty", null)
return
}
@@ -481,11 +472,8 @@ public class NetworkingModule(
requestBuilder.method(method, wrapRequestBodyWithProgressEmitter(requestBody, requestId))
addRequest(requestId)
val request = requestBuilder.build()
NetworkEventUtil.onCreateRequest(requestId, request)
client
.newCall(request)
.newCall(requestBuilder.build())
.enqueue(
object : Callback {
override fun onFailure(call: Call, e: IOException) {
@@ -495,7 +483,7 @@ public class NetworkingModule(
removeRequest(requestId)
val errorMessage =
e.message ?: ("Error while executing request: ${e.javaClass.simpleName}")
NetworkEventUtil.onRequestError(reactApplicationContext, requestId, errorMessage, e)
ResponseUtil.onRequestError(reactApplicationContext, requestId, errorMessage, e)
}
@Throws(IOException::class)
@@ -505,8 +493,12 @@ public class NetworkingModule(
}
removeRequest(requestId)
// Before we touch the body send headers to JS
NetworkEventUtil.onResponseReceived(
reactApplicationContext, requestId, url, response)
ResponseUtil.onResponseReceived(
reactApplicationContext,
requestId,
response.code(),
translateHeaders(response.headers()),
response.request().url().toString())
try {
// OkHttp implements something called transparent gzip, which mean that it will
@@ -525,7 +517,7 @@ public class NetworkingModule(
// https://github.com/square/okhttp/blob/5b37cda9e00626f43acf354df145fd452c3031f1/okhttp/src/main/java/okhttp3/internal/http/BridgeInterceptor.java#L76-L111
var responseBody: ResponseBody? = response.body()
if (responseBody == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Response body is null", null)
return
}
@@ -545,12 +537,9 @@ public class NetworkingModule(
// Check if a handler is registered
for (responseHandler in responseHandlers) {
if (responseHandler.supports(responseType)) {
val responseData = responseBody.bytes()
val res = responseHandler.toResponseData(responseData)
NetworkEventUtil.onDataReceived(
reactApplicationContext, requestId, res, responseData)
NetworkEventUtil.onRequestSuccess(
reactApplicationContext, requestId, responseBody.contentLength())
val res = responseHandler.toResponseData(responseBody)
ResponseUtil.onDataReceived(reactApplicationContext, requestId, res)
ResponseUtil.onRequestSuccess(reactApplicationContext, requestId)
return
}
}
@@ -560,8 +549,7 @@ public class NetworkingModule(
// periodically send response data updates to JS.
if (useIncrementalUpdates && responseType == "text") {
readWithProgress(requestId, responseBody)
NetworkEventUtil.onRequestSuccess(
reactApplicationContext, requestId, responseBody.contentLength())
ResponseUtil.onRequestSuccess(reactApplicationContext, requestId)
return
}
@@ -578,19 +566,17 @@ public class NetworkingModule(
// Javascript layer.
// Introduced to fix issue #7463.
} else {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, e.message, e)
}
}
} else if (responseType == "base64") {
responseString = Base64.encodeToString(responseBody.bytes(), Base64.NO_WRAP)
}
NetworkEventUtil.onDataReceived(
reactApplicationContext, requestId, responseString, responseType)
NetworkEventUtil.onRequestSuccess(
reactApplicationContext, requestId, responseBody.contentLength())
ResponseUtil.onDataReceived(reactApplicationContext, requestId, responseString)
ResponseUtil.onRequestSuccess(reactApplicationContext, requestId)
} catch (e: IOException) {
NetworkEventUtil.onRequestError(reactApplicationContext, requestId, e.message, e)
ResponseUtil.onRequestError(reactApplicationContext, requestId, e.message, e)
}
}
})
@@ -612,7 +598,7 @@ public class NetworkingModule(
override fun onProgress(bytesWritten: Long, contentLength: Long, done: Boolean) {
val now = System.nanoTime()
if (done || shouldDispatch(now, last)) {
NetworkEventUtil.onDataSend(
ResponseUtil.onDataSend(
reactApplicationContext, requestId, bytesWritten, contentLength)
last = now
}
@@ -647,7 +633,7 @@ public class NetworkingModule(
var read: Int
val reactApplicationContext = getReactApplicationContextIfActiveOrWarn()
while ((inputStream.read(buffer).also { read = it }) != -1) {
NetworkEventUtil.onIncrementalDataReceived(
ResponseUtil.onIncrementalDataReceived(
reactApplicationContext,
requestId,
streamDecoder.decodeNext(buffer, read),
@@ -705,8 +691,7 @@ public class NetworkingModule(
val multipartBuilder = MultipartBody.Builder()
val mediaType = MediaType.parse(contentType)
if (mediaType == null) {
NetworkEventUtil.onRequestError(
reactApplicationContext, requestId, "Invalid media type.", null)
ResponseUtil.onRequestError(reactApplicationContext, requestId, "Invalid media type.", null)
return null
}
multipartBuilder.setType(mediaType)
@@ -714,7 +699,7 @@ public class NetworkingModule(
for (i in 0 until body.size()) {
val bodyPart = body.getMap(i)
if (bodyPart == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Unrecognized FormData part.", null)
return null
}
@@ -723,7 +708,7 @@ public class NetworkingModule(
val headersArray = bodyPart.getArray("headers")
var headers = extractHeaders(headersArray, null)
if (headers == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext,
requestId,
"Missing or invalid header format for FormData part.",
@@ -747,7 +732,7 @@ public class NetworkingModule(
} else if (bodyPart.hasKey(REQUEST_BODY_KEY_URI) &&
bodyPart.getString(REQUEST_BODY_KEY_URI) != null) {
if (partContentType == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext,
requestId,
"Binary FormData part needs a content-type header.",
@@ -756,14 +741,14 @@ public class NetworkingModule(
}
val fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI)
if (fileContentUriStr == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Body must have a valid file uri", null)
return null
}
val fileInputStream =
RequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr)
if (fileInputStream == null) {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext,
requestId,
"Could not retrieve file for uri $fileContentUriStr",
@@ -772,7 +757,7 @@ public class NetworkingModule(
}
multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream))
} else {
NetworkEventUtil.onRequestError(
ResponseUtil.onRequestError(
reactApplicationContext, requestId, "Unrecognized FormData part.", null)
}
}
@@ -842,5 +827,20 @@ public class NetworkingModule(
}
private fun shouldDispatch(now: Long, last: Long): Boolean = last + CHUNK_TIMEOUT_NS < now
private fun translateHeaders(headers: Headers): WritableMap {
val responseHeaders = Bundle()
for (i in 0..<headers.size()) {
val headerName = headers.name(i)
// multiple values for the same header
if (responseHeaders.containsKey(headerName)) {
responseHeaders.putString(
headerName, "${responseHeaders.getString(headerName)}, ${headers.value(i)}")
} else {
responseHeaders.putString(headerName, headers.value(i))
}
}
return Arguments.fromBundle(responseHeaders)
}
}
}
@@ -0,0 +1,133 @@
/*
* 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.modules.network
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.buildReadableArray
import java.net.SocketTimeoutException
/** Util methods to send network responses to JS. */
internal object ResponseUtil {
@JvmStatic
fun onDataSend(
reactContext: ReactApplicationContext?,
requestId: Int,
progress: Long,
total: Long
) {
reactContext?.emitDeviceEvent(
"didSendNetworkData",
buildReadableArray {
add(requestId)
add(progress.toInt())
add(total.toInt())
})
}
@JvmStatic
fun onIncrementalDataReceived(
reactContext: ReactApplicationContext?,
requestId: Int,
data: String?,
progress: Long,
total: Long
) {
reactContext?.emitDeviceEvent(
"didReceiveNetworkIncrementalData",
buildReadableArray {
add(requestId)
add(data)
add(progress.toInt())
add(total.toInt())
})
}
@JvmStatic
fun onDataReceivedProgress(
reactContext: ReactApplicationContext?,
requestId: Int,
progress: Long,
total: Long
) {
reactContext?.emitDeviceEvent(
"didReceiveNetworkDataProgress",
buildReadableArray {
add(requestId)
add(progress.toInt())
add(total.toInt())
})
}
@JvmStatic
fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: String?) {
reactContext?.emitDeviceEvent(
"didReceiveNetworkData",
buildReadableArray {
add(requestId)
add(data)
})
}
@JvmStatic
fun onDataReceived(reactContext: ReactApplicationContext?, requestId: Int, data: WritableMap?) {
reactContext?.emitDeviceEvent(
"didReceiveNetworkData",
Arguments.createArray().apply {
pushInt(requestId)
pushMap(data)
})
}
@JvmStatic
fun onRequestError(
reactContext: ReactApplicationContext?,
requestId: Int,
error: String?,
e: Throwable?
) {
reactContext?.emitDeviceEvent(
"didCompleteNetworkResponse",
buildReadableArray {
add(requestId)
add(error)
if (e?.javaClass == SocketTimeoutException::class.java) {
add(true) // last argument is a time out boolean
}
})
}
@JvmStatic
fun onRequestSuccess(reactContext: ReactApplicationContext?, requestId: Int) {
reactContext?.emitDeviceEvent(
"didCompleteNetworkResponse",
buildReadableArray {
add(requestId)
addNull()
})
}
@JvmStatic
fun onResponseReceived(
reactContext: ReactApplicationContext?,
requestId: Int,
statusCode: Int,
headers: WritableMap?,
url: String?
) {
reactContext?.emitDeviceEvent(
"didReceiveNetworkResponse",
Arguments.createArray().apply {
pushInt(requestId)
pushInt(statusCode)
pushMap(headers)
pushString(url)
})
}
}
@@ -253,18 +253,11 @@ public class ReactHostImpl(
val currentActivity = this.currentActivity
if (currentActivity != null) {
val isSameActivity = activity === currentActivity
if (!isSameActivity) {
val currentActivityClass = currentActivity.javaClass.simpleName
val activityClass = if (activity == null) "null" else activity.javaClass.simpleName
val isNotSameActivityMessage =
"Pausing an activity that is not the current activity, this is incorrect! Current activity: $currentActivityClass Paused activity: $activityClass"
if (ReactNativeFeatureFlags.skipActivityIdentityAssertionOnHostPause()) {
log(method, isNotSameActivityMessage)
} else {
Assertions.assertCondition(isSameActivity, isNotSameActivityMessage)
}
}
val currentActivityClass = currentActivity.javaClass.simpleName
val activityClass = if (activity == null) "null" else activity.javaClass.simpleName
Assertions.assertCondition(
activity === currentActivity,
"Pausing an activity that is not the current activity, this is incorrect! Current activity: $currentActivityClass Paused activity: $activityClass")
}
maybeEnableDevSupport(false)
@@ -13,10 +13,8 @@ import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.window.layout.WindowMetricsCalculator
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
/**
* Holds an instance of the current DisplayMetrics so we don't have to thread it through all the
@@ -64,19 +62,9 @@ public object DisplayMetricsHolder {
@JvmStatic
public fun initDisplayMetrics(context: Context) {
val displayMetrics = context.resources.displayMetrics
val windowDisplayMetrics = DisplayMetrics()
windowDisplayMetrics = displayMetrics
val screenDisplayMetrics = DisplayMetrics()
windowDisplayMetrics.setTo(displayMetrics)
screenDisplayMetrics.setTo(displayMetrics)
if (isEdgeToEdgeFeatureFlagOn) {
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context).let {
windowDisplayMetrics.widthPixels = it.bounds.width()
windowDisplayMetrics.heightPixels = it.bounds.height()
}
}
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
// Get the real display metrics if we are using API level 17 or higher.
// The real metrics include system decor elements (e.g. soft menu bar).
@@ -84,8 +72,6 @@ public object DisplayMetricsHolder {
// See:
// http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
@Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(screenDisplayMetrics)
DisplayMetricsHolder.windowDisplayMetrics = windowDisplayMetrics
DisplayMetricsHolder.screenDisplayMetrics = screenDisplayMetrics
}
@@ -19,7 +19,7 @@ import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel
import com.facebook.react.uimanager.events.Event
/** Event used to notify JS component about changes of its position or dimensions. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.WARNING)
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
public class OnLayoutEvent private constructor() : Event<OnLayoutEvent>() {
@VisibleForTesting internal var x: Int = 0
@VisibleForTesting internal var y: Int = 0
@@ -60,7 +60,7 @@ public class OnLayoutEvent private constructor() : Event<OnLayoutEvent>() {
public companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"OnLayoutEvent", LegacyArchitectureLogLevel.WARNING)
"OnLayoutEvent", LegacyArchitectureLogLevel.ERROR)
}
private val EVENTS_POOL: SynchronizedPool<OnLayoutEvent> = SynchronizedPool<OnLayoutEvent>(20)
@@ -8,12 +8,10 @@
package com.facebook.react.uimanager
import com.facebook.common.logging.FLog
import com.facebook.react.bridge.NativeArray
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.facebook.react.common.ReactConstants
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
public object TransformHelper {
@@ -71,14 +69,6 @@ public object TransformHelper {
transformOrigin: ReadableArray?,
allowPercentageResolution: Boolean
) {
if (allowPercentageResolution &&
ReactNativeFeatureFlags.useNativeTransformHelperAndroid() &&
transforms is NativeArray &&
transformOrigin is NativeArray?) {
nativeProcessTransform(transforms, result, viewWidth, viewHeight, transformOrigin)
return
}
val helperMatrix = helperMatrix.get()!!
MatrixMathHelper.resetIdentityMatrix(result)
val offsets =
@@ -230,13 +220,4 @@ public object TransformHelper {
return doubleArrayOf(newTranslateX, newTranslateY, newTranslateZ)
}
@JvmStatic
private external fun nativeProcessTransform(
transforms: NativeArray,
result: DoubleArray,
viewWidth: Float,
viewHeight: Float,
transformOrigin: NativeArray?
)
}
@@ -30,12 +30,15 @@ internal class DebuggingOverlayManager :
override fun getDelegate(): ViewManagerDelegate<DebuggingOverlay> = delegate
override fun highlightTraceUpdates(view: DebuggingOverlay, updates: ReadableArray): Unit {
override fun highlightTraceUpdates(
view: DebuggingOverlay,
providedTraceUpdates: ReadableArray
): Unit {
val formattedTraceUpdates = mutableListOf<TraceUpdate>()
var successfullyParsedPayload = true
for (i in 0 until updates.size()) {
val traceUpdate = updates.getMap(i) ?: continue
for (i in 0 until providedTraceUpdates.size()) {
val traceUpdate = providedTraceUpdates.getMap(i) ?: continue
val serializedRectangle = traceUpdate.getMap("rectangle")
if (serializedRectangle == null) {
ReactSoftExceptionLogger.logSoftException(
@@ -79,12 +82,12 @@ internal class DebuggingOverlayManager :
}
}
override fun highlightElements(view: DebuggingOverlay, elements: ReadableArray): Unit {
override fun highlightElements(view: DebuggingOverlay, providedElements: ReadableArray): Unit {
val elementsRectangles = mutableListOf<RectF>()
var successfullyParsedPayload = true
for (i in 0 until elements.size()) {
val element = elements.getMap(i) ?: continue
for (i in 0 until providedElements.size()) {
val element = providedElements.getMap(i) ?: continue
try {
val left = element.getDouble("x").toFloat()
val top = element.getDouble("y").toFloat()
@@ -108,20 +108,20 @@ public class ReactDrawerLayoutManager :
view.setDrawerWidth(widthInPx)
}
public override fun setDrawerWidth(view: ReactDrawerLayout, value: Float?) {
val widthInPx = value?.let { Math.round(it.dpToPx()) } ?: ReactDrawerLayout.DEFAULT_DRAWER_WIDTH
public override fun setDrawerWidth(view: ReactDrawerLayout, width: Float?) {
val widthInPx = width?.let { Math.round(it.dpToPx()) } ?: ReactDrawerLayout.DEFAULT_DRAWER_WIDTH
view.setDrawerWidth(widthInPx)
}
@ReactProp(name = "drawerLockMode")
public override fun setDrawerLockMode(view: ReactDrawerLayout, value: String?) {
when (value) {
public override fun setDrawerLockMode(view: ReactDrawerLayout, drawerLockMode: String?) {
when (drawerLockMode) {
null,
"unlocked" -> view.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
"locked-closed" -> view.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
"locked-open" -> view.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN)
else -> {
FLog.w(ReactConstants.TAG, "Unknown drawerLockMode $value")
FLog.w(ReactConstants.TAG, "Unknown drawerLockMode $drawerLockMode")
view.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
}
@@ -38,34 +38,37 @@ internal class ReactModalHostManager :
}
@ReactProp(name = "animationType")
override fun setAnimationType(view: ReactModalHostView, value: String?) {
if (value != null) {
view.animationType = value
override fun setAnimationType(view: ReactModalHostView, animationType: String?) {
if (animationType != null) {
view.animationType = animationType
}
}
@ReactProp(name = "transparent")
override fun setTransparent(view: ReactModalHostView, value: Boolean) {
view.transparent = value
override fun setTransparent(view: ReactModalHostView, transparent: Boolean) {
view.transparent = transparent
}
@ReactProp(name = "statusBarTranslucent")
override fun setStatusBarTranslucent(view: ReactModalHostView, value: Boolean) {
view.statusBarTranslucent = value
override fun setStatusBarTranslucent(view: ReactModalHostView, statusBarTranslucent: Boolean) {
view.statusBarTranslucent = statusBarTranslucent
}
@ReactProp(name = "navigationBarTranslucent")
override fun setNavigationBarTranslucent(view: ReactModalHostView, value: Boolean) {
view.navigationBarTranslucent = value
override fun setNavigationBarTranslucent(
view: ReactModalHostView,
navigationBarTranslucent: Boolean
) {
view.navigationBarTranslucent = navigationBarTranslucent
}
@ReactProp(name = "hardwareAccelerated")
override fun setHardwareAccelerated(view: ReactModalHostView, value: Boolean) {
view.hardwareAccelerated = value
override fun setHardwareAccelerated(view: ReactModalHostView, hardwareAccelerated: Boolean) {
view.hardwareAccelerated = hardwareAccelerated
}
@ReactProp(name = "visible")
override fun setVisible(view: ReactModalHostView, value: Boolean) {
override fun setVisible(view: ReactModalHostView, visible: Boolean) {
// iOS only
}
@@ -49,28 +49,28 @@ internal class ReactProgressBarViewManager :
}
@ReactProp(name = PROP_STYLE)
override fun setStyleAttr(view: ProgressBarContainerView, value: String?) {
view.setStyle(value)
override fun setStyleAttr(view: ProgressBarContainerView, styleName: String?) {
view.setStyle(styleName)
}
@ReactProp(name = ViewProps.COLOR, customType = "Color")
override fun setColor(view: ProgressBarContainerView, value: Int?) {
view.color = value
override fun setColor(view: ProgressBarContainerView, color: Int?) {
view.color = color
}
@ReactProp(name = PROP_INDETERMINATE)
override fun setIndeterminate(view: ProgressBarContainerView, value: Boolean) {
view.indeterminate = value
override fun setIndeterminate(view: ProgressBarContainerView, indeterminate: Boolean) {
view.indeterminate = indeterminate
}
@ReactProp(name = PROP_PROGRESS)
override fun setProgress(view: ProgressBarContainerView, value: Double) {
view.progress = value
override fun setProgress(view: ProgressBarContainerView, progress: Double) {
view.progress = progress
}
@ReactProp(name = PROP_ANIMATING)
override fun setAnimating(view: ProgressBarContainerView, value: Boolean) {
view.animating = value
override fun setAnimating(view: ProgressBarContainerView, animating: Boolean) {
view.animating = animating
}
override fun setTestID(view: ProgressBarContainerView, value: String?) {
@@ -377,9 +377,6 @@ public class ReactScrollView extends ScrollView
}
ReactScrollViewHelper.emitLayoutEvent(this);
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
@Override
@@ -489,9 +486,6 @@ public class ReactScrollView extends ScrollView
this,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity());
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT);
@@ -11,7 +11,6 @@ import android.graphics.Rect
import android.view.ViewGroup
import com.facebook.common.logging.FLog
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.views.virtual.VirtualViewMode
import java.util.*
@@ -46,19 +45,22 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
return true
}
internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
ReactScrollViewHelper.ScrollListener {
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
private val prerenderRatio: Int = 1
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
private val emptyRect: Rect = Rect()
private val visibleRect: Rect = Rect()
private val prerenderRect: Rect = Rect()
public fun onChange(virtualView: VirtualView) {
if (virtualViews.add(virtualView)) {
debugLog("add", { "virtualViewID=${virtualView.virtualViewID}" })
} else {
debugLog("update", { "virtualViewID=${virtualView.virtualViewID}" })
init {
ReactScrollViewHelper.addScrollListener(this)
}
public fun add(virtualView: VirtualView) {
assert(virtualViews.add(virtualView)) {
"Attempting to add duplicate VirtualView: ${virtualView.virtualViewID}"
}
updateModes(virtualView)
}
@@ -67,13 +69,35 @@ internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
assert(virtualViews.remove(virtualView)) {
"Attempting to remove non-existent VirtualView: ${virtualView.virtualViewID}"
}
debugLog("remove", { "virtualViewID=${virtualView.virtualViewID}" })
}
// Called on ScrollView onLayout or onScroll
public fun updateState() {
debugLog("VirtualViewContainer.updateState")
updateModes()
// ReactScrollViewHelper.ScrollListener.onLayout
// Emitted from ScrollView's onLayout
override fun onLayout(scrollView: ViewGroup?) {
// ReactScrollViewHelper is global
if (this.scrollView == scrollView) {
debugLog("ReactScrollViewHelper.onLayout")
updateModes()
}
}
// ReactScrollViewHelper.ScrollListener.onScroll
// Emitted from ScrollView's onLayout
override fun onScroll(
scrollView: ViewGroup?,
scrollEventType: ScrollEventType?,
xVelocity: Float,
yVelocity: Float
) {
// ReactScrollViewHelper is global
if (this.scrollView == scrollView) {
debugLog("ReactScrollViewHelper.onScroll")
updateModes()
}
}
public fun update(virtualView: VirtualView) {
updateModes(virtualView)
}
private fun updateModes(virtualView: VirtualView? = null) {
@@ -86,26 +110,17 @@ internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
val virtualViewsIt = if (virtualView != null) listOf(virtualView) else virtualViews
virtualViewsIt.forEach { vv ->
val rect = vv.containerRelativeRect
var mode = VirtualViewMode.Hidden
var thresholdRect = emptyRect
when {
rect.isEmpty -> {}
rectsOverlap(rect, visibleRect) -> {
mode = VirtualViewMode.Visible
thresholdRect = visibleRect
vv.onModeChange(VirtualViewMode.Visible, visibleRect)
}
rectsOverlap(rect, prerenderRect) -> {
mode = VirtualViewMode.Prerender
thresholdRect = prerenderRect
vv.onModeChange(VirtualViewMode.Prerender, prerenderRect)
}
else -> {
vv.onModeChange(VirtualViewMode.Hidden, emptyRect)
}
else -> {}
}
debugLog(
"updateModes",
{ "virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect" })
vv.onModeChange(mode, thresholdRect)
}
}
}
@@ -42,19 +42,19 @@ internal open class SwipeRefreshLayoutManager :
override fun getName(): String = REACT_CLASS
@ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
override fun setEnabled(view: ReactSwipeRefreshLayout, value: Boolean) {
view.isEnabled = value
override fun setEnabled(view: ReactSwipeRefreshLayout, enabled: Boolean) {
view.isEnabled = enabled
}
@ReactProp(name = "colors", customType = "ColorArray")
override fun setColors(view: ReactSwipeRefreshLayout, value: ReadableArray?) {
if (value != null) {
val colorValues = IntArray(value.size())
for (i in 0..<value.size()) {
if (value.getType(i) == ReadableType.Map) {
colorValues[i] = ColorPropConverter.getColor(value.getMap(i), view.context, 0)
override fun setColors(view: ReactSwipeRefreshLayout, colors: ReadableArray?) {
if (colors != null) {
val colorValues = IntArray(colors.size())
for (i in 0..<colors.size()) {
if (colors.getType(i) == ReadableType.Map) {
colorValues[i] = ColorPropConverter.getColor(colors.getMap(i), view.context, 0)
} else {
colorValues[i] = value.getInt(i)
colorValues[i] = colors.getInt(i)
}
}
view.setColorSchemeColors(*colorValues)
@@ -64,8 +64,8 @@ internal open class SwipeRefreshLayoutManager :
}
@ReactProp(name = "progressBackgroundColor", customType = "Color")
override fun setProgressBackgroundColor(view: ReactSwipeRefreshLayout, value: Int?) {
view.setProgressBackgroundColorSchemeColor(value ?: Color.TRANSPARENT)
override fun setProgressBackgroundColor(view: ReactSwipeRefreshLayout, color: Int?) {
view.setProgressBackgroundColorSchemeColor(color ?: Color.TRANSPARENT)
}
// TODO(T46143833): Remove this method once the 'size' prop has been migrated to String in JS.
@@ -73,13 +73,13 @@ internal open class SwipeRefreshLayoutManager :
view.setSize(value)
}
override fun setSize(view: ReactSwipeRefreshLayout, value: String?) {
if (value == null || value.equals("default")) {
override fun setSize(view: ReactSwipeRefreshLayout, size: String?) {
if (size == null || size.equals("default")) {
view.setSize(SwipeRefreshLayout.DEFAULT)
} else if (value.equals("large")) {
} else if (size.equals("large")) {
view.setSize(SwipeRefreshLayout.LARGE)
} else {
throw IllegalArgumentException("Size must be 'default' or 'large', received: $value")
throw IllegalArgumentException("Size must be 'default' or 'large', received: $size")
}
}
@@ -97,13 +97,13 @@ internal open class SwipeRefreshLayoutManager :
}
@ReactProp(name = "refreshing")
override fun setRefreshing(view: ReactSwipeRefreshLayout, value: Boolean) {
view.isRefreshing = value
override fun setRefreshing(view: ReactSwipeRefreshLayout, refreshing: Boolean) {
view.isRefreshing = refreshing
}
@ReactProp(name = "progressViewOffset", defaultFloat = 0f)
override fun setProgressViewOffset(view: ReactSwipeRefreshLayout, value: Float) {
view.setProgressViewOffset(value)
override fun setProgressViewOffset(view: ReactSwipeRefreshLayout, offset: Float) {
view.setProgressViewOffset(offset)
}
override fun setNativeRefreshing(view: ReactSwipeRefreshLayout, value: Boolean) {
@@ -46,18 +46,18 @@ internal class ReactSwitchManager :
}
@ReactProp(name = "disabled", defaultBoolean = false)
override fun setDisabled(view: ReactSwitch, value: Boolean) {
view.isEnabled = !value
override fun setDisabled(view: ReactSwitch, disabled: Boolean) {
view.isEnabled = !disabled
}
@ReactProp(name = ViewProps.ENABLED, defaultBoolean = true)
override fun setEnabled(view: ReactSwitch, value: Boolean) {
view.isEnabled = value
override fun setEnabled(view: ReactSwitch, enabled: Boolean) {
view.isEnabled = enabled
}
@ReactProp(name = ViewProps.ON)
override fun setOn(view: ReactSwitch, value: Boolean) {
setValueInternal(view, value)
override fun setOn(view: ReactSwitch, on: Boolean) {
setValueInternal(view, on)
}
@ReactProp(name = "value")
@@ -66,28 +66,28 @@ internal class ReactSwitchManager :
}
@ReactProp(name = "thumbTintColor", customType = "Color")
override fun setThumbTintColor(view: ReactSwitch, value: Int?) {
setThumbColor(view, value)
override fun setThumbTintColor(view: ReactSwitch, color: Int?) {
setThumbColor(view, color)
}
@ReactProp(name = "thumbColor", customType = "Color")
override fun setThumbColor(view: ReactSwitch, value: Int?) {
view.setThumbColor(value)
override fun setThumbColor(view: ReactSwitch, color: Int?) {
view.setThumbColor(color)
}
@ReactProp(name = "trackColorForFalse", customType = "Color")
override fun setTrackColorForFalse(view: ReactSwitch, value: Int?) {
view.setTrackColorForFalse(value)
override fun setTrackColorForFalse(view: ReactSwitch, color: Int?) {
view.setTrackColorForFalse(color)
}
@ReactProp(name = "trackColorForTrue", customType = "Color")
override fun setTrackColorForTrue(view: ReactSwitch, value: Int?) {
view.setTrackColorForTrue(value)
override fun setTrackColorForTrue(view: ReactSwitch, color: Int?) {
view.setTrackColorForTrue(color)
}
@ReactProp(name = "trackTintColor", customType = "Color")
override fun setTrackTintColor(view: ReactSwitch, value: Int?) {
view.setTrackColor(value)
override fun setTrackTintColor(view: ReactSwitch, color: Int?) {
view.setTrackColor(color)
}
override fun setNativeValue(view: ReactSwitch, value: Boolean) {
@@ -1102,8 +1102,7 @@ internal object TextLayoutManager {
// There's a bug on Samsung devices where calling getPrimaryHorizontal on
// the last offset in the layout will result in an endless loop. Work around
// this bug by avoiding getPrimaryHorizontal in that case.
if (!ReactNativeFeatureFlags.disableOldAndroidAttachmentMetricsWorkarounds() &&
start == text.length - 1) {
if (start == text.length - 1) {
val endsWithNewLine = text.length > 0 && text[layout.getLineEnd(line) - 1] == '\n'
val lineWidth = if (endsWithNewLine) layout.getLineMax(line) else layout.getLineWidth(line)
placeholderLeftPosition =
@@ -1128,9 +1127,7 @@ internal object TextLayoutManager {
placeholderLeftPosition =
if (characterAndParagraphDirectionMatch) layout.getPrimaryHorizontal(start)
else layout.getSecondaryHorizontal(start)
if (!ReactNativeFeatureFlags.disableOldAndroidAttachmentMetricsWorkarounds() &&
isRtlParagraph &&
!isRtlChar) {
if (isRtlParagraph && !isRtlChar) {
// Adjust `placeholderLeftPosition` to work around an Android bug.
// The bug is when the paragraph is RTL and `setSingleLine(true)`, some layout
// methods such as `getPrimaryHorizontal`, `getSecondaryHorizontal`, and
@@ -35,8 +35,8 @@ internal class ReactUnimplementedViewManager :
override fun getName(): String = REACT_CLASS
@ReactProp(name = "name")
override fun setName(view: ReactUnimplementedView, value: String?): Unit =
view.setName(value ?: "<null component name>")
override fun setName(view: ReactUnimplementedView, name: String?): Unit =
view.setName(name ?: "<null component name>")
internal companion object {
const val REACT_CLASS: String = "UnimplementedNativeView"
@@ -101,7 +101,7 @@ public class ReactVirtualView(context: Context) :
ReactScrollViewHelper.removeScrollListener(this)
ReactScrollViewHelper.removeLayoutChangeListener(this)
if (detectWindowFocus) {
viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
}
cleanupLayoutListeners()
}
@@ -34,7 +34,6 @@ public class ReactVirtualViewExperimental(context: Context) :
override val containerRelativeRect: Rect = Rect()
private var offsetX: Int = 0
private var offsetY: Int = 0
private var hadLayout: Boolean = false
internal val nativeId: String?
get() = getTag(R.id.view_tag_native_id) as? String
@@ -46,20 +45,14 @@ public class ReactVirtualViewExperimental(context: Context) :
@VisibleForTesting
internal fun doAttachedToWindow() {
scrollView = getScrollView()
// onAttachedToWindow is usually called before layout but there are cases where it's called
// after. If called after, we need to report the updated layout to the VirtualViewContainer
if (hadLayout) {
updateParentOffset()
reportChangeToContainer()
}
// Assuming that layout has been called before this
scrollView = getScrollView()?.also { scrollView?.virtualViewContainerState?.add(this) }
}
/** From [View#onLayout] */
// This is when the view itself has layout changes
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
hadLayout = true
if (changed) {
containerRelativeRect.set(
left + offsetX,
@@ -67,7 +60,7 @@ public class ReactVirtualViewExperimental(context: Context) :
right + offsetX,
bottom + offsetY,
)
reportChangeToContainer()
updateContainer()
}
}
@@ -84,8 +77,24 @@ public class ReactVirtualViewExperimental(context: Context) :
oldBottom: Int
) {
if (oldLeft != left || oldTop != top) {
updateParentOffset()
reportChangeToContainer()
val virtualViewScrollView = scrollView ?: return
offsetX = 0
offsetY = 0
var parent: ViewParent? = parent
while (parent != null && parent != virtualViewScrollView) {
if (parent is View) {
offsetX += parent.left
offsetY += parent.top
}
parent = parent.parent
}
containerRelativeRect.set(
left + offsetX,
top + offsetY,
right + offsetX,
bottom + offsetY,
)
updateContainer()
}
}
@@ -100,8 +109,6 @@ public class ReactVirtualViewExperimental(context: Context) :
scrollView = null
mode = null
modeChangeEmitter = null
hadLayout = false
containerRelativeRect.setEmpty()
}
override val virtualViewID: String
@@ -149,28 +156,8 @@ public class ReactVirtualViewExperimental(context: Context) :
}
}
private fun updateParentOffset() {
val virtualViewScrollView = scrollView ?: return
offsetX = 0
offsetY = 0
var parent: ViewParent? = parent
while (parent != null && parent != virtualViewScrollView) {
if (parent is View) {
offsetX += parent.left
offsetY += parent.top
}
parent = parent.parent
}
containerRelativeRect.set(
left + offsetX,
top + offsetY,
right + offsetX,
bottom + offsetY,
)
}
private fun reportChangeToContainer() {
scrollView?.virtualViewContainerState?.onChange(this)
private fun updateContainer() {
scrollView?.virtualViewContainerState?.update(this)
}
private fun getScrollView(): VirtualViewContainer? = traverseParentStack(true)
@@ -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<<f310a5dc27fd655eab4445cb25d4c85c>>
* @generated SignedSource<<62ecfcd8d00ce03d659715bbb3f4a235>>
*/
/**
@@ -63,12 +63,6 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool disableOldAndroidAttachmentMetricsWorkarounds() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("disableOldAndroidAttachmentMetricsWorkarounds");
return method(javaProvider_);
}
bool disableTextLayoutManagerCacheAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("disableTextLayoutManagerCacheAndroid");
@@ -171,12 +165,6 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool enableImmediateUpdateModeForContentOffsetChanges() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableImmediateUpdateModeForContentOffsetChanges");
return method(javaProvider_);
}
bool enableInteropViewManagerClassLookUpOptimizationIOS() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableInteropViewManagerClassLookUpOptimizationIOS");
@@ -243,6 +231,12 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool enableSynchronousStateUpdates() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableSynchronousStateUpdates");
return method(javaProvider_);
}
bool enableViewCulling() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableViewCulling");
@@ -315,24 +309,6 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("preventShadowTreeCommitExhaustionWithLocking");
return method(javaProvider_);
}
bool releaseImageDataWhenConsumed() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("releaseImageDataWhenConsumed");
return method(javaProvider_);
}
bool skipActivityIdentityAssertionOnHostPause() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("skipActivityIdentityAssertionOnHostPause");
return method(javaProvider_);
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("traceTurboModulePromiseRejectionsOnAndroid");
@@ -357,18 +333,6 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool useNativeEqualsInNativeReadableArrayAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useNativeEqualsInNativeReadableArrayAndroid");
return method(javaProvider_);
}
bool useNativeTransformHelperAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useNativeTransformHelperAndroid");
return method(javaProvider_);
}
bool useNativeViewConfigsInBridgelessMode() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useNativeViewConfigsInBridgelessMode");
@@ -435,11 +399,6 @@ bool JReactNativeFeatureFlagsCxxInterop::disableMountItemReorderingAndroid(
return ReactNativeFeatureFlags::disableMountItemReorderingAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::disableOldAndroidAttachmentMetricsWorkarounds(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::disableOldAndroidAttachmentMetricsWorkarounds();
}
bool JReactNativeFeatureFlagsCxxInterop::disableTextLayoutManagerCacheAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::disableTextLayoutManagerCacheAndroid();
@@ -525,11 +484,6 @@ bool JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox(
return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
}
bool JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges();
}
bool JReactNativeFeatureFlagsCxxInterop::enableInteropViewManagerClassLookUpOptimizationIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS();
@@ -585,6 +539,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableResourceTimingAPI(
return ReactNativeFeatureFlags::enableResourceTimingAPI();
}
bool JReactNativeFeatureFlagsCxxInterop::enableSynchronousStateUpdates(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableSynchronousStateUpdates();
}
bool JReactNativeFeatureFlagsCxxInterop::enableViewCulling(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableViewCulling();
@@ -645,21 +604,6 @@ double JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize(
return ReactNativeFeatureFlags::preparedTextCacheSize();
}
bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking();
}
bool JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::releaseImageDataWhenConsumed();
}
bool JReactNativeFeatureFlagsCxxInterop::skipActivityIdentityAssertionOnHostPause(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause();
}
bool JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid();
@@ -680,16 +624,6 @@ bool JReactNativeFeatureFlagsCxxInterop::useFabricInterop(
return ReactNativeFeatureFlags::useFabricInterop();
}
bool JReactNativeFeatureFlagsCxxInterop::useNativeEqualsInNativeReadableArrayAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::useNativeTransformHelperAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useNativeTransformHelperAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::useNativeViewConfigsInBridgelessMode(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode();
@@ -768,9 +702,6 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"disableMountItemReorderingAndroid",
JReactNativeFeatureFlagsCxxInterop::disableMountItemReorderingAndroid),
makeNativeMethod(
"disableOldAndroidAttachmentMetricsWorkarounds",
JReactNativeFeatureFlagsCxxInterop::disableOldAndroidAttachmentMetricsWorkarounds),
makeNativeMethod(
"disableTextLayoutManagerCacheAndroid",
JReactNativeFeatureFlagsCxxInterop::disableTextLayoutManagerCacheAndroid),
@@ -822,9 +753,6 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableIOSViewClipToPaddingBox",
JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox),
makeNativeMethod(
"enableImmediateUpdateModeForContentOffsetChanges",
JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges),
makeNativeMethod(
"enableInteropViewManagerClassLookUpOptimizationIOS",
JReactNativeFeatureFlagsCxxInterop::enableInteropViewManagerClassLookUpOptimizationIOS),
@@ -858,6 +786,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableResourceTimingAPI",
JReactNativeFeatureFlagsCxxInterop::enableResourceTimingAPI),
makeNativeMethod(
"enableSynchronousStateUpdates",
JReactNativeFeatureFlagsCxxInterop::enableSynchronousStateUpdates),
makeNativeMethod(
"enableViewCulling",
JReactNativeFeatureFlagsCxxInterop::enableViewCulling),
@@ -894,15 +825,6 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"preparedTextCacheSize",
JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize),
makeNativeMethod(
"preventShadowTreeCommitExhaustionWithLocking",
JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking),
makeNativeMethod(
"releaseImageDataWhenConsumed",
JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed),
makeNativeMethod(
"skipActivityIdentityAssertionOnHostPause",
JReactNativeFeatureFlagsCxxInterop::skipActivityIdentityAssertionOnHostPause),
makeNativeMethod(
"traceTurboModulePromiseRejectionsOnAndroid",
JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid),
@@ -915,12 +837,6 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"useFabricInterop",
JReactNativeFeatureFlagsCxxInterop::useFabricInterop),
makeNativeMethod(
"useNativeEqualsInNativeReadableArrayAndroid",
JReactNativeFeatureFlagsCxxInterop::useNativeEqualsInNativeReadableArrayAndroid),
makeNativeMethod(
"useNativeTransformHelperAndroid",
JReactNativeFeatureFlagsCxxInterop::useNativeTransformHelperAndroid),
makeNativeMethod(
"useNativeViewConfigsInBridgelessMode",
JReactNativeFeatureFlagsCxxInterop::useNativeViewConfigsInBridgelessMode),

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