mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
38
Commits
@@ -14,23 +14,23 @@ runs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hermes-workspace
|
||||
path: 'D:\tmp\hermes'
|
||||
path: 'C:\tmp\hermes'
|
||||
- name: Set up workspace
|
||||
shell: powershell
|
||||
run: |
|
||||
mkdir -p D:\tmp\hermes\osx-bin
|
||||
mkdir -p C:\tmp\hermes\osx-bin
|
||||
mkdir -p .\packages\react-native\sdks\hermes
|
||||
cp -r -Force D:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
|
||||
cp -r -Force C:\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: |
|
||||
D:\tmp\hermes\win64-bin\
|
||||
D:\tmp\hermes\hermes\icu\
|
||||
D:\tmp\hermes\hermes\deps\
|
||||
D:\tmp\hermes\hermes\build_release\
|
||||
C:\tmp\hermes\win64-bin\
|
||||
C:\tmp\hermes\hermes\icu\
|
||||
C:\tmp\hermes\hermes\deps\
|
||||
C:\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: D:\tmp\hermes\win64-bin\
|
||||
path: C:\tmp\hermes\win64-bin\
|
||||
|
||||
@@ -128,9 +128,9 @@ jobs:
|
||||
runs-on: windows-2025
|
||||
needs: prepare_hermes_workspace
|
||||
env:
|
||||
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'
|
||||
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'
|
||||
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: 'D:\tmp\hermes'
|
||||
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
|
||||
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
|
||||
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'
|
||||
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: 'D:\tmp\hermes'
|
||||
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
|
||||
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
|
||||
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'
|
||||
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
|
||||
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
|
||||
CMAKE_DIR: 'C:\Program Files\CMake\bin'
|
||||
@@ -583,7 +583,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: ["24", "22"]
|
||||
node-version: ["24", "22", "20"]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"files": [
|
||||
"path-support.js",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
|
||||
@@ -43,6 +43,6 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
"BUILD_INFO"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0",
|
||||
"node": ">= 20.19.4",
|
||||
"electron": ">=36.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -10,9 +10,25 @@
|
||||
|
||||
// $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(2),
|
||||
argv: process.argv.slice(app.isPackaged ? 1 : 2),
|
||||
});
|
||||
|
||||
if (!gotTheLock) {
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"ws": "^6.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"selfsigned": "^2.4.1",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
|
||||
@@ -22,6 +22,6 @@
|
||||
"hermes-eslint": "0.29.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
"hermes-eslint": "0.29.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "./gradlew build",
|
||||
|
||||
+2
@@ -18,6 +18,7 @@ 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
|
||||
@@ -84,6 +85,7 @@ class ReactPlugin : Plugin<Project> {
|
||||
configureAutolinking(project, extension)
|
||||
configureCodegen(project, extension, rootExtension, isLibrary = false)
|
||||
configureResources(project, extension)
|
||||
configureBuildTypesForApp(project)
|
||||
}
|
||||
|
||||
// Library Only Configuration
|
||||
|
||||
+21
@@ -19,6 +19,7 @@ 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
|
||||
@@ -27,6 +28,26 @@ import org.w3c.dom.Element
|
||||
@Suppress("UnstableApiUsage")
|
||||
internal object AgpConfiguratorUtils {
|
||||
|
||||
fun configureBuildTypesForApp(project: Project) {
|
||||
val action =
|
||||
Action<AppliedPlugin> {
|
||||
project.extensions
|
||||
.getByType(ApplicationAndroidComponentsExtension::class.java)
|
||||
.finalizeDsl { ext ->
|
||||
ext.buildTypes {
|
||||
val debug =
|
||||
getByName("debug").apply {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
||||
}
|
||||
getByName("release").apply {
|
||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
project.pluginManager.withPlugin("com.android.application", action)
|
||||
}
|
||||
|
||||
fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
|
||||
val action =
|
||||
Action<AppliedPlugin> {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
|
||||
@@ -30,6 +30,6 @@
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('console.timeStamp()', () => {
|
||||
});
|
||||
|
||||
it("doesn't throw when label is not specified", () => {
|
||||
// $FlowExpectedError[incompatible-call] not passing label intentionally
|
||||
expect(() => console.timeStamp()).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -25,7 +26,6 @@ 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[extra-arg]
|
||||
// $FlowExpectedError[incompatible-call]
|
||||
console.timeStamp('label', true, null, {}, [], () => {}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
@@ -569,10 +569,7 @@ function consoleAssertPolyfill(expression, label) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {}
|
||||
function stub() {}
|
||||
|
||||
if (global.nativeLoggingHook) {
|
||||
const originalConsole = global.console;
|
||||
@@ -585,7 +582,11 @@ if (global.nativeLoggingHook) {
|
||||
}
|
||||
|
||||
global.console = {
|
||||
timeStamp: consoleTimeStampPolyfill,
|
||||
time: stub,
|
||||
timeEnd: stub,
|
||||
timeStamp: stub,
|
||||
count: stub,
|
||||
countReset: stub,
|
||||
...(originalConsole ?? {}),
|
||||
error: getNativeLogFunction(LOG_LEVELS.error),
|
||||
info: getNativeLogFunction(LOG_LEVELS.info),
|
||||
@@ -676,7 +677,6 @@ if (global.nativeLoggingHook) {
|
||||
});
|
||||
}
|
||||
} else if (!global.console) {
|
||||
function stub() {}
|
||||
const log = global.print || stub;
|
||||
|
||||
global.console = {
|
||||
@@ -692,6 +692,8 @@ if (global.nativeLoggingHook) {
|
||||
}
|
||||
},
|
||||
clear: stub,
|
||||
count: stub,
|
||||
countReset: stub,
|
||||
dir: stub,
|
||||
dirxml: stub,
|
||||
group: stub,
|
||||
@@ -700,6 +702,8 @@ if (global.nativeLoggingHook) {
|
||||
profile: stub,
|
||||
profileEnd: stub,
|
||||
table: stub,
|
||||
time: stub,
|
||||
timeEnd: stub,
|
||||
timeStamp: stub,
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"files": [
|
||||
"console.js",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
|
||||
+22
-14
@@ -38,19 +38,6 @@ 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.
|
||||
@@ -234,7 +221,28 @@ const getPreset = (src, options) => {
|
||||
plugins: [require('@babel/plugin-transform-flow-strip-types')],
|
||||
},
|
||||
{
|
||||
plugins: defaultPlugins,
|
||||
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')],
|
||||
],
|
||||
},
|
||||
{
|
||||
test: isTypeScriptSource,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn clean && node scripts/build.js --verbose",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
|
||||
@@ -31,12 +31,8 @@ function mockQueueMicrotask() {
|
||||
}
|
||||
|
||||
describe('Animated', () => {
|
||||
let ReactNativeFeatureFlags;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
|
||||
ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
|
||||
});
|
||||
|
||||
mockQueueMicrotask();
|
||||
@@ -113,50 +109,7 @@ 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');
|
||||
|
||||
@@ -175,10 +128,6 @@ describe('Animated', () => {
|
||||
});
|
||||
|
||||
it('restores default values only on update (in a microtask)', async () => {
|
||||
ReactNativeFeatureFlags.override({
|
||||
scheduleAnimatedCleanupInMicrotask: () => true,
|
||||
});
|
||||
|
||||
const __restoreDefaultValues = jest.spyOn(
|
||||
AnimatedProps.prototype,
|
||||
'__restoreDefaultValues',
|
||||
@@ -213,10 +162,6 @@ 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();
|
||||
|
||||
|
||||
+1
@@ -49,6 +49,7 @@ 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,12 +8,13 @@
|
||||
* @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 {
|
||||
|
||||
+118
-171
@@ -4,180 +4,32 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @flow strict
|
||||
* @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 {Task, SimpleTask, PromiseTask} from './TaskQueue';
|
||||
export type SimpleTask = {
|
||||
name: string,
|
||||
run: () => void,
|
||||
};
|
||||
export type PromiseTask = {
|
||||
name: string,
|
||||
gen: () => Promise<void>,
|
||||
};
|
||||
export type Task = SimpleTask | PromiseTask | (() => void);
|
||||
|
||||
export type Handle = number;
|
||||
|
||||
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();
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,11 +83,106 @@ function _processUpdate() {
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
const InteractionManager = (
|
||||
ReactNativeFeatureFlags.disableInteractionManager()
|
||||
? // $FlowFixMe[incompatible-variance]
|
||||
require('./InteractionManagerStub').default
|
||||
: InteractionManagerImpl
|
||||
) as typeof InteractionManagerImpl;
|
||||
const InteractionManagerStub = {
|
||||
Events: {
|
||||
interactionStart: 'interactionStart',
|
||||
interactionComplete: 'interactionComplete',
|
||||
},
|
||||
|
||||
export default InteractionManager;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -1,184 +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.
|
||||
*
|
||||
* @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;
|
||||
@@ -1,197 +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.
|
||||
*
|
||||
* @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
@@ -1,342 +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.
|
||||
*
|
||||
* @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);
|
||||
});
|
||||
});
|
||||
@@ -1,169 +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.
|
||||
*
|
||||
* @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,7 +400,6 @@ 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,
|
||||
@@ -455,7 +454,6 @@ 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,
|
||||
|
||||
@@ -67,7 +67,7 @@ template <typename T>
|
||||
facebook::react::LazyVector<T> RCTBridgingToVec(id value, T (^ctor)(id element))
|
||||
{
|
||||
NSArray *array = RCTBridgingToArray(value);
|
||||
using _size_t = typename facebook::react::LazyVector<T>::size_type;
|
||||
typedef typename facebook::react::LazyVector<T>::size_type _size_t;
|
||||
_size_t size = static_cast<_size_t>(array.count);
|
||||
return facebook::react::LazyVector<T>::fromUnsafeRawValue(array, size, ctor);
|
||||
}
|
||||
|
||||
@@ -10,21 +10,21 @@
|
||||
|
||||
import typeof {enable} from 'promise/setimmediate/rejection-tracking';
|
||||
|
||||
import LogBox from './LogBox/LogBox';
|
||||
import ExceptionsManager from './Core/ExceptionsManager';
|
||||
|
||||
let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
|
||||
allRejections: true,
|
||||
onUnhandled: (id, rejection = {}) => {
|
||||
onUnhandled: (id, rejection) => {
|
||||
let message: string;
|
||||
let stack: ?string;
|
||||
|
||||
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
|
||||
const stringValue = Object.prototype.toString.call(rejection);
|
||||
if (stringValue === '[object Error]') {
|
||||
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
|
||||
message = Error.prototype.toString.call(rejection);
|
||||
const error: Error = (rejection: $FlowFixMe);
|
||||
stack = error.stack;
|
||||
} else {
|
||||
try {
|
||||
message = require('pretty-format').format(rejection);
|
||||
@@ -34,37 +34,23 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
const warning = `Possible unhandled promise rejection (id: ${id}):\n${
|
||||
message ?? ''
|
||||
}`;
|
||||
if (__DEV__) {
|
||||
LogBox.addLog({
|
||||
level: 'warn',
|
||||
message: {
|
||||
content: warning,
|
||||
substitutions: [],
|
||||
ExceptionsManager.handleException(
|
||||
new Error(
|
||||
`Uncaught (in promise, id: ${id})${message ? `: "${message}"` : ''}`,
|
||||
{
|
||||
cause: rejection,
|
||||
},
|
||||
componentStack: [],
|
||||
componentStackType: null,
|
||||
stack,
|
||||
category: 'possible_unhandled_promise_rejection',
|
||||
});
|
||||
} else {
|
||||
console.warn(warning);
|
||||
}
|
||||
),
|
||||
false /* isFatal */,
|
||||
);
|
||||
},
|
||||
onHandled: id => {
|
||||
const warning =
|
||||
`Promise rejection handled (id: ${id})\n` +
|
||||
'This means you can ignore any previous messages of the form ' +
|
||||
`"Possible unhandled promise rejection (id: ${id}):"`;
|
||||
`"Uncaught (in promise, id: ${id})"`;
|
||||
console.warn(warning);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -710,13 +710,13 @@ NSData *__nullable RCTGzipData(NSData *__nullable input, float level)
|
||||
|
||||
void *libz = dlopen("/usr/lib/libz.dylib", RTLD_LAZY);
|
||||
|
||||
using DeflateInit2_ = int (*)(z_streamp, int, int, int, int, int, const char *, int);
|
||||
typedef int (*DeflateInit2_)(z_streamp, int, int, int, int, int, const char *, int);
|
||||
DeflateInit2_ deflateInit2_ = (DeflateInit2_)dlsym(libz, "deflateInit2_");
|
||||
|
||||
using Deflate = int (*)(z_streamp, int);
|
||||
typedef int (*Deflate)(z_streamp, int);
|
||||
Deflate deflate = (Deflate)dlsym(libz, "deflate");
|
||||
|
||||
using DeflateEnd = int (*)(z_streamp);
|
||||
typedef int (*DeflateEnd)(z_streamp);
|
||||
DeflateEnd deflateEnd = (DeflateEnd)dlsym(libz, "deflateEnd");
|
||||
|
||||
z_stream stream;
|
||||
|
||||
@@ -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 final fun getReactHost ()Lcom/facebook/react/ReactHost;
|
||||
protected final fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
|
||||
protected fun getReactHost ()Lcom/facebook/react/ReactHost;
|
||||
protected 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
|
||||
|
||||
+2
-2
@@ -113,7 +113,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
|
||||
* somewhere.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
protected val reactNativeHost: ReactNativeHost
|
||||
protected open 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 val reactHost: ReactHost?
|
||||
protected open val reactHost: ReactHost?
|
||||
get() = (application as ReactApplication).reactHost
|
||||
|
||||
protected val reactContext: ReactContext?
|
||||
|
||||
@@ -135,6 +135,10 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
|
||||
private void init() {
|
||||
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
|
||||
setClipChildren(false);
|
||||
|
||||
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
|
||||
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+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<<99019349a8f3642c42e6dac5f1476e7f>>
|
||||
* @generated SignedSource<<c5ed8a904dcda7ade87527233b879b92>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -101,7 +101,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun enableVirtualViewDebugFeatures(): Boolean = false
|
||||
|
||||
override fun enableVirtualViewRenderState(): Boolean = false
|
||||
override fun enableVirtualViewRenderState(): Boolean = true
|
||||
|
||||
override fun enableVirtualViewWindowFocusDetection(): Boolean = false
|
||||
|
||||
|
||||
-4
@@ -23,10 +23,6 @@ import com.facebook.react.common.build.ReactBuildConfig
|
||||
@SuppressLint("UseReactNativeNewArchitectureFeatureFlagDetector")
|
||||
public object ReactNativeNewArchitectureFeatureFlags {
|
||||
|
||||
@JvmStatic
|
||||
public fun isNewArchitectureStrictModeEnabled(): Boolean =
|
||||
ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE
|
||||
|
||||
@JvmStatic
|
||||
public fun enableBridgelessArchitecture(): Boolean {
|
||||
if (ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE) {
|
||||
|
||||
+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.ERROR)
|
||||
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.WARNING)
|
||||
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.ERROR)
|
||||
"OnLayoutEvent", LegacyArchitectureLogLevel.WARNING)
|
||||
}
|
||||
|
||||
private val EVENTS_POOL: SynchronizedPool<OnLayoutEvent> = SynchronizedPool<OnLayoutEvent>(20)
|
||||
|
||||
+6
@@ -377,6 +377,9 @@ public class ReactScrollView extends ScrollView
|
||||
}
|
||||
|
||||
ReactScrollViewHelper.emitLayoutEvent(this);
|
||||
if (mVirtualViewContainerState != null) {
|
||||
mVirtualViewContainerState.updateState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -486,6 +489,9 @@ public class ReactScrollView extends ScrollView
|
||||
this,
|
||||
mOnScrollDispatchHelper.getXFlingVelocity(),
|
||||
mOnScrollDispatchHelper.getYFlingVelocity());
|
||||
if (mVirtualViewContainerState != null) {
|
||||
mVirtualViewContainerState.updateState();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT);
|
||||
|
||||
+27
-42
@@ -11,6 +11,7 @@ import android.graphics.Rect
|
||||
import android.view.ViewGroup
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.common.build.ReactBuildConfig
|
||||
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
|
||||
import com.facebook.react.views.virtual.VirtualViewMode
|
||||
import java.util.*
|
||||
|
||||
@@ -45,22 +46,19 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
|
||||
ReactScrollViewHelper.ScrollListener {
|
||||
internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
|
||||
|
||||
private val prerenderRatio: Int = 1
|
||||
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
|
||||
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
|
||||
private val emptyRect: Rect = Rect()
|
||||
private val visibleRect: Rect = Rect()
|
||||
private val prerenderRect: Rect = Rect()
|
||||
|
||||
init {
|
||||
ReactScrollViewHelper.addScrollListener(this)
|
||||
}
|
||||
|
||||
public fun add(virtualView: VirtualView) {
|
||||
assert(virtualViews.add(virtualView)) {
|
||||
"Attempting to add duplicate VirtualView: ${virtualView.virtualViewID}"
|
||||
public fun onChange(virtualView: VirtualView) {
|
||||
if (virtualViews.add(virtualView)) {
|
||||
debugLog("add", { "virtualViewID=${virtualView.virtualViewID}" })
|
||||
} else {
|
||||
debugLog("update", { "virtualViewID=${virtualView.virtualViewID}" })
|
||||
}
|
||||
updateModes(virtualView)
|
||||
}
|
||||
@@ -69,35 +67,13 @@ internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
|
||||
assert(virtualViews.remove(virtualView)) {
|
||||
"Attempting to remove non-existent VirtualView: ${virtualView.virtualViewID}"
|
||||
}
|
||||
debugLog("remove", { "virtualViewID=${virtualView.virtualViewID}" })
|
||||
}
|
||||
|
||||
// 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)
|
||||
// Called on ScrollView onLayout or onScroll
|
||||
public fun updateState() {
|
||||
debugLog("VirtualViewContainer.updateState")
|
||||
updateModes()
|
||||
}
|
||||
|
||||
private fun updateModes(virtualView: VirtualView? = null) {
|
||||
@@ -110,17 +86,26 @@ internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
|
||||
val virtualViewsIt = if (virtualView != null) listOf(virtualView) else virtualViews
|
||||
virtualViewsIt.forEach { vv ->
|
||||
val rect = vv.containerRelativeRect
|
||||
|
||||
var mode = VirtualViewMode.Hidden
|
||||
var thresholdRect = emptyRect
|
||||
when {
|
||||
rect.isEmpty -> {}
|
||||
rectsOverlap(rect, visibleRect) -> {
|
||||
vv.onModeChange(VirtualViewMode.Visible, visibleRect)
|
||||
mode = VirtualViewMode.Visible
|
||||
thresholdRect = visibleRect
|
||||
}
|
||||
rectsOverlap(rect, prerenderRect) -> {
|
||||
vv.onModeChange(VirtualViewMode.Prerender, prerenderRect)
|
||||
}
|
||||
else -> {
|
||||
vv.onModeChange(VirtualViewMode.Hidden, emptyRect)
|
||||
mode = VirtualViewMode.Prerender
|
||||
thresholdRect = prerenderRect
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
debugLog(
|
||||
"updateModes",
|
||||
{ "virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect" })
|
||||
vv.onModeChange(mode, thresholdRect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+36
-23
@@ -34,6 +34,7 @@ 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
|
||||
@@ -45,14 +46,20 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun doAttachedToWindow() {
|
||||
// Assuming that layout has been called before this
|
||||
scrollView = getScrollView()?.also { scrollView?.virtualViewContainerState?.add(this) }
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
/** 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,
|
||||
@@ -60,7 +67,7 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
right + offsetX,
|
||||
bottom + offsetY,
|
||||
)
|
||||
updateContainer()
|
||||
reportChangeToContainer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,24 +84,8 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
oldBottom: Int
|
||||
) {
|
||||
if (oldLeft != left || oldTop != top) {
|
||||
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()
|
||||
updateParentOffset()
|
||||
reportChangeToContainer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +100,8 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
scrollView = null
|
||||
mode = null
|
||||
modeChangeEmitter = null
|
||||
hadLayout = false
|
||||
containerRelativeRect.setEmpty()
|
||||
}
|
||||
|
||||
override val virtualViewID: String
|
||||
@@ -156,8 +149,28 @@ public class ReactVirtualViewExperimental(context: Context) :
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateContainer() {
|
||||
scrollView?.virtualViewContainerState?.update(this)
|
||||
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 getScrollView(): VirtualViewContainer? = traverseParentStack(true)
|
||||
|
||||
@@ -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)
|
||||
target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact rrc_view)
|
||||
target_compile_reactnative_options(reactnativejni_common PRIVATE)
|
||||
target_compile_options(reactnativejni_common PRIVATE -Wno-unused-lambda-capture)
|
||||
|
||||
|
||||
+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<<d7534fe6e3b398e56c179da8233e3b0a>>
|
||||
* @generated SignedSource<<614fe0894f6235d879a0b29e7dab7759>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -184,7 +184,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
|
||||
}
|
||||
|
||||
bool enableVirtualViewRenderState() override {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool enableVirtualViewWindowFocusDetection() override {
|
||||
|
||||
-15
@@ -49,21 +49,6 @@ NativeIntersectionObserver::NativeIntersectionObserver(
|
||||
std::shared_ptr<CallInvoker> jsInvoker)
|
||||
: NativeIntersectionObserverCxxSpec(std::move(jsInvoker)) {}
|
||||
|
||||
void NativeIntersectionObserver::observe(
|
||||
jsi::Runtime& runtime,
|
||||
NativeIntersectionObserverObserveOptions options) {
|
||||
observeV2(runtime, std::move(options));
|
||||
}
|
||||
|
||||
void NativeIntersectionObserver::unobserve(
|
||||
jsi::Runtime& runtime,
|
||||
IntersectionObserverObserverId intersectionObserverId,
|
||||
std::shared_ptr<const ShadowNode> targetShadowNode) {
|
||||
auto token =
|
||||
tokenFromShadowNodeFamily(runtime, targetShadowNode->getFamilyShared());
|
||||
unobserveV2(runtime, intersectionObserverId, std::move(token));
|
||||
}
|
||||
|
||||
jsi::Object NativeIntersectionObserver::observeV2(
|
||||
jsi::Runtime& runtime,
|
||||
NativeIntersectionObserverObserveOptions options) {
|
||||
|
||||
-13
@@ -64,19 +64,6 @@ class NativeIntersectionObserver
|
||||
public:
|
||||
NativeIntersectionObserver(std::shared_ptr<CallInvoker> jsInvoker);
|
||||
|
||||
// TODO(T223605846): Remove legacy observe method
|
||||
[[deprecated("Please use observeV2")]]
|
||||
void observe(
|
||||
jsi::Runtime& runtime,
|
||||
NativeIntersectionObserverObserveOptions options);
|
||||
|
||||
// TODO(T223605846): Remove legacy unobserve method
|
||||
[[deprecated("Please use unobserveV2")]]
|
||||
void unobserve(
|
||||
jsi::Runtime& runtime,
|
||||
IntersectionObserverObserverId intersectionObserverId,
|
||||
std::shared_ptr<const ShadowNode> targetShadowNode);
|
||||
|
||||
jsi::Object observeV2(
|
||||
jsi::Runtime& runtime,
|
||||
NativeIntersectionObserverObserveOptions options);
|
||||
|
||||
@@ -251,11 +251,12 @@ CommitStatus ShadowTree::commit(
|
||||
if (status != CommitStatus::Failed) {
|
||||
return status;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock(commitMutex_);
|
||||
return tryCommit(transaction, commitOptions);
|
||||
return tryCommit(transaction, commitOptions, true);
|
||||
}
|
||||
} else {
|
||||
while (true) {
|
||||
@@ -275,7 +276,8 @@ CommitStatus ShadowTree::commit(
|
||||
|
||||
CommitStatus ShadowTree::tryCommit(
|
||||
const ShadowTreeCommitTransaction& transaction,
|
||||
const CommitOptions& commitOptions) const {
|
||||
const CommitOptions& commitOptions,
|
||||
bool hasLocked) const {
|
||||
TraceSection s("ShadowTree::commit");
|
||||
|
||||
auto telemetry = TransactionTelemetry{};
|
||||
@@ -287,7 +289,10 @@ CommitStatus ShadowTree::tryCommit(
|
||||
|
||||
{
|
||||
// Reading `currentRevision_` in shared manner.
|
||||
std::shared_lock lock(commitMutex_);
|
||||
std::shared_lock lock(commitMutex_, std::defer_lock);
|
||||
if (!hasLocked) {
|
||||
lock.lock();
|
||||
}
|
||||
commitMode = commitMode_;
|
||||
oldRevision = currentRevision_;
|
||||
}
|
||||
@@ -328,7 +333,10 @@ CommitStatus ShadowTree::tryCommit(
|
||||
|
||||
{
|
||||
// Updating `currentRevision_` in unique manner if it hasn't changed.
|
||||
std::unique_lock lock(commitMutex_);
|
||||
std::unique_lock lock(commitMutex_, std::defer_lock);
|
||||
if (!hasLocked) {
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
if (currentRevision_.number != oldRevision.number) {
|
||||
return CommitStatus::Failed;
|
||||
|
||||
@@ -111,7 +111,8 @@ class ShadowTree final {
|
||||
*/
|
||||
CommitStatus tryCommit(
|
||||
const ShadowTreeCommitTransaction& transaction,
|
||||
const CommitOptions& commitOptions) const;
|
||||
const CommitOptions& commitOptions,
|
||||
bool hasLocked = false) const;
|
||||
|
||||
/*
|
||||
* Calls `tryCommit` in a loop until it finishes successfully.
|
||||
|
||||
+5
-6
@@ -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<<83d07f37fd6028027511209f02e7f056>>
|
||||
* @generated SignedSource<<a942544e271327a89ec40682d3ec736d>>
|
||||
*
|
||||
* This file was generated by scripts/js-api/build-types/index.js.
|
||||
*/
|
||||
@@ -257,18 +257,17 @@ declare const Image: ImageType
|
||||
declare const ImageViewNativeComponent_default: HostComponent<ImageHostComponentProps>
|
||||
declare const InputAccessoryView: typeof InputAccessoryView_default
|
||||
declare const InputAccessoryView_default: React.ComponentType<InputAccessoryViewProps>
|
||||
declare const InteractionManager: typeof InteractionManager_default
|
||||
declare const InteractionManager_default: typeof InteractionManagerImpl
|
||||
declare const InteractionManagerImpl: {
|
||||
declare const InteractionManager: typeof InteractionManagerStub_default
|
||||
declare const InteractionManagerStub_default: {
|
||||
Events: {
|
||||
interactionComplete: "interactionComplete"
|
||||
interactionStart: "interactionStart"
|
||||
}
|
||||
addListener: (
|
||||
addListener(
|
||||
eventType: string,
|
||||
listener: (...args: any) => unknown,
|
||||
context: unknown,
|
||||
) => EventSubscription
|
||||
): EventSubscription
|
||||
clearInteractionHandle(handle: Handle): void
|
||||
createInteractionHandle(): Handle
|
||||
runAfterInteractions(task: null | Task | undefined): {
|
||||
|
||||
@@ -12,6 +12,71 @@
|
||||
|
||||
declare var window: typeof globalThis;
|
||||
|
||||
type DevToolsColor =
|
||||
| 'primary'
|
||||
| 'primary-light'
|
||||
| 'primary-dark'
|
||||
| 'secondary'
|
||||
| 'secondary-light'
|
||||
| 'secondary-dark'
|
||||
| 'tertiary'
|
||||
| 'tertiary-light'
|
||||
| 'tertiary-dark'
|
||||
| 'warning'
|
||||
| 'error';
|
||||
|
||||
// $FlowExpectedError[libdef-override] Flow core definitions are incomplete.
|
||||
declare var console: {
|
||||
// Logging
|
||||
log(...data: $ReadOnlyArray<mixed>): void,
|
||||
trace(...data: $ReadOnlyArray<mixed>): void,
|
||||
debug(...data: $ReadOnlyArray<mixed>): void,
|
||||
info(...data: $ReadOnlyArray<mixed>): void,
|
||||
warn(...data: $ReadOnlyArray<mixed>): void,
|
||||
error(...data: $ReadOnlyArray<mixed>): void,
|
||||
|
||||
// Grouping
|
||||
group(...data: $ReadOnlyArray<mixed>): void,
|
||||
groupCollapsed(...data: $ReadOnlyArray<mixed>): void,
|
||||
groupEnd(): void,
|
||||
|
||||
// Printing
|
||||
table(
|
||||
tabularData:
|
||||
| $ReadOnly<{[key: string]: mixed, ...}>
|
||||
| $ReadOnlyArray<$ReadOnly<{[key: string]: mixed, ...}>>
|
||||
| $ReadOnlyArray<$ReadOnlyArray<mixed>>,
|
||||
): void,
|
||||
dir(...data: $ReadOnlyArray<mixed>): void,
|
||||
dirxml(...data: $ReadOnlyArray<mixed>): void,
|
||||
clear(): void,
|
||||
|
||||
// Utilities
|
||||
assert(condition: mixed, ...data: $ReadOnlyArray<mixed>): void,
|
||||
|
||||
// Profiling
|
||||
profile(name?: string): void,
|
||||
profileEnd(name?: string): void,
|
||||
|
||||
// Counts
|
||||
count(label?: string): void,
|
||||
countReset(label?: string): void,
|
||||
|
||||
// Timing / tracing
|
||||
time(label?: string): void,
|
||||
timeEnd(label?: string): void,
|
||||
timeStamp(
|
||||
label?: string,
|
||||
start?: string | number,
|
||||
end?: string | number,
|
||||
trackName?: string,
|
||||
trackGroup?: string,
|
||||
color?: DevToolsColor,
|
||||
): void,
|
||||
|
||||
...
|
||||
};
|
||||
|
||||
declare class Navigator {
|
||||
product: 'ReactNative';
|
||||
appName?: ?string;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
],
|
||||
"bugs": "https://github.com/facebook/react-native/issues",
|
||||
"engines": {
|
||||
"node": ">= 22.14.0"
|
||||
"node": ">= 20.19.4"
|
||||
},
|
||||
"bin": {
|
||||
"react-native": "cli.js"
|
||||
@@ -106,6 +106,7 @@
|
||||
"ReactCommon",
|
||||
"README.md",
|
||||
"rn-get-polyfills.js",
|
||||
"scripts/replace-rncore-version.js",
|
||||
"scripts/bundle.js",
|
||||
"scripts/cocoapods",
|
||||
"scripts/codegen",
|
||||
|
||||
@@ -462,22 +462,24 @@ const definitions: FeatureFlagDefinitions = {
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enableVirtualViewRenderState: {
|
||||
defaultValue: false,
|
||||
defaultValue: true,
|
||||
metadata: {
|
||||
dateAdded: '2025-06-25',
|
||||
description:
|
||||
'Enables reading render state when dispatching VirtualView events.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'operational',
|
||||
purpose: 'experimentation',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enableVirtualViewWindowFocusDetection: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
dateAdded: '2025-06-24',
|
||||
description:
|
||||
'Enables window focus detection for prioritizing VirtualView events.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'operational',
|
||||
purpose: 'experimentation',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
@@ -708,17 +710,6 @@ const definitions: FeatureFlagDefinitions = {
|
||||
|
||||
jsOnly: {
|
||||
...testDefinitions.jsOnly,
|
||||
alwaysFlattenAnimatedStyles: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
dateAdded: '2025-06-02',
|
||||
description:
|
||||
'Changes `Animated` to always flatten style, fixing a bug with shadowed `AnimatedNode` instances.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'experimentation',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
animatedShouldDebounceQueueFlush: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
@@ -741,16 +732,6 @@ const definitions: FeatureFlagDefinitions = {
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
avoidStateUpdateInAnimatedPropsMemo: {
|
||||
defaultValue: true,
|
||||
metadata: {
|
||||
description:
|
||||
'Changes `useAnimatedPropsMemo` to avoid state updates to invalidate the cached `AnimatedProps`.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'release',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
deferFlatListFocusChangeRenderUpdate: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
@@ -762,16 +743,6 @@ const definitions: FeatureFlagDefinitions = {
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
disableInteractionManager: {
|
||||
defaultValue: true,
|
||||
metadata: {
|
||||
description:
|
||||
'Disables InteractionManager and replaces its scheduler with `setImmediate`.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'release',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enableAccessToHostTreeInFabric: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
@@ -814,16 +785,6 @@ const definitions: FeatureFlagDefinitions = {
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
scheduleAnimatedCleanupInMicrotask: {
|
||||
defaultValue: true,
|
||||
metadata: {
|
||||
description:
|
||||
'Changes the cleanup of `AnimatedProps` to occur in a microtask instead of synchronously during effect cleanup (for unmount) or subsequent mounts (for updates).',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'release',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
shouldUseAnimatedObjectForTransform: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
@@ -856,16 +817,6 @@ const definitions: FeatureFlagDefinitions = {
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
utilizeTokensInIntersectionObserver: {
|
||||
defaultValue: true,
|
||||
metadata: {
|
||||
dateAdded: '2025-05-06',
|
||||
description: 'Use tokens in IntersectionObserver vs ShadowNode.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'experimentation',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -89,13 +89,6 @@ export default function createAnimatedPropsHook(
|
||||
};
|
||||
});
|
||||
|
||||
// NOTE: This feature flag must be evaluated inside the hook because this
|
||||
// module factory can be evaluated much sooner, before overrides are set.
|
||||
const useAnimatedPropsLifecycle =
|
||||
ReactNativeFeatureFlags.scheduleAnimatedCleanupInMicrotask()
|
||||
? useAnimatedPropsLifecycleWithCleanupInMicrotask
|
||||
: useAnimatedPropsLifecycleWithPrevNodeRef;
|
||||
|
||||
useAnimatedPropsLifecycle(node);
|
||||
|
||||
// TODO: This "effect" does three things:
|
||||
@@ -262,44 +255,6 @@ function addAnimatedValuesListenersToProps(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the lifecycle of the supplied `AnimatedProps` by invoking `__attach`
|
||||
* and `__detach`. However, this is more complicated because `AnimatedProps`
|
||||
* uses reference counting to determine when to recursively detach its children
|
||||
* nodes. So in order to optimize this, we avoid detaching until the next attach
|
||||
* unless we are unmounting.
|
||||
*/
|
||||
function useAnimatedPropsLifecycleWithPrevNodeRef(node: AnimatedProps): void {
|
||||
const prevNodeRef = useRef<?AnimatedProps>(null);
|
||||
const isUnmountingRef = useRef<boolean>(false);
|
||||
|
||||
useInsertionEffect(() => {
|
||||
isUnmountingRef.current = false;
|
||||
return () => {
|
||||
isUnmountingRef.current = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useInsertionEffect(() => {
|
||||
node.__attach();
|
||||
if (prevNodeRef.current != null) {
|
||||
const prevNode = prevNodeRef.current;
|
||||
// TODO: Stop restoring default values (unless `reset` is called).
|
||||
prevNode.__restoreDefaultValues();
|
||||
prevNode.__detach();
|
||||
prevNodeRef.current = null;
|
||||
}
|
||||
return () => {
|
||||
if (isUnmountingRef.current) {
|
||||
// NOTE: Do not restore default values on unmount, see D18197735.
|
||||
node.__detach();
|
||||
} else {
|
||||
prevNodeRef.current = node;
|
||||
}
|
||||
};
|
||||
}, [node]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages the lifecycle of the supplied `AnimatedProps` by invoking `__attach`
|
||||
* and `__detach`. However, `__detach` occurs in a microtask for these reasons:
|
||||
@@ -314,9 +269,7 @@ function useAnimatedPropsLifecycleWithPrevNodeRef(node: AnimatedProps): void {
|
||||
* callbacks may update state, which is unsupported and will force synchronous
|
||||
* updates.
|
||||
*/
|
||||
function useAnimatedPropsLifecycleWithCleanupInMicrotask(
|
||||
node: AnimatedProps,
|
||||
): void {
|
||||
function useAnimatedPropsLifecycle(node: AnimatedProps): void {
|
||||
const isMounted = useRef<boolean>(false);
|
||||
|
||||
useInsertionEffect(() => {
|
||||
|
||||
@@ -16,9 +16,8 @@ import {AnimatedEvent} from '../../../Libraries/Animated/AnimatedEvent';
|
||||
import AnimatedNode from '../../../Libraries/Animated/nodes/AnimatedNode';
|
||||
import {isPlainObject} from '../../../Libraries/Animated/nodes/AnimatedObject';
|
||||
import flattenStyle from '../../../Libraries/StyleSheet/flattenStyle';
|
||||
import * as ReactNativeFeatureFlags from '../featureflags/ReactNativeFeatureFlags';
|
||||
import nullthrows from 'nullthrows';
|
||||
import {useInsertionEffect, useMemo, useRef, useState} from 'react';
|
||||
import {useInsertionEffect, useMemo, useRef} from 'react';
|
||||
|
||||
type CompositeKey = {
|
||||
style?: {[string]: CompositeKeyComponent},
|
||||
@@ -64,19 +63,6 @@ export function createAnimatedPropsMemoHook(
|
||||
return function useAnimatedPropsMemo(
|
||||
create: () => AnimatedProps,
|
||||
props: $ReadOnly<{[string]: mixed}>,
|
||||
): AnimatedProps {
|
||||
// NOTE: This feature flag must be evaluated inside the hook because this
|
||||
// module factory can be evaluated much sooner, before overrides are set.
|
||||
const useAnimatedPropsImpl =
|
||||
ReactNativeFeatureFlags.avoidStateUpdateInAnimatedPropsMemo()
|
||||
? useAnimatedPropsMemo_ref
|
||||
: useAnimatedPropsMemo_state;
|
||||
return useAnimatedPropsImpl(create, props);
|
||||
};
|
||||
|
||||
function useAnimatedPropsMemo_ref(
|
||||
create: () => AnimatedProps,
|
||||
props: $ReadOnly<{[string]: mixed}>,
|
||||
): AnimatedProps {
|
||||
const compositeKey = useMemo(
|
||||
() => createCompositeKeyForProps(props, allowlist),
|
||||
@@ -102,39 +88,7 @@ export function createAnimatedPropsMemoHook(
|
||||
}, [next]);
|
||||
|
||||
return next.node;
|
||||
}
|
||||
|
||||
function useAnimatedPropsMemo_state(
|
||||
create: () => AnimatedProps,
|
||||
props: $ReadOnly<{[string]: mixed}>,
|
||||
): AnimatedProps {
|
||||
const compositeKey = useMemo(
|
||||
() => createCompositeKeyForProps(props, allowlist),
|
||||
[props],
|
||||
);
|
||||
|
||||
const [state, setState] = useState<{
|
||||
allowlist: ?AnimatedPropsAllowlist,
|
||||
compositeKey: $ReadOnlyCompositeKey | null,
|
||||
value: AnimatedProps,
|
||||
}>(() => ({
|
||||
allowlist,
|
||||
compositeKey,
|
||||
value: create(),
|
||||
}));
|
||||
|
||||
if (
|
||||
state.allowlist !== allowlist ||
|
||||
!areCompositeKeysEqual(state.compositeKey, compositeKey)
|
||||
) {
|
||||
setState({
|
||||
allowlist,
|
||||
compositeKey,
|
||||
value: create(),
|
||||
});
|
||||
}
|
||||
return state.value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ import type ReadOnlyElement from '../../webapis/dom/nodes/ReadOnlyElement';
|
||||
import type {NativeModeChangeEvent} from './VirtualViewNativeComponent';
|
||||
|
||||
import StyleSheet from '../../../../Libraries/StyleSheet/StyleSheet';
|
||||
import VirtualViewExperimentalNativeComponent from './VirtualViewExperimentalNativeComponent';
|
||||
import VirtualViewNativeComponent from './VirtualViewNativeComponent';
|
||||
import nullthrows from 'nullthrows';
|
||||
import * as React from 'react';
|
||||
@@ -59,9 +60,16 @@ const NotHidden = null;
|
||||
|
||||
type State = HiddenHeight | typeof NotHidden;
|
||||
|
||||
function createVirtualView(initialState: State): VirtualViewComponent {
|
||||
function createVirtualView(
|
||||
initialState: State,
|
||||
experimental: boolean,
|
||||
): VirtualViewComponent {
|
||||
const initialHidden = initialState !== NotHidden;
|
||||
|
||||
const NativeComponent = experimental
|
||||
? VirtualViewExperimentalNativeComponent
|
||||
: VirtualViewNativeComponent;
|
||||
|
||||
component VirtualView(
|
||||
children?: React.Node,
|
||||
nativeID?: string,
|
||||
@@ -112,7 +120,7 @@ function createVirtualView(initialState: State): VirtualViewComponent {
|
||||
};
|
||||
|
||||
return (
|
||||
<VirtualViewNativeComponent
|
||||
<NativeComponent
|
||||
initialHidden={initialHidden}
|
||||
nativeID={nativeID}
|
||||
ref={ref}
|
||||
@@ -130,16 +138,24 @@ function createVirtualView(initialState: State): VirtualViewComponent {
|
||||
}
|
||||
onModeChange={handleModeChange}>
|
||||
{isHidden ? null : children}
|
||||
</VirtualViewNativeComponent>
|
||||
</NativeComponent>
|
||||
);
|
||||
}
|
||||
return VirtualView;
|
||||
}
|
||||
|
||||
export default createVirtualView(NotHidden) as VirtualViewComponent;
|
||||
export default createVirtualView(NotHidden, false) as VirtualViewComponent;
|
||||
|
||||
export function createHiddenVirtualView(height: number): VirtualViewComponent {
|
||||
return createVirtualView(height as HiddenHeight);
|
||||
export const VirtualViewExperimental = createVirtualView(
|
||||
NotHidden,
|
||||
true,
|
||||
) as VirtualViewComponent;
|
||||
|
||||
export function createHiddenVirtualView(
|
||||
height: number,
|
||||
experimental: boolean,
|
||||
): VirtualViewComponent {
|
||||
return createVirtualView(height as HiddenHeight, experimental);
|
||||
}
|
||||
|
||||
export const _logs: {states?: Array<State>} = {};
|
||||
|
||||
@@ -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<<d30fc4e36ad600353c9b9098fd71430a>>
|
||||
* @generated SignedSource<<a5b97c3df370225b7601f7375bb03ce0>>
|
||||
* @flow strict
|
||||
* @noformat
|
||||
*/
|
||||
@@ -29,21 +29,16 @@ import {
|
||||
|
||||
export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
|
||||
jsOnlyTestFlag: Getter<boolean>,
|
||||
alwaysFlattenAnimatedStyles: Getter<boolean>,
|
||||
animatedShouldDebounceQueueFlush: Getter<boolean>,
|
||||
animatedShouldUseSingleOp: Getter<boolean>,
|
||||
avoidStateUpdateInAnimatedPropsMemo: Getter<boolean>,
|
||||
deferFlatListFocusChangeRenderUpdate: Getter<boolean>,
|
||||
disableInteractionManager: Getter<boolean>,
|
||||
enableAccessToHostTreeInFabric: Getter<boolean>,
|
||||
fixVirtualizeListCollapseWindowSize: Getter<boolean>,
|
||||
isLayoutAnimationEnabled: Getter<boolean>,
|
||||
reduceDefaultPropsInView: Getter<boolean>,
|
||||
scheduleAnimatedCleanupInMicrotask: Getter<boolean>,
|
||||
shouldUseAnimatedObjectForTransform: Getter<boolean>,
|
||||
shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean>,
|
||||
shouldUseSetNativePropsInFabric: Getter<boolean>,
|
||||
utilizeTokensInIntersectionObserver: Getter<boolean>,
|
||||
}>;
|
||||
|
||||
export type ReactNativeFeatureFlagsJsOnlyOverrides = OverridesFor<ReactNativeFeatureFlagsJsOnly>;
|
||||
@@ -120,11 +115,6 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
|
||||
*/
|
||||
export const jsOnlyTestFlag: Getter<boolean> = createJavaScriptFlagGetter('jsOnlyTestFlag', false);
|
||||
|
||||
/**
|
||||
* Changes `Animated` to always flatten style, fixing a bug with shadowed `AnimatedNode` instances.
|
||||
*/
|
||||
export const alwaysFlattenAnimatedStyles: Getter<boolean> = createJavaScriptFlagGetter('alwaysFlattenAnimatedStyles', false);
|
||||
|
||||
/**
|
||||
* Enables an experimental flush-queue debouncing in Animated.js.
|
||||
*/
|
||||
@@ -135,21 +125,11 @@ export const animatedShouldDebounceQueueFlush: Getter<boolean> = createJavaScrip
|
||||
*/
|
||||
export const animatedShouldUseSingleOp: Getter<boolean> = createJavaScriptFlagGetter('animatedShouldUseSingleOp', false);
|
||||
|
||||
/**
|
||||
* Changes `useAnimatedPropsMemo` to avoid state updates to invalidate the cached `AnimatedProps`.
|
||||
*/
|
||||
export const avoidStateUpdateInAnimatedPropsMemo: Getter<boolean> = createJavaScriptFlagGetter('avoidStateUpdateInAnimatedPropsMemo', true);
|
||||
|
||||
/**
|
||||
* Use the deferred cell render update mechanism for focus change in FlatList.
|
||||
*/
|
||||
export const deferFlatListFocusChangeRenderUpdate: Getter<boolean> = createJavaScriptFlagGetter('deferFlatListFocusChangeRenderUpdate', false);
|
||||
|
||||
/**
|
||||
* Disables InteractionManager and replaces its scheduler with `setImmediate`.
|
||||
*/
|
||||
export const disableInteractionManager: Getter<boolean> = createJavaScriptFlagGetter('disableInteractionManager', true);
|
||||
|
||||
/**
|
||||
* Enables access to the host tree in Fabric using DOM-compatible APIs.
|
||||
*/
|
||||
@@ -170,11 +150,6 @@ export const isLayoutAnimationEnabled: Getter<boolean> = createJavaScriptFlagGet
|
||||
*/
|
||||
export const reduceDefaultPropsInView: Getter<boolean> = createJavaScriptFlagGetter('reduceDefaultPropsInView', true);
|
||||
|
||||
/**
|
||||
* Changes the cleanup of `AnimatedProps` to occur in a microtask instead of synchronously during effect cleanup (for unmount) or subsequent mounts (for updates).
|
||||
*/
|
||||
export const scheduleAnimatedCleanupInMicrotask: Getter<boolean> = createJavaScriptFlagGetter('scheduleAnimatedCleanupInMicrotask', true);
|
||||
|
||||
/**
|
||||
* Enables use of AnimatedObject for animating transform values.
|
||||
*/
|
||||
@@ -190,11 +165,6 @@ export const shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean> = cre
|
||||
*/
|
||||
export const shouldUseSetNativePropsInFabric: Getter<boolean> = createJavaScriptFlagGetter('shouldUseSetNativePropsInFabric', true);
|
||||
|
||||
/**
|
||||
* Use tokens in IntersectionObserver vs ShadowNode.
|
||||
*/
|
||||
export const utilizeTokensInIntersectionObserver: Getter<boolean> = createJavaScriptFlagGetter('utilizeTokensInIntersectionObserver', true);
|
||||
|
||||
/**
|
||||
* Common flag for testing. Do NOT modify.
|
||||
*/
|
||||
@@ -358,7 +328,7 @@ export const enableVirtualViewDebugFeatures: Getter<boolean> = createNativeFlagG
|
||||
/**
|
||||
* Enables reading render state when dispatching VirtualView events.
|
||||
*/
|
||||
export const enableVirtualViewRenderState: Getter<boolean> = createNativeFlagGetter('enableVirtualViewRenderState', false);
|
||||
export const enableVirtualViewRenderState: Getter<boolean> = createNativeFlagGetter('enableVirtualViewRenderState', true);
|
||||
/**
|
||||
* Enables window focus detection for prioritizing VirtualView events.
|
||||
*/
|
||||
|
||||
+30
-34
@@ -4,7 +4,6 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @fantom_flags utilizeTokensInIntersectionObserver:*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
@@ -20,7 +19,6 @@ import * as Fantom from '@react-native/fantom';
|
||||
import * as React from 'react';
|
||||
import {createRef, useState} from 'react';
|
||||
import {ScrollView, View} from 'react-native';
|
||||
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
|
||||
import setUpIntersectionObserver from 'react-native/src/private/setup/setUpIntersectionObserver';
|
||||
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
|
||||
import DOMRectReadOnly from 'react-native/src/private/webapis/geometry/DOMRectReadOnly';
|
||||
@@ -1494,44 +1492,42 @@ describe('IntersectionObserver', () => {
|
||||
});
|
||||
});
|
||||
|
||||
if (ReactNativeFeatureFlags.utilizeTokensInIntersectionObserver()) {
|
||||
it('should not retain initial children of observed targets', () => {
|
||||
const root = Fantom.createRoot();
|
||||
observer = new IntersectionObserver(() => {});
|
||||
it('should not retain initial children of observed targets', () => {
|
||||
const root = Fantom.createRoot();
|
||||
observer = new IntersectionObserver(() => {});
|
||||
|
||||
const [getReferenceCount, ref] = createShadowNodeReferenceCountingRef();
|
||||
const [getReferenceCount, ref] = createShadowNodeReferenceCountingRef();
|
||||
|
||||
const observeRef: React.RefSetter<
|
||||
React.ElementRef<typeof View>,
|
||||
> = instance => {
|
||||
const element = ensureReactNativeElement(instance);
|
||||
observer.observe(element);
|
||||
return () => {
|
||||
observer.unobserve(element);
|
||||
};
|
||||
const observeRef: React.RefSetter<
|
||||
React.ElementRef<typeof View>,
|
||||
> = instance => {
|
||||
const element = ensureReactNativeElement(instance);
|
||||
observer.observe(element);
|
||||
return () => {
|
||||
observer.unobserve(element);
|
||||
};
|
||||
};
|
||||
|
||||
function Observe({children}: $ReadOnly<{children?: React.Node}>) {
|
||||
return <View ref={observeRef}>{children}</View>;
|
||||
}
|
||||
function Observe({children}: $ReadOnly<{children?: React.Node}>) {
|
||||
return <View ref={observeRef}>{children}</View>;
|
||||
}
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Observe>
|
||||
<View ref={ref} />
|
||||
</Observe>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(getReferenceCount()).toBeGreaterThan(0);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Observe />);
|
||||
});
|
||||
|
||||
expect(getReferenceCount()).toBe(0);
|
||||
Fantom.runTask(() => {
|
||||
root.render(
|
||||
<Observe>
|
||||
<View ref={ref} />
|
||||
</Observe>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
expect(getReferenceCount()).toBeGreaterThan(0);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<Observe />);
|
||||
});
|
||||
|
||||
expect(getReferenceCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('should NOT report multiple entries when observing a target that exists and we modify it later in the same tick', () => {
|
||||
const root = Fantom.createRoot({
|
||||
|
||||
+19
-74
@@ -27,13 +27,13 @@ import type {NativeIntersectionObserverToken} from '../specs/NativeIntersectionO
|
||||
|
||||
import * as Systrace from '../../../../../Libraries/Performance/Systrace';
|
||||
import warnOnce from '../../../../../Libraries/Utilities/warnOnce';
|
||||
import * as ReactNativeFeatureFlags from '../../../featureflags/ReactNativeFeatureFlags';
|
||||
import {
|
||||
getInstanceHandle,
|
||||
getNativeNodeReference,
|
||||
} from '../../dom/nodes/internals/NodeInternals';
|
||||
import {createIntersectionObserverEntry} from '../IntersectionObserverEntry';
|
||||
import NativeIntersectionObserver from '../specs/NativeIntersectionObserver';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
export type IntersectionObserverId = number;
|
||||
|
||||
@@ -69,36 +69,11 @@ function setTargetForInstanceHandle(
|
||||
instanceHandleToTargetMap.set(key, target);
|
||||
}
|
||||
|
||||
// The mapping between ReactNativeElement and their corresponding shadow node
|
||||
// also needs to be kept here because React removes the link when unmounting.
|
||||
const targetToShadowNodeMap: WeakMap<
|
||||
ReactNativeElement,
|
||||
ReturnType<typeof getNativeNodeReference>,
|
||||
> = new WeakMap();
|
||||
|
||||
const targetToTokenMap: WeakMap<
|
||||
ReactNativeElement,
|
||||
NativeIntersectionObserverToken,
|
||||
> = new WeakMap();
|
||||
|
||||
let modernNativeIntersectionObserver =
|
||||
NativeIntersectionObserver == null
|
||||
? null
|
||||
: NativeIntersectionObserver.observeV2 == null ||
|
||||
NativeIntersectionObserver.unobserveV2 == null
|
||||
? null
|
||||
: {
|
||||
observe: NativeIntersectionObserver.observeV2,
|
||||
unobserve: NativeIntersectionObserver.unobserveV2,
|
||||
};
|
||||
|
||||
if (
|
||||
modernNativeIntersectionObserver &&
|
||||
!ReactNativeFeatureFlags.utilizeTokensInIntersectionObserver()
|
||||
) {
|
||||
modernNativeIntersectionObserver = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given intersection observer and returns a unique ID for it,
|
||||
* which is required to start observing targets.
|
||||
@@ -189,34 +164,19 @@ export function observe({
|
||||
// access it even after the instance handle has been unmounted.
|
||||
setTargetForInstanceHandle(instanceHandle, target);
|
||||
|
||||
if (modernNativeIntersectionObserver == null) {
|
||||
// Same for the mapping between the target and its shadow node.
|
||||
targetToShadowNodeMap.set(target, targetNativeNodeReference);
|
||||
}
|
||||
|
||||
if (!isConnected) {
|
||||
NativeIntersectionObserver.connect(notifyIntersectionObservers);
|
||||
isConnected = true;
|
||||
}
|
||||
|
||||
if (modernNativeIntersectionObserver == null) {
|
||||
NativeIntersectionObserver.observe({
|
||||
intersectionObserverId,
|
||||
rootShadowNode: rootNativeNodeReference,
|
||||
targetShadowNode: targetNativeNodeReference,
|
||||
thresholds: registeredObserver.observer.thresholds,
|
||||
rootThresholds: registeredObserver.observer.rnRootThresholds,
|
||||
});
|
||||
} else {
|
||||
const token = modernNativeIntersectionObserver.observe({
|
||||
intersectionObserverId,
|
||||
rootShadowNode: rootNativeNodeReference,
|
||||
targetShadowNode: targetNativeNodeReference,
|
||||
thresholds: registeredObserver.observer.thresholds,
|
||||
rootThresholds: registeredObserver.observer.rnRootThresholds,
|
||||
});
|
||||
targetToTokenMap.set(target, token);
|
||||
}
|
||||
const token = nullthrows(NativeIntersectionObserver.observeV2)({
|
||||
intersectionObserverId,
|
||||
rootShadowNode: rootNativeNodeReference,
|
||||
targetShadowNode: targetNativeNodeReference,
|
||||
thresholds: registeredObserver.observer.thresholds,
|
||||
rootThresholds: registeredObserver.observer.rnRootThresholds,
|
||||
});
|
||||
targetToTokenMap.set(target, token);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -240,33 +200,18 @@ export function unobserve(
|
||||
return;
|
||||
}
|
||||
|
||||
if (modernNativeIntersectionObserver == null) {
|
||||
const targetNativeNodeReference = targetToShadowNodeMap.get(target);
|
||||
if (targetNativeNodeReference == null) {
|
||||
console.error(
|
||||
'IntersectionObserverManager: could not find registration data for target',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
NativeIntersectionObserver.unobserve(
|
||||
intersectionObserverId,
|
||||
targetNativeNodeReference,
|
||||
);
|
||||
} else {
|
||||
const targetToken = targetToTokenMap.get(target);
|
||||
if (targetToken == null) {
|
||||
console.error(
|
||||
'IntersectionObserverManager: could not find registration data for target',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
modernNativeIntersectionObserver.unobserve(
|
||||
intersectionObserverId,
|
||||
targetToken,
|
||||
const targetToken = targetToTokenMap.get(target);
|
||||
if (targetToken == null) {
|
||||
console.error(
|
||||
'IntersectionObserverManager: could not find registration data for target',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
nullthrows(NativeIntersectionObserver.unobserveV2)(
|
||||
intersectionObserverId,
|
||||
targetToken,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
-4
@@ -34,10 +34,6 @@ export type NativeIntersectionObserverObserveOptions = {
|
||||
export opaque type NativeIntersectionObserverToken = mixed;
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
// TODO(T223605846): Remove legacy observe method
|
||||
+observe: (options: NativeIntersectionObserverObserveOptions) => void;
|
||||
// TODO(T223605846): Remove legacy unobserve method
|
||||
+unobserve: (intersectionObserverId: number, targetShadowNode: mixed) => void;
|
||||
+observeV2?: (
|
||||
options: NativeIntersectionObserverObserveOptions,
|
||||
) => NativeIntersectionObserverToken;
|
||||
|
||||
@@ -15,9 +15,11 @@ import type {
|
||||
PerformanceEntryJSON,
|
||||
} from './PerformanceEntry';
|
||||
|
||||
import {warnNoNativePerformance} from './internals/Utilities';
|
||||
import {PerformanceEntry} from './PerformanceEntry';
|
||||
import NativePerformance from './specs/NativePerformance';
|
||||
import MaybeNativePerformance from './specs/NativePerformance';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
const NativePerformance = nullthrows(MaybeNativePerformance);
|
||||
|
||||
export type PerformanceEventTimingJSON = {
|
||||
...PerformanceEntryJSON,
|
||||
@@ -86,14 +88,8 @@ function getCachedEventCounts(): Map<string, number> {
|
||||
return cachedEventCounts;
|
||||
}
|
||||
|
||||
if (!NativePerformance || !NativePerformance?.getEventCounts) {
|
||||
warnNoNativePerformance();
|
||||
cachedEventCounts = new Map();
|
||||
return cachedEventCounts;
|
||||
}
|
||||
|
||||
const eventCounts = new Map<string, number>(
|
||||
NativePerformance.getEventCounts?.() ?? [],
|
||||
NativePerformance.getEventCounts() ?? [],
|
||||
);
|
||||
cachedEventCounts = eventCounts;
|
||||
|
||||
|
||||
@@ -35,8 +35,9 @@ import {
|
||||
} from './internals/Utilities';
|
||||
import MemoryInfo from './MemoryInfo';
|
||||
import ReactNativeStartupTiming from './ReactNativeStartupTiming';
|
||||
import NativePerformance from './specs/NativePerformance';
|
||||
import MaybeNativePerformance from './specs/NativePerformance';
|
||||
import {PerformanceMark, PerformanceMeasure} from './UserTiming';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
export type PerformanceMeasureOptions =
|
||||
| $ReadOnly<{
|
||||
@@ -58,11 +59,13 @@ export type PerformanceMeasureOptions =
|
||||
const ENTRY_TYPES_AVAILABLE_FROM_TIMELINE: $ReadOnlyArray<PerformanceEntryType> =
|
||||
['mark', 'measure'];
|
||||
|
||||
const cachedReportMark = NativePerformance?.reportMark;
|
||||
const cachedReportMeasure = NativePerformance?.reportMeasure;
|
||||
const cachedGetMarkTime = NativePerformance?.getMarkTime;
|
||||
const cachedNativeClearMarks = NativePerformance?.clearMarks;
|
||||
const cachedNativeClearMeasures = NativePerformance?.clearMeasures;
|
||||
const NativePerformance = nullthrows(MaybeNativePerformance);
|
||||
|
||||
const cachedReportMark = NativePerformance.reportMark;
|
||||
const cachedReportMeasure = NativePerformance.reportMeasure;
|
||||
const cachedGetMarkTime = NativePerformance.getMarkTime;
|
||||
const cachedNativeClearMarks = NativePerformance.clearMarks;
|
||||
const cachedNativeClearMeasures = NativePerformance.clearMeasures;
|
||||
|
||||
const MARK_OPTIONS_REUSABLE_OBJECT: {...PerformanceMarkOptions} = {
|
||||
startTime: 0,
|
||||
@@ -98,53 +101,46 @@ export default class Performance {
|
||||
|
||||
// Get the current JS memory information.
|
||||
get memory(): MemoryInfo {
|
||||
if (NativePerformance?.getSimpleMemoryInfo) {
|
||||
// JSI API implementations may have different variants of names for the JS
|
||||
// heap information we need here. We will parse the result based on our
|
||||
// guess of the implementation for now.
|
||||
const memoryInfo = NativePerformance.getSimpleMemoryInfo();
|
||||
if (memoryInfo.hasOwnProperty('hermes_heapSize')) {
|
||||
// We got memory information from Hermes
|
||||
const {
|
||||
hermes_heapSize: totalJSHeapSize,
|
||||
hermes_allocatedBytes: usedJSHeapSize,
|
||||
} = memoryInfo;
|
||||
// JSI API implementations may have different variants of names for the JS
|
||||
// heap information we need here. We will parse the result based on our
|
||||
// guess of the implementation for now.
|
||||
const memoryInfo = NativePerformance.getSimpleMemoryInfo();
|
||||
if (memoryInfo.hasOwnProperty('hermes_heapSize')) {
|
||||
// We got memory information from Hermes
|
||||
const {
|
||||
hermes_heapSize: totalJSHeapSize,
|
||||
hermes_allocatedBytes: usedJSHeapSize,
|
||||
} = memoryInfo;
|
||||
|
||||
return new MemoryInfo({
|
||||
jsHeapSizeLimit: null, // We don't know the heap size limit from Hermes.
|
||||
totalJSHeapSize,
|
||||
usedJSHeapSize,
|
||||
});
|
||||
} else {
|
||||
// JSC and V8 has no native implementations for memory information in JSI::Instrumentation
|
||||
return new MemoryInfo();
|
||||
}
|
||||
return new MemoryInfo({
|
||||
jsHeapSizeLimit: null, // We don't know the heap size limit from Hermes.
|
||||
totalJSHeapSize,
|
||||
usedJSHeapSize,
|
||||
});
|
||||
} else {
|
||||
// JSC and V8 has no native implementations for memory information in JSI::Instrumentation
|
||||
return new MemoryInfo();
|
||||
}
|
||||
|
||||
return new MemoryInfo();
|
||||
}
|
||||
|
||||
// Startup metrics is not used in web, but only in React Native.
|
||||
get rnStartupTiming(): ReactNativeStartupTiming {
|
||||
if (NativePerformance?.getReactNativeStartupTiming) {
|
||||
const {
|
||||
startTime,
|
||||
endTime,
|
||||
initializeRuntimeStart,
|
||||
initializeRuntimeEnd,
|
||||
executeJavaScriptBundleEntryPointStart,
|
||||
executeJavaScriptBundleEntryPointEnd,
|
||||
} = NativePerformance.getReactNativeStartupTiming();
|
||||
return new ReactNativeStartupTiming({
|
||||
startTime,
|
||||
endTime,
|
||||
initializeRuntimeStart,
|
||||
initializeRuntimeEnd,
|
||||
executeJavaScriptBundleEntryPointStart,
|
||||
executeJavaScriptBundleEntryPointEnd,
|
||||
});
|
||||
}
|
||||
return new ReactNativeStartupTiming();
|
||||
const {
|
||||
startTime,
|
||||
endTime,
|
||||
initializeRuntimeStart,
|
||||
initializeRuntimeEnd,
|
||||
executeJavaScriptBundleEntryPointStart,
|
||||
executeJavaScriptBundleEntryPointEnd,
|
||||
} = NativePerformance.getReactNativeStartupTiming();
|
||||
return new ReactNativeStartupTiming({
|
||||
startTime,
|
||||
endTime,
|
||||
initializeRuntimeStart,
|
||||
initializeRuntimeEnd,
|
||||
executeJavaScriptBundleEntryPointStart,
|
||||
executeJavaScriptBundleEntryPointEnd,
|
||||
});
|
||||
}
|
||||
|
||||
mark(
|
||||
@@ -217,11 +213,6 @@ export default class Performance {
|
||||
}
|
||||
|
||||
clearMarks(markName?: string): void {
|
||||
if (!cachedNativeClearMarks) {
|
||||
warnNoNativePerformance();
|
||||
return;
|
||||
}
|
||||
|
||||
cachedNativeClearMarks(markName);
|
||||
}
|
||||
|
||||
@@ -414,11 +405,6 @@ export default class Performance {
|
||||
}
|
||||
|
||||
clearMeasures(measureName?: string): void {
|
||||
if (!cachedNativeClearMeasures) {
|
||||
warnNoNativePerformance();
|
||||
return;
|
||||
}
|
||||
|
||||
cachedNativeClearMeasures(measureName);
|
||||
}
|
||||
|
||||
@@ -434,10 +420,6 @@ export default class Performance {
|
||||
* https://www.w3.org/TR/performance-timeline/#extensions-to-the-performance-interface
|
||||
*/
|
||||
getEntries(): PerformanceEntryList {
|
||||
if (!NativePerformance?.getEntries) {
|
||||
warnNoNativePerformance();
|
||||
return [];
|
||||
}
|
||||
return NativePerformance.getEntries().map(rawToPerformanceEntry);
|
||||
}
|
||||
|
||||
@@ -450,11 +432,6 @@ export default class Performance {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!NativePerformance?.getEntriesByType) {
|
||||
warnNoNativePerformance();
|
||||
return [];
|
||||
}
|
||||
|
||||
return NativePerformance.getEntriesByType(
|
||||
performanceEntryTypeToRaw(entryType),
|
||||
).map(rawToPerformanceEntry);
|
||||
@@ -472,11 +449,6 @@ export default class Performance {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!NativePerformance?.getEntriesByName) {
|
||||
warnNoNativePerformance();
|
||||
return [];
|
||||
}
|
||||
|
||||
return NativePerformance.getEntriesByName(
|
||||
entryName,
|
||||
entryType != null ? performanceEntryTypeToRaw(entryType) : undefined,
|
||||
|
||||
+8
-29
@@ -21,12 +21,13 @@ import {
|
||||
rawToPerformanceEntry,
|
||||
rawToPerformanceEntryType,
|
||||
} from './internals/RawPerformanceEntry';
|
||||
import {warnNoNativePerformance} from './internals/Utilities';
|
||||
import NativePerformance from './specs/NativePerformance';
|
||||
import MaybeNativePerformance from './specs/NativePerformance';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
export {PerformanceEntry} from './PerformanceEntry';
|
||||
|
||||
const NativePerformance = nullthrows(MaybeNativePerformance);
|
||||
|
||||
export class PerformanceObserverEntryList {
|
||||
#entries: PerformanceEntryList;
|
||||
|
||||
@@ -75,13 +76,6 @@ export interface PerformanceObserverInit {
|
||||
}
|
||||
|
||||
function getSupportedPerformanceEntryTypes(): $ReadOnlyArray<PerformanceEntryType> {
|
||||
if (!NativePerformance) {
|
||||
return Object.freeze([]);
|
||||
}
|
||||
if (!NativePerformance.getSupportedPerformanceEntryTypes) {
|
||||
// fallback if getSupportedPerformanceEntryTypes is not defined on native side
|
||||
return Object.freeze(['mark', 'measure', 'event']);
|
||||
}
|
||||
return Object.freeze(
|
||||
NativePerformance.getSupportedPerformanceEntryTypes().map(
|
||||
rawToPerformanceEntryType,
|
||||
@@ -120,11 +114,6 @@ export class PerformanceObserver {
|
||||
}
|
||||
|
||||
observe(options: PerformanceObserverInit): void {
|
||||
if (!NativePerformance || NativePerformance.observe == null) {
|
||||
warnNoNativePerformance();
|
||||
return;
|
||||
}
|
||||
|
||||
this.#validateObserveOptions(options);
|
||||
|
||||
if (this.#nativeObserverHandle == null) {
|
||||
@@ -135,12 +124,12 @@ export class PerformanceObserver {
|
||||
|
||||
if (options.entryTypes) {
|
||||
this.#type = 'multiple';
|
||||
NativePerformance.observe?.(observerHandle, {
|
||||
NativePerformance.observe(observerHandle, {
|
||||
entryTypes: options.entryTypes.map(performanceEntryTypeToRaw),
|
||||
});
|
||||
} else if (options.type) {
|
||||
this.#type = 'single';
|
||||
NativePerformance.observe?.(observerHandle, {
|
||||
NativePerformance.observe(observerHandle, {
|
||||
type: performanceEntryTypeToRaw(options.type),
|
||||
buffered: options.buffered,
|
||||
durationThreshold: options.durationThreshold,
|
||||
@@ -149,12 +138,7 @@ export class PerformanceObserver {
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
if (!NativePerformance) {
|
||||
warnNoNativePerformance();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#nativeObserverHandle == null || !NativePerformance.disconnect) {
|
||||
if (this.#nativeObserverHandle == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -162,16 +146,11 @@ export class PerformanceObserver {
|
||||
}
|
||||
|
||||
#createNativeObserver(): OpaqueNativeObserverHandle | null {
|
||||
if (!NativePerformance || !NativePerformance.createObserver) {
|
||||
warnNoNativePerformance();
|
||||
return null;
|
||||
}
|
||||
|
||||
this.#calledAtLeastOnce = false;
|
||||
|
||||
const observerHandle: OpaqueNativeObserverHandle =
|
||||
NativePerformance.createObserver(() => {
|
||||
const rawEntries = NativePerformance.takeRecords?.(
|
||||
const rawEntries = NativePerformance.takeRecords(
|
||||
observerHandle,
|
||||
true, // sort records
|
||||
);
|
||||
@@ -185,7 +164,7 @@ export class PerformanceObserver {
|
||||
let droppedEntriesCount = 0;
|
||||
if (!this.#calledAtLeastOnce) {
|
||||
droppedEntriesCount =
|
||||
NativePerformance.getDroppedEntriesCount?.(observerHandle) ?? 0;
|
||||
NativePerformance.getDroppedEntriesCount(observerHandle);
|
||||
this.#calledAtLeastOnce = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,19 @@
|
||||
// flowlint unsafe-getters-setters:off
|
||||
|
||||
import type {DOMHighResTimeStamp} from './PerformanceEntry';
|
||||
import type {
|
||||
ExtensionMarkerPayload,
|
||||
ExtensionTrackEntryPayload,
|
||||
} from './UserTimingExtensibility';
|
||||
|
||||
import {getCurrentTimeStamp} from './internals/Utilities';
|
||||
import {PerformanceEntry} from './PerformanceEntry';
|
||||
|
||||
export type DetailType = mixed;
|
||||
export type DetailType =
|
||||
| mixed
|
||||
// This will effectively ignored by Flow (mixed | anything = mixed)
|
||||
// but we'll use it as documentation for how to use the extensibility API.
|
||||
| {devtools?: ExtensionMarkerPayload | ExtensionTrackEntryPayload, ...};
|
||||
|
||||
export type PerformanceMarkOptions = $ReadOnly<{
|
||||
detail?: DetailType,
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
export type DevToolsColor =
|
||||
| 'primary'
|
||||
| 'primary-light'
|
||||
| 'primary-dark'
|
||||
| 'secondary'
|
||||
| 'secondary-light'
|
||||
| 'secondary-dark'
|
||||
| 'tertiary'
|
||||
| 'tertiary-light'
|
||||
| 'tertiary-dark'
|
||||
| 'warning'
|
||||
| 'error';
|
||||
|
||||
export interface ExtensionTrackEntryPayload {
|
||||
dataType?: 'track-entry'; // Defaults to "track-entry"
|
||||
color?: DevToolsColor; // Defaults to "primary"
|
||||
track: string; // Required: Name of the custom track
|
||||
trackGroup?: string; // Optional: Group for organizing tracks
|
||||
properties?: Array<[string, string]>; // Key-value pairs for detailed view
|
||||
tooltipText?: string; // Short description for tooltip
|
||||
}
|
||||
|
||||
export interface ExtensionMarkerPayload {
|
||||
dataType: 'marker'; // Required: Identifies as a marker
|
||||
color?: DevToolsColor; // Defaults to "primary"
|
||||
properties?: Array<[string, string]>; // Key-value pairs for detailed view
|
||||
tooltipText?: string; // Short description for tooltip
|
||||
}
|
||||
+4
-2
@@ -13,7 +13,7 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
|
||||
import type Performance from 'react-native/src/private/webapis/performance/Performance';
|
||||
import type {PerformanceObserverEntryList} from 'react-native/src/private/webapis/performance/PerformanceObserver';
|
||||
|
||||
import NativePerformance from '../specs/NativePerformance';
|
||||
import MaybeNativePerformance from '../specs/NativePerformance';
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import nullthrows from 'nullthrows';
|
||||
import {useState} from 'react';
|
||||
@@ -22,6 +22,8 @@ import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerfor
|
||||
import {PerformanceEventTiming} from 'react-native/src/private/webapis/performance/EventTiming';
|
||||
import {PerformanceObserver} from 'react-native/src/private/webapis/performance/PerformanceObserver';
|
||||
|
||||
const NativePerformance = nullthrows(MaybeNativePerformance);
|
||||
|
||||
setUpPerformanceObserver();
|
||||
|
||||
declare var performance: Performance;
|
||||
@@ -193,7 +195,7 @@ describe('Event Timing API', () => {
|
||||
});
|
||||
|
||||
it('reports number of dispatched events via performance.eventCounts', () => {
|
||||
NativePerformance?.clearEventCountsForTesting?.();
|
||||
NativePerformance.clearEventCountsForTesting?.();
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
Fantom.runTask(() => {
|
||||
|
||||
+13
-10
@@ -18,8 +18,11 @@ import type {
|
||||
|
||||
import ensureInstance from '../../../__tests__/utilities/ensureInstance';
|
||||
import DOMException from '../../errors/DOMException';
|
||||
import NativePerformance from '../specs/NativePerformance';
|
||||
import MaybeNativePerformance from '../specs/NativePerformance';
|
||||
import {PerformanceMark, PerformanceMeasure} from '../UserTiming';
|
||||
import nullthrows from 'nullthrows';
|
||||
|
||||
const NativePerformance = nullthrows(MaybeNativePerformance);
|
||||
|
||||
declare var performance: Performance;
|
||||
|
||||
@@ -44,7 +47,7 @@ describe('User Timing', () => {
|
||||
|
||||
describe('mark', () => {
|
||||
it('works with default timestamp', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
const mark = performance.mark('mark-now');
|
||||
|
||||
@@ -144,7 +147,7 @@ describe('User Timing', () => {
|
||||
describe('measure', () => {
|
||||
describe('with measureOptions', () => {
|
||||
it('uses 0 as default start and now as default end', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
const measure = performance.measure('measure-with-defaults', {});
|
||||
|
||||
@@ -157,7 +160,7 @@ describe('User Timing', () => {
|
||||
});
|
||||
|
||||
it('works with a start timestamp', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
const measure = performance.measure('measure-with-start-timestamp', {
|
||||
start: 10,
|
||||
@@ -172,7 +175,7 @@ describe('User Timing', () => {
|
||||
});
|
||||
|
||||
it('works with start mark', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
performance.mark('start-mark', {
|
||||
startTime: 10,
|
||||
@@ -191,7 +194,7 @@ describe('User Timing', () => {
|
||||
});
|
||||
|
||||
it('works with end mark', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
performance.mark('end-mark', {
|
||||
startTime: 50,
|
||||
@@ -210,7 +213,7 @@ describe('User Timing', () => {
|
||||
});
|
||||
|
||||
it('works with start mark and end mark', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
performance.mark('start-mark', {
|
||||
startTime: 10,
|
||||
@@ -375,7 +378,7 @@ describe('User Timing', () => {
|
||||
|
||||
describe('with startMark / endMark', () => {
|
||||
it('uses 0 as default start and now as default end', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
const measure = performance.measure('measure-with-defaults');
|
||||
|
||||
@@ -388,7 +391,7 @@ describe('User Timing', () => {
|
||||
});
|
||||
|
||||
it('works with startMark', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
performance.mark('start-mark', {
|
||||
startTime: 10,
|
||||
@@ -408,7 +411,7 @@ describe('User Timing', () => {
|
||||
});
|
||||
|
||||
it('works with startMark and endMark', () => {
|
||||
NativePerformance?.setCurrentTimeStampForTesting?.(25);
|
||||
NativePerformance.setCurrentTimeStampForTesting?.(25);
|
||||
|
||||
performance.mark('start-mark', {
|
||||
startTime: 10,
|
||||
|
||||
+13
-13
@@ -53,7 +53,7 @@ export type PerformanceObserverInit = {
|
||||
};
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
+now?: () => number;
|
||||
+now: () => number;
|
||||
+reportMark?: (name: string, startTime: number, entry: mixed) => void;
|
||||
+reportMeasure?: (
|
||||
name: string,
|
||||
@@ -62,36 +62,36 @@ export interface Spec extends TurboModule {
|
||||
entry: mixed,
|
||||
) => void;
|
||||
+getMarkTime?: (name: string) => ?number;
|
||||
+clearMarks?: (entryName?: string) => void;
|
||||
+clearMeasures?: (entryName?: string) => void;
|
||||
+getEntries?: () => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
+getEntriesByName?: (
|
||||
+clearMarks: (entryName?: string) => void;
|
||||
+clearMeasures: (entryName?: string) => void;
|
||||
+getEntries: () => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
+getEntriesByName: (
|
||||
entryName: string,
|
||||
entryType?: ?RawPerformanceEntryType,
|
||||
) => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
+getEntriesByType?: (
|
||||
+getEntriesByType: (
|
||||
entryType: RawPerformanceEntryType,
|
||||
) => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
+getEventCounts?: () => $ReadOnlyArray<[string, number]>;
|
||||
+getEventCounts: () => $ReadOnlyArray<[string, number]>;
|
||||
+getSimpleMemoryInfo: () => NativeMemoryInfo;
|
||||
+getReactNativeStartupTiming: () => ReactNativeStartupTiming;
|
||||
|
||||
+createObserver?: (
|
||||
+createObserver: (
|
||||
callback: NativeBatchedObserverCallback,
|
||||
) => OpaqueNativeObserverHandle;
|
||||
+getDroppedEntriesCount?: (observer: OpaqueNativeObserverHandle) => number;
|
||||
+getDroppedEntriesCount: (observer: OpaqueNativeObserverHandle) => number;
|
||||
|
||||
+observe?: (
|
||||
+observe: (
|
||||
observer: OpaqueNativeObserverHandle,
|
||||
options: PerformanceObserverInit,
|
||||
) => void;
|
||||
+disconnect?: (observer: OpaqueNativeObserverHandle) => void;
|
||||
+takeRecords?: (
|
||||
+disconnect: (observer: OpaqueNativeObserverHandle) => void;
|
||||
+takeRecords: (
|
||||
observer: OpaqueNativeObserverHandle,
|
||||
sort: boolean,
|
||||
) => $ReadOnlyArray<RawPerformanceEntry>;
|
||||
|
||||
+getSupportedPerformanceEntryTypes?: () => $ReadOnlyArray<RawPerformanceEntryType>;
|
||||
+getSupportedPerformanceEntryTypes: () => $ReadOnlyArray<RawPerformanceEntryType>;
|
||||
|
||||
+setCurrentTimeStampForTesting?: (timeStamp: number) => void;
|
||||
+clearEventCountsForTesting?: () => void;
|
||||
|
||||
@@ -67,6 +67,25 @@ function replaceRNDepsConfiguration(
|
||||
|
||||
console.log('Extracting the tarball', tarballURLPath);
|
||||
execSync(`tar -xf ${tarballURLPath} -C ${finalLocation}`);
|
||||
|
||||
// Now we need to remove the extra third-party folder as we do in the podspec's prepare-script
|
||||
// We need to take the ReactNativeDependencies.xcframework folder and move it up one level
|
||||
// from ${finalLocation}/packages/react-native/third-party/ to ${finalLocation}/packages/react-native/
|
||||
console.log('Resolving ReactNativeDependencies.xcframework folder structure');
|
||||
const thirdPartyPath = `${finalLocation}/packages/react-native/third-party`;
|
||||
const sourcePath = `${thirdPartyPath}/ReactNativeDependencies.xcframework`;
|
||||
const destinationPath = `${finalLocation}/packages/react-native/ReactNativeDependencies.xcframework`;
|
||||
if (fs.existsSync(sourcePath)) {
|
||||
fs.renameSync(sourcePath, destinationPath);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Expected ReactNativeDependencies.xcframework to be at ${sourcePath}, but it was not found.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (fs.existsSync(thirdPartyPath)) {
|
||||
fs.rmSync(thirdPartyPath, {force: true, recursive: true});
|
||||
}
|
||||
}
|
||||
|
||||
function updateLastBuildConfiguration(configuration /*: string */) {
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
|
||||
</manifest>
|
||||
@@ -50,6 +50,7 @@
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:usesCleartextTraffic="${usesCleartextTraffic}"
|
||||
android:name=".RNTesterApplication"
|
||||
android:allowBackup="true"
|
||||
android:banner="@drawable/tv_banner"
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import RNTesterBlock from '../../components/RNTesterBlock';
|
||||
import RNTesterPage from '../../components/RNTesterPage';
|
||||
import RNTesterText from '../../components/RNTesterText';
|
||||
@@ -282,4 +284,4 @@ exports.examples = [
|
||||
return <AccessibilityAndroidExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
import type {EventSubscription, GestureResponderEvent} from 'react-native';
|
||||
|
||||
import RNTesterBlock from '../../components/RNTesterBlock';
|
||||
@@ -2250,4 +2251,4 @@ exports.examples = [
|
||||
return <LabelCooptingExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
const {RNTesterThemeContext} = require('../../components/RNTesterTheme');
|
||||
const React = require('react');
|
||||
const {Alert, Text, View} = require('react-native');
|
||||
@@ -83,4 +85,4 @@ exports.examples = [
|
||||
return <AccessibilityIOSExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
import type {HostInstance} from 'react-native';
|
||||
|
||||
import {RNTesterThemeContext} from '../../components/RNTesterTheme';
|
||||
@@ -573,4 +574,4 @@ exports.examples = [
|
||||
return <ShareScreenshotAnchorExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import AnExSet from './AnExSet';
|
||||
import React from 'react';
|
||||
import {
|
||||
@@ -396,4 +398,4 @@ exports.examples = [
|
||||
return <AnExApp />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
import type {ColorSchemeName} from 'react-native';
|
||||
|
||||
import RNTesterText from '../../components/RNTesterText';
|
||||
@@ -245,4 +246,4 @@ exports.examples = [
|
||||
return <ToggleNativeAppearance />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import * as React from 'react';
|
||||
import {Image, StyleSheet, View} from 'react-native';
|
||||
|
||||
@@ -191,4 +193,4 @@ exports.examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
const {RNTesterThemeContext} = require('../../components/RNTesterTheme');
|
||||
const React = require('react');
|
||||
const {Alert, Button, StyleSheet, View} = require('react-native');
|
||||
@@ -221,7 +223,7 @@ exports.examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import RNTesterBlock from '../../components/RNTesterBlock';
|
||||
import RNTesterPage from '../../components/RNTesterPage';
|
||||
import RNTesterText from '../../components/RNTesterText';
|
||||
@@ -116,4 +118,4 @@ exports.examples = [
|
||||
return <ContentURLAndroidExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
import type {Node} from 'react';
|
||||
|
||||
import React from 'react';
|
||||
@@ -37,4 +38,4 @@ exports.examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import * as React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
|
||||
@@ -107,4 +109,4 @@ exports.examples = [
|
||||
description: 'Views with a cursor do not get flattened',
|
||||
render: CursorExampleViewFlattening,
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import * as React from 'react';
|
||||
import {Alert, Button, DevSettings} from 'react-native';
|
||||
|
||||
@@ -44,4 +46,4 @@ exports.examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import RNTesterText from '../../components/RNTesterText';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Dimensions, useWindowDimensions} from 'react-native';
|
||||
@@ -60,4 +62,4 @@ exports.examples = [
|
||||
return <DimensionsSubscription dim="screen" />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
import type {Node} from 'react';
|
||||
|
||||
import {RNTesterThemeContext} from '../../components/RNTesterTheme';
|
||||
@@ -104,4 +105,4 @@ exports.examples = [
|
||||
return <Drawer />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
import type {ViewProps} from 'react-native';
|
||||
|
||||
import React, {useState} from 'react';
|
||||
@@ -155,4 +156,4 @@ exports.examples = [
|
||||
return <AddChildrenForInteropLayer testID="add-children" />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import RNTesterText from '../../components/RNTesterText';
|
||||
import {useState} from 'react';
|
||||
import {Alert, Pressable, StyleSheet, TextInput, View} from 'react-native';
|
||||
@@ -184,5 +186,5 @@ export default {
|
||||
return <PressableExample />;
|
||||
},
|
||||
},
|
||||
],
|
||||
] as Array<RNTesterModuleExample>,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import {useTheme} from '../../components/RNTesterTheme';
|
||||
import {useState} from 'react';
|
||||
import {
|
||||
@@ -111,4 +113,4 @@ exports.examples = [
|
||||
return <InputAccessoryViewExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import RNTesterText from '../../components/RNTesterText';
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
@@ -19,7 +21,7 @@ export const category = 'Other';
|
||||
export const description =
|
||||
'Examples of passing invalid prop values and how they fall back to expected defaults.';
|
||||
|
||||
export const examples = [
|
||||
export const examples: Array<RNTesterModuleExample> = [
|
||||
{
|
||||
title: 'View flex',
|
||||
render(): React.Node {
|
||||
|
||||
+3
-1
@@ -8,6 +8,8 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import RNTesterText from '../../components/RNTesterText';
|
||||
import React from 'react';
|
||||
import {PanResponder, ScrollView, StyleSheet, View} from 'react-native';
|
||||
@@ -60,7 +62,7 @@ exports.examples = [
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import React, {useState} from 'react';
|
||||
import {
|
||||
Alert,
|
||||
@@ -276,4 +278,4 @@ exports.examples = [
|
||||
return <KeyboardAvoidingContentContainerStyle />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
|
||||
import RNTesterText from '../../components/RNTesterText';
|
||||
import React from 'react';
|
||||
import {
|
||||
@@ -389,4 +391,4 @@ exports.examples = [
|
||||
return <LayoutUpdateExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
import type {
|
||||
ViewLayout,
|
||||
ViewLayoutEvent,
|
||||
@@ -163,4 +164,4 @@ exports.examples = [
|
||||
return <LayoutEventExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
|
||||
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||
|
||||
import RNTesterBlock from '../../components/RNTesterBlock';
|
||||
@@ -210,4 +211,4 @@ exports.examples = [
|
||||
return <LayoutExample />;
|
||||
},
|
||||
},
|
||||
];
|
||||
] as Array<RNTesterModuleExample>;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user