Compare commits

..
Author SHA1 Message Date
Nicola CortiandFacebook GitHub Bot 185443e299 Migrate RNTester to use {usesCleartextTraffic} Manifest Placeholder (#52620)
Summary:
This creates a `debugOptimized` build type for React Native Android, meaning that we can run C++ optimization on the debug build, while still having the debugger enabled. This is aimed at improving the developer experience for folks developing on low-end devices or emulators.

Users that intend to debug can still use the `debug` variant where the full debug symbols are shipped.

## Changelog:

[ANDROID] [ADDED] - Create a debugOptimized buildType for Android


Test Plan:
Tested locally with RNTester by doing:

```
./gradlew installDebugOptimized
```

This is the output of the 3 generated .aar. The size difference is a proof that we're correctly stripping out the C++ debug symbols:

<img width="193" height="54" alt="Screenshot 2025-07-15 at 17 49 50" src="https://github.com/user-attachments/assets/584a0e8d-2d17-40d4-ac29-da09049d6554" />
<img width="235" height="51" alt="Screenshot 2025-07-15 at 17 49 39" src="https://github.com/user-attachments/assets/eda8f9e7-3509-4334-8c16-990e55caa04d" />
<img width="184" height="52" alt="Screenshot 2025-07-15 at 17 49 32" src="https://github.com/user-attachments/assets/a5c94385-bc00-4484-b43e-088ee039827f" />

Rollback Plan:

Reviewed By: cipolleschi

Differential Revision: D78351347

Pulled By: cortinico
2025-07-18 07:43:44 -07:00
119 changed files with 795 additions and 1295 deletions
+1 -1
View File
@@ -104,4 +104,4 @@ untyped-import
untyped-type-import
[version]
^0.276.0
^0.275.0
@@ -15,6 +15,8 @@ runs:
steps:
- name: Setup xcode
uses: ./.github/actions/setup-xcode
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Restore Hermes workspace
uses: ./.github/actions/restore-hermes-workspace
- name: Restore Cached Artifacts
@@ -43,8 +45,6 @@ runs:
echo "ARTIFACTS_EXIST=true" >> $GITHUB_ENV
echo "ARTIFACTS_EXIST=true" >> $GITHUB_OUTPUT
fi
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Yarn- Install Dependencies
if: ${{ steps.check_if_apple_artifacts_are_there.outputs.ARTIFACTS_EXIST != 'true' }}
uses: ./.github/actions/yarn-install
+2 -2
View File
@@ -116,12 +116,12 @@ runs:
- name: Print Artifacts Directory
shell: bash
run: ls -lR ./packages/react-native/ReactAndroid/external-artifacts/artifacts/
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Setup gradle
uses: ./.github/actions/setup-gradle
with:
cache-encryption-key: ${{ inputs.gradle-cache-encryption-key }}
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build packages
@@ -14,8 +14,6 @@ inputs:
runs:
using: composite
steps:
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Yarn install
uses: ./.github/actions/yarn-install
- name: Configure Git
-2
View File
@@ -35,8 +35,6 @@ runs:
with:
java-version: '17'
distribution: 'zulu'
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Run yarn install
uses: ./.github/actions/yarn-install
- name: Start Metro in Debug
@@ -17,6 +17,9 @@ outputs:
runs:
using: composite
steps:
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Setup hermes version
shell: bash
id: hermes-version
@@ -64,8 +67,6 @@ runs:
echo "HERMES_CACHED=true" >> "$GITHUB_OUTPUT"
fi
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Yarn- Install Dependencies
if: ${{ steps.meaningful-cache.outputs.HERMES_CACHED != 'true' }}
uses: ./.github/actions/yarn-install
@@ -23,8 +23,6 @@ runs:
uses: ./.github/actions/setup-xcode
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Run yarn install
uses: ./.github/actions/yarn-install
- name: Create Hermes folder
shell: bash
run: mkdir -p "$HERMES_WS_DIR"
@@ -36,6 +34,8 @@ runs:
- name: Print Downloaded hermes
shell: bash
run: ls -lR "$HERMES_WS_DIR"
- name: Run yarn
uses: ./.github/actions/yarn-install
- name: Setup ruby
uses: ruby/setup-ruby@v1
with:
+2
View File
@@ -2,6 +2,8 @@ name: yarn-install
runs:
using: composite
steps:
- name: Setup node.js
uses: ./.github/actions/setup-node
- name: Install dependencies
shell: bash
run: |
+1 -2
View File
@@ -583,8 +583,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: ["24", "22"]
# node-version: ["24", "22", "20.19.4"]
node-version: ["24", "22", "20"]
steps:
- name: Checkout
uses: actions/checkout@v4
+36
View File
@@ -0,0 +1,36 @@
{
"arrowParens": "avoid",
"bracketSameLine": true,
"bracketSpacing": false,
"requirePragma": true,
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "lf",
"overrides": [
{
"files": ["*.code-workspace"],
"options": {
"parser": "json"
}
},
{
"files": [
"*.js",
"*.js.flow"
],
"options": {
"parser": "hermes"
}
},
{
"files": [
"**/__docs__/*.md"
],
"options": {
"parser": "markdown",
"proseWrap": "always",
"requirePragma": false
}
}
]
}
-48
View File
@@ -1,48 +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.
*
* @format
*/
module.exports = {
arrowParens: 'avoid',
bracketSameLine: true,
bracketSpacing: false,
requirePragma: true,
singleQuote: true,
trailingComma: 'all',
endOfLine: 'lf',
plugins: [
// Using module.parent and createRequire hack to simulate prettier v2 plugin resolution behavior.
// The hack allows us to resolve the plugin from the install location of prettier.
(module.parent
? require('module').createRequire(module.parent.id)
: require
).resolve('prettier-plugin-hermes-parser'),
],
overrides: [
{
files: ['*.code-workspace'],
options: {
parser: 'json',
},
},
{
files: ['*.js', '*.js.flow'],
options: {
parser: 'hermes',
},
},
{
files: ['**/__docs__/*.md'],
options: {
parser: 'markdown',
proseWrap: 'always',
requirePragma: false,
},
},
],
};
+3 -3
View File
@@ -56,8 +56,8 @@
"@electron/packager": "^18.3.6",
"@jest/create-cache-key-function": "^29.7.0",
"@microsoft/api-extractor": "^7.52.2",
"@react-native/metro-babel-transformer": "0.82.0-main",
"@react-native/metro-config": "0.82.0-main",
"@react-native/metro-babel-transformer": "0.81.0-main",
"@react-native/metro-config": "0.81.0-main",
"@tsconfig/node22": "22.0.2",
"@types/react": "^19.1.0",
"@typescript-eslint/parser": "^8.36.0",
@@ -82,7 +82,7 @@
"eslint-plugin-redundant-undefined": "^0.4.0",
"eslint-plugin-relay": "^1.8.3",
"flow-api-translator": "0.29.1",
"flow-bin": "^0.276.0",
"flow-bin": "^0.275.0",
"glob": "^7.1.1",
"hermes-eslint": "0.29.1",
"hermes-transform": "0.29.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/assets-registry",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Asset support code for React Native.",
"license": "MIT",
"repository": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/babel-plugin-codegen",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Babel plugin to generate native module and view manager code for React Native.",
"license": "MIT",
"repository": {
@@ -26,7 +26,7 @@
],
"dependencies": {
"@babel/traverse": "^7.25.3",
"@react-native/codegen": "0.82.0-main"
"@react-native/codegen": "0.81.0-main"
},
"devDependencies": {
"@babel/core": "^7.25.2"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/community-cli-plugin",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Core CLI commands for React Native",
"keywords": [
"react-native",
@@ -22,7 +22,7 @@
"dist"
],
"dependencies": {
"@react-native/dev-middleware": "0.82.0-main",
"@react-native/dev-middleware": "0.81.0-main",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"metro": "^0.83.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/core-cli-utils",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "React Native CLI library for Frameworks to build on",
"license": "MIT",
"main": "./src/index.flow.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/debugger-frontend",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Debugger frontend for React Native based on Chrome DevTools",
"keywords": [
"react-native",
@@ -1,62 +0,0 @@
#!/usr/bin/env dotslash
// @generated SignedSource<<686df5695b32a90cd465412d979a1a3f>>
{
"name": "React Native DevTools",
"platforms": {
"linux-aarch64": {
"size": 113511487,
"hash": "sha256",
"digest": "c22f7d5e029357f05055ce7c56cc284a0d441d558e103a7b7ebae118c67d0b95",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQPRilWDJV8xyKEv9lqYn7Hf2kyZg6nqW8KctGZCXsU95lS_MTFyAmEULrB3J6hGCjqBY2Zl-uq_FjERAIgzbZFWX2eceacTbutBSOKEj6QTZzkVN_L1zPh2lGh24x29MHpkwUzw4E-kAXV43f-11QxqD7orI1QicyOnsmUqeRNKE_QjM9rNRsw6iE8"
}
],
"format": "tar.gz",
"path": "React Native DevTools-linux-arm64/React Native DevTools"
},
"linux-x86_64": {
"size": 113244728,
"hash": "sha256",
"digest": "d749aee18d6c969f033511ed631b439c578068cc20786974bc0ee707c6c2f177",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQP8NAAXjHJ-9NJThip1nnmVim4yiS1tdYAXmWl2Remiluq5f13nXA8YEadsmGRLWxXz2WJouHXSK47ea4a30fxewyKeTt0niFn3T-lr_91m3Ve5ZS-FOZ_9CRVCkv5zC-Z1FlXJGOnphfWetIU10Pw9tho3IzjdSNlXle0XTrkJ2AyKSl4cKPaX"
}
],
"format": "tar.gz",
"path": "React Native DevTools-linux-x64/React Native DevTools"
},
"macos-aarch64": {
"size": 108805847,
"hash": "sha256",
"digest": "dc4a6cfa3d2d8646db8793ef497e43ad72be54bb14ed96345d059880ff72ce1e",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQPuZx9G4h30mUDM0hZ8y74LbzAi7-S6jnOqq8uyfDdIptLwIgpb8CKB8F2tduvaBFMGd7obwXd2NVy01r6XKVdgAK5-QTp8PYUPUE7VSCi6QAqobXcQ2uq_lY-jgyj8XkV4Ua4gA6KR609Bmh-beSTOrSqMymqVodaGqoGjB_9jVwfD99_PfFlex-Ta"
}
],
"format": "tar.gz",
"path": "React Native DevTools.app/Contents/MacOS/React Native DevTools"
},
"macos-x86_64": {
"size": 113766610,
"hash": "sha256",
"digest": "74178859ce6a1c26c32055e192b1b123822c76c827ea86a6dfdb5463b89b1ace",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQN6DjGhr8_L7N_cwrh8pi3cfNcbn9dot_QQpgwe3Vv-780FDgi6JUeMvbo_wbFoT2rogxfHZ0-NbXbKdWYwhGUoGeQuikT57QnVMOYuPUTjlQUgYy0Ng9XPhq3iSEYwlMV1XsUJuPFRUg-hIHcgaaA55JoTLXZ1fLYydhHnmgRxGHc4pxaHvSpTtQ"
}
],
"format": "tar.gz",
"path": "React Native DevTools.app/Contents/MacOS/React Native DevTools"
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/debugger-shell",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Experimental debugger shell for React Native for use with @react-native/debugger-frontend",
"keywords": [
"react-native",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/dev-middleware",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Dev server middleware for React Native",
"keywords": [
"react-native",
@@ -23,7 +23,7 @@
],
"dependencies": {
"@isaacs/ttlcache": "^1.4.1",
"@react-native/debugger-frontend": "0.82.0-main",
"@react-native/debugger-frontend": "0.81.0-main",
"chrome-launcher": "^0.15.2",
"chromium-edge-launcher": "^0.2.0",
"connect": "^3.6.5",
@@ -184,7 +184,7 @@ describe.each(['HTTP', 'HTTPS'])(
describe.each(['10.0.2.2:8080', '[::1]', 'example.com:2000'])(
'%s aliasing to and from localhost',
sourceHost => {
test('Debugger.scriptParsed - in source map fetching', async () => {
test('in source map fetching during Debugger.scriptParsed', async () => {
serverRef.app.use('/source-map', serveStaticJson({version: 3}));
const {device, debugger_} = await createAndConnectTarget(
serverRef,
@@ -220,53 +220,6 @@ describe.each(['HTTP', 'HTTPS'])(
}
});
test('Runtime.consoleAPICalled - in location resolutions', async () => {
const {device, debugger_} = await createAndConnectTarget(
serverRef,
autoCleanup.signal,
{
app: 'bar-app',
id: 'page1',
title: 'bar-title',
vm: 'bar-vm',
},
{
deviceHostHeader: sourceHost,
},
);
try {
const consoleMessage = await sendFromTargetToDebugger(
device,
debugger_,
'page1',
{
method: 'Runtime.consoleAPICalled',
params: {
stackTrace: {
callFrames: [
{
url: `${protocol.toLowerCase()}://${sourceHost}/bundleFile:1:2`,
field2: 'aaa',
},
{
url: `${protocol.toLowerCase()}://${sourceHost}/bundleFile:5:4`,
field3: 'bbb',
},
],
},
},
},
);
expect(consoleMessage.params.stackTrace.callFrames).toEqual([
{url: `${serverRef.serverBaseUrl}/bundleFile:1:2`, field2: 'aaa'},
{url: `${serverRef.serverBaseUrl}/bundleFile:5:4`, field3: 'bbb'},
]);
} finally {
device.close();
debugger_.close();
}
});
test('in Debugger.setBreakpointByUrl', async () => {
const {device, debugger_} = await createAndConnectTarget(
serverRef,
@@ -844,23 +844,6 @@ export default class Device {
this.#isLegacyPageReloading = false;
}
if (payload.method === 'Runtime.consoleAPICalled') {
const callFrames = payload.params?.stackTrace?.callFrames ?? [];
for (const callFrame of callFrames) {
if (callFrame.url) {
const parsedUrl = this.#tryParseHTTPURL(callFrame.url);
if (parsedUrl) {
// Rewrite device-relative URLs pointing to the server so that they're
// reachable from the frontend.
callFrame.url = this.#deviceRelativeUrlToDebuggerRelativeUrl(
parsedUrl,
debuggerInfo,
).href;
}
}
}
}
}
/**
@@ -48,7 +48,6 @@ export type CDPClientMessage =
export type CDPServerMessage =
| CDPEvent<'Debugger.scriptParsed'>
| CDPEvent<'Runtime.consoleAPICalled'>
| CDPEvent<>
| CDPResponse<'Debugger.getScriptSource'>
| CDPResponse<>;
@@ -84,27 +84,10 @@ export interface Debugger {
*/
sourceMapURL: string,
};
ConsoleAPICalled: {
args: Array<{type: string, value: string}>,
executionContextId: number,
stackTrace: {
timestamp: number,
type: string,
callFrames: Array<{
columnNumber: number,
lineNumber: number,
functionName: string,
scriptId: string,
url: string,
}>,
},
};
}
export type Events = {
'Debugger.scriptParsed': Debugger['ScriptParsedEvent'],
'Runtime.consoleAPICalled': Debugger['ConsoleAPICalled'],
[method: string]: JSONSerializable,
};
@@ -1,6 +1,6 @@
{
"name": "@react-native/eslint-config",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "ESLint config for React Native",
"license": "MIT",
"repository": {
@@ -22,7 +22,7 @@
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/eslint-parser": "^7.25.1",
"@react-native/eslint-plugin": "0.82.0-main",
"@react-native/eslint-plugin": "0.81.0-main",
"@typescript-eslint/eslint-plugin": "^8.36.0",
"@typescript-eslint/parser": "^8.36.0",
"eslint-config-prettier": "^8.5.0",
@@ -1,6 +1,6 @@
{
"name": "@react-native/eslint-plugin",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "ESLint rules for @react-native/eslint-config",
"license": "MIT",
"repository": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/eslint-plugin-specs",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "ESLint rules to validate NativeModule and Component Specs",
"license": "MIT",
"repository": {
@@ -26,7 +26,7 @@
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@react-native/codegen": "0.82.0-main",
"@react-native/codegen": "0.81.0-main",
"make-dir": "^2.1.0",
"pirates": "^4.0.1",
"source-map-support": "0.5.0"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/gradle-plugin",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Gradle Plugin for React Native",
"license": "MIT",
"repository": {
@@ -100,10 +100,10 @@ abstract class ReactExtension @Inject constructor(val project: Project) {
* Allows to specify the debuggable variants (by default just 'debug'). Variants in this list will
* not be bundled (the bundle file will not be created and won't be copied over).
*
* Default: ['debug', 'debugOptimized']
* Default: ['debug']
*/
val debuggableVariants: ListProperty<String> =
objects.listProperty(String::class.java).convention(listOf("debug", "debugOptimized"))
objects.listProperty(String::class.java).convention(listOf("debug"))
/** Hermes Config */
@@ -42,16 +42,6 @@ internal object AgpConfiguratorUtils {
getByName("release").apply {
manifestPlaceholders["usesCleartextTraffic"] = "false"
}
maybeCreate("debugOptimized").apply {
manifestPlaceholders["usesCleartextTraffic"] = "true"
initWith(debug)
externalNativeBuild {
cmake {
arguments("-DCMAKE_BUILD_TYPE=Release")
matchingFallbacks += listOf("release")
}
}
}
}
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/metro-config",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Metro configuration for React Native.",
"license": "MIT",
"repository": {
@@ -26,8 +26,8 @@
"dist"
],
"dependencies": {
"@react-native/js-polyfills": "0.82.0-main",
"@react-native/metro-babel-transformer": "0.82.0-main",
"@react-native/js-polyfills": "0.81.0-main",
"@react-native/metro-babel-transformer": "0.81.0-main",
"metro-config": "^0.83.0",
"metro-runtime": "^0.83.0"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/new-app-screen",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "NewAppScreen component for React Native",
"keywords": [
"react-native"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/normalize-colors",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Color normalization for React Native.",
"license": "MIT",
"repository": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/js-polyfills",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Polyfills for React Native.",
"license": "MIT",
"repository": {
@@ -1,6 +1,6 @@
{
"name": "@react-native/babel-preset",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Babel preset for React Native applications",
"repository": {
"type": "git",
@@ -66,7 +66,7 @@
"@babel/plugin-transform-typescript": "^7.25.2",
"@babel/plugin-transform-unicode-regex": "^7.24.7",
"@babel/template": "^7.25.0",
"@react-native/babel-plugin-codegen": "0.82.0-main",
"@react-native/babel-plugin-codegen": "0.81.0-main",
"babel-plugin-syntax-hermes-parser": "0.29.1",
"babel-plugin-transform-flow-enums": "^0.0.2",
"react-refresh": "^0.14.0"
@@ -1,6 +1,6 @@
{
"name": "@react-native/metro-babel-transformer",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Babel transformer for React Native applications.",
"repository": {
"type": "git",
@@ -27,7 +27,7 @@
],
"dependencies": {
"@babel/core": "^7.25.2",
"@react-native/babel-preset": "0.82.0-main",
"@react-native/babel-preset": "0.81.0-main",
"hermes-parser": "0.29.1",
"nullthrows": "^1.1.1"
},
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@react-native/codegen",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Code generation tools for React Native",
"license": "MIT",
"repository": {
@@ -1,6 +1,6 @@
{
"name": "@react-native/compatibility-check",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "Check a React Native app's boundary between JS and Native for incompatibilities",
"license": "MIT",
"repository": {
@@ -29,7 +29,7 @@
"dist"
],
"dependencies": {
"@react-native/codegen": "0.82.0-main"
"@react-native/codegen": "0.81.0-main"
},
"devDependencies": {
"flow-remove-types": "^2.237.2",
@@ -1,6 +1,6 @@
{
"name": "@react-native/popup-menu-android",
"version": "0.82.0-main",
"version": "0.81.0-main",
"description": "PopupMenu for the Android platform",
"main": "index.js",
"files": [
@@ -21,7 +21,7 @@
},
"license": "MIT",
"devDependencies": {
"@react-native/codegen": "0.82.0-main"
"@react-native/codegen": "0.81.0-main"
},
"peerDependencies": {
"@types/react": "^19.1.0",
@@ -1,6 +1,6 @@
{
"name": "@react-native/oss-library-example",
"version": "0.82.0-main",
"version": "0.81.0-main",
"private": true,
"description": "Package that includes native module exapmle, native component example, targets both the old and the new architecture. It should serve as an example of a real-world OSS library.",
"license": "MIT",
@@ -26,7 +26,7 @@
],
"devDependencies": {
"@babel/core": "^7.25.2",
"@react-native/babel-preset": "0.82.0-main",
"@react-native/babel-preset": "0.81.0-main",
"react-native": "1000.0.0"
},
"peerDependencies": {
@@ -30,7 +30,6 @@ export default function queueMicrotask(callback: Function) {
}
// Try to reuse a lazily allocated resolved promise from closure.
// $FlowFixMe[constant-condition]
(resolvedPromise || (resolvedPromise = Promise.resolve()))
.then(callback)
.catch(error =>
@@ -50,9 +50,8 @@ class LazyVector {
const_reference at(size_type pos) const {
#ifndef _LIBCPP_NO_EXCEPTIONS
if (!(pos < _size)) {
if (!(pos < _size))
throw std::out_of_range("out of range");
}
#else
assert(pos < _size || !"out of range");
#endif
@@ -25,13 +25,11 @@ static NSUInteger RCTDeviceFreeMemory(void)
kern_return_t kern;
kern = host_page_size(host_port, &page_size);
if (kern != KERN_SUCCESS) {
if (kern != KERN_SUCCESS)
return 0;
}
kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size);
if (kern != KERN_SUCCESS) {
if (kern != KERN_SUCCESS)
return 0;
}
return (vm_stat.free_count - vm_stat.speculative_count) * page_size;
}
@@ -280,9 +278,8 @@ static NSUInteger RCTDeviceFreeMemory(void)
- (void)calculateMaxBufferCount
{
NSUInteger bytes = CGImageGetBytesPerRow(self.currentFrame.CGImage) * CGImageGetHeight(self.currentFrame.CGImage);
if (bytes == 0) {
if (bytes == 0)
bytes = 1024;
}
NSUInteger max = 0;
if (self.maxBufferSize > 0) {
@@ -66,7 +66,7 @@
static NSString *const RCTJSThreadName = @"com.facebook.react.JavaScript";
using RCTPendingCall = void (^)();
typedef void (^RCTPendingCall)();
using namespace facebook::jsi;
using namespace facebook::react;
@@ -11,11 +11,11 @@
#import <jsinspector-modern/ReactCdp.h>
using RCTInspectorNetworkListener = facebook::react::jsinspector_modern::NetworkRequestListener;
typedef facebook::react::jsinspector_modern::NetworkRequestListener RCTInspectorNetworkListener;
using RCTInspectorNetworkExecutor = facebook::react::jsinspector_modern::ScopedExecutor<RCTInspectorNetworkListener>;
typedef facebook::react::jsinspector_modern::ScopedExecutor<RCTInspectorNetworkListener> RCTInspectorNetworkExecutor;
using RCTInspectorLoadNetworkResourceRequest = facebook::react::jsinspector_modern::LoadNetworkResourceRequest;
typedef facebook::react::jsinspector_modern::LoadNetworkResourceRequest RCTInspectorLoadNetworkResourceRequest;
/**
* A helper class that wraps around NSURLSession to make network requests.
@@ -8,7 +8,7 @@
#import "RCTInspectorNetworkHelper.h"
#import <React/RCTLog.h>
using ListenerBlock = void (^)(RCTInspectorNetworkListener *);
typedef void (^ListenerBlock)(RCTInspectorNetworkListener *);
@interface RCTInspectorNetworkHelper () <NSURLSessionDataDelegate>
@property (nonatomic, strong) NSURLSession *session;
+2 -2
View File
@@ -205,7 +205,7 @@ RCT_ENUM_CONVERTER(
UIFontWeightRegular,
doubleValue)
using RCTFontStyle = BOOL;
typedef BOOL RCTFontStyle;
RCT_ENUM_CONVERTER(
RCTFontStyle,
(@{
@@ -216,7 +216,7 @@ RCT_ENUM_CONVERTER(
NO,
boolValue)
using RCTFontVariantDescriptor = NSDictionary;
typedef NSDictionary RCTFontVariantDescriptor;
+ (RCTFontVariantDescriptor *)RCTFontVariantDescriptor:(id)json
{
static NSDictionary *mapping;
@@ -71,9 +71,8 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
- (void)beginRefreshingProgrammatically
{
if (!_hasMovedToWindow) {
if (!_hasMovedToWindow)
return;
}
UInt64 beginRefreshingTimestamp = _currentRefreshingStateTimestamp;
_refreshingProgrammatically = YES;
@@ -109,9 +108,8 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder)
- (void)endRefreshingProgrammatically
{
if (!_hasMovedToWindow) {
if (!_hasMovedToWindow)
return;
}
// The contentOffset of the scrollview MUST be greater than the contentInset before calling
// endRefreshing otherwise the next pull to refresh will not work properly.
UIScrollView *scrollView = self.scrollView;
@@ -531,21 +531,20 @@ public abstract interface class com/facebook/react/bridge/ActivityEventListener
public fun onUserLeaveHint (Landroid/app/Activity;)V
}
public final class com/facebook/react/bridge/Arguments {
public static final field INSTANCE Lcom/facebook/react/bridge/Arguments;
public static final fun createArray ()Lcom/facebook/react/bridge/WritableArray;
public static final fun createMap ()Lcom/facebook/react/bridge/WritableMap;
public static final fun fromArray (Ljava/lang/Object;)Lcom/facebook/react/bridge/WritableArray;
public static final fun fromBundle (Landroid/os/Bundle;)Lcom/facebook/react/bridge/WritableMap;
public static final fun fromJavaArgs (Ljava/lang/Object;)Lcom/facebook/react/bridge/WritableNativeArray;
public static final fun fromJavaArgs ([Ljava/lang/Object;)Lcom/facebook/react/bridge/WritableNativeArray;
public static final fun fromList (Ljava/util/List;)Lcom/facebook/react/bridge/WritableArray;
public static final fun makeNativeArray (Ljava/lang/Object;)Lcom/facebook/react/bridge/WritableNativeArray;
public static final fun makeNativeArray (Ljava/util/List;)Lcom/facebook/react/bridge/WritableNativeArray;
public static final fun makeNativeMap (Landroid/os/Bundle;)Lcom/facebook/react/bridge/WritableNativeMap;
public static final fun makeNativeMap (Ljava/util/Map;)Lcom/facebook/react/bridge/WritableNativeMap;
public static final fun toBundle (Lcom/facebook/react/bridge/ReadableMap;)Landroid/os/Bundle;
public static final fun toList (Lcom/facebook/react/bridge/ReadableArray;)Ljava/util/ArrayList;
public class com/facebook/react/bridge/Arguments {
public fun <init> ()V
public static fun createArray ()Lcom/facebook/react/bridge/WritableArray;
public static fun createMap ()Lcom/facebook/react/bridge/WritableMap;
public static fun fromArray (Ljava/lang/Object;)Lcom/facebook/react/bridge/WritableArray;
public static fun fromBundle (Landroid/os/Bundle;)Lcom/facebook/react/bridge/WritableMap;
public static fun fromJavaArgs ([Ljava/lang/Object;)Lcom/facebook/react/bridge/WritableNativeArray;
public static fun fromList (Ljava/util/List;)Lcom/facebook/react/bridge/WritableArray;
public static fun makeNativeArray (Ljava/lang/Object;)Lcom/facebook/react/bridge/WritableNativeArray;
public static fun makeNativeArray (Ljava/util/List;)Lcom/facebook/react/bridge/WritableNativeArray;
public static fun makeNativeMap (Landroid/os/Bundle;)Lcom/facebook/react/bridge/WritableNativeMap;
public static fun makeNativeMap (Ljava/util/Map;)Lcom/facebook/react/bridge/WritableNativeMap;
public static fun toBundle (Lcom/facebook/react/bridge/ReadableMap;)Landroid/os/Bundle;
public static fun toList (Lcom/facebook/react/bridge/ReadableArray;)Ljava/util/ArrayList;
}
public final class com/facebook/react/bridge/AssertionException : java/lang/RuntimeException {
@@ -4404,6 +4403,10 @@ public abstract interface class com/facebook/react/uimanager/UIManagerModule$Cus
public abstract fun resolveCustomEventName (Ljava/lang/String;)Ljava/lang/String;
}
public abstract interface class com/facebook/react/uimanager/UIManagerModuleListener {
public abstract fun willDispatchViewUpdates (Lcom/facebook/react/uimanager/UIManagerModule;)V
}
public class com/facebook/react/uimanager/UIViewOperationQueue {
public static final field DEFAULT_MIN_TIME_LEFT_IN_FRAME_FOR_NONBATCHED_OPERATION_MS I
public fun <init> (Lcom/facebook/react/bridge/ReactApplicationContext;Lcom/facebook/react/uimanager/NativeViewHierarchyManager;I)V
@@ -599,7 +599,7 @@ android {
publishing {
multipleVariants {
withSourcesJar()
allVariants()
includeBuildTypeValues("debug", "release")
}
}
@@ -607,15 +607,6 @@ android {
unitTests { isIncludeAndroidResources = true }
targetSdk = libs.versions.targetSdk.get().toInt()
}
buildTypes {
create("debugOptimized") {
initWith(getByName("debug"))
externalNativeBuild {
cmake { arguments("-DCMAKE_BUILD_TYPE=Release", "-DREACT_NATIVE_DEBUG_OPTIMIZED=True") }
}
}
}
}
tasks.withType<KotlinCompile>().configureEach {
@@ -630,6 +621,7 @@ dependencies {
api(libs.androidx.autofill)
api(libs.androidx.swiperefreshlayout)
api(libs.androidx.tracing)
api(libs.androidx.window)
api(libs.fbjni)
api(libs.fresco)
@@ -306,12 +306,6 @@ android {
}
}
}
buildTypes {
create("debugOptimized") {
initWith(getByName("debug"))
externalNativeBuild { cmake { arguments("-DCMAKE_BUILD_TYPE=Release") } }
}
}
}
sourceSets.getByName("main") {
@@ -869,7 +869,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private int mDeviceRotation = 0;
/* package */ CustomGlobalLayoutListener() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext());
mVisibleViewArea = new Rect();
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
}
@@ -992,7 +992,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
return;
}
mDeviceRotation = rotation;
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
DisplayMetricsHolder.initDisplayMetrics(getContext());
emitOrientationChanged(rotation);
}
@@ -0,0 +1,423 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridge;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.proguard.annotations.DoNotStrip;
import java.lang.reflect.Array;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Nullsafe(Nullsafe.Mode.LOCAL)
@DoNotStrip
public class Arguments {
private static @Nullable Object makeNativeObject(@Nullable Object object) {
if (object == null) {
return null;
} else if (object instanceof Float
|| object instanceof Long
|| object instanceof Byte
|| object instanceof Short) {
return ((Number) object).doubleValue();
} else if (object.getClass().isArray()) {
return makeNativeArray(object);
} else if (object instanceof List) {
return makeNativeArray((List) object);
} else if (object instanceof Map) {
return makeNativeMap((Map<String, Object>) object);
} else if (object instanceof Bundle) {
return makeNativeMap((Bundle) object);
} else if (object instanceof JavaOnlyMap) {
return makeNativeMap(((JavaOnlyMap) object).toHashMap());
} else if (object instanceof JavaOnlyArray) {
return makeNativeArray(((JavaOnlyArray) object).toArrayList());
} else {
// Boolean, Integer, Double, String, WritableNativeArray, WritableNativeMap
return object;
}
}
/**
* This method converts a List into a NativeArray. The data types supported are boolean, int,
* float, double, and String. List, Map, and Bundle objects, as well as arrays, containing values
* of the above types and/or null, or any recursive arrangement of these, are also supported. The
* best way to think of this is a way to generate a Java representation of a json list, from Java
* types which have a natural representation in json.
*/
public static WritableNativeArray makeNativeArray(@Nullable List objects) {
WritableNativeArray nativeArray = new WritableNativeArray();
if (objects == null) {
return nativeArray;
}
for (Object elem : objects) {
elem = makeNativeObject(elem);
if (elem == null) {
nativeArray.pushNull();
} else if (elem instanceof Boolean) {
nativeArray.pushBoolean((Boolean) elem);
} else if (elem instanceof Integer) {
nativeArray.pushInt((Integer) elem);
} else if (elem instanceof Double) {
nativeArray.pushDouble((Double) elem);
} else if (elem instanceof String) {
nativeArray.pushString((String) elem);
} else if (elem instanceof WritableNativeArray) {
nativeArray.pushArray((WritableNativeArray) elem);
} else if (elem instanceof WritableNativeMap) {
nativeArray.pushMap((WritableNativeMap) elem);
} else {
throw new IllegalArgumentException("Could not convert " + elem.getClass());
}
}
return nativeArray;
}
/**
* This overload is like the above, but uses reflection to operate on any primitive or object
* type.
*/
public static <T> WritableNativeArray makeNativeArray(final @Nullable Object objects) {
if (objects == null) {
return new WritableNativeArray();
}
// No explicit check for objects's type here. If it's not an array, the
// Array methods will throw IllegalArgumentException.
return makeNativeArray(
new AbstractList() {
public int size() {
return Array.getLength(objects);
}
public @Nullable Object get(int index) {
return Array.get(objects, index);
}
});
}
private static void addEntry(WritableNativeMap nativeMap, String key, @Nullable Object value) {
value = makeNativeObject(value);
if (value == null) {
nativeMap.putNull(key);
} else if (value instanceof Boolean) {
nativeMap.putBoolean(key, (Boolean) value);
} else if (value instanceof Integer) {
nativeMap.putInt(key, (Integer) value);
} else if (value instanceof Number) {
nativeMap.putDouble(key, ((Number) value).doubleValue());
} else if (value instanceof String) {
nativeMap.putString(key, (String) value);
} else if (value instanceof WritableNativeArray) {
nativeMap.putArray(key, (WritableNativeArray) value);
} else if (value instanceof WritableNativeMap) {
nativeMap.putMap(key, (WritableNativeMap) value);
} else {
throw new IllegalArgumentException("Could not convert " + value.getClass());
}
}
/**
* This method converts a Map into a NativeMap. Value types are supported as with makeNativeArray.
* The best way to think of this is a way to generate a Java representation of a json object, from
* Java types which have a natural representation in json.
*/
@DoNotStrip
public static WritableNativeMap makeNativeMap(@Nullable Map<String, Object> objects) {
WritableNativeMap nativeMap = new WritableNativeMap();
if (objects == null) {
return nativeMap;
}
for (Map.Entry<String, Object> entry : objects.entrySet()) {
addEntry(nativeMap, entry.getKey(), entry.getValue());
}
return nativeMap;
}
/** Like the above, but takes a Bundle instead of a Map. */
@DoNotStrip
public static WritableNativeMap makeNativeMap(@Nullable Bundle bundle) {
WritableNativeMap nativeMap = new WritableNativeMap();
if (bundle == null) {
return nativeMap;
}
for (String key : bundle.keySet()) {
addEntry(nativeMap, key, bundle.get(key));
}
return nativeMap;
}
/** This method should be used when you need to stub out creating NativeArrays in unit tests. */
public static WritableArray createArray() {
return new WritableNativeArray();
}
/** This method should be used when you need to stub out creating NativeMaps in unit tests. */
public static WritableMap createMap() {
return new WritableNativeMap();
}
public static WritableNativeArray fromJavaArgs(Object[] args) {
WritableNativeArray arguments = new WritableNativeArray();
for (int i = 0; i < args.length; i++) {
Object argument = args[i];
if (argument == null) {
arguments.pushNull();
continue;
}
Class argumentClass = argument.getClass();
if (argumentClass == Boolean.class) {
arguments.pushBoolean(((Boolean) argument).booleanValue());
} else if (argumentClass == Integer.class) {
arguments.pushDouble(((Integer) argument).doubleValue());
} else if (argumentClass == Double.class) {
arguments.pushDouble(((Double) argument).doubleValue());
} else if (argumentClass == Float.class) {
arguments.pushDouble(((Float) argument).doubleValue());
} else if (argumentClass == String.class) {
arguments.pushString(argument.toString());
} else if (argumentClass == WritableNativeMap.class) {
arguments.pushMap((WritableNativeMap) argument);
} else if (argumentClass == WritableNativeArray.class) {
arguments.pushArray((WritableNativeArray) argument);
} else {
throw new RuntimeException("Cannot convert argument of type " + argumentClass);
}
}
return arguments;
}
/**
* Convert an array to a {@link WritableArray}.
*
* @param array the array to convert. Supported types are: {@code String[]}, {@code Bundle[]},
* {@code int[]}, {@code float[]}, {@code double[]}, {@code boolean[]}.
* @return the converted {@link WritableArray}
* @throws IllegalArgumentException if the passed object is none of the above types
*/
public static WritableArray fromArray(Object array) {
WritableArray catalystArray = createArray();
if (array instanceof String[]) {
for (String v : (String[]) array) {
catalystArray.pushString(v);
}
} else if (array instanceof Bundle[]) {
for (Bundle v : (Bundle[]) array) {
catalystArray.pushMap(fromBundle(v));
}
} else if (array instanceof int[]) {
for (int v : (int[]) array) {
catalystArray.pushInt(v);
}
} else if (array instanceof float[]) {
for (float v : (float[]) array) {
catalystArray.pushDouble(v);
}
} else if (array instanceof double[]) {
for (double v : (double[]) array) {
catalystArray.pushDouble(v);
}
} else if (array instanceof boolean[]) {
for (boolean v : (boolean[]) array) {
catalystArray.pushBoolean(v);
}
} else if (array instanceof Parcelable[]) {
for (Parcelable v : (Parcelable[]) array) {
if (v instanceof Bundle) {
catalystArray.pushMap(fromBundle((Bundle) v));
} else {
throw new IllegalArgumentException("Unexpected array member type " + v.getClass());
}
}
} else {
throw new IllegalArgumentException("Unknown array type " + array.getClass());
}
return catalystArray;
}
/**
* Convert a {@link List} to a {@link WritableArray}.
*
* @param list the list to convert. Supported value types are: {@code null}, {@code String},
* {@code Bundle}, {@code List}, {@code Number}, {@code Boolean}, and all array types
* supported in {@link #fromArray(Object)}.
* @return the converted {@link WritableArray}
* @throws IllegalArgumentException if one of the values from the passed list is none of the above
* types
*/
public static WritableArray fromList(List list) {
WritableArray catalystArray = createArray();
for (Object obj : list) {
if (obj == null) {
catalystArray.pushNull();
} else if (obj.getClass().isArray()) {
catalystArray.pushArray(fromArray(obj));
} else if (obj instanceof Bundle) {
catalystArray.pushMap(fromBundle((Bundle) obj));
} else if (obj instanceof List) {
catalystArray.pushArray(fromList((List) obj));
} else if (obj instanceof String) {
catalystArray.pushString((String) obj);
} else if (obj instanceof Integer) {
catalystArray.pushInt((Integer) obj);
} else if (obj instanceof Number) {
catalystArray.pushDouble(((Number) obj).doubleValue());
} else if (obj instanceof Boolean) {
catalystArray.pushBoolean((Boolean) obj);
} else {
throw new IllegalArgumentException("Unknown value type " + obj.getClass());
}
}
return catalystArray;
}
/**
* Convert a {@link Bundle} to a {@link WritableMap}. Supported key types in the bundle are:
*
* <p>
*
* <ul>
* <li>primitive types: int, float, double, boolean
* <li>arrays supported by {@link #fromArray(Object)}
* <li>lists supported by {@link #fromList(List)}
* <li>{@link Bundle} objects that are recursively converted to maps
* </ul>
*
* @param bundle the {@link Bundle} to convert
* @return the converted {@link WritableMap}
* @throws IllegalArgumentException if there are keys of unsupported types
*/
public static WritableMap fromBundle(Bundle bundle) {
WritableMap map = createMap();
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
if (value == null) {
map.putNull(key);
} else if (value.getClass().isArray()) {
map.putArray(key, fromArray(value));
} else if (value instanceof String) {
map.putString(key, (String) value);
} else if (value instanceof Number) {
if (value instanceof Integer) {
map.putInt(key, (Integer) value);
} else {
map.putDouble(key, ((Number) value).doubleValue());
}
} else if (value instanceof Boolean) {
map.putBoolean(key, (Boolean) value);
} else if (value instanceof Bundle) {
map.putMap(key, fromBundle((Bundle) value));
} else if (value instanceof List) {
map.putArray(key, fromList((List) value));
} else {
throw new IllegalArgumentException("Could not convert " + value.getClass());
}
}
return map;
}
/**
* Convert a {@link WritableArray} to a {@link ArrayList}.
*
* @param readableArray the {@link WritableArray} to convert.
* @return the converted {@link ArrayList}.
*/
@Nullable
public static ArrayList toList(@Nullable ReadableArray readableArray) {
if (readableArray == null) {
return null;
}
ArrayList list = new ArrayList();
for (int i = 0; i < readableArray.size(); i++) {
switch (readableArray.getType(i)) {
case Null:
list.add(null);
break;
case Boolean:
list.add(readableArray.getBoolean(i));
break;
case Number:
double number = readableArray.getDouble(i);
if (number == Math.rint(number)) {
// Add as an integer
list.add((int) number);
} else {
// Add as a double
list.add(number);
}
break;
case String:
list.add(readableArray.getString(i));
break;
case Map:
list.add(toBundle(readableArray.getMap(i)));
break;
case Array:
list.add(toList(readableArray.getArray(i)));
break;
default:
throw new IllegalArgumentException("Could not convert object in array.");
}
}
return list;
}
/**
* Convert a {@link WritableMap} to a {@link Bundle}. Note: Each array is converted to an {@link
* ArrayList}.
*
* @param readableMap the {@link WritableMap} to convert.
* @return the converted {@link Bundle}.
*/
@Nullable
public static Bundle toBundle(@Nullable ReadableMap readableMap) {
if (readableMap == null) {
return null;
}
ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
Bundle bundle = new Bundle();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
ReadableType readableType = readableMap.getType(key);
switch (readableType) {
case Null:
bundle.putString(key, null);
break;
case Boolean:
bundle.putBoolean(key, readableMap.getBoolean(key));
break;
case Number:
// Can be int or double.
bundle.putDouble(key, readableMap.getDouble(key));
break;
case String:
bundle.putString(key, readableMap.getString(key));
break;
case Map:
bundle.putBundle(key, toBundle(readableMap.getMap(key)));
break;
case Array:
bundle.putSerializable(key, toList(readableMap.getArray(key)));
break;
default:
throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
}
}
return bundle;
}
}
@@ -1,356 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.bridge
import android.os.Bundle
import android.os.Parcelable
import com.facebook.proguard.annotations.DoNotStrip
import java.util.AbstractList
import kotlin.math.round
@DoNotStrip
public object Arguments {
@Suppress("UNCHECKED_CAST")
private fun makeNativeObject(value: Any?): Any? =
when {
value == null -> null
value is Float || value is Long || value is Byte || value is Short ->
(value as Number).toDouble()
value.javaClass.isArray -> makeNativeArray<Any>(value)
value is List<*> -> makeNativeArray(value)
value is Map<*, *> -> makeNativeMap(value as Map<String, Any?>)
value is Bundle -> makeNativeMap(value)
value is JavaOnlyMap -> makeNativeMap(value.toHashMap())
value is JavaOnlyArray -> makeNativeArray(value.toArrayList())
else -> value // Boolean, Integer, Double, String, WritableNativeArray, WritableNativeMap
}
/**
* This method converts a List into a NativeArray. The data types supported are boolean, int,
* float, double, and String. List, Map, and Bundle objects, as well as arrays, containing values
* of the above types and/or null, or any recursive arrangement of these, are also supported. The
* best way to think of this is a way to generate a Java representation of a json list, from Java
* types which have a natural representation in json.
*/
@JvmStatic
public fun makeNativeArray(objects: List<*>?): WritableNativeArray {
val nativeArray = WritableNativeArray()
if (objects == null) {
return nativeArray
}
for (elem in objects) {
when (val value = makeNativeObject(elem)) {
null -> nativeArray.pushNull()
is Boolean -> nativeArray.pushBoolean(value)
is Int -> nativeArray.pushInt(value)
is Double -> nativeArray.pushDouble(value)
is String -> nativeArray.pushString(value)
is WritableNativeArray -> nativeArray.pushArray(value)
is WritableNativeMap -> nativeArray.pushMap(value)
else -> throw IllegalArgumentException("Could not convert ${value.javaClass}")
}
}
return nativeArray
}
/**
* This overload is like the above, but uses reflection to operate on any primitive or object
* type.
*/
@JvmStatic
public fun <T> makeNativeArray(objects: Any?): WritableNativeArray {
if (objects == null) {
return WritableNativeArray()
}
// No explicit check for objects's type here. If it's not an array, the
// Array methods will throw IllegalArgumentException.
return makeNativeArray(
object : AbstractList<Any?>() {
override val size: Int
get() = java.lang.reflect.Array.getLength(objects)
override fun get(index: Int): Any? = java.lang.reflect.Array.get(objects, index)
})
}
private fun addEntry(nativeMap: WritableNativeMap, key: String, value: Any?) {
when (val nativeObjectValue = makeNativeObject(value)) {
null -> nativeMap.putNull(key)
is Boolean -> nativeMap.putBoolean(key, nativeObjectValue)
is Int -> nativeMap.putInt(key, nativeObjectValue)
is Number -> nativeMap.putDouble(key, nativeObjectValue.toDouble())
is String -> nativeMap.putString(key, nativeObjectValue)
is WritableNativeArray -> nativeMap.putArray(key, nativeObjectValue)
is WritableNativeMap -> nativeMap.putMap(key, nativeObjectValue)
else -> throw IllegalArgumentException("Could not convert ${nativeObjectValue.javaClass}")
}
}
/**
* This method converts a Map into a NativeMap. Value types are supported as with makeNativeArray.
* The best way to think of this is a way to generate a Java representation of a json object, from
* Java types which have a natural representation in json.
*/
@DoNotStrip
@JvmStatic
public fun makeNativeMap(objects: Map<String, Any?>?): WritableNativeMap {
val nativeMap = WritableNativeMap()
if (objects == null) {
return nativeMap
}
for ((key, value) in objects) {
addEntry(nativeMap, key, value)
}
return nativeMap
}
/** Like the above, but takes a Bundle instead of a Map. */
@DoNotStrip
@JvmStatic
@Suppress("DEPRECATION")
public fun makeNativeMap(bundle: Bundle?): WritableNativeMap {
val nativeMap = WritableNativeMap()
if (bundle == null) {
return nativeMap
}
for (key in bundle.keySet()) {
addEntry(nativeMap, key, bundle[key])
}
return nativeMap
}
/** This method should be used when you need to stub out creating NativeArrays in unit tests. */
@JvmStatic public fun createArray(): WritableArray = WritableNativeArray()
/** This method should be used when you need to stub out creating NativeMaps in unit tests. */
@JvmStatic public fun createMap(): WritableMap = WritableNativeMap()
@Suppress("UNCHECKED_CAST")
@JvmStatic
@Deprecated(
"Use fromJavaArgs(Array<Any?>) instead. This method is added only to retain compatibility with Java consumers.")
public fun fromJavaArgs(args: Any?): WritableNativeArray = fromJavaArgs(args as Array<Any?>)
@JvmStatic
public fun fromJavaArgs(args: Array<Any?>): WritableNativeArray {
val arguments = WritableNativeArray()
for (i in args.indices) {
val argument = args[i]
when (val argumentClass = argument?.javaClass) {
null -> arguments.pushNull()
Boolean::class.java,
java.lang.Boolean::class.java -> arguments.pushBoolean(argument as Boolean)
Int::class.java,
java.lang.Integer::class.java -> arguments.pushDouble((argument as Number).toDouble())
Double::class.java,
java.lang.Double::class.java -> arguments.pushDouble((argument as Double))
Float::class.java -> arguments.pushDouble((argument as Float).toDouble())
java.lang.Float::class.java -> arguments.pushDouble((argument as Float).toDouble())
String::class.java -> arguments.pushString(argument.toString())
WritableNativeMap::class.java -> arguments.pushMap(argument as WritableNativeMap)
WritableNativeArray::class.java -> arguments.pushArray(argument as WritableNativeArray)
else -> throw RuntimeException("Cannot convert argument of type $argumentClass")
}
}
return arguments
}
/**
* Convert an array to a [WritableArray].
*
* @param array the array to convert. Supported types are: `String[]`, `Bundle[]`, `int[]`,
* `float[]`, `double[]`, `boolean[]`.
* @return the converted [WritableArray]
* @throws IllegalArgumentException if the passed object is none of the above types
*/
@JvmStatic
@Suppress("UNCHECKED_CAST")
public fun fromArray(array: Any): WritableArray {
val catalystArray = createArray()
when {
array is Array<*> && array.isArrayOf<String>() -> {
for (v in array as Array<String?>) {
catalystArray.pushString(v)
}
}
array is Array<*> && array.isArrayOf<Bundle>() -> {
for (v in array as Array<Bundle>) {
catalystArray.pushMap(fromBundle(v))
}
}
array is IntArray -> {
for (v in array) {
catalystArray.pushInt(v)
}
}
array is FloatArray -> {
for (v in array) {
catalystArray.pushDouble(v.toDouble())
}
}
array is DoubleArray -> {
for (v in array) {
catalystArray.pushDouble(v)
}
}
array is BooleanArray -> {
for (v in array) {
catalystArray.pushBoolean(v)
}
}
array is Array<*> && array.isArrayOf<Parcelable>() -> {
for (v in array as Array<Parcelable>) {
if (v is Bundle) {
catalystArray.pushMap(fromBundle(v))
} else {
throw IllegalArgumentException("Unexpected array member type ${v.javaClass}")
}
}
}
else -> throw IllegalArgumentException("Unknown array type ${array.javaClass}")
}
return catalystArray
}
/**
* Convert a [List] to a [WritableArray].
*
* @param list the list to convert. Supported value types are: `null`, `String`, `Bundle`, `List`,
* `Number`, `Boolean`, and all array types supported in [.fromArray].
* @return the converted [WritableArray]
* @throws IllegalArgumentException if one of the values from the passed list is none of the above
* types
*/
@JvmStatic
public fun fromList(list: List<*>): WritableArray {
val catalystArray = createArray()
for (obj in list) {
when {
obj == null -> catalystArray.pushNull()
obj.javaClass.isArray -> catalystArray.pushArray(fromArray(obj))
obj is Bundle -> catalystArray.pushMap(fromBundle(obj))
obj is List<*> -> catalystArray.pushArray(fromList(obj))
obj is String -> catalystArray.pushString(obj)
obj is Int -> catalystArray.pushInt(obj)
obj is Number -> catalystArray.pushDouble(obj.toDouble())
obj is Boolean -> catalystArray.pushBoolean(obj)
else -> throw IllegalArgumentException("Unknown value type ${obj.javaClass}")
}
}
return catalystArray
}
/**
* Convert a [Bundle] to a [WritableMap]. Supported key types in the bundle are:
* * primitive types: int, float, double, boolean
* * arrays supported by [.fromArray]
* * lists supported by [.fromList]
* * [Bundle] objects that are recursively converted to maps
*
* @param bundle the [Bundle] to convert
* @return the converted [WritableMap]
* @throws IllegalArgumentException if there are keys of unsupported types
*/
@JvmStatic
@Suppress("DEPRECATION")
public fun fromBundle(bundle: Bundle): WritableMap {
val map = createMap()
for (key in bundle.keySet()) {
val value = bundle[key]
when {
value == null -> map.putNull(key)
value.javaClass.isArray -> map.putArray(key, fromArray(value))
value is String -> map.putString(key, value)
value is Number -> {
if (value is Int) {
map.putInt(key, value)
} else {
map.putDouble(key, value.toDouble())
}
}
value is Boolean -> map.putBoolean(key, value)
value is Bundle -> map.putMap(key, fromBundle(value))
value is List<*> -> map.putArray(key, fromList(value))
else -> throw IllegalArgumentException("Could not convert ${value.javaClass}")
}
}
return map
}
/**
* Convert a [WritableArray] to a [ArrayList].
*
* @param readableArray the [WritableArray] to convert.
* @return the converted [ArrayList].
*/
@JvmStatic
@Suppress("REDUNDANT_ELSE_IN_WHEN")
public fun toList(readableArray: ReadableArray?): ArrayList<Any?>? {
if (readableArray == null) {
return null
}
val list: ArrayList<Any?> = ArrayList()
for (i in 0..<readableArray.size()) {
when (readableArray.getType(i)) {
ReadableType.Null -> list.add(null)
ReadableType.Boolean -> list.add(readableArray.getBoolean(i))
ReadableType.Number -> {
val number = readableArray.getDouble(i)
if (number == round(number)) {
// Add as an integer
list.add(number.toInt())
} else {
// Add as a double
list.add(number)
}
}
ReadableType.String -> list.add(readableArray.getString(i))
ReadableType.Map -> list.add(toBundle(readableArray.getMap(i)))
ReadableType.Array -> list.add(toList(readableArray.getArray(i)))
else -> throw IllegalArgumentException("Could not convert object in array.")
}
}
return list
}
/**
* Convert a [WritableMap] to a [Bundle]. Note: Each array is converted to an [ArrayList].
*
* @param readableMap the [WritableMap] to convert.
* @return the converted [Bundle].
*/
@JvmStatic
@Suppress("REDUNDANT_ELSE_IN_WHEN")
public fun toBundle(readableMap: ReadableMap?): Bundle? {
if (readableMap == null) {
return null
}
val iterator = readableMap.keySetIterator()
val bundle = Bundle()
while (iterator.hasNextKey()) {
val key = iterator.nextKey()
when (readableMap.getType(key)) {
ReadableType.Null -> bundle.putString(key, null)
ReadableType.Boolean -> bundle.putBoolean(key, readableMap.getBoolean(key))
ReadableType.Number ->
bundle.putDouble(key, readableMap.getDouble(key)) // Can be int or double.
ReadableType.String -> bundle.putString(key, readableMap.getString(key))
ReadableType.Map -> bundle.putBundle(key, toBundle(readableMap.getMap(key)))
ReadableType.Array -> bundle.putSerializable(key, toList(readableMap.getArray(key)))
else -> throw IllegalArgumentException("Could not convert object with key: $key.")
}
}
return bundle
}
}
@@ -22,8 +22,7 @@ internal class CallbackImpl(private val jsInstance: JSInstance, private val call
throw RuntimeException(
"Illegal callback invocation from native module. This callback type only permits a single invocation from native code.")
}
@Suppress("UNCHECKED_CAST")
jsInstance.invokeCallback(callbackId, Arguments.fromJavaArgs(args as Array<Any?>))
jsInstance.invokeCallback(callbackId, Arguments.fromJavaArgs(args))
invoked = true
}
@@ -15,7 +15,7 @@ import com.facebook.proguard.annotations.DoNotStrip
public class CxxCallbackImpl @DoNotStrip private constructor() : HybridClassBase(), Callback {
override fun invoke(vararg args: Any?) {
@Suppress("UNCHECKED_CAST") nativeInvoke(Arguments.fromJavaArgs(args as Array<Any?>))
nativeInvoke(Arguments.fromJavaArgs(args))
}
private external fun nativeInvoke(arguments: NativeArray)
@@ -675,18 +675,6 @@ public class FabricUIManager
: null);
}
@AnyThread
@ThreadConfined(ANY)
@UnstableReactNativeAPI
public PreparedLayout reusePreparedLayoutWithNewReactTags(
PreparedLayout preparedLayout, int[] reactTags) {
return new PreparedLayout(
preparedLayout.getLayout(),
preparedLayout.getMaximumNumberOfLines(),
preparedLayout.getVerticalOffset(),
reactTags);
}
@AnyThread
@ThreadConfined(ANY)
@UnstableReactNativeAPI
@@ -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<<93aab733661b558c1701b728c18b3d00>>
* @generated SignedSource<<c8b19934d19d6b4514a0395edecb1330>>
*/
/**
@@ -23,5 +23,5 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android :
// We could use JNI to get the defaults from C++,
// but that is more expensive than just duplicating the defaults here.
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean = true
}
@@ -21,6 +21,7 @@ import com.facebook.react.bridge.NativeArray
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UIManager
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.common.annotations.FrameworkAPI
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.common.build.ReactBuildConfig
@@ -104,8 +105,9 @@ internal class BridgelessReactContext(context: Context, private val reactHost: R
private val reactHost: ReactHostImpl,
private val jsModuleInterface: Class<out JavaScriptModule>
) : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<Any?>): Any? {
val jsArgs: NativeArray = Arguments.fromJavaArgs(args)
override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any? {
val jsArgs: NativeArray =
if (args != null) Arguments.fromJavaArgs(args) else WritableNativeArray()
reactHost.callFunctionOnModule(
JavaScriptModuleRegistry.getJSModuleName(jsModuleInterface), method.name, jsArgs)
return null
@@ -13,8 +13,10 @@ 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
@@ -62,9 +64,19 @@ public object DisplayMetricsHolder {
@JvmStatic
public fun initDisplayMetrics(context: Context) {
val displayMetrics = context.resources.displayMetrics
windowDisplayMetrics = displayMetrics
val 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).
@@ -72,6 +84,8 @@ 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
}
@@ -13,10 +13,10 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
/** Listener used to hook into the UIManager update process. */
@Deprecated("Use UIManagerListener instead. This will be deleted in some future release.")
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
internal interface UIManagerModuleListener {
public interface UIManagerModuleListener {
/**
* Called right before view updates are dispatched at the end of a batch. This is useful if a
* module needs to add UIBlocks to the queue before it is flushed.
*/
fun willDispatchViewUpdates(uiManager: UIManagerModule)
public fun willDispatchViewUpdates(uiManager: UIManagerModule)
}
@@ -256,13 +256,8 @@ public class ReactModalHostView(context: ThemedReactContext) :
if (createNewDialog) {
dismiss()
} else {
// With Props 2.0 the view creation could include initial props. This means the dialog might
// still have to be created before the properties can be set. We only update properties if the
// dialog was already initialized.
dialog?.let {
updateProperties()
return
}
updateProperties()
return
}
// Reset the flag since we are going to create a new dialog
@@ -407,9 +407,6 @@ public class ReactScrollView extends ScrollView
if (mMaintainVisibleContentPositionHelper != null) {
mMaintainVisibleContentPositionHelper.stop();
}
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.cleanup();
}
}
@Override
@@ -9,7 +9,6 @@ package com.facebook.react.views.scroll
import android.graphics.Rect
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.facebook.common.logging.FLog
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
@@ -47,34 +46,13 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
return true
}
internal class VirtualViewContainerState {
internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
private val detectWindowFocus = ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
private val emptyRect: Rect = Rect()
private val visibleRect: Rect = Rect()
private val prerenderRect: Rect = Rect()
private val onWindowFocusChangeListener =
ViewTreeObserver.OnWindowFocusChangeListener {
debugLog("onWindowFocusChanged")
updateModes()
}
private val scrollView: ViewGroup
constructor(scrollView: ViewGroup) {
this.scrollView = scrollView
if (detectWindowFocus) {
scrollView.viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
}
}
public fun cleanup() {
if (detectWindowFocus) {
scrollView.viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
}
}
public fun onChange(virtualView: VirtualView) {
if (virtualViews.add(virtualView)) {
@@ -94,7 +72,7 @@ internal class VirtualViewContainerState {
// Called on ScrollView onLayout or onScroll
public fun updateState() {
debugLog("updateState")
debugLog("VirtualViewContainer.updateState")
updateModes()
}
@@ -114,16 +92,8 @@ internal class VirtualViewContainerState {
when {
rect.isEmpty -> {}
rectsOverlap(rect, visibleRect) -> {
mode = VirtualViewMode.Visible
thresholdRect = visibleRect
if (detectWindowFocus) {
if (scrollView.hasWindowFocus()) {
mode = VirtualViewMode.Visible
} else {
mode = VirtualViewMode.Prerender
}
} else {
mode = VirtualViewMode.Visible
}
}
rectsOverlap(rect, prerenderRect) -> {
mode = VirtualViewMode.Prerender
@@ -145,7 +115,7 @@ private val IS_DEBUG_BUILD =
ReactBuildConfig.DEBUG || ReactBuildConfig.IS_INTERNAL_BUILD || ReactBuildConfig.ENABLE_PERFETTO
internal inline fun debugLog(subtag: String, block: () -> String = { "" }) {
if (IS_DEBUG_BUILD && ReactNativeFeatureFlags.enableVirtualViewDebugFeatures()) {
if (IS_DEBUG_BUILD) {
FLog.d("$DEBUG_TAG:$subtag", block())
}
}
@@ -18,6 +18,5 @@ import com.facebook.proguard.annotations.DoNotStrip
internal class PreparedLayout(
val layout: Layout,
val maximumNumberOfLines: Int,
val verticalOffset: Float,
val reactTags: IntArray,
val verticalOffset: Float
)
@@ -27,7 +27,7 @@ import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.uimanager.BackgroundStyleApplicator
import com.facebook.react.uimanager.ReactCompoundView
import com.facebook.react.uimanager.style.Overflow
import com.facebook.react.views.text.internal.span.ReactFragmentIndexSpan
import com.facebook.react.views.text.internal.span.ReactTagSpan
import kotlin.collections.ArrayList
import kotlin.math.roundToInt
@@ -310,9 +310,8 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
override fun hasOverlappingRendering(): Boolean = false
override fun reactTagForTouch(touchX: Float, touchY: Float): Int =
getSpanInCoords(touchX.roundToInt(), touchY.roundToInt(), ReactFragmentIndexSpan::class.java)
?.fragmentIndex
?.let { preparedLayout?.reactTags[it] } ?: id
getSpanInCoords(touchX.roundToInt(), touchY.roundToInt(), ReactTagSpan::class.java)?.reactTag
?: id
@RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private object Api34Utils {
@@ -42,8 +42,6 @@ import com.facebook.react.views.text.internal.span.ReactAbsoluteSizeSpan
import com.facebook.react.views.text.internal.span.ReactBackgroundColorSpan
import com.facebook.react.views.text.internal.span.ReactClickableSpan
import com.facebook.react.views.text.internal.span.ReactForegroundColorSpan
import com.facebook.react.views.text.internal.span.ReactFragmentIndexSpan
import com.facebook.react.views.text.internal.span.ReactLinkSpan
import com.facebook.react.views.text.internal.span.ReactOpacitySpan
import com.facebook.react.views.text.internal.span.ReactStrikethroughSpan
import com.facebook.react.views.text.internal.span.ReactTagSpan
@@ -220,8 +218,7 @@ internal object TextLayoutManager {
context: Context,
fragments: MapBuffer,
sb: SpannableStringBuilder,
ops: MutableList<SetSpanOperation>,
outputReactTags: IntArray?
ops: MutableList<SetSpanOperation>
) {
for (i in 0 until fragments.count) {
val fragment = fragments.getMapBuffer(i)
@@ -252,11 +249,7 @@ internal object TextLayoutManager {
(textAttributes.mAccessibilityRole ==
ReactAccessibilityDelegate.AccessibilityRole.LINK)
if (roleIsLink) {
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) {
ops.add(SetSpanOperation(start, end, ReactLinkSpan(i)))
} else {
ops.add(SetSpanOperation(start, end, ReactClickableSpan(reactTag)))
}
ops.add(SetSpanOperation(start, end, ReactClickableSpan(reactTag)))
}
if (textAttributes.mIsColorSet) {
ops.add(SetSpanOperation(start, end, ReactForegroundColorSpan(textAttributes.mColor)))
@@ -314,14 +307,7 @@ internal object TextLayoutManager {
start, end, CustomLineHeightSpan(textAttributes.effectiveLineHeight)))
}
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) {
ops.add(SetSpanOperation(start, end, ReactFragmentIndexSpan(i)))
if (outputReactTags != null) {
outputReactTags[i] = reactTag
}
} else {
ops.add(SetSpanOperation(start, end, ReactTagSpan(reactTag)))
}
ops.add(SetSpanOperation(start, end, ReactTagSpan(reactTag)))
}
}
}
@@ -337,8 +323,7 @@ internal object TextLayoutManager {
private fun buildSpannableFromFragmentsOptimized(
context: Context,
fragments: MapBuffer,
outputReactTags: IntArray?
fragments: MapBuffer
): Spannable {
val text = StringBuilder()
val parsedFragments = ArrayList<FragmentAttributes>(fragments.count)
@@ -378,7 +363,7 @@ internal object TextLayoutManager {
val spannable = SpannableString(text)
var start = 0
for ((i, fragment) in parsedFragments.withIndex()) {
for (fragment in parsedFragments) {
val end = start + fragment.length
val spanFlags =
if (start == 0) Spannable.SPAN_INCLUSIVE_INCLUSIVE else Spannable.SPAN_EXCLUSIVE_INCLUSIVE
@@ -401,11 +386,7 @@ internal object TextLayoutManager {
ReactAccessibilityDelegate.AccessibilityRole.LINK)
if (roleIsLink) {
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) {
spannable.setSpan(ReactLinkSpan(i), start, end, spanFlags)
} else {
spannable.setSpan(ReactClickableSpan(fragment.reactTag), start, end, spanFlags)
}
spannable.setSpan(ReactClickableSpan(fragment.reactTag), start, end, spanFlags)
}
if (fragment.props.isColorSet) {
@@ -472,14 +453,7 @@ internal object TextLayoutManager {
CustomLineHeightSpan(fragment.props.effectiveLineHeight), start, end, spanFlags)
}
if (ReactNativeFeatureFlags.enablePreparedTextLayout()) {
spannable.setSpan(ReactFragmentIndexSpan(i), start, end, spanFlags)
if (outputReactTags != null) {
outputReactTags[i] = fragment.reactTag
}
} else {
spannable.setSpan(ReactTagSpan(fragment.reactTag), start, end, spanFlags)
}
spannable.setSpan(ReactTagSpan(fragment.reactTag), start, end, spanFlags)
}
start = end
@@ -500,10 +474,7 @@ internal object TextLayoutManager {
} else {
text =
createSpannableFromAttributedString(
context,
attributedString.getMapBuffer(AS_KEY_FRAGMENTS),
reactTextViewManagerCallback,
null)
context, attributedString, reactTextViewManagerCallback)
}
return text
@@ -511,12 +482,13 @@ internal object TextLayoutManager {
private fun createSpannableFromAttributedString(
context: Context,
fragments: MapBuffer,
reactTextViewManagerCallback: ReactTextViewManagerCallback?,
outputReactTags: IntArray?
attributedString: MapBuffer,
reactTextViewManagerCallback: ReactTextViewManagerCallback?
): Spannable {
if (ReactNativeFeatureFlags.enableAndroidTextMeasurementOptimizations()) {
val spannable = buildSpannableFromFragmentsOptimized(context, fragments, outputReactTags)
val spannable =
buildSpannableFromFragmentsOptimized(
context, attributedString.getMapBuffer(AS_KEY_FRAGMENTS))
reactTextViewManagerCallback?.onPostProcessSpannable(spannable)
return spannable
@@ -528,7 +500,7 @@ internal object TextLayoutManager {
// a new spannable will be wiped out
val ops: MutableList<SetSpanOperation> = ArrayList()
buildSpannableFromFragments(context, fragments, sb, ops, outputReactTags)
buildSpannableFromFragments(context, attributedString.getMapBuffer(AS_KEY_FRAGMENTS), sb, ops)
// TODO T31905686: add support for inline Images
// While setting the Spans on the final text, we also check whether any of them are images.
@@ -784,11 +756,7 @@ internal object TextLayoutManager {
heightYogaMeasureMode: YogaMeasureMode,
reactTextViewManagerCallback: ReactTextViewManagerCallback?
): PreparedLayout {
val fragments = attributedString.getMapBuffer(AS_KEY_FRAGMENTS)
val reactTags = IntArray(fragments.count)
val text =
createSpannableFromAttributedString(
context, fragments, reactTextViewManagerCallback, reactTags)
val text = getOrCreateSpannableForText(context, attributedString, reactTextViewManagerCallback)
val baseTextAttributes =
TextAttributeProps.fromMapBuffer(attributedString.getMapBuffer(AS_KEY_BASE_ATTRIBUTES))
val layout =
@@ -811,7 +779,7 @@ internal object TextLayoutManager {
getVerticalOffset(
layout, paragraphAttributes, height, heightYogaMeasureMode, maximumNumberOfLines)
return PreparedLayout(layout, maximumNumberOfLines, verticalOffset, reactTags)
return PreparedLayout(layout, maximumNumberOfLines, verticalOffset)
}
@JvmStatic
@@ -1,14 +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.views.text.internal.span
/**
* Maps a section of the text to the index of the AttributedString fragment originally used to
* create it.
*/
internal class ReactFragmentIndexSpan(val fragmentIndex: Int) : ReactSpan
@@ -1,47 +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.views.text.internal.span
import android.text.TextPaint
import android.text.style.ClickableSpan
import android.view.View
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.views.text.PreparedLayoutTextView
import com.facebook.react.views.text.TextLayoutManager
import com.facebook.react.views.view.ViewGroupClickEvent
/**
* This class is used in [TextLayoutManager] to linkify and style a span of text with
* accessibilityRole="link". This is needed to make nested Text components accessible.
*
* For example, if your React component looks like this:
* ```js
* <Text>
* Some text with
* <Text onPress={onPress} accessible={true} accessibilityRole="link">a link</Text>
* in the middle.
* </Text>
* ```
*/
internal class ReactLinkSpan(val fragmentIndex: Int) : ClickableSpan(), ReactSpan {
override fun onClick(view: View) {
val context = view.context as ReactContext
val textView = view as? PreparedLayoutTextView ?: return
val preparedLayout = textView.preparedLayout ?: return
val reactTag = preparedLayout.reactTags[fragmentIndex]
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, reactTag)
eventDispatcher?.dispatchEvent(
ViewGroupClickEvent(UIManagerHelper.getSurfaceId(context), reactTag))
}
override fun updateDrawState(ds: TextPaint) {
// no super call so we don't change the link color or add an underline by default, as the
// superclass does.
}
}
@@ -15,7 +15,6 @@ import androidx.annotation.VisibleForTesting
import com.facebook.common.logging.FLog
import com.facebook.react.R
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.uimanager.ReactRoot
import com.facebook.react.views.scroll.VirtualView
import com.facebook.react.views.scroll.VirtualViewContainer
@@ -203,7 +202,7 @@ public class ReactVirtualViewExperimental(context: Context) :
}
internal inline fun debugLog(subtag: String, block: () -> String = { "" }) {
if (IS_DEBUG_BUILD && ReactNativeFeatureFlags.enableVirtualViewDebugFeatures()) {
if (IS_DEBUG_BUILD) {
FLog.d("$DEBUG_TAG:$subtag", "${block()} [$id][$nativeId]")
}
}
@@ -25,6 +25,4 @@ target_link_libraries(
reactnative
)
target_compile_reactnative_options(hermes_executor PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(hermes_executor PRIVATE -DHERMES_ENABLE_DEBUGGER=1)
endif()
target_compile_options(hermes_executor PRIVATE $<$<CONFIG:Debug>:-DHERMES_ENABLE_DEBUGGER=1>)
@@ -27,6 +27,4 @@ target_link_libraries(hermesinstancejni
)
target_compile_reactnative_options(hermesinstancejni PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(hermesinstancejni PRIVATE -DHERMES_ENABLE_DEBUGGER=1)
endif ()
target_compile_options(hermesinstancejni PRIVATE $<$<CONFIG:Debug>:-DHERMES_ENABLE_DEBUGGER=1>)
@@ -17,9 +17,7 @@ add_library(rninstance
)
target_compile_reactnative_options(rninstance PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(rninstance PRIVATE -DHERMES_ENABLE_DEBUGGER=1)
endif ()
target_compile_options(rninstance PRIVATE $<$<CONFIG:Debug>:-DHERMES_ENABLE_DEBUGGER=1>)
target_merge_so(rninstance)
target_include_directories(rninstance PUBLIC .)
@@ -11,6 +11,9 @@ import android.app.Activity
import android.content.Context
import android.graphics.Rect
import android.util.DisplayMetrics
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.WritableMap
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
import com.facebook.react.uimanager.DisplayMetricsHolder
import com.facebook.react.uimanager.events.Event
@@ -18,7 +21,6 @@ import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.views.scroll.ReactScrollView
import com.facebook.react.views.virtual.VirtualViewMode
import com.facebook.react.views.virtual.VirtualViewModeChangeEvent
import com.facebook.testutils.shadows.ShadowArguments
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
@@ -30,10 +32,8 @@ import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/** Tests [ReactVirtualView] */
@Config(shadows = [ShadowArguments::class])
@RunWith(RobolectricTestRunner::class)
class ReactVirtualViewTest {
@@ -45,6 +45,9 @@ class ReactVirtualViewTest {
context = Robolectric.buildActivity(Activity::class.java).create().get()
val arguments = mockStatic(Arguments::class.java)
arguments.`when`<WritableMap> { Arguments.createMap() }.thenAnswer { JavaOnlyMap() }
val displayMetricsHolder = mockStatic(DisplayMetricsHolder::class.java)
displayMetricsHolder
.`when`<DisplayMetrics> { DisplayMetricsHolder.getWindowDisplayMetrics() }
@@ -18,18 +18,15 @@ import org.robolectric.annotation.Implements
import org.robolectric.shadow.api.Shadow
@Implements(Arguments::class)
class ShadowArguments {
object ShadowArguments {
@JvmStatic @Implementation fun createArray(): WritableArray = JavaOnlyArray()
companion object {
@JvmStatic @Implementation fun createArray(): WritableArray = JavaOnlyArray()
@JvmStatic @Implementation fun createMap(): WritableMap = JavaOnlyMap()
@JvmStatic @Implementation fun createMap(): WritableMap = JavaOnlyMap()
@JvmStatic
@Implementation
fun fromJavaArgs(args: Array<Any?>): WritableNativeArray =
WritableNativeArray().apply {
(Shadow.extract(this) as ShadowNativeArray).backingArray = JavaOnlyArray.of(*args)
}
}
@JvmStatic
@Implementation
fun fromJavaArgs(args: Array<Any?>): WritableNativeArray =
WritableNativeArray().apply {
(Shadow.extract(this) as ShadowNativeArray).backingArray = JavaOnlyArray.of(*args)
}
}
@@ -1,47 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <ReactCommon/CallInvoker.h>
#include <jsi/jsi.h>
#include <list>
#include <memory>
namespace facebook::react {
class TestCallInvoker : public CallInvoker {
public:
explicit TestCallInvoker(std::shared_ptr<facebook::jsi::Runtime> runtime)
: runtime_(runtime) {}
void invokeAsync(CallFunc&& func) noexcept override {
queue_.push_back(std::move(func));
}
void invokeSync(CallFunc&& func) override {
if (auto runtime = runtime_.lock()) {
func(*runtime);
}
}
void flushQueue() {
if (auto runtime = runtime_.lock()) {
while (!queue_.empty()) {
queue_.front()(*runtime);
queue_.pop_front();
runtime->drainMicrotasks(); // Run microtasks every cycle.
}
}
}
private:
std::list<CallFunc> queue_{};
std::weak_ptr<facebook::jsi::Runtime> runtime_{};
};
} // namespace facebook::react
@@ -53,9 +53,9 @@ class CxxModule {
class SyncTagType {};
public:
using Provider = std::function<std::unique_ptr<CxxModule>()>;
typedef std::function<std::unique_ptr<CxxModule>()> Provider;
using Callback = std::function<void(std::vector<folly::dynamic>)>;
typedef std::function<void(std::vector<folly::dynamic>)> Callback;
constexpr static AsyncTagType AsyncTag = AsyncTagType();
constexpr static SyncTagType SyncTag = SyncTagType();
@@ -68,8 +68,8 @@ namespace detail {
// only for types compatible with folly::dynamic.
template <typename T>
struct is_dynamic {
using type = typename std::
enable_if<std::is_assignable<folly::dynamic, T>::value, T>::type;
typedef typename std::
enable_if<std::is_assignable<folly::dynamic, T>::value, T>::type type;
};
} // end namespace detail
@@ -16,9 +16,8 @@ using namespace facebook::react;
namespace {
int tempFileFromString(std::string contents) {
const char* tmpDir = getenv("TMPDIR");
if (tmpDir == nullptr) {
if (tmpDir == nullptr)
tmpDir = "/tmp";
}
std::string tmp{tmpDir};
tmp += "/temp.XXXXXX";
@@ -26,7 +26,7 @@ target_link_libraries(hermes_executor_common
)
target_compile_reactnative_options(hermes_executor_common PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
if(${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(
hermes_executor_common
PRIVATE
@@ -17,7 +17,7 @@ add_library(hermes_inspector_modern
target_compile_reactnative_options(hermes_inspector_modern PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
if(${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(
hermes_inspector_modern
PRIVATE
@@ -28,9 +28,7 @@ target_link_libraries(jsinspector
reactperflogger
)
target_compile_reactnative_options(jsinspector PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(jsinspector PRIVATE
-DREACT_NATIVE_DEBUGGER_ENABLED=1
-DREACT_NATIVE_DEBUGGER_ENABLED_DEVONLY=1
)
endif ()
target_compile_options(jsinspector PRIVATE
$<$<CONFIG:Debug>:-DREACT_NATIVE_DEBUGGER_ENABLED=1>
$<$<CONFIG:Debug>:-DREACT_NATIVE_DEBUGGER_ENABLED_DEVONLY=1>
)
@@ -9,6 +9,7 @@
#include "UniqueMonostate.h"
#include <cinttypes>
#include <optional>
#include <string>
#include <unordered_set>
@@ -18,7 +19,7 @@ namespace facebook::react::jsinspector_modern {
struct ExecutionContextDescription {
int32_t id{};
std::string origin;
std::string origin{""};
std::string name{"<anonymous>"};
std::optional<std::string> uniqueId;
};
@@ -36,8 +36,8 @@ namespace facebook::react::jsinspector_modern {
class HostAgent::Impl final {
public:
explicit Impl(
HostAgent& /*hostAgent*/,
const FrontendChannel& frontendChannel,
HostAgent& hostAgent,
FrontendChannel frontendChannel,
HostTargetController& targetController,
HostTargetMetadata hostMetadata,
SessionState& sessionState,
@@ -126,11 +126,11 @@ class HostAgent::Impl final {
else if (req.method == "Page.reload") {
targetController_.getDelegate().onReload({
.ignoreCache =
req.params.isObject() && (req.params.count("ignoreCache") != 0u)
req.params.isObject() && req.params.count("ignoreCache")
? std::optional(req.params.at("ignoreCache").asBool())
: std::nullopt,
.scriptToEvaluateOnLoad = req.params.isObject() &&
(req.params.count("scriptToEvaluateOnLoad") != 0u)
req.params.count("scriptToEvaluateOnLoad")
? std::optional(
req.params.at("scriptToEvaluateOnLoad").asString())
: std::nullopt,
@@ -139,8 +139,7 @@ class HostAgent::Impl final {
shouldSendOKResponse = true;
isFinishedHandlingRequest = true;
} else if (req.method == "Overlay.setPausedInDebuggerMessage") {
auto message =
req.params.isObject() && (req.params.count("message") != 0u)
auto message = req.params.isObject() && req.params.count("message")
? std::optional(req.params.at("message").asString())
: std::nullopt;
if (!isPausedInDebuggerOverlayVisible_ && message.has_value()) {
@@ -45,13 +45,13 @@ class HostTargetSession {
targetController,
std::move(hostMetadata),
state_,
std::move(executor)) {}
executor) {}
/**
* Called by CallbackLocalConnection to send a message to this Session's
* Agent.
*/
void operator()(const std::string& message) {
void operator()(std::string message) {
cdp::PreparsedRequest request;
// Messages may be invalid JSON, or have unexpected types.
try {
@@ -91,7 +91,7 @@ class HostTargetSession {
* there's no current instance.
*/
void setCurrentInstance(InstanceTarget* instance) {
if (instance != nullptr) {
if (instance) {
hostAgent_.setCurrentInstanceAgent(
instance->createAgent(frontendChannel_, state_));
} else {
@@ -148,7 +148,7 @@ std::shared_ptr<HostTarget> HostTarget::create(
HostTargetDelegate& delegate,
VoidExecutor executor) {
std::shared_ptr<HostTarget> hostTarget{new HostTarget(delegate)};
hostTarget->setExecutor(std::move(executor));
hostTarget->setExecutor(executor);
return hostTarget;
}
@@ -166,7 +166,7 @@ std::unique_ptr<ILocalConnection> HostTarget::connect(
session->setCurrentInstance(currentInstance_.get());
sessions_.insert(std::weak_ptr(session));
return std::make_unique<CallbackLocalConnection>(
[session](const std::string& message) { (*session)(message); });
[session](std::string message) { (*session)(message); });
}
HostTarget::~HostTarget() {
@@ -228,7 +228,10 @@ void HostTargetController::incrementPauseOverlayCounter() {
bool HostTargetController::decrementPauseOverlayCounter() {
assert(pauseOverlayCounter_ > 0 && "Pause overlay counter underflow");
return --pauseOverlayCounter_ != 0;
if (--pauseOverlayCounter_ == 0) {
return false;
}
return true;
}
namespace {
@@ -10,6 +10,8 @@
#include <cassert>
#include <list>
#include <mutex>
#include <tuple>
#include <unordered_map>
namespace facebook::react::jsinspector_modern {
@@ -22,7 +24,7 @@ IRemoteConnection::~IRemoteConnection() {}
IInspector::~IInspector() {}
IPageStatusListener::~IPageStatusListener() {}
folly::dynamic targetCapabilitiesToDynamic(
const folly::dynamic targetCapabilitiesToDynamic(
const InspectorTargetCapabilities& capabilities) {
return folly::dynamic::object(
"nativePageReloads", capabilities.nativePageReloads)(
@@ -54,7 +56,7 @@ class InspectorImpl : public IInspector {
public:
Page(
int id,
const std::string& description,
const std::string& title,
const std::string& vm,
ConnectFunc connectFunc,
InspectorTargetCapabilities capabilities);
@@ -85,7 +87,7 @@ InspectorImpl::Page::Page(
description_(description),
vm_(vm),
connectFunc_(std::move(connectFunc)),
capabilities_(capabilities) {}
capabilities_(std::move(capabilities)) {}
InspectorImpl::Page::operator InspectorPageDescription() const {
return InspectorPageDescription{
@@ -122,7 +124,7 @@ void InspectorImpl::removePage(int pageId) {
std::scoped_lock lock(mutex_);
if (pages_.erase(pageId) != 0) {
for (auto& listenerWeak : listeners_) {
for (auto listenerWeak : listeners_) {
if (auto listener = listenerWeak.lock()) {
listener->onPageRemoved(pageId);
}
@@ -136,7 +138,6 @@ std::vector<InspectorPageDescription> InspectorImpl::getPages() const {
std::vector<InspectorPageDescription> inspectorPages;
// pages_ is a std::map keyed on an incremental id, so this is insertion
// ordered.
inspectorPages.reserve(pages_.size());
for (auto& it : pages_) {
inspectorPages.push_back(InspectorPageDescription(it.second));
}
@@ -39,7 +39,7 @@ struct InspectorTargetCapabilities {
bool prefersFuseboxFrontend = false;
};
folly::dynamic targetCapabilitiesToDynamic(
const folly::dynamic targetCapabilitiesToDynamic(
const InspectorTargetCapabilities& capabilities);
struct InspectorPageDescription {
@@ -16,7 +16,7 @@ InstanceAgent::InstanceAgent(
FrontendChannel frontendChannel,
InstanceTarget& target,
SessionState& sessionState)
: frontendChannel_(std::move(frontendChannel)),
: frontendChannel_(frontendChannel),
target_(target),
sessionState_(sessionState) {
(void)target_;
@@ -36,7 +36,7 @@ bool InstanceAgent::handleRequest(const cdp::PreparsedRequest& req) {
void InstanceAgent::setCurrentRuntime(RuntimeTarget* runtimeTarget) {
auto previousRuntimeAgent = std::move(runtimeAgent_);
if (runtimeTarget != nullptr) {
if (runtimeTarget) {
runtimeAgent_ = runtimeTarget->createAgent(frontendChannel_, sessionState_);
} else {
runtimeAgent_.reset();
@@ -18,7 +18,7 @@ std::shared_ptr<InstanceTarget> InstanceTarget::create(
VoidExecutor executor) {
std::shared_ptr<InstanceTarget> instanceTarget{
new InstanceTarget(executionContextManager, delegate)};
instanceTarget->setExecutor(std::move(executor));
instanceTarget->setExecutor(executor);
return instanceTarget;
}
@@ -16,7 +16,9 @@
#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/RuntimeAgent.h>
#include <list>
#include <memory>
#include <optional>
namespace facebook::react::jsinspector_modern {
@@ -14,7 +14,7 @@ enum ReactNativeLogLevel {
ReactNativeLogLevelFatal = 4
};
using reactnativelogfunctype = void (*)(ReactNativeLogLevel, const char*);
typedef void (*reactnativelogfunctype)(ReactNativeLogLevel, const char*);
#ifdef __cplusplus
extern "C" {
@@ -7,7 +7,6 @@
#pragma once
#include <ReactCommon/TestCallInvoker.h>
#include <gtest/gtest.h>
#include <hermes/hermes.h>
#include <react/bridging/Bridging.h>
@@ -16,24 +15,34 @@
namespace facebook::react {
class BridgingTest : public ::testing::Test {
class TestCallInvoker : public CallInvoker {
public:
BridgingTest(BridgingTest& other) = delete;
BridgingTest& operator=(BridgingTest& other) = delete;
BridgingTest(BridgingTest&& other) = delete;
BridgingTest& operator=(BridgingTest&& other) = delete;
void invokeAsync(CallFunc&& fn) noexcept override {
queue_.push_back(std::move(fn));
}
void invokeSync(CallFunc&&) override {
FAIL() << "JSCallInvoker does not support invokeSync()";
}
private:
friend class BridgingTest;
std::list<CallFunc> queue_;
};
class BridgingTest : public ::testing::Test {
protected:
BridgingTest()
: runtime(hermes::makeHermesRuntime(
: invoker(std::make_shared<TestCallInvoker>()),
runtime(hermes::makeHermesRuntime(
::hermes::vm::RuntimeConfig::Builder()
// Make promises work with Hermes microtasks.
.withMicrotaskQueue(true)
.build())),
rt(*runtime),
invoker(std::make_shared<TestCallInvoker>(runtime)) {}
rt(*runtime) {}
~BridgingTest() override {
~BridgingTest() {
LongLivedObjectCollection::get(rt).clear();
}
@@ -53,12 +62,16 @@ class BridgingTest : public ::testing::Test {
}
void flushQueue() {
invoker->flushQueue();
while (!invoker->queue_.empty()) {
invoker->queue_.front()(*runtime);
invoker->queue_.pop_front();
rt.drainMicrotasks(); // Run microtasks every cycle.
}
}
std::shared_ptr<jsi::Runtime> runtime;
jsi::Runtime& rt;
std::shared_ptr<TestCallInvoker> invoker;
std::unique_ptr<jsi::Runtime> runtime;
jsi::Runtime& rt;
};
} // namespace facebook::react
@@ -21,6 +21,6 @@ endif()
target_compile_reactnative_options(react_debug PRIVATE)
target_compile_options(react_debug PRIVATE -Wpedantic)
if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug AND NOT REACT_NATIVE_DEBUG_OPTIMIZED)
if(NOT ${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(react_debug PUBLIC -DNDEBUG)
endif()
@@ -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<<16c5fdf431579bbfd454a28c06f28c41>>
* @generated SignedSource<<1de02178e1be302bb4b19501950b260a>>
*/
/**
@@ -27,9 +27,7 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur
public:
ReactNativeFeatureFlagsOverridesOSSExperimental() = default;
bool preventShadowTreeCommitExhaustionWithLocking() override {
return true;
}
};
} // namespace facebook::react
@@ -0,0 +1,11 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
// This header is left here for compatibility reasons.
#include <react/bridging/CallbackWrapper.h>
@@ -0,0 +1,11 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
// This header is left here for compatibility reasons.
#include <react/bridging/LongLivedObject.h>
@@ -1,31 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <ReactCommon/TestCallInvoker.h>
#include <gtest/gtest.h>
#include <hermes/hermes.h>
#include <memory>
namespace facebook::react {
template <typename T, typename... Args>
class TurboModuleTestFixture : public ::testing::Test {
public:
explicit TurboModuleTestFixture(Args... args)
: runtime_(facebook::hermes::makeHermesRuntime()),
jsInvoker_(std::make_shared<TestCallInvoker>(runtime_)),
module_(std::make_shared<T>(jsInvoker_, std::forward<Args>(args)...)) {}
protected:
std::shared_ptr<jsi::Runtime> runtime_{};
std::shared_ptr<TestCallInvoker> jsInvoker_{};
std::shared_ptr<T> module_;
};
} // namespace facebook::react
@@ -190,8 +190,7 @@ TextMeasurement TextLayoutManager::measure(
};
auto measurement =
(ReactNativeFeatureFlags::disableTextLayoutManagerCacheAndroid() ||
ReactNativeFeatureFlags::enablePreparedTextLayout())
ReactNativeFeatureFlags::disableTextLayoutManagerCacheAndroid()
? measureText()
: textMeasureCache_.get(
{.attributedString = attributedString,
@@ -311,19 +310,14 @@ TextLayoutManager::PreparedLayout TextLayoutManager::prepareLayout(
jfloat,
jfloat)>("prepareTextLayout");
static auto reusePreparedLayoutWithNewReactTags =
jni::findClassStatic("com/facebook/react/fabric/FabricUIManager")
->getMethod<JPreparedLayout::javaobject(
JPreparedLayout::javaobject, jintArray)>(
"reusePreparedLayoutWithNewReactTags");
const auto [key, preparedText] = preparedTextCache_.getWithKey(
return preparedTextCache_.get(
{.attributedString = attributedString,
.paragraphAttributes = paragraphAttributes,
.layoutConstraints = layoutConstraints},
[&]() {
[&] {
const auto& fabricUIManager =
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
auto attributedStringMB = JReadableMapBuffer::createWithContents(
toMapBuffer(attributedString));
auto paragraphAttributesMB = JReadableMapBuffer::createWithContents(
@@ -342,39 +336,6 @@ TextLayoutManager::PreparedLayout TextLayoutManager::prepareLayout(
minimumSize.height,
maximumSize.height))};
});
// PreparedTextCacheKey allows equality of layouts which are the same
// display-wise, but ShadowView fragments (and thus react tags) may have
// changed.
const auto& fragments = attributedString.getFragments();
const auto& cacheKeyFragments = key->attributedString.getFragments();
bool needsNewReactTags = [&] {
for (size_t i = 0; i < fragments.size(); i++) {
if (fragments[i].parentShadowView.tag !=
cacheKeyFragments[i].parentShadowView.tag) {
return true;
}
}
return false;
}();
if (needsNewReactTags) {
std::vector<int> reactTags(fragments.size());
for (size_t i = 0; i < reactTags.size(); i++) {
reactTags[i] = fragments[i].parentShadowView.tag;
}
auto javaReactTags = jni::JArrayInt::newArray(fragments.size());
javaReactTags->setRegion(
0, static_cast<jsize>(reactTags.size()), reactTags.data());
const auto& fabricUIManager =
contextContainer_->at<jni::global_ref<jobject>>("FabricUIManager");
return PreparedLayout{jni::make_global(reusePreparedLayoutWithNewReactTags(
fabricUIManager, preparedText->get(), javaReactTags.get()))};
} else {
return PreparedLayout{*preparedText};
}
}
TextMeasurement TextLayoutManager::measurePreparedLayout(
@@ -16,9 +16,7 @@ add_library(bridgeless
${bridgeless_SRC}
)
target_compile_reactnative_options(bridgeless PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
target_compile_options(bridgeless PRIVATE -DHERMES_ENABLE_DEBUGGER=1)
endif ()
target_compile_options(bridgeless PRIVATE $<$<CONFIG:Debug>:-DHERMES_ENABLE_DEBUGGER=1>)
target_include_directories(bridgeless PUBLIC .)
react_native_android_selector(fabricjni fabricjni "")
@@ -29,7 +29,7 @@ target_link_libraries(bridgelesshermes
)
target_compile_reactnative_options(bridgelesshermes PRIVATE)
if(${CMAKE_BUILD_TYPE} MATCHES Debug OR REACT_NATIVE_DEBUG_OPTIMIZED)
if(${CMAKE_BUILD_TYPE} MATCHES Debug)
target_compile_options(
bridgelesshermes
PRIVATE

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