mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1930a94722 |
+1
-1
@@ -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
|
||||
|
||||
@@ -14,23 +14,23 @@ runs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hermes-workspace
|
||||
path: 'C:\tmp\hermes'
|
||||
path: 'D:\tmp\hermes'
|
||||
- name: Set up workspace
|
||||
shell: powershell
|
||||
run: |
|
||||
mkdir -p C:\tmp\hermes\osx-bin
|
||||
mkdir -p D:\tmp\hermes\osx-bin
|
||||
mkdir -p .\packages\react-native\sdks\hermes
|
||||
cp -r -Force C:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
|
||||
cp -r -Force D:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
|
||||
cp -r -Force .\packages\react-native\sdks\hermes-engine\utils\* .\packages\react-native\sdks\hermes\.
|
||||
- name: Windows cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: v3-hermes-${{ github.job }}-windows-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}
|
||||
path: |
|
||||
C:\tmp\hermes\win64-bin\
|
||||
C:\tmp\hermes\hermes\icu\
|
||||
C:\tmp\hermes\hermes\deps\
|
||||
C:\tmp\hermes\hermes\build_release\
|
||||
D:\tmp\hermes\win64-bin\
|
||||
D:\tmp\hermes\hermes\icu\
|
||||
D:\tmp\hermes\hermes\deps\
|
||||
D:\tmp\hermes\hermes\build_release\
|
||||
- name: setup-msbuild
|
||||
uses: microsoft/setup-msbuild@v1.3.2
|
||||
- name: Set up workspace
|
||||
@@ -83,4 +83,4 @@ runs:
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
with:
|
||||
name: hermes-win64-bin
|
||||
path: C:\tmp\hermes\win64-bin\
|
||||
path: D:\tmp\hermes\win64-bin\
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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: |
|
||||
|
||||
@@ -128,9 +128,9 @@ jobs:
|
||||
runs-on: windows-2025
|
||||
needs: prepare_hermes_workspace
|
||||
env:
|
||||
HERMES_WS_DIR: 'C:\tmp\hermes'
|
||||
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
|
||||
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
|
||||
HERMES_WS_DIR: 'D:\tmp\hermes'
|
||||
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
|
||||
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
|
||||
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
|
||||
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
|
||||
CMAKE_DIR: 'C:\Program Files\CMake\bin'
|
||||
|
||||
@@ -125,9 +125,9 @@ jobs:
|
||||
runs-on: windows-2025
|
||||
needs: prepare_hermes_workspace
|
||||
env:
|
||||
HERMES_WS_DIR: 'C:\tmp\hermes'
|
||||
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
|
||||
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
|
||||
HERMES_WS_DIR: 'D:\tmp\hermes'
|
||||
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
|
||||
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
|
||||
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
|
||||
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
|
||||
CMAKE_DIR: 'C:\Program Files\CMake\bin'
|
||||
|
||||
@@ -382,9 +382,9 @@ jobs:
|
||||
runs-on: windows-2025
|
||||
needs: prepare_hermes_workspace
|
||||
env:
|
||||
HERMES_WS_DIR: 'C:\tmp\hermes'
|
||||
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
|
||||
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
|
||||
HERMES_WS_DIR: 'D:\tmp\hermes'
|
||||
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
|
||||
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
|
||||
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
|
||||
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
|
||||
CMAKE_DIR: 'C:\Program Files\CMake\bin'
|
||||
@@ -584,7 +584,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: ["24", "22"]
|
||||
# node-version: ["24", "22", "20.19.4"]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
+36
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -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,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": {
|
||||
@@ -17,7 +17,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"files": [
|
||||
"path-support.js",
|
||||
|
||||
@@ -1,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": {
|
||||
@@ -19,14 +19,14 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"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"
|
||||
|
||||
@@ -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",
|
||||
@@ -43,6 +43,6 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -21,7 +21,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -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",
|
||||
@@ -20,6 +20,6 @@
|
||||
"BUILD_INFO"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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",
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4",
|
||||
"node": ">= 22.14.0",
|
||||
"electron": ">=36.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -10,25 +10,9 @@
|
||||
|
||||
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
|
||||
const {app} = require('electron') as any;
|
||||
const util = require('util');
|
||||
|
||||
// Handle global command line arguments which don't require a window
|
||||
// or the single instance lock to be held.
|
||||
const {
|
||||
values: {version = false},
|
||||
} = util.parseArgs({
|
||||
options: {version: {type: 'boolean'}},
|
||||
args: process.argv.slice(app.isPackaged ? 1 : 2),
|
||||
strict: false,
|
||||
});
|
||||
if (version) {
|
||||
console.log(`${app.getName()} v${app.getVersion()}`);
|
||||
// Not app.quit() - we want to exit immediately without initialising the graphical subsystem.
|
||||
app.exit(0);
|
||||
}
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock({
|
||||
argv: process.argv.slice(app.isPackaged ? 1 : 2),
|
||||
argv: process.argv.slice(2),
|
||||
});
|
||||
|
||||
if (!gotTheLock) {
|
||||
|
||||
@@ -1,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",
|
||||
@@ -35,7 +35,7 @@
|
||||
"ws": "^6.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"selfsigned": "^2.4.1",
|
||||
|
||||
@@ -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": {
|
||||
@@ -16,13 +16,13 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"main": "index.js",
|
||||
"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": {
|
||||
@@ -22,6 +22,6 @@
|
||||
"hermes-eslint": "0.29.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -36,6 +36,6 @@
|
||||
"hermes-eslint": "0.29.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "./gradlew build",
|
||||
|
||||
+2
-2
@@ -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 */
|
||||
|
||||
|
||||
-2
@@ -18,7 +18,6 @@ import com.facebook.react.tasks.GenerateEntryPointTask
|
||||
import com.facebook.react.tasks.GeneratePackageListTask
|
||||
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
|
||||
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
|
||||
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildTypesForApp
|
||||
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevServerLocation
|
||||
import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
|
||||
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
|
||||
@@ -85,7 +84,6 @@ class ReactPlugin : Plugin<Project> {
|
||||
configureAutolinking(project, extension)
|
||||
configureCodegen(project, extension, rootExtension, isLibrary = false)
|
||||
configureResources(project, extension)
|
||||
configureBuildTypesForApp(project)
|
||||
}
|
||||
|
||||
// Library Only Configuration
|
||||
|
||||
-31
@@ -19,7 +19,6 @@ import java.net.Inet4Address
|
||||
import java.net.NetworkInterface
|
||||
import javax.xml.parsers.DocumentBuilder
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import kotlin.plus
|
||||
import org.gradle.api.Action
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.AppliedPlugin
|
||||
@@ -28,36 +27,6 @@ import org.w3c.dom.Element
|
||||
@Suppress("UnstableApiUsage")
|
||||
internal object AgpConfiguratorUtils {
|
||||
|
||||
fun configureBuildTypesForApp(project: Project) {
|
||||
val action =
|
||||
Action<AppliedPlugin> {
|
||||
project.extensions
|
||||
.getByType(ApplicationAndroidComponentsExtension::class.java)
|
||||
.finalizeDsl { ext ->
|
||||
ext.buildTypes {
|
||||
val debug =
|
||||
getByName("debug").apply {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
}
|
||||
getByName("release").apply {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
}
|
||||
maybeCreate("debugOptimized").apply {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
initWith(debug)
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DCMAKE_BUILD_TYPE=Release")
|
||||
matchingFallbacks += listOf("release")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
project.pluginManager.withPlugin("com.android.application", action)
|
||||
}
|
||||
|
||||
fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
|
||||
val action =
|
||||
Action<AppliedPlugin> {
|
||||
|
||||
@@ -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": {
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
@@ -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,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"
|
||||
@@ -30,6 +30,6 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -16,7 +16,6 @@ describe('console.timeStamp()', () => {
|
||||
});
|
||||
|
||||
it("doesn't throw when label is not specified", () => {
|
||||
// $FlowExpectedError[incompatible-call] not passing label intentionally
|
||||
expect(() => console.timeStamp()).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -26,6 +25,7 @@ describe('console.timeStamp()', () => {
|
||||
|
||||
it("doesn't throw when additional arguments are specified", () => {
|
||||
expect(() =>
|
||||
// $FlowExpectedError[extra-arg]
|
||||
console.timeStamp('label', 100, 500, 'Track', 'Group', 'error'),
|
||||
).not.toThrow();
|
||||
});
|
||||
@@ -34,7 +34,7 @@ describe('console.timeStamp()', () => {
|
||||
// $FlowExpectedError[incompatible-call]
|
||||
expect(() => console.timeStamp({})).not.toThrow();
|
||||
expect(() =>
|
||||
// $FlowExpectedError[incompatible-call]
|
||||
// $FlowExpectedError[extra-arg]
|
||||
console.timeStamp('label', true, null, {}, [], () => {}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -569,7 +569,10 @@ function consoleAssertPolyfill(expression, label) {
|
||||
}
|
||||
}
|
||||
|
||||
function stub() {}
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp_static.
|
||||
// Non-standard API for recording markers on a timeline of the Performance instrumentation.
|
||||
// The actual logging is not provided by definition.
|
||||
function consoleTimeStampPolyfill() {}
|
||||
|
||||
if (global.nativeLoggingHook) {
|
||||
const originalConsole = global.console;
|
||||
@@ -582,11 +585,7 @@ if (global.nativeLoggingHook) {
|
||||
}
|
||||
|
||||
global.console = {
|
||||
time: stub,
|
||||
timeEnd: stub,
|
||||
timeStamp: stub,
|
||||
count: stub,
|
||||
countReset: stub,
|
||||
timeStamp: consoleTimeStampPolyfill,
|
||||
...(originalConsole ?? {}),
|
||||
error: getNativeLogFunction(LOG_LEVELS.error),
|
||||
info: getNativeLogFunction(LOG_LEVELS.info),
|
||||
@@ -677,6 +676,7 @@ if (global.nativeLoggingHook) {
|
||||
});
|
||||
}
|
||||
} else if (!global.console) {
|
||||
function stub() {}
|
||||
const log = global.print || stub;
|
||||
|
||||
global.console = {
|
||||
@@ -692,8 +692,6 @@ if (global.nativeLoggingHook) {
|
||||
}
|
||||
},
|
||||
clear: stub,
|
||||
count: stub,
|
||||
countReset: stub,
|
||||
dir: stub,
|
||||
dirxml: stub,
|
||||
group: stub,
|
||||
@@ -702,8 +700,6 @@ if (global.nativeLoggingHook) {
|
||||
profile: stub,
|
||||
profileEnd: stub,
|
||||
table: stub,
|
||||
time: stub,
|
||||
timeEnd: stub,
|
||||
timeStamp: stub,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,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": {
|
||||
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"files": [
|
||||
"console.js",
|
||||
|
||||
@@ -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",
|
||||
@@ -13,7 +13,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
@@ -66,7 +66,7 @@
|
||||
"@babel/plugin-transform-typescript": "^7.25.2",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.24.7",
|
||||
"@babel/template": "^7.25.0",
|
||||
"@react-native/babel-plugin-codegen": "0.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"
|
||||
|
||||
+14
-22
@@ -38,6 +38,19 @@ function isFirstParty(fileName) {
|
||||
// use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
|
||||
const loose = true;
|
||||
|
||||
const defaultPlugins = [
|
||||
[require('babel-plugin-syntax-hermes-parser'), {parseLangTypes: 'flow'}],
|
||||
[require('babel-plugin-transform-flow-enums')],
|
||||
[require('@babel/plugin-transform-block-scoping')],
|
||||
[require('@babel/plugin-transform-class-properties'), {loose}],
|
||||
[require('@babel/plugin-transform-private-methods'), {loose}],
|
||||
[require('@babel/plugin-transform-private-property-in-object'), {loose}],
|
||||
[require('@babel/plugin-syntax-dynamic-import')],
|
||||
[require('@babel/plugin-syntax-export-default-from')],
|
||||
...passthroughSyntaxPlugins,
|
||||
[require('@babel/plugin-transform-unicode-regex')],
|
||||
];
|
||||
|
||||
// For Static Hermes testing (experimental), the hermes-canary transformProfile
|
||||
// is used to enable regenerator (and some related lowering passes) because SH
|
||||
// requires more Babel lowering than Hermes temporarily.
|
||||
@@ -221,28 +234,7 @@ const getPreset = (src, options) => {
|
||||
plugins: [require('@babel/plugin-transform-flow-strip-types')],
|
||||
},
|
||||
{
|
||||
plugins: [
|
||||
[
|
||||
require('babel-plugin-syntax-hermes-parser'),
|
||||
{
|
||||
parseLangTypes: 'flow',
|
||||
reactRuntimeTarget: '19',
|
||||
...options.hermesParserOptions,
|
||||
},
|
||||
],
|
||||
[require('babel-plugin-transform-flow-enums')],
|
||||
[require('@babel/plugin-transform-block-scoping')],
|
||||
[require('@babel/plugin-transform-class-properties'), {loose}],
|
||||
[require('@babel/plugin-transform-private-methods'), {loose}],
|
||||
[
|
||||
require('@babel/plugin-transform-private-property-in-object'),
|
||||
{loose},
|
||||
],
|
||||
[require('@babel/plugin-syntax-dynamic-import')],
|
||||
[require('@babel/plugin-syntax-export-default-from')],
|
||||
...passthroughSyntaxPlugins,
|
||||
[require('@babel/plugin-transform-unicode-regex')],
|
||||
],
|
||||
plugins: defaultPlugins,
|
||||
},
|
||||
{
|
||||
test: isTypeScriptSource,
|
||||
|
||||
@@ -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",
|
||||
@@ -14,7 +14,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
@@ -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,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": {
|
||||
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn clean && node scripts/build.js --verbose",
|
||||
|
||||
@@ -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": {
|
||||
@@ -19,7 +19,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 20.19.4"
|
||||
"node": ">= 22.14.0"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
@@ -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": {
|
||||
|
||||
@@ -31,8 +31,12 @@ function mockQueueMicrotask() {
|
||||
}
|
||||
|
||||
describe('Animated', () => {
|
||||
let ReactNativeFeatureFlags;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
|
||||
});
|
||||
|
||||
mockQueueMicrotask();
|
||||
@@ -109,7 +113,50 @@ describe('Animated', () => {
|
||||
expect(callback.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('does not detach on updates', async () => {
|
||||
ReactNativeFeatureFlags.override({
|
||||
scheduleAnimatedCleanupInMicrotask: () => false,
|
||||
});
|
||||
|
||||
const opacity = new Animated.Value(0);
|
||||
jest.spyOn(opacity, '__detach');
|
||||
|
||||
const root = await create(<Animated.View style={{opacity}} />);
|
||||
expect(opacity.__detach).not.toBeCalled();
|
||||
|
||||
await update(root, <Animated.View style={{opacity}} />);
|
||||
expect(opacity.__detach).not.toBeCalled();
|
||||
|
||||
await unmount(root);
|
||||
expect(opacity.__detach).toBeCalled();
|
||||
});
|
||||
|
||||
it('stops animation when detached', async () => {
|
||||
ReactNativeFeatureFlags.override({
|
||||
scheduleAnimatedCleanupInMicrotask: () => false,
|
||||
});
|
||||
|
||||
const opacity = new Animated.Value(0);
|
||||
const callback = jest.fn();
|
||||
|
||||
const root = await create(<Animated.View style={{opacity}} />);
|
||||
|
||||
Animated.timing(opacity, {
|
||||
toValue: 10,
|
||||
duration: 1000,
|
||||
useNativeDriver: false,
|
||||
}).start(callback);
|
||||
|
||||
await unmount(root);
|
||||
|
||||
expect(callback).toBeCalledWith({finished: false});
|
||||
});
|
||||
|
||||
it('detaches only on unmount (in a microtask)', async () => {
|
||||
ReactNativeFeatureFlags.override({
|
||||
scheduleAnimatedCleanupInMicrotask: () => true,
|
||||
});
|
||||
|
||||
const opacity = new Animated.Value(0);
|
||||
jest.spyOn(opacity, '__detach');
|
||||
|
||||
@@ -128,6 +175,10 @@ describe('Animated', () => {
|
||||
});
|
||||
|
||||
it('restores default values only on update (in a microtask)', async () => {
|
||||
ReactNativeFeatureFlags.override({
|
||||
scheduleAnimatedCleanupInMicrotask: () => true,
|
||||
});
|
||||
|
||||
const __restoreDefaultValues = jest.spyOn(
|
||||
AnimatedProps.prototype,
|
||||
'__restoreDefaultValues',
|
||||
@@ -162,6 +213,10 @@ describe('Animated', () => {
|
||||
});
|
||||
|
||||
it('stops animation when detached (in a microtask)', async () => {
|
||||
ReactNativeFeatureFlags.override({
|
||||
scheduleAnimatedCleanupInMicrotask: () => true,
|
||||
});
|
||||
|
||||
const opacity = new Animated.Value(0);
|
||||
const callback = jest.fn();
|
||||
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
-1
@@ -49,7 +49,6 @@ function _defineCheckVersionTests() {
|
||||
// $FlowFixMe[cannot-write]
|
||||
console.error = jest.fn();
|
||||
// $FlowFixMe[cannot-write]
|
||||
// $FlowFixMe[unsafe-addition]
|
||||
global.console = {error: jest.fn(error => (consoleOutput += error))};
|
||||
spyOnConsoleError = jest.spyOn(global.console, 'error');
|
||||
});
|
||||
|
||||
@@ -8,13 +8,12 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import Performance from '../../src/private/webapis/performance/Performance';
|
||||
import NativePerformance from '../../src/private/webapis/performance/specs/NativePerformance';
|
||||
|
||||
// In case if the native implementation of the Performance API is available, use it,
|
||||
// otherwise fall back to the legacy/default one, which only defines 'Performance.now()'
|
||||
if (NativePerformance) {
|
||||
const Performance =
|
||||
require('../../src/private/webapis/performance/Performance').default;
|
||||
// $FlowExpectedError[cannot-write]
|
||||
global.performance = new Performance();
|
||||
} else {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+171
-118
@@ -4,32 +4,180 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
|
||||
import type {Task} from './TaskQueue';
|
||||
|
||||
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
import EventEmitter from '../vendor/emitter/EventEmitter';
|
||||
|
||||
const BatchedBridge = require('../BatchedBridge/BatchedBridge').default;
|
||||
const TaskQueue = require('./TaskQueue').default;
|
||||
const invariant = require('invariant');
|
||||
|
||||
export type SimpleTask = {
|
||||
name: string,
|
||||
run: () => void,
|
||||
};
|
||||
export type PromiseTask = {
|
||||
name: string,
|
||||
gen: () => Promise<void>,
|
||||
};
|
||||
export type Task = SimpleTask | PromiseTask | (() => void);
|
||||
export type {Task, SimpleTask, PromiseTask} from './TaskQueue';
|
||||
|
||||
export type Handle = number;
|
||||
|
||||
// NOTE: The original implementation of `InteractionManager` never rejected
|
||||
// the returned promise. This preserves that behavior in the stub.
|
||||
function reject(error: Error): void {
|
||||
setTimeout(() => {
|
||||
throw error;
|
||||
}, 0);
|
||||
const _emitter = new EventEmitter<{
|
||||
interactionComplete: [],
|
||||
interactionStart: [],
|
||||
}>();
|
||||
|
||||
const DEBUG_DELAY: 0 = 0;
|
||||
const DEBUG: false = false;
|
||||
|
||||
const InteractionManagerImpl = {
|
||||
Events: {
|
||||
interactionStart: 'interactionStart',
|
||||
interactionComplete: 'interactionComplete',
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedule a function to run after all interactions have completed. Returns a cancellable
|
||||
* "promise".
|
||||
*/
|
||||
runAfterInteractions(task: ?Task): {
|
||||
then: <U>(
|
||||
onFulfill?: ?(void) => ?(Promise<U> | U),
|
||||
onReject?: ?(error: mixed) => ?(Promise<U> | U),
|
||||
) => Promise<U>,
|
||||
cancel: () => void,
|
||||
...
|
||||
} {
|
||||
const tasks: Array<Task> = [];
|
||||
const promise = new Promise((resolve: () => void) => {
|
||||
_scheduleUpdate();
|
||||
if (task) {
|
||||
tasks.push(task);
|
||||
}
|
||||
tasks.push({
|
||||
run: resolve,
|
||||
name: 'resolve ' + ((task && task.name) || '?'),
|
||||
});
|
||||
_taskQueue.enqueueTasks(tasks);
|
||||
});
|
||||
return {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
then: promise.then.bind(promise),
|
||||
cancel: function () {
|
||||
_taskQueue.cancelTasks(tasks);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has started.
|
||||
*/
|
||||
createInteractionHandle(): Handle {
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG && console.log('InteractionManager: create interaction handle');
|
||||
_scheduleUpdate();
|
||||
const handle = ++_inc;
|
||||
_addInteractionSet.add(handle);
|
||||
return handle;
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has completed.
|
||||
*/
|
||||
clearInteractionHandle(handle: Handle) {
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG && console.log('InteractionManager: clear interaction handle');
|
||||
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
|
||||
_scheduleUpdate();
|
||||
_addInteractionSet.delete(handle);
|
||||
_deleteInteractionSet.add(handle);
|
||||
},
|
||||
|
||||
// $FlowFixMe[unclear-type] unclear type of _emitter
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
addListener: _emitter.addListener.bind(_emitter) as (
|
||||
eventType: string,
|
||||
// $FlowFixMe[unclear-type] unclear type of arguments
|
||||
listener: (...args: any) => mixed,
|
||||
context: mixed,
|
||||
) => EventSubscription,
|
||||
|
||||
/**
|
||||
* A positive number will use setTimeout to schedule any tasks after the
|
||||
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
|
||||
* executed in one setImmediate batch (default).
|
||||
*/
|
||||
setDeadline(deadline: number) {
|
||||
_deadline = deadline;
|
||||
},
|
||||
};
|
||||
|
||||
const _interactionSet = new Set<number | Handle>();
|
||||
const _addInteractionSet = new Set<number | Handle>();
|
||||
const _deleteInteractionSet = new Set<Handle>();
|
||||
const _taskQueue = new TaskQueue({onMoreTasks: _scheduleUpdate});
|
||||
let _nextUpdateHandle: $FlowFixMe | TimeoutID = 0;
|
||||
let _inc = 0;
|
||||
let _deadline = -1;
|
||||
|
||||
/**
|
||||
* Schedule an asynchronous update to the interaction state.
|
||||
*/
|
||||
function _scheduleUpdate() {
|
||||
if (!_nextUpdateHandle) {
|
||||
if (_deadline > 0) {
|
||||
_nextUpdateHandle = setTimeout(_processUpdate, 0 + DEBUG_DELAY);
|
||||
} else {
|
||||
_nextUpdateHandle = setImmediate(_processUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify listeners, process queue, etc
|
||||
*/
|
||||
function _processUpdate() {
|
||||
_nextUpdateHandle = 0;
|
||||
|
||||
const interactionCount = _interactionSet.size;
|
||||
_addInteractionSet.forEach(handle => _interactionSet.add(handle));
|
||||
_deleteInteractionSet.forEach(handle => _interactionSet.delete(handle));
|
||||
const nextInteractionCount = _interactionSet.size;
|
||||
|
||||
if (interactionCount !== 0 && nextInteractionCount === 0) {
|
||||
// transition from 1+ --> 0 interactions
|
||||
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
|
||||
* https://fburl.com/workplace/6291gfvu */
|
||||
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
|
||||
* https://fburl.com/workplace/6291gfvu */
|
||||
_emitter.emit(InteractionManager.Events.interactionComplete);
|
||||
} else if (interactionCount === 0 && nextInteractionCount !== 0) {
|
||||
// transition from 0 --> 1+ interactions
|
||||
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
|
||||
* https://fburl.com/workplace/6291gfvu */
|
||||
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
|
||||
* https://fburl.com/workplace/6291gfvu */
|
||||
_emitter.emit(InteractionManager.Events.interactionStart);
|
||||
}
|
||||
|
||||
// process the queue regardless of a transition
|
||||
if (nextInteractionCount === 0) {
|
||||
while (_taskQueue.hasTasksToProcess()) {
|
||||
_taskQueue.processNext();
|
||||
if (
|
||||
_deadline > 0 &&
|
||||
BatchedBridge.getEventLoopRunningTime() >= _deadline
|
||||
) {
|
||||
// Hit deadline before processing all tasks, so process more later.
|
||||
_scheduleUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_addInteractionSet.clear();
|
||||
_deleteInteractionSet.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,106 +231,11 @@ function reject(error: Error): void {
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
const InteractionManagerStub = {
|
||||
Events: {
|
||||
interactionStart: 'interactionStart',
|
||||
interactionComplete: 'interactionComplete',
|
||||
},
|
||||
const InteractionManager = (
|
||||
ReactNativeFeatureFlags.disableInteractionManager()
|
||||
? // $FlowFixMe[incompatible-variance]
|
||||
require('./InteractionManagerStub').default
|
||||
: InteractionManagerImpl
|
||||
) as typeof InteractionManagerImpl;
|
||||
|
||||
/**
|
||||
* Schedule a function to run after all interactions have completed. Returns a cancellable
|
||||
* "promise".
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
runAfterInteractions(task: ?Task): {
|
||||
then: <U>(
|
||||
onFulfill?: ?(void) => ?(Promise<U> | U),
|
||||
onReject?: ?(error: mixed) => ?(Promise<U> | U),
|
||||
) => Promise<U>,
|
||||
cancel: () => void,
|
||||
...
|
||||
} {
|
||||
let immediateID: ?$FlowIssue;
|
||||
const promise = new Promise(resolve => {
|
||||
immediateID = setImmediate(() => {
|
||||
if (typeof task === 'object' && task !== null) {
|
||||
if (typeof task.gen === 'function') {
|
||||
task.gen().then(resolve, reject);
|
||||
} else if (typeof task.run === 'function') {
|
||||
try {
|
||||
task.run();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
|
||||
}
|
||||
} else if (typeof task === 'function') {
|
||||
try {
|
||||
task();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
reject(new TypeError('Invalid task of type: ' + typeof task));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
then: promise.then.bind(promise),
|
||||
cancel() {
|
||||
clearImmediate(immediateID);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has started.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
createInteractionHandle(): Handle {
|
||||
return -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has completed.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
clearInteractionHandle(handle: Handle) {
|
||||
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
addListener(
|
||||
eventType: string,
|
||||
// $FlowIgnore[unclear-type]
|
||||
listener: (...args: any) => mixed,
|
||||
context: mixed,
|
||||
): EventSubscription {
|
||||
return {
|
||||
remove() {},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* A positive number will use setTimeout to schedule any tasks after the
|
||||
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
|
||||
* executed in one setImmediate batch (default).
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
setDeadline(deadline: number) {
|
||||
// Do nothing.
|
||||
},
|
||||
};
|
||||
|
||||
export default InteractionManagerStub;
|
||||
export default InteractionManager;
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
export type Handle = number;
|
||||
|
||||
type Task =
|
||||
| {
|
||||
name: string,
|
||||
run: () => void,
|
||||
}
|
||||
| {
|
||||
name: string,
|
||||
gen: () => Promise<void>,
|
||||
}
|
||||
| (() => void);
|
||||
|
||||
// NOTE: The original implementation of `InteractionManager` never rejected
|
||||
// the returned promise. This preserves that behavior in the stub.
|
||||
function reject(error: Error): void {
|
||||
setTimeout(() => {
|
||||
throw error;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* InteractionManager allows long-running work to be scheduled after any
|
||||
* interactions/animations have completed. In particular, this allows JavaScript
|
||||
* animations to run smoothly.
|
||||
*
|
||||
* Applications can schedule tasks to run after interactions with the following:
|
||||
*
|
||||
* ```
|
||||
* InteractionManager.runAfterInteractions(() => {
|
||||
* // ...long-running synchronous task...
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Compare this to other scheduling alternatives:
|
||||
*
|
||||
* - requestAnimationFrame(): for code that animates a view over time.
|
||||
* - setImmediate/setTimeout(): run code later, note this may delay animations.
|
||||
* - runAfterInteractions(): run code later, without delaying active animations.
|
||||
*
|
||||
* The touch handling system considers one or more active touches to be an
|
||||
* 'interaction' and will delay `runAfterInteractions()` callbacks until all
|
||||
* touches have ended or been cancelled.
|
||||
*
|
||||
* InteractionManager also allows applications to register animations by
|
||||
* creating an interaction 'handle' on animation start, and clearing it upon
|
||||
* completion:
|
||||
*
|
||||
* ```
|
||||
* var handle = InteractionManager.createInteractionHandle();
|
||||
* // run animation... (`runAfterInteractions` tasks are queued)
|
||||
* // later, on animation completion:
|
||||
* InteractionManager.clearInteractionHandle(handle);
|
||||
* // queued tasks run if all handles were cleared
|
||||
* ```
|
||||
*
|
||||
* `runAfterInteractions` takes either a plain callback function, or a
|
||||
* `PromiseTask` object with a `gen` method that returns a `Promise`. If a
|
||||
* `PromiseTask` is supplied, then it is fully resolved (including asynchronous
|
||||
* dependencies that also schedule more tasks via `runAfterInteractions`) before
|
||||
* starting on the next task that might have been queued up synchronously
|
||||
* earlier.
|
||||
*
|
||||
* By default, queued tasks are executed together in a loop in one
|
||||
* `setImmediate` batch. If `setDeadline` is called with a positive number, then
|
||||
* tasks will only be executed until the deadline (in terms of js event loop run
|
||||
* time) approaches, at which point execution will yield via setTimeout,
|
||||
* allowing events such as touches to start interactions and block queued tasks
|
||||
* from executing, making apps more responsive.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
const InteractionManagerStub = {
|
||||
Events: {
|
||||
interactionStart: 'interactionStart',
|
||||
interactionComplete: 'interactionComplete',
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedule a function to run after all interactions have completed. Returns a cancellable
|
||||
* "promise".
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
runAfterInteractions(task: ?Task): {
|
||||
then: <U>(
|
||||
onFulfill?: ?(void) => ?(Promise<U> | U),
|
||||
onReject?: ?(error: mixed) => ?(Promise<U> | U),
|
||||
) => Promise<U>,
|
||||
cancel: () => void,
|
||||
...
|
||||
} {
|
||||
let immediateID: ?$FlowIssue;
|
||||
const promise = new Promise(resolve => {
|
||||
immediateID = setImmediate(() => {
|
||||
if (typeof task === 'object' && task !== null) {
|
||||
if (typeof task.gen === 'function') {
|
||||
task.gen().then(resolve, reject);
|
||||
} else if (typeof task.run === 'function') {
|
||||
try {
|
||||
task.run();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
|
||||
}
|
||||
} else if (typeof task === 'function') {
|
||||
try {
|
||||
task();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
reject(new TypeError('Invalid task of type: ' + typeof task));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
then: promise.then.bind(promise),
|
||||
cancel() {
|
||||
clearImmediate(immediateID);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has started.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
createInteractionHandle(): Handle {
|
||||
return -1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify manager that an interaction has completed.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
clearInteractionHandle(handle: Handle) {
|
||||
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
addListener(): EventSubscription {
|
||||
return {
|
||||
remove() {},
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* A positive number will use setTimeout to schedule any tasks after the
|
||||
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
|
||||
* executed in one setImmediate batch (default).
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
setDeadline(deadline: number) {
|
||||
// Do nothing.
|
||||
},
|
||||
};
|
||||
|
||||
export default InteractionManagerStub;
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
export type SimpleTask = {
|
||||
name: string,
|
||||
run: () => void,
|
||||
};
|
||||
export type PromiseTask = {
|
||||
name: string,
|
||||
gen: () => Promise<void>,
|
||||
};
|
||||
export type Task = SimpleTask | PromiseTask | (() => void);
|
||||
|
||||
const DEBUG: false = false;
|
||||
|
||||
/**
|
||||
* TaskQueue - A system for queueing and executing a mix of simple callbacks and
|
||||
* trees of dependent tasks based on Promises. No tasks are executed unless
|
||||
* `processNext` is called.
|
||||
*
|
||||
* `enqueue` takes a Task object with either a simple `run` callback, or a
|
||||
* `gen` function that returns a `Promise` and puts it in the queue. If a gen
|
||||
* function is supplied, then the promise it returns will block execution of
|
||||
* tasks already in the queue until it resolves. This can be used to make sure
|
||||
* the first task is fully resolved (including asynchronous dependencies that
|
||||
* also schedule more tasks via `enqueue`) before starting on the next task.
|
||||
* The `onMoreTasks` constructor argument is used to inform the owner that an
|
||||
* async task has resolved and that the queue should be processed again.
|
||||
*
|
||||
* Note: Tasks are only actually executed with explicit calls to `processNext`.
|
||||
*/
|
||||
class TaskQueue {
|
||||
/**
|
||||
* TaskQueue instances are self contained and independent, so multiple tasks
|
||||
* of varying semantics and priority can operate together.
|
||||
*
|
||||
* `onMoreTasks` is invoked when `PromiseTask`s resolve if there are more
|
||||
* tasks to process.
|
||||
*/
|
||||
constructor({onMoreTasks}: {onMoreTasks: () => void, ...}) {
|
||||
this._onMoreTasks = onMoreTasks;
|
||||
this._queueStack = [{tasks: [], popable: false}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task to the queue. It is recommended to name your tasks for easier
|
||||
* async debugging. Tasks will not be executed until `processNext` is called
|
||||
* explicitly.
|
||||
*/
|
||||
enqueue(task: Task): void {
|
||||
this._getCurrentQueue().push(task);
|
||||
}
|
||||
|
||||
enqueueTasks(tasks: Array<Task>): void {
|
||||
tasks.forEach(task => this.enqueue(task));
|
||||
}
|
||||
|
||||
cancelTasks(tasksToCancel: Array<Task>): void {
|
||||
// search through all tasks and remove them.
|
||||
this._queueStack = this._queueStack
|
||||
.map(queue => ({
|
||||
...queue,
|
||||
tasks: queue.tasks.filter(task => tasksToCancel.indexOf(task) === -1),
|
||||
}))
|
||||
.filter((queue, idx) => queue.tasks.length > 0 || idx === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if `processNext` should be called.
|
||||
*
|
||||
* @returns {boolean} Returns true if there are tasks that are ready to be
|
||||
* processed with `processNext`, or returns false if there are no more tasks
|
||||
* to be processed right now, although there may be tasks in the queue that
|
||||
* are blocked by earlier `PromiseTask`s that haven't resolved yet.
|
||||
* `onMoreTasks` will be called after each `PromiseTask` resolves if there are
|
||||
* tasks ready to run at that point.
|
||||
*/
|
||||
hasTasksToProcess(): boolean {
|
||||
return this._getCurrentQueue().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the next task in the queue.
|
||||
*/
|
||||
processNext(): void {
|
||||
const queue = this._getCurrentQueue();
|
||||
if (queue.length) {
|
||||
const task = queue.shift();
|
||||
try {
|
||||
if (typeof task === 'object' && task.gen) {
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG && console.log('TaskQueue: genPromise for task ' + task.name);
|
||||
this._genPromise(task);
|
||||
} else if (typeof task === 'object' && task.run) {
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG && console.log('TaskQueue: run task ' + task.name);
|
||||
task.run();
|
||||
} else {
|
||||
invariant(
|
||||
typeof task === 'function',
|
||||
'Expected Function, SimpleTask, or PromiseTask, but got:\n' +
|
||||
JSON.stringify(task, null, 2),
|
||||
);
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG && console.log('TaskQueue: run anonymous task');
|
||||
task();
|
||||
}
|
||||
} catch (e) {
|
||||
e.message =
|
||||
// $FlowFixMe[incompatible-type]
|
||||
// $FlowFixMe[incompatible-use]
|
||||
'TaskQueue: Error with task ' + (task.name || '') + ': ' + e.message;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_queueStack: Array<{
|
||||
tasks: Array<Task>,
|
||||
popable: boolean,
|
||||
...
|
||||
}>;
|
||||
_onMoreTasks: () => void;
|
||||
|
||||
_getCurrentQueue(): Array<Task> {
|
||||
const stackIdx = this._queueStack.length - 1;
|
||||
const queue = this._queueStack[stackIdx];
|
||||
if (
|
||||
queue.popable &&
|
||||
queue.tasks.length === 0 &&
|
||||
this._queueStack.length > 1
|
||||
) {
|
||||
this._queueStack.pop();
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG &&
|
||||
console.log('TaskQueue: popped queue: ', {
|
||||
stackIdx,
|
||||
queueStackSize: this._queueStack.length,
|
||||
});
|
||||
return this._getCurrentQueue();
|
||||
} else {
|
||||
return queue.tasks;
|
||||
}
|
||||
}
|
||||
|
||||
_genPromise(task: PromiseTask) {
|
||||
// Each async task pushes it's own queue onto the queue stack. This
|
||||
// effectively defers execution of previously queued tasks until the promise
|
||||
// resolves, at which point we allow the new queue to be popped, which
|
||||
// happens once it is fully processed.
|
||||
this._queueStack.push({tasks: [], popable: false});
|
||||
const stackIdx = this._queueStack.length - 1;
|
||||
const stackItem = this._queueStack[stackIdx];
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG && console.log('TaskQueue: push new queue: ', {stackIdx});
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG && console.log('TaskQueue: exec gen task ' + task.name);
|
||||
task
|
||||
.gen()
|
||||
.then(() => {
|
||||
/* $FlowFixMe[constant-condition] Error discovered during Constant
|
||||
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
|
||||
DEBUG &&
|
||||
console.log('TaskQueue: onThen for gen task ' + task.name, {
|
||||
stackIdx,
|
||||
queueStackSize: this._queueStack.length,
|
||||
});
|
||||
stackItem.popable = true;
|
||||
this.hasTasksToProcess() && this._onMoreTasks();
|
||||
})
|
||||
.catch(ex => {
|
||||
setTimeout(() => {
|
||||
ex.message = `TaskQueue: Error resolving Promise in task ${task.name}: ${ex.message}`;
|
||||
throw ex;
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default TaskQueue;
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @noflow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {ReactNativeFeatureFlagsJsOnlyOverrides} from '../../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
|
||||
function importModules(overrides: ReactNativeFeatureFlagsJsOnlyOverrides) {
|
||||
const ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
|
||||
|
||||
// Make sure to setup overrides before importing any modules.
|
||||
ReactNativeFeatureFlags.override(overrides);
|
||||
|
||||
const BatchedBridge = require('../../BatchedBridge/BatchedBridge').default;
|
||||
const InteractionManager = require('../InteractionManager').default;
|
||||
|
||||
jest.mock('../../vendor/core/ErrorUtils');
|
||||
jest.mock('../../BatchedBridge/BatchedBridge');
|
||||
|
||||
return {
|
||||
BatchedBridge,
|
||||
InteractionManager,
|
||||
};
|
||||
}
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
const itif = (condition: boolean) => (condition ? it : it.skip);
|
||||
|
||||
function expectToBeCalledOnce(
|
||||
fn: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
|
||||
): void {
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
}
|
||||
|
||||
describe('InteractionManager', () => {
|
||||
let InteractionManager;
|
||||
let interactionStart;
|
||||
let interactionComplete;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
({InteractionManager} = importModules({
|
||||
disableInteractionManager: () => false,
|
||||
}));
|
||||
|
||||
interactionStart = jest.fn();
|
||||
interactionComplete = jest.fn();
|
||||
|
||||
InteractionManager.addListener(
|
||||
InteractionManager.Events.interactionStart,
|
||||
interactionStart,
|
||||
);
|
||||
InteractionManager.addListener(
|
||||
InteractionManager.Events.interactionComplete,
|
||||
interactionComplete,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when clearing an undefined handle', () => {
|
||||
// $FlowExpectedError[incompatible-call]
|
||||
expect(() => InteractionManager.clearInteractionHandle()).toThrow();
|
||||
});
|
||||
|
||||
it('notifies asynchronously when interaction starts', () => {
|
||||
InteractionManager.createInteractionHandle();
|
||||
expect(interactionStart).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(interactionStart).toBeCalled();
|
||||
expect(interactionComplete).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('notifies asynchronously when interaction stops', () => {
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
jest.runAllTimers();
|
||||
interactionStart.mockClear();
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
expect(interactionComplete).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(interactionStart).not.toBeCalled();
|
||||
expect(interactionComplete).toBeCalled();
|
||||
});
|
||||
|
||||
it('does not notify when started & stopped in same event loop', () => {
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(interactionStart).not.toBeCalled();
|
||||
expect(interactionComplete).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does not notify when going from two -> one active interactions', () => {
|
||||
InteractionManager.createInteractionHandle();
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
jest.runAllTimers();
|
||||
|
||||
interactionStart.mockClear();
|
||||
interactionComplete.mockClear();
|
||||
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
jest.runAllTimers();
|
||||
expect(interactionStart).not.toBeCalled();
|
||||
expect(interactionComplete).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('runs tasks asynchronously when there are interactions', () => {
|
||||
const task = jest.fn();
|
||||
InteractionManager.runAfterInteractions(task);
|
||||
expect(task).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(task).toBeCalled();
|
||||
});
|
||||
|
||||
it('runs tasks when interactions complete', () => {
|
||||
const task = jest.fn();
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
InteractionManager.runAfterInteractions(task);
|
||||
|
||||
jest.runAllTimers();
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
expect(task).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(task).toBeCalled();
|
||||
});
|
||||
|
||||
it('does not run tasks twice', () => {
|
||||
const task1 = jest.fn();
|
||||
const task2 = jest.fn();
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
jest.runAllTimers();
|
||||
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
jest.runAllTimers();
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
});
|
||||
|
||||
it('runs tasks added while processing previous tasks', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
});
|
||||
const task2 = jest.fn();
|
||||
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
expect(task2).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(task1).toBeCalled();
|
||||
expect(task2).toBeCalled();
|
||||
});
|
||||
|
||||
it('allows tasks to be cancelled', () => {
|
||||
const task1 = jest.fn();
|
||||
const task2 = jest.fn();
|
||||
const promise1 = InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
expect(task1).not.toBeCalled();
|
||||
expect(task2).not.toBeCalled();
|
||||
promise1.cancel();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(task1).not.toBeCalled();
|
||||
expect(task2).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('promise tasks', () => {
|
||||
let BatchedBridge;
|
||||
let InteractionManager;
|
||||
let sequenceId;
|
||||
|
||||
function createSequenceTask(expectedSequenceId: number) {
|
||||
return jest.fn(() => {
|
||||
expect(++sequenceId).toBe(expectedSequenceId);
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
({BatchedBridge, InteractionManager} = importModules({
|
||||
disableInteractionManager: () => false,
|
||||
}));
|
||||
|
||||
sequenceId = 0;
|
||||
});
|
||||
|
||||
it('should run a basic promise task', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
return new Promise(resolve => resolve());
|
||||
});
|
||||
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
|
||||
jest.runAllTimers();
|
||||
expectToBeCalledOnce(task1);
|
||||
});
|
||||
|
||||
it('should handle nested promises', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
return new Promise(resolve => {
|
||||
InteractionManager.runAfterInteractions({
|
||||
gen: task2,
|
||||
name: 'gen2',
|
||||
}).then(resolve);
|
||||
});
|
||||
});
|
||||
const task2 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
return new Promise(resolve => resolve());
|
||||
});
|
||||
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
|
||||
jest.runAllTimers();
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
|
||||
it('should pause promise tasks during interactions then resume', () => {
|
||||
const task1 = createSequenceTask(1);
|
||||
const task2 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
InteractionManager.runAfterInteractions(task3).then(resolve);
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task3 = createSequenceTask(3);
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
|
||||
jest.runOnlyPendingTimers();
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
jest.runAllTimers();
|
||||
jest.runAllTimers(); // Just to be sure...
|
||||
expect(task3).not.toBeCalled();
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
jest.runAllTimers();
|
||||
expectToBeCalledOnce(task3);
|
||||
});
|
||||
|
||||
it('should execute tasks in loop within deadline', () => {
|
||||
InteractionManager.setDeadline(100);
|
||||
BatchedBridge.getEventLoopRunningTime.mockReturnValue(10);
|
||||
const task1 = createSequenceTask(1);
|
||||
const task2 = createSequenceTask(2);
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
|
||||
it('should execute tasks one at a time if deadline exceeded', () => {
|
||||
InteractionManager.setDeadline(100);
|
||||
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
|
||||
const task1 = createSequenceTask(1);
|
||||
const task2 = createSequenceTask(2);
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expect(task2).not.toBeCalled();
|
||||
|
||||
jest.runOnlyPendingTimers(); // resolve1
|
||||
jest.runOnlyPendingTimers(); // task2
|
||||
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
|
||||
const bigAsyncTest = resolveTest => {
|
||||
jest.useRealTimers();
|
||||
|
||||
const task1 = createSequenceTask(1);
|
||||
const task2 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
return new Promise(resolve => {
|
||||
InteractionManager.runAfterInteractions(task3);
|
||||
setTimeout(() => {
|
||||
InteractionManager.runAfterInteractions({
|
||||
gen: task4,
|
||||
name: 'gen4',
|
||||
}).then(resolve);
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task3 = createSequenceTask(3);
|
||||
const task4 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(4);
|
||||
return new Promise(resolve => {
|
||||
InteractionManager.runAfterInteractions(task5).then(resolve);
|
||||
});
|
||||
});
|
||||
const task5 = createSequenceTask(5);
|
||||
const task6 = createSequenceTask(6);
|
||||
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
|
||||
InteractionManager.runAfterInteractions(task6);
|
||||
|
||||
setTimeout(() => {
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task3);
|
||||
expectToBeCalledOnce(task4);
|
||||
expectToBeCalledOnce(task5);
|
||||
expectToBeCalledOnce(task6);
|
||||
|
||||
resolveTest();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
itif(!isWindows)(
|
||||
'resolves async tasks recursively before other queued tasks',
|
||||
() => {
|
||||
return new Promise(bigAsyncTest);
|
||||
},
|
||||
);
|
||||
|
||||
itif(!isWindows)('should also work with a deadline', () => {
|
||||
InteractionManager.setDeadline(100);
|
||||
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
|
||||
return new Promise(bigAsyncTest);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,169 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @noflow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Promise = require('promise');
|
||||
|
||||
function expectToBeCalledOnce(fn) {
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
}
|
||||
|
||||
function clearTaskQueue(taskQueue) {
|
||||
do {
|
||||
jest.runAllTimers();
|
||||
taskQueue.processNext();
|
||||
jest.runAllTimers();
|
||||
} while (taskQueue.hasTasksToProcess());
|
||||
}
|
||||
|
||||
describe('TaskQueue', () => {
|
||||
let taskQueue;
|
||||
let onMoreTasks;
|
||||
let sequenceId;
|
||||
function createSequenceTask(expectedSequenceId) {
|
||||
return jest.fn(() => {
|
||||
expect(++sequenceId).toBe(expectedSequenceId);
|
||||
});
|
||||
}
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
onMoreTasks = jest.fn();
|
||||
const TaskQueue = require('../TaskQueue').default;
|
||||
taskQueue = new TaskQueue({onMoreTasks});
|
||||
sequenceId = 0;
|
||||
});
|
||||
|
||||
it('should run a basic task', () => {
|
||||
const task1 = createSequenceTask(1);
|
||||
taskQueue.enqueue({run: task1, name: 'run1'});
|
||||
expect(taskQueue.hasTasksToProcess()).toBe(true);
|
||||
taskQueue.processNext();
|
||||
expectToBeCalledOnce(task1);
|
||||
});
|
||||
|
||||
it('should handle blocking promise task', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
resolve();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task2 = createSequenceTask(2);
|
||||
taskQueue.enqueue({gen: task1, name: 'gen1'});
|
||||
taskQueue.enqueue({run: task2, name: 'run2'});
|
||||
|
||||
taskQueue.processNext();
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expect(task2).not.toBeCalled();
|
||||
expect(onMoreTasks).not.toBeCalled();
|
||||
expect(taskQueue.hasTasksToProcess()).toBe(false);
|
||||
|
||||
clearTaskQueue(taskQueue);
|
||||
|
||||
expectToBeCalledOnce(onMoreTasks);
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
|
||||
it('should handle nested simple tasks', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
taskQueue.enqueue({run: task3, name: 'run3'});
|
||||
});
|
||||
const task2 = createSequenceTask(2);
|
||||
const task3 = createSequenceTask(3);
|
||||
taskQueue.enqueue({run: task1, name: 'run1'});
|
||||
taskQueue.enqueue({run: task2, name: 'run2'}); // not blocked by task 1
|
||||
|
||||
clearTaskQueue(taskQueue);
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task3);
|
||||
});
|
||||
|
||||
it('should handle nested promises', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
taskQueue.enqueue({gen: task2, name: 'gen2'});
|
||||
taskQueue.enqueue({run: resolve, name: 'resolve1'});
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task2 = jest.fn(() => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
taskQueue.enqueue({run: task3, name: 'run3'});
|
||||
taskQueue.enqueue({run: resolve, name: 'resolve2'});
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task3 = createSequenceTask(3);
|
||||
const task4 = createSequenceTask(4);
|
||||
taskQueue.enqueue({gen: task1, name: 'gen1'});
|
||||
taskQueue.enqueue({run: task4, name: 'run4'}); // blocked by task 1 promise
|
||||
|
||||
clearTaskQueue(taskQueue);
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task3);
|
||||
expectToBeCalledOnce(task4);
|
||||
});
|
||||
|
||||
it('should be able to cancel tasks', () => {
|
||||
const task1 = jest.fn();
|
||||
const task2 = createSequenceTask(1);
|
||||
const task3 = jest.fn();
|
||||
const task4 = createSequenceTask(2);
|
||||
taskQueue.enqueue(task1);
|
||||
taskQueue.enqueue(task2);
|
||||
taskQueue.enqueue(task3);
|
||||
taskQueue.enqueue(task4);
|
||||
taskQueue.cancelTasks([task1, task3]);
|
||||
clearTaskQueue(taskQueue);
|
||||
expect(task1).not.toBeCalled();
|
||||
expect(task3).not.toBeCalled();
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task4);
|
||||
expect(taskQueue.hasTasksToProcess()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not crash when last task is cancelled', () => {
|
||||
const task1 = jest.fn();
|
||||
taskQueue.enqueue(task1);
|
||||
taskQueue.cancelTasks([task1]);
|
||||
clearTaskQueue(taskQueue);
|
||||
expect(task1).not.toBeCalled();
|
||||
expect(taskQueue.hasTasksToProcess()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not crash when task is cancelled between being started and resolved', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
|
||||
taskQueue.enqueue({gen: task1, name: 'gen1'});
|
||||
taskQueue.processNext();
|
||||
taskQueue.cancelTasks([task1]);
|
||||
|
||||
jest.runAllTimers();
|
||||
});
|
||||
});
|
||||
@@ -400,6 +400,7 @@ class XMLHttpRequest extends EventTarget {
|
||||
if (XMLHttpRequest._profiling) {
|
||||
console.timeStamp(
|
||||
'Incremental Data: ' + this._getMeasureURL(),
|
||||
// $FlowFixMe[extra-arg] Add correct typing for `console.timeStamp`
|
||||
start,
|
||||
undefined,
|
||||
PERFORMANCE_TRACK_NAME,
|
||||
@@ -454,6 +455,7 @@ class XMLHttpRequest extends EventTarget {
|
||||
const start = this._startTime;
|
||||
console.timeStamp(
|
||||
this._getMeasureURL(),
|
||||
// $FlowFixMe[extra-arg] Add correct typing for `console.timeStamp`
|
||||
start,
|
||||
undefined,
|
||||
PERFORMANCE_TRACK_NAME,
|
||||
|
||||
@@ -10,21 +10,21 @@
|
||||
|
||||
import typeof {enable} from 'promise/setimmediate/rejection-tracking';
|
||||
|
||||
import ExceptionsManager from './Core/ExceptionsManager';
|
||||
import LogBox from './LogBox/LogBox';
|
||||
|
||||
let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
|
||||
allRejections: true,
|
||||
onUnhandled: (id, rejection) => {
|
||||
onUnhandled: (id, rejection = {}) => {
|
||||
let message: string;
|
||||
let stack: ?string;
|
||||
|
||||
if (rejection === undefined) {
|
||||
message = '';
|
||||
} else if (
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
Object.prototype.toString.call(rejection) === '[object Error]'
|
||||
) {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
const stringValue = Object.prototype.toString.call(rejection);
|
||||
if (stringValue === '[object Error]') {
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
message = Error.prototype.toString.call(rejection);
|
||||
const error: Error = (rejection: $FlowFixMe);
|
||||
stack = error.stack;
|
||||
} else {
|
||||
try {
|
||||
message = require('pretty-format').format(rejection);
|
||||
@@ -34,23 +34,37 @@ let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
|
||||
? rejection
|
||||
: JSON.stringify((rejection: $FlowFixMe));
|
||||
}
|
||||
// It could although this object is not a standard error, it still has stack information to unwind
|
||||
// $FlowFixMe ignore types just check if stack is there
|
||||
if (rejection?.stack && typeof rejection.stack === 'string') {
|
||||
stack = rejection.stack;
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionsManager.handleException(
|
||||
new Error(
|
||||
`Uncaught (in promise, id: ${id})${message ? `: "${message}"` : ''}`,
|
||||
{
|
||||
cause: rejection,
|
||||
const warning = `Possible unhandled promise rejection (id: ${id}):\n${
|
||||
message ?? ''
|
||||
}`;
|
||||
if (__DEV__) {
|
||||
LogBox.addLog({
|
||||
level: 'warn',
|
||||
message: {
|
||||
content: warning,
|
||||
substitutions: [],
|
||||
},
|
||||
),
|
||||
false /* isFatal */,
|
||||
);
|
||||
componentStack: [],
|
||||
componentStackType: null,
|
||||
stack,
|
||||
category: 'possible_unhandled_promise_rejection',
|
||||
});
|
||||
} else {
|
||||
console.warn(warning);
|
||||
}
|
||||
},
|
||||
onHandled: id => {
|
||||
const warning =
|
||||
`Promise rejection handled (id: ${id})\n` +
|
||||
'This means you can ignore any previous messages of the form ' +
|
||||
`"Uncaught (in promise, id: ${id})"`;
|
||||
`"Possible unhandled promise rejection (id: ${id}):"`;
|
||||
console.warn(warning);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -31,8 +31,8 @@ public abstract class com/facebook/react/HeadlessJsTaskService : android/app/Ser
|
||||
public fun <init> ()V
|
||||
public static final fun acquireWakeLockNow (Landroid/content/Context;)V
|
||||
protected final fun getReactContext ()Lcom/facebook/react/bridge/ReactContext;
|
||||
protected fun getReactHost ()Lcom/facebook/react/ReactHost;
|
||||
protected fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
|
||||
protected final fun getReactHost ()Lcom/facebook/react/ReactHost;
|
||||
protected final fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
|
||||
protected fun getTaskConfig (Landroid/content/Intent;)Lcom/facebook/react/jstasks/HeadlessJsTaskConfig;
|
||||
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
|
||||
public fun onDestroy ()V
|
||||
@@ -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") {
|
||||
|
||||
+2
-2
@@ -113,7 +113,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
|
||||
* somewhere.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
protected open val reactNativeHost: ReactNativeHost
|
||||
protected val reactNativeHost: ReactNativeHost
|
||||
get() = (application as ReactApplication).reactNativeHost
|
||||
|
||||
/**
|
||||
@@ -121,7 +121,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
|
||||
* [ReactApplication] and calls [ReactApplication.reactHost]. This method assumes it is called in
|
||||
* new architecture and returns null if not.
|
||||
*/
|
||||
protected open val reactHost: ReactHost?
|
||||
protected val reactHost: ReactHost?
|
||||
get() = (application as ReactApplication).reactHost
|
||||
|
||||
protected val reactContext: ReactContext?
|
||||
|
||||
+2
-6
@@ -135,10 +135,6 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
private void init() {
|
||||
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
|
||||
setClipChildren(false);
|
||||
|
||||
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
|
||||
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -869,7 +865,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
private int mDeviceRotation = 0;
|
||||
|
||||
/* package */ CustomGlobalLayoutListener() {
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
|
||||
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext());
|
||||
mVisibleViewArea = new Rect();
|
||||
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
|
||||
}
|
||||
@@ -992,7 +988,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
return;
|
||||
}
|
||||
mDeviceRotation = rotation;
|
||||
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
|
||||
DisplayMetricsHolder.initDisplayMetrics(getContext());
|
||||
emitOrientationChanged(rotation);
|
||||
}
|
||||
|
||||
|
||||
+423
@@ -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;
|
||||
}
|
||||
}
|
||||
-356
@@ -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
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
-12
@@ -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
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<c5ed8a904dcda7ade87527233b879b92>>
|
||||
* @generated SignedSource<<99019349a8f3642c42e6dac5f1476e7f>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -101,7 +101,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun enableVirtualViewDebugFeatures(): Boolean = false
|
||||
|
||||
override fun enableVirtualViewRenderState(): Boolean = true
|
||||
override fun enableVirtualViewRenderState(): Boolean = false
|
||||
|
||||
override fun enableVirtualViewWindowFocusDetection(): Boolean = false
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -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
|
||||
|
||||
+15
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel
|
||||
import com.facebook.react.uimanager.events.Event
|
||||
|
||||
/** Event used to notify JS component about changes of its position or dimensions. */
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.WARNING)
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
|
||||
public class OnLayoutEvent private constructor() : Event<OnLayoutEvent>() {
|
||||
@VisibleForTesting internal var x: Int = 0
|
||||
@VisibleForTesting internal var y: Int = 0
|
||||
@@ -60,7 +60,7 @@ public class OnLayoutEvent private constructor() : Event<OnLayoutEvent>() {
|
||||
public companion object {
|
||||
init {
|
||||
LegacyArchitectureLogger.assertLegacyArchitecture(
|
||||
"OnLayoutEvent", LegacyArchitectureLogLevel.WARNING)
|
||||
"OnLayoutEvent", LegacyArchitectureLogLevel.ERROR)
|
||||
}
|
||||
|
||||
private val EVENTS_POOL: SynchronizedPool<OnLayoutEvent> = SynchronizedPool<OnLayoutEvent>(20)
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
+2
-7
@@ -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
|
||||
|
||||
-9
@@ -377,9 +377,6 @@ public class ReactScrollView extends ScrollView
|
||||
}
|
||||
|
||||
ReactScrollViewHelper.emitLayoutEvent(this);
|
||||
if (mVirtualViewContainerState != null) {
|
||||
mVirtualViewContainerState.updateState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -407,9 +404,6 @@ public class ReactScrollView extends ScrollView
|
||||
if (mMaintainVisibleContentPositionHelper != null) {
|
||||
mMaintainVisibleContentPositionHelper.stop();
|
||||
}
|
||||
if (mVirtualViewContainerState != null) {
|
||||
mVirtualViewContainerState.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -492,9 +486,6 @@ public class ReactScrollView extends ScrollView
|
||||
this,
|
||||
mOnScrollDispatchHelper.getXFlingVelocity(),
|
||||
mOnScrollDispatchHelper.getYFlingVelocity());
|
||||
if (mVirtualViewContainerState != null) {
|
||||
mVirtualViewContainerState.updateState();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
|
||||
+41
-56
@@ -9,10 +9,8 @@ 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
|
||||
import com.facebook.react.views.virtual.VirtualViewMode
|
||||
import java.util.*
|
||||
|
||||
@@ -47,40 +45,22 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
internal class VirtualViewContainerState {
|
||||
|
||||
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
|
||||
private val detectWindowFocus = ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()
|
||||
internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
|
||||
ReactScrollViewHelper.ScrollListener {
|
||||
|
||||
private val prerenderRatio: Int = 1
|
||||
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
|
||||
private val emptyRect: Rect = Rect()
|
||||
private val visibleRect: Rect = Rect()
|
||||
private val prerenderRect: Rect = Rect()
|
||||
private val onWindowFocusChangeListener =
|
||||
ViewTreeObserver.OnWindowFocusChangeListener {
|
||||
debugLog("onWindowFocusChanged")
|
||||
updateModes()
|
||||
}
|
||||
private val scrollView: ViewGroup
|
||||
|
||||
constructor(scrollView: ViewGroup) {
|
||||
this.scrollView = scrollView
|
||||
if (detectWindowFocus) {
|
||||
scrollView.viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
init {
|
||||
ReactScrollViewHelper.addScrollListener(this)
|
||||
}
|
||||
|
||||
public fun cleanup() {
|
||||
if (detectWindowFocus) {
|
||||
scrollView.viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
|
||||
}
|
||||
}
|
||||
|
||||
public fun onChange(virtualView: VirtualView) {
|
||||
if (virtualViews.add(virtualView)) {
|
||||
debugLog("add", { "virtualViewID=${virtualView.virtualViewID}" })
|
||||
} else {
|
||||
debugLog("update", { "virtualViewID=${virtualView.virtualViewID}" })
|
||||
public fun add(virtualView: VirtualView) {
|
||||
assert(virtualViews.add(virtualView)) {
|
||||
"Attempting to add duplicate VirtualView: ${virtualView.virtualViewID}"
|
||||
}
|
||||
updateModes(virtualView)
|
||||
}
|
||||
@@ -89,13 +69,35 @@ internal class VirtualViewContainerState {
|
||||
assert(virtualViews.remove(virtualView)) {
|
||||
"Attempting to remove non-existent VirtualView: ${virtualView.virtualViewID}"
|
||||
}
|
||||
debugLog("remove", { "virtualViewID=${virtualView.virtualViewID}" })
|
||||
}
|
||||
|
||||
// Called on ScrollView onLayout or onScroll
|
||||
public fun updateState() {
|
||||
debugLog("updateState")
|
||||
updateModes()
|
||||
// ReactScrollViewHelper.ScrollListener.onLayout
|
||||
// Emitted from ScrollView's onLayout
|
||||
override fun onLayout(scrollView: ViewGroup?) {
|
||||
// ReactScrollViewHelper is global
|
||||
if (this.scrollView == scrollView) {
|
||||
debugLog("ReactScrollViewHelper.onLayout")
|
||||
updateModes()
|
||||
}
|
||||
}
|
||||
|
||||
// ReactScrollViewHelper.ScrollListener.onScroll
|
||||
// Emitted from ScrollView's onLayout
|
||||
override fun onScroll(
|
||||
scrollView: ViewGroup?,
|
||||
scrollEventType: ScrollEventType?,
|
||||
xVelocity: Float,
|
||||
yVelocity: Float
|
||||
) {
|
||||
// ReactScrollViewHelper is global
|
||||
if (this.scrollView == scrollView) {
|
||||
debugLog("ReactScrollViewHelper.onScroll")
|
||||
updateModes()
|
||||
}
|
||||
}
|
||||
|
||||
public fun update(virtualView: VirtualView) {
|
||||
updateModes(virtualView)
|
||||
}
|
||||
|
||||
private fun updateModes(virtualView: VirtualView? = null) {
|
||||
@@ -108,34 +110,17 @@ internal class VirtualViewContainerState {
|
||||
val virtualViewsIt = if (virtualView != null) listOf(virtualView) else virtualViews
|
||||
virtualViewsIt.forEach { vv ->
|
||||
val rect = vv.containerRelativeRect
|
||||
|
||||
var mode = VirtualViewMode.Hidden
|
||||
var thresholdRect = emptyRect
|
||||
when {
|
||||
rect.isEmpty -> {}
|
||||
rectsOverlap(rect, visibleRect) -> {
|
||||
thresholdRect = visibleRect
|
||||
if (detectWindowFocus) {
|
||||
if (scrollView.hasWindowFocus()) {
|
||||
mode = VirtualViewMode.Visible
|
||||
} else {
|
||||
mode = VirtualViewMode.Prerender
|
||||
}
|
||||
} else {
|
||||
mode = VirtualViewMode.Visible
|
||||
}
|
||||
vv.onModeChange(VirtualViewMode.Visible, visibleRect)
|
||||
}
|
||||
rectsOverlap(rect, prerenderRect) -> {
|
||||
mode = VirtualViewMode.Prerender
|
||||
thresholdRect = prerenderRect
|
||||
vv.onModeChange(VirtualViewMode.Prerender, prerenderRect)
|
||||
}
|
||||
else -> {
|
||||
vv.onModeChange(VirtualViewMode.Hidden, emptyRect)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
debugLog(
|
||||
"updateModes",
|
||||
{ "virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect" })
|
||||
vv.onModeChange(mode, thresholdRect)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,7 +130,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())
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -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
|
||||
)
|
||||
|
||||
+3
-4
@@ -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 {
|
||||
|
||||
+16
-48
@@ -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
|
||||
|
||||
-14
@@ -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
|
||||
-47
@@ -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.
|
||||
}
|
||||
}
|
||||
+24
-38
@@ -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
|
||||
@@ -35,7 +34,6 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
override val containerRelativeRect: Rect = Rect()
|
||||
private var offsetX: Int = 0
|
||||
private var offsetY: Int = 0
|
||||
private var hadLayout: Boolean = false
|
||||
|
||||
internal val nativeId: String?
|
||||
get() = getTag(R.id.view_tag_native_id) as? String
|
||||
@@ -47,20 +45,14 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun doAttachedToWindow() {
|
||||
scrollView = getScrollView()
|
||||
// onAttachedToWindow is usually called before layout but there are cases where it's called
|
||||
// after. If called after, we need to report the updated layout to the VirtualViewContainer
|
||||
if (hadLayout) {
|
||||
updateParentOffset()
|
||||
reportChangeToContainer()
|
||||
}
|
||||
// Assuming that layout has been called before this
|
||||
scrollView = getScrollView()?.also { scrollView?.virtualViewContainerState?.add(this) }
|
||||
}
|
||||
|
||||
/** From [View#onLayout] */
|
||||
// This is when the view itself has layout changes
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
hadLayout = true
|
||||
if (changed) {
|
||||
containerRelativeRect.set(
|
||||
left + offsetX,
|
||||
@@ -68,7 +60,7 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
right + offsetX,
|
||||
bottom + offsetY,
|
||||
)
|
||||
reportChangeToContainer()
|
||||
updateContainer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +77,24 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
oldBottom: Int
|
||||
) {
|
||||
if (oldLeft != left || oldTop != top) {
|
||||
updateParentOffset()
|
||||
reportChangeToContainer()
|
||||
val virtualViewScrollView = scrollView ?: return
|
||||
offsetX = 0
|
||||
offsetY = 0
|
||||
var parent: ViewParent? = parent
|
||||
while (parent != null && parent != virtualViewScrollView) {
|
||||
if (parent is View) {
|
||||
offsetX += parent.left
|
||||
offsetY += parent.top
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
containerRelativeRect.set(
|
||||
left + offsetX,
|
||||
top + offsetY,
|
||||
right + offsetX,
|
||||
bottom + offsetY,
|
||||
)
|
||||
updateContainer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,8 +109,6 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
scrollView = null
|
||||
mode = null
|
||||
modeChangeEmitter = null
|
||||
hadLayout = false
|
||||
containerRelativeRect.setEmpty()
|
||||
}
|
||||
|
||||
override val virtualViewID: String
|
||||
@@ -150,28 +156,8 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateParentOffset() {
|
||||
val virtualViewScrollView = scrollView ?: return
|
||||
offsetX = 0
|
||||
offsetY = 0
|
||||
var parent: ViewParent? = parent
|
||||
while (parent != null && parent != virtualViewScrollView) {
|
||||
if (parent is View) {
|
||||
offsetX += parent.left
|
||||
offsetY += parent.top
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
containerRelativeRect.set(
|
||||
left + offsetX,
|
||||
top + offsetY,
|
||||
right + offsetX,
|
||||
bottom + offsetY,
|
||||
)
|
||||
}
|
||||
|
||||
private fun reportChangeToContainer() {
|
||||
scrollView?.virtualViewContainerState?.onChange(this)
|
||||
private fun updateContainer() {
|
||||
scrollView?.virtualViewContainerState?.update(this)
|
||||
}
|
||||
|
||||
private fun getScrollView(): VirtualViewContainer? = traverseParentStack(true)
|
||||
@@ -203,7 +189,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]")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-3
@@ -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>)
|
||||
|
||||
@@ -38,7 +38,7 @@ add_library(
|
||||
target_merge_so(reactnativejni_common)
|
||||
target_include_directories(reactnativejni_common PUBLIC ../../)
|
||||
|
||||
target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact rrc_view)
|
||||
target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact)
|
||||
target_compile_reactnative_options(reactnativejni_common PRIVATE)
|
||||
target_compile_options(reactnativejni_common PRIVATE -Wno-unused-lambda-capture)
|
||||
|
||||
|
||||
+1
-3
@@ -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 .)
|
||||
|
||||
+6
-3
@@ -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() }
|
||||
|
||||
+9
-12
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user