Compare commits

..
Author SHA1 Message Date
CodemodService Bot ff75e731b3 Fix CQS signal modernize-use-using in xplat/js/react-native-github/packages [B] (#52656)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52656

Reviewed By: dtolnay

Differential Revision: D78460296
2025-07-17 08:23:30 -07:00
142 changed files with 1841 additions and 872 deletions
@@ -14,23 +14,23 @@ runs:
uses: actions/download-artifact@v4
with:
name: hermes-workspace
path: 'C:\tmp\hermes'
path: 'D:\tmp\hermes'
- name: Set up workspace
shell: powershell
run: |
mkdir -p C:\tmp\hermes\osx-bin
mkdir -p D:\tmp\hermes\osx-bin
mkdir -p .\packages\react-native\sdks\hermes
cp -r -Force C:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
cp -r -Force D:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
cp -r -Force .\packages\react-native\sdks\hermes-engine\utils\* .\packages\react-native\sdks\hermes\.
- name: Windows cache
uses: actions/cache@v4
with:
key: v3-hermes-${{ github.job }}-windows-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}
path: |
C:\tmp\hermes\win64-bin\
C:\tmp\hermes\hermes\icu\
C:\tmp\hermes\hermes\deps\
C:\tmp\hermes\hermes\build_release\
D:\tmp\hermes\win64-bin\
D:\tmp\hermes\hermes\icu\
D:\tmp\hermes\hermes\deps\
D:\tmp\hermes\hermes\build_release\
- name: setup-msbuild
uses: microsoft/setup-msbuild@v1.3.2
- name: Set up workspace
@@ -83,4 +83,4 @@ runs:
uses: actions/upload-artifact@v4.3.4
with:
name: hermes-win64-bin
path: C:\tmp\hermes\win64-bin\
path: D:\tmp\hermes\win64-bin\
+3 -3
View File
@@ -128,9 +128,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
+3 -3
View File
@@ -125,9 +125,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
+4 -4
View File
@@ -382,9 +382,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
@@ -583,7 +583,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: ["24", "22", "20"]
node-version: ["24", "22"]
steps:
- name: Checkout
uses: actions/checkout@v4
+1 -1
View File
@@ -17,7 +17,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"path-support.js",
+1 -1
View File
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"index.js"
+1 -1
View File
@@ -43,6 +43,6 @@
}
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"dist"
+1 -1
View File
@@ -20,6 +20,6 @@
"BUILD_INFO"
],
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -26,7 +26,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 20.19.4",
"node": ">= 22.14.0",
"electron": ">=36.3.0"
},
"dependencies": {
@@ -10,25 +10,9 @@
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
const {app} = require('electron') as any;
const util = require('util');
// Handle global command line arguments which don't require a window
// or the single instance lock to be held.
const {
values: {version = false},
} = util.parseArgs({
options: {version: {type: 'boolean'}},
args: process.argv.slice(app.isPackaged ? 1 : 2),
strict: false,
});
if (version) {
console.log(`${app.getName()} v${app.getVersion()}`);
// Not app.quit() - we want to exit immediately without initialising the graphical subsystem.
app.exit(0);
}
const gotTheLock = app.requestSingleInstanceLock({
argv: process.argv.slice(app.isPackaged ? 1 : 2),
argv: process.argv.slice(2),
});
if (!gotTheLock) {
+1 -1
View File
@@ -35,7 +35,7 @@
"ws": "^6.2.3"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"devDependencies": {
"selfsigned": "^2.4.1",
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "index.js",
"dependencies": {
@@ -22,6 +22,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -36,6 +36,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"scripts": {
"build": "./gradlew build",
@@ -18,7 +18,6 @@ import com.facebook.react.tasks.GenerateEntryPointTask
import com.facebook.react.tasks.GeneratePackageListTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildTypesForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevServerLocation
import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
@@ -85,7 +84,6 @@ class ReactPlugin : Plugin<Project> {
configureAutolinking(project, extension)
configureCodegen(project, extension, rootExtension, isLibrary = false)
configureResources(project, extension)
configureBuildTypesForApp(project)
}
// Library Only Configuration
@@ -19,7 +19,6 @@ import java.net.Inet4Address
import java.net.NetworkInterface
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.plus
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
@@ -28,26 +27,6 @@ import org.w3c.dom.Element
@Suppress("UnstableApiUsage")
internal object AgpConfiguratorUtils {
fun configureBuildTypesForApp(project: Project) {
val action =
Action<AppliedPlugin> {
project.extensions
.getByType(ApplicationAndroidComponentsExtension::class.java)
.finalizeDsl { ext ->
ext.buildTypes {
val debug =
getByName("debug").apply {
manifestPlaceholders["usesCleartextTraffic"] = "true"
}
getByName("release").apply {
manifestPlaceholders["usesCleartextTraffic"] = "false"
}
}
}
}
project.pluginManager.withPlugin("com.android.application", action)
}
fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
val action =
Action<AppliedPlugin> {
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"exports": {
".": "./src/index.js",
+1 -1
View File
@@ -30,6 +30,6 @@
}
},
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
}
}
@@ -16,7 +16,6 @@ describe('console.timeStamp()', () => {
});
it("doesn't throw when label is not specified", () => {
// $FlowExpectedError[incompatible-call] not passing label intentionally
expect(() => console.timeStamp()).not.toThrow();
});
@@ -26,6 +25,7 @@ describe('console.timeStamp()', () => {
it("doesn't throw when additional arguments are specified", () => {
expect(() =>
// $FlowExpectedError[extra-arg]
console.timeStamp('label', 100, 500, 'Track', 'Group', 'error'),
).not.toThrow();
});
@@ -34,7 +34,7 @@ describe('console.timeStamp()', () => {
// $FlowExpectedError[incompatible-call]
expect(() => console.timeStamp({})).not.toThrow();
expect(() =>
// $FlowExpectedError[incompatible-call]
// $FlowExpectedError[extra-arg]
console.timeStamp('label', true, null, {}, [], () => {}),
).not.toThrow();
});
+6 -10
View File
@@ -569,7 +569,10 @@ function consoleAssertPolyfill(expression, label) {
}
}
function stub() {}
// https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp_static.
// Non-standard API for recording markers on a timeline of the Performance instrumentation.
// The actual logging is not provided by definition.
function consoleTimeStampPolyfill() {}
if (global.nativeLoggingHook) {
const originalConsole = global.console;
@@ -582,11 +585,7 @@ if (global.nativeLoggingHook) {
}
global.console = {
time: stub,
timeEnd: stub,
timeStamp: stub,
count: stub,
countReset: stub,
timeStamp: consoleTimeStampPolyfill,
...(originalConsole ?? {}),
error: getNativeLogFunction(LOG_LEVELS.error),
info: getNativeLogFunction(LOG_LEVELS.info),
@@ -677,6 +676,7 @@ if (global.nativeLoggingHook) {
});
}
} else if (!global.console) {
function stub() {}
const log = global.print || stub;
global.console = {
@@ -692,8 +692,6 @@ if (global.nativeLoggingHook) {
}
},
clear: stub,
count: stub,
countReset: stub,
dir: stub,
dirxml: stub,
group: stub,
@@ -702,8 +700,6 @@ if (global.nativeLoggingHook) {
profile: stub,
profileEnd: stub,
table: stub,
time: stub,
timeEnd: stub,
timeStamp: stub,
};
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"files": [
"console.js",
@@ -13,7 +13,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "src/index.js",
"files": [
+14 -22
View File
@@ -38,6 +38,19 @@ function isFirstParty(fileName) {
// use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
const loose = true;
const defaultPlugins = [
[require('babel-plugin-syntax-hermes-parser'), {parseLangTypes: 'flow'}],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-transform-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[require('@babel/plugin-transform-private-property-in-object'), {loose}],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
];
// For Static Hermes testing (experimental), the hermes-canary transformProfile
// is used to enable regenerator (and some related lowering passes) because SH
// requires more Babel lowering than Hermes temporarily.
@@ -221,28 +234,7 @@ const getPreset = (src, options) => {
plugins: [require('@babel/plugin-transform-flow-strip-types')],
},
{
plugins: [
[
require('babel-plugin-syntax-hermes-parser'),
{
parseLangTypes: 'flow',
reactRuntimeTarget: '19',
...options.hermesParserOptions,
},
],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-transform-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[
require('@babel/plugin-transform-private-property-in-object'),
{loose},
],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
],
plugins: defaultPlugins,
},
{
test: isTypeScriptSource,
@@ -14,7 +14,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"main": "src/index.js",
"files": [
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"scripts": {
"build": "yarn clean && node scripts/build.js --verbose",
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"exports": {
".": "./src/index.js",
@@ -31,8 +31,12 @@ function mockQueueMicrotask() {
}
describe('Animated', () => {
let ReactNativeFeatureFlags;
beforeEach(() => {
jest.resetModules();
ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
});
mockQueueMicrotask();
@@ -109,7 +113,50 @@ describe('Animated', () => {
expect(callback.mock.calls.length).toBe(1);
});
it('does not detach on updates', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => false,
});
const opacity = new Animated.Value(0);
jest.spyOn(opacity, '__detach');
const root = await create(<Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
await update(root, <Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
await unmount(root);
expect(opacity.__detach).toBeCalled();
});
it('stops animation when detached', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => false,
});
const opacity = new Animated.Value(0);
const callback = jest.fn();
const root = await create(<Animated.View style={{opacity}} />);
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: false,
}).start(callback);
await unmount(root);
expect(callback).toBeCalledWith({finished: false});
});
it('detaches only on unmount (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const opacity = new Animated.Value(0);
jest.spyOn(opacity, '__detach');
@@ -128,6 +175,10 @@ describe('Animated', () => {
});
it('restores default values only on update (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const __restoreDefaultValues = jest.spyOn(
AnimatedProps.prototype,
'__restoreDefaultValues',
@@ -162,6 +213,10 @@ describe('Animated', () => {
});
it('stops animation when detached (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const opacity = new Animated.Value(0);
const callback = jest.fn();
@@ -49,7 +49,6 @@ function _defineCheckVersionTests() {
// $FlowFixMe[cannot-write]
console.error = jest.fn();
// $FlowFixMe[cannot-write]
// $FlowFixMe[unsafe-addition]
global.console = {error: jest.fn(error => (consoleOutput += error))};
spyOnConsoleError = jest.spyOn(global.console, 'error');
});
+1 -2
View File
@@ -8,13 +8,12 @@
* @format
*/
import Performance from '../../src/private/webapis/performance/Performance';
import NativePerformance from '../../src/private/webapis/performance/specs/NativePerformance';
// In case if the native implementation of the Performance API is available, use it,
// otherwise fall back to the legacy/default one, which only defines 'Performance.now()'
if (NativePerformance) {
const Performance =
require('../../src/private/webapis/performance/Performance').default;
// $FlowExpectedError[cannot-write]
global.performance = new Performance();
} else {
@@ -4,32 +4,180 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @flow strict-local
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import type {Task} from './TaskQueue';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import EventEmitter from '../vendor/emitter/EventEmitter';
const BatchedBridge = require('../BatchedBridge/BatchedBridge').default;
const TaskQueue = require('./TaskQueue').default;
const invariant = require('invariant');
export type SimpleTask = {
name: string,
run: () => void,
};
export type PromiseTask = {
name: string,
gen: () => Promise<void>,
};
export type Task = SimpleTask | PromiseTask | (() => void);
export type {Task, SimpleTask, PromiseTask} from './TaskQueue';
export type Handle = number;
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
const _emitter = new EventEmitter<{
interactionComplete: [],
interactionStart: [],
}>();
const DEBUG_DELAY: 0 = 0;
const DEBUG: false = false;
const InteractionManagerImpl = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
const tasks: Array<Task> = [];
const promise = new Promise((resolve: () => void) => {
_scheduleUpdate();
if (task) {
tasks.push(task);
}
tasks.push({
run: resolve,
name: 'resolve ' + ((task && task.name) || '?'),
});
_taskQueue.enqueueTasks(tasks);
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel: function () {
_taskQueue.cancelTasks(tasks);
},
};
},
/**
* Notify manager that an interaction has started.
*/
createInteractionHandle(): Handle {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('InteractionManager: create interaction handle');
_scheduleUpdate();
const handle = ++_inc;
_addInteractionSet.add(handle);
return handle;
},
/**
* Notify manager that an interaction has completed.
*/
clearInteractionHandle(handle: Handle) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('InteractionManager: clear interaction handle');
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
_scheduleUpdate();
_addInteractionSet.delete(handle);
_deleteInteractionSet.add(handle);
},
// $FlowFixMe[unclear-type] unclear type of _emitter
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
addListener: _emitter.addListener.bind(_emitter) as (
eventType: string,
// $FlowFixMe[unclear-type] unclear type of arguments
listener: (...args: any) => mixed,
context: mixed,
) => EventSubscription,
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*/
setDeadline(deadline: number) {
_deadline = deadline;
},
};
const _interactionSet = new Set<number | Handle>();
const _addInteractionSet = new Set<number | Handle>();
const _deleteInteractionSet = new Set<Handle>();
const _taskQueue = new TaskQueue({onMoreTasks: _scheduleUpdate});
let _nextUpdateHandle: $FlowFixMe | TimeoutID = 0;
let _inc = 0;
let _deadline = -1;
/**
* Schedule an asynchronous update to the interaction state.
*/
function _scheduleUpdate() {
if (!_nextUpdateHandle) {
if (_deadline > 0) {
_nextUpdateHandle = setTimeout(_processUpdate, 0 + DEBUG_DELAY);
} else {
_nextUpdateHandle = setImmediate(_processUpdate);
}
}
}
/**
* Notify listeners, process queue, etc
*/
function _processUpdate() {
_nextUpdateHandle = 0;
const interactionCount = _interactionSet.size;
_addInteractionSet.forEach(handle => _interactionSet.add(handle));
_deleteInteractionSet.forEach(handle => _interactionSet.delete(handle));
const nextInteractionCount = _interactionSet.size;
if (interactionCount !== 0 && nextInteractionCount === 0) {
// transition from 1+ --> 0 interactions
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
_emitter.emit(InteractionManager.Events.interactionComplete);
} else if (interactionCount === 0 && nextInteractionCount !== 0) {
// transition from 0 --> 1+ interactions
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
_emitter.emit(InteractionManager.Events.interactionStart);
}
// process the queue regardless of a transition
if (nextInteractionCount === 0) {
while (_taskQueue.hasTasksToProcess()) {
_taskQueue.processNext();
if (
_deadline > 0 &&
BatchedBridge.getEventLoopRunningTime() >= _deadline
) {
// Hit deadline before processing all tasks, so process more later.
_scheduleUpdate();
break;
}
}
}
_addInteractionSet.clear();
_deleteInteractionSet.clear();
}
/**
@@ -83,106 +231,11 @@ function reject(error: Error): void {
*
* @deprecated
*/
const InteractionManagerStub = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
const InteractionManager = (
ReactNativeFeatureFlags.disableInteractionManager()
? // $FlowFixMe[incompatible-variance]
require('./InteractionManagerStub').default
: InteractionManagerImpl
) as typeof InteractionManagerImpl;
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*
* @deprecated
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
task.gen().then(resolve, reject);
} else if (typeof task.run === 'function') {
try {
task.run();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
}
} else if (typeof task === 'function') {
try {
task();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError('Invalid task of type: ' + typeof task));
}
});
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel() {
clearImmediate(immediateID);
},
};
},
/**
* Notify manager that an interaction has started.
*
* @deprecated
*/
createInteractionHandle(): Handle {
return -1;
},
/**
* Notify manager that an interaction has completed.
*
* @deprecated
*/
clearInteractionHandle(handle: Handle) {
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
},
/**
* @deprecated
*/
addListener(
eventType: string,
// $FlowIgnore[unclear-type]
listener: (...args: any) => mixed,
context: mixed,
): EventSubscription {
return {
remove() {},
};
},
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*
* @deprecated
*/
setDeadline(deadline: number) {
// Do nothing.
},
};
export default InteractionManagerStub;
export default InteractionManager;
@@ -0,0 +1,184 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
const invariant = require('invariant');
export type Handle = number;
type Task =
| {
name: string,
run: () => void,
}
| {
name: string,
gen: () => Promise<void>,
}
| (() => void);
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
}
/**
* InteractionManager allows long-running work to be scheduled after any
* interactions/animations have completed. In particular, this allows JavaScript
* animations to run smoothly.
*
* Applications can schedule tasks to run after interactions with the following:
*
* ```
* InteractionManager.runAfterInteractions(() => {
* // ...long-running synchronous task...
* });
* ```
*
* Compare this to other scheduling alternatives:
*
* - requestAnimationFrame(): for code that animates a view over time.
* - setImmediate/setTimeout(): run code later, note this may delay animations.
* - runAfterInteractions(): run code later, without delaying active animations.
*
* The touch handling system considers one or more active touches to be an
* 'interaction' and will delay `runAfterInteractions()` callbacks until all
* touches have ended or been cancelled.
*
* InteractionManager also allows applications to register animations by
* creating an interaction 'handle' on animation start, and clearing it upon
* completion:
*
* ```
* var handle = InteractionManager.createInteractionHandle();
* // run animation... (`runAfterInteractions` tasks are queued)
* // later, on animation completion:
* InteractionManager.clearInteractionHandle(handle);
* // queued tasks run if all handles were cleared
* ```
*
* `runAfterInteractions` takes either a plain callback function, or a
* `PromiseTask` object with a `gen` method that returns a `Promise`. If a
* `PromiseTask` is supplied, then it is fully resolved (including asynchronous
* dependencies that also schedule more tasks via `runAfterInteractions`) before
* starting on the next task that might have been queued up synchronously
* earlier.
*
* By default, queued tasks are executed together in a loop in one
* `setImmediate` batch. If `setDeadline` is called with a positive number, then
* tasks will only be executed until the deadline (in terms of js event loop run
* time) approaches, at which point execution will yield via setTimeout,
* allowing events such as touches to start interactions and block queued tasks
* from executing, making apps more responsive.
*
* @deprecated
*/
const InteractionManagerStub = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*
* @deprecated
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
task.gen().then(resolve, reject);
} else if (typeof task.run === 'function') {
try {
task.run();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
}
} else if (typeof task === 'function') {
try {
task();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError('Invalid task of type: ' + typeof task));
}
});
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel() {
clearImmediate(immediateID);
},
};
},
/**
* Notify manager that an interaction has started.
*
* @deprecated
*/
createInteractionHandle(): Handle {
return -1;
},
/**
* Notify manager that an interaction has completed.
*
* @deprecated
*/
clearInteractionHandle(handle: Handle) {
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
},
/**
* @deprecated
*/
addListener(): EventSubscription {
return {
remove() {},
};
},
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*
* @deprecated
*/
setDeadline(deadline: number) {
// Do nothing.
},
};
export default InteractionManagerStub;
+197
View File
@@ -0,0 +1,197 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
*/
'use strict';
const invariant = require('invariant');
export type SimpleTask = {
name: string,
run: () => void,
};
export type PromiseTask = {
name: string,
gen: () => Promise<void>,
};
export type Task = SimpleTask | PromiseTask | (() => void);
const DEBUG: false = false;
/**
* TaskQueue - A system for queueing and executing a mix of simple callbacks and
* trees of dependent tasks based on Promises. No tasks are executed unless
* `processNext` is called.
*
* `enqueue` takes a Task object with either a simple `run` callback, or a
* `gen` function that returns a `Promise` and puts it in the queue. If a gen
* function is supplied, then the promise it returns will block execution of
* tasks already in the queue until it resolves. This can be used to make sure
* the first task is fully resolved (including asynchronous dependencies that
* also schedule more tasks via `enqueue`) before starting on the next task.
* The `onMoreTasks` constructor argument is used to inform the owner that an
* async task has resolved and that the queue should be processed again.
*
* Note: Tasks are only actually executed with explicit calls to `processNext`.
*/
class TaskQueue {
/**
* TaskQueue instances are self contained and independent, so multiple tasks
* of varying semantics and priority can operate together.
*
* `onMoreTasks` is invoked when `PromiseTask`s resolve if there are more
* tasks to process.
*/
constructor({onMoreTasks}: {onMoreTasks: () => void, ...}) {
this._onMoreTasks = onMoreTasks;
this._queueStack = [{tasks: [], popable: false}];
}
/**
* Add a task to the queue. It is recommended to name your tasks for easier
* async debugging. Tasks will not be executed until `processNext` is called
* explicitly.
*/
enqueue(task: Task): void {
this._getCurrentQueue().push(task);
}
enqueueTasks(tasks: Array<Task>): void {
tasks.forEach(task => this.enqueue(task));
}
cancelTasks(tasksToCancel: Array<Task>): void {
// search through all tasks and remove them.
this._queueStack = this._queueStack
.map(queue => ({
...queue,
tasks: queue.tasks.filter(task => tasksToCancel.indexOf(task) === -1),
}))
.filter((queue, idx) => queue.tasks.length > 0 || idx === 0);
}
/**
* Check to see if `processNext` should be called.
*
* @returns {boolean} Returns true if there are tasks that are ready to be
* processed with `processNext`, or returns false if there are no more tasks
* to be processed right now, although there may be tasks in the queue that
* are blocked by earlier `PromiseTask`s that haven't resolved yet.
* `onMoreTasks` will be called after each `PromiseTask` resolves if there are
* tasks ready to run at that point.
*/
hasTasksToProcess(): boolean {
return this._getCurrentQueue().length > 0;
}
/**
* Executes the next task in the queue.
*/
processNext(): void {
const queue = this._getCurrentQueue();
if (queue.length) {
const task = queue.shift();
try {
if (typeof task === 'object' && task.gen) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: genPromise for task ' + task.name);
this._genPromise(task);
} else if (typeof task === 'object' && task.run) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: run task ' + task.name);
task.run();
} else {
invariant(
typeof task === 'function',
'Expected Function, SimpleTask, or PromiseTask, but got:\n' +
JSON.stringify(task, null, 2),
);
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: run anonymous task');
task();
}
} catch (e) {
e.message =
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
'TaskQueue: Error with task ' + (task.name || '') + ': ' + e.message;
throw e;
}
}
}
_queueStack: Array<{
tasks: Array<Task>,
popable: boolean,
...
}>;
_onMoreTasks: () => void;
_getCurrentQueue(): Array<Task> {
const stackIdx = this._queueStack.length - 1;
const queue = this._queueStack[stackIdx];
if (
queue.popable &&
queue.tasks.length === 0 &&
this._queueStack.length > 1
) {
this._queueStack.pop();
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG &&
console.log('TaskQueue: popped queue: ', {
stackIdx,
queueStackSize: this._queueStack.length,
});
return this._getCurrentQueue();
} else {
return queue.tasks;
}
}
_genPromise(task: PromiseTask) {
// Each async task pushes it's own queue onto the queue stack. This
// effectively defers execution of previously queued tasks until the promise
// resolves, at which point we allow the new queue to be popped, which
// happens once it is fully processed.
this._queueStack.push({tasks: [], popable: false});
const stackIdx = this._queueStack.length - 1;
const stackItem = this._queueStack[stackIdx];
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: push new queue: ', {stackIdx});
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: exec gen task ' + task.name);
task
.gen()
.then(() => {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG &&
console.log('TaskQueue: onThen for gen task ' + task.name, {
stackIdx,
queueStackSize: this._queueStack.length,
});
stackItem.popable = true;
this.hasTasksToProcess() && this._onMoreTasks();
})
.catch(ex => {
setTimeout(() => {
ex.message = `TaskQueue: Error resolving Promise in task ${task.name}: ${ex.message}`;
throw ex;
}, 0);
});
}
}
export default TaskQueue;
@@ -0,0 +1,342 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
* @format
*/
'use strict';
import type {ReactNativeFeatureFlagsJsOnlyOverrides} from '../../../src/private/featureflags/ReactNativeFeatureFlags';
function importModules(overrides: ReactNativeFeatureFlagsJsOnlyOverrides) {
const ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
// Make sure to setup overrides before importing any modules.
ReactNativeFeatureFlags.override(overrides);
const BatchedBridge = require('../../BatchedBridge/BatchedBridge').default;
const InteractionManager = require('../InteractionManager').default;
jest.mock('../../vendor/core/ErrorUtils');
jest.mock('../../BatchedBridge/BatchedBridge');
return {
BatchedBridge,
InteractionManager,
};
}
const isWindows = process.platform === 'win32';
const itif = (condition: boolean) => (condition ? it : it.skip);
function expectToBeCalledOnce(
fn: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
): void {
expect(fn.mock.calls.length).toBe(1);
}
describe('InteractionManager', () => {
let InteractionManager;
let interactionStart;
let interactionComplete;
beforeEach(() => {
jest.resetModules();
({InteractionManager} = importModules({
disableInteractionManager: () => false,
}));
interactionStart = jest.fn();
interactionComplete = jest.fn();
InteractionManager.addListener(
InteractionManager.Events.interactionStart,
interactionStart,
);
InteractionManager.addListener(
InteractionManager.Events.interactionComplete,
interactionComplete,
);
});
it('throws when clearing an undefined handle', () => {
// $FlowExpectedError[incompatible-call]
expect(() => InteractionManager.clearInteractionHandle()).toThrow();
});
it('notifies asynchronously when interaction starts', () => {
InteractionManager.createInteractionHandle();
expect(interactionStart).not.toBeCalled();
jest.runAllTimers();
expect(interactionStart).toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('notifies asynchronously when interaction stops', () => {
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
interactionStart.mockClear();
InteractionManager.clearInteractionHandle(handle);
expect(interactionComplete).not.toBeCalled();
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).toBeCalled();
});
it('does not notify when started & stopped in same event loop', () => {
const handle = InteractionManager.createInteractionHandle();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('does not notify when going from two -> one active interactions', () => {
InteractionManager.createInteractionHandle();
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
interactionStart.mockClear();
interactionComplete.mockClear();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('runs tasks asynchronously when there are interactions', () => {
const task = jest.fn();
InteractionManager.runAfterInteractions(task);
expect(task).not.toBeCalled();
jest.runAllTimers();
expect(task).toBeCalled();
});
it('runs tasks when interactions complete', () => {
const task = jest.fn();
const handle = InteractionManager.createInteractionHandle();
InteractionManager.runAfterInteractions(task);
jest.runAllTimers();
InteractionManager.clearInteractionHandle(handle);
expect(task).not.toBeCalled();
jest.runAllTimers();
expect(task).toBeCalled();
});
it('does not run tasks twice', () => {
const task1 = jest.fn();
const task2 = jest.fn();
InteractionManager.runAfterInteractions(task1);
jest.runAllTimers();
InteractionManager.runAfterInteractions(task2);
jest.runAllTimers();
expectToBeCalledOnce(task1);
});
it('runs tasks added while processing previous tasks', () => {
const task1 = jest.fn(() => {
InteractionManager.runAfterInteractions(task2);
});
const task2 = jest.fn();
InteractionManager.runAfterInteractions(task1);
expect(task2).not.toBeCalled();
jest.runAllTimers();
expect(task1).toBeCalled();
expect(task2).toBeCalled();
});
it('allows tasks to be cancelled', () => {
const task1 = jest.fn();
const task2 = jest.fn();
const promise1 = InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
expect(task1).not.toBeCalled();
expect(task2).not.toBeCalled();
promise1.cancel();
jest.runAllTimers();
expect(task1).not.toBeCalled();
expect(task2).toBeCalled();
});
});
describe('promise tasks', () => {
let BatchedBridge;
let InteractionManager;
let sequenceId;
function createSequenceTask(expectedSequenceId: number) {
return jest.fn(() => {
expect(++sequenceId).toBe(expectedSequenceId);
});
}
beforeEach(() => {
jest.resetModules();
({BatchedBridge, InteractionManager} = importModules({
disableInteractionManager: () => false,
}));
sequenceId = 0;
});
it('should run a basic promise task', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
return new Promise(resolve => resolve());
});
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
jest.runAllTimers();
expectToBeCalledOnce(task1);
});
it('should handle nested promises', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
return new Promise(resolve => {
InteractionManager.runAfterInteractions({
gen: task2,
name: 'gen2',
}).then(resolve);
});
});
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => resolve());
});
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
jest.runAllTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
});
it('should pause promise tasks during interactions then resume', () => {
const task1 = createSequenceTask(1);
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => {
setTimeout(() => {
InteractionManager.runAfterInteractions(task3).then(resolve);
}, 1);
});
});
const task3 = createSequenceTask(3);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
jest.runAllTimers(); // Just to be sure...
expect(task3).not.toBeCalled();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expectToBeCalledOnce(task3);
});
it('should execute tasks in loop within deadline', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(10);
const task1 = createSequenceTask(1);
const task2 = createSequenceTask(2);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
});
it('should execute tasks one at a time if deadline exceeded', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
const task1 = createSequenceTask(1);
const task2 = createSequenceTask(2);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expect(task2).not.toBeCalled();
jest.runOnlyPendingTimers(); // resolve1
jest.runOnlyPendingTimers(); // task2
expectToBeCalledOnce(task2);
});
const bigAsyncTest = resolveTest => {
jest.useRealTimers();
const task1 = createSequenceTask(1);
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => {
InteractionManager.runAfterInteractions(task3);
setTimeout(() => {
InteractionManager.runAfterInteractions({
gen: task4,
name: 'gen4',
}).then(resolve);
}, 1);
});
});
const task3 = createSequenceTask(3);
const task4 = jest.fn(() => {
expect(++sequenceId).toBe(4);
return new Promise(resolve => {
InteractionManager.runAfterInteractions(task5).then(resolve);
});
});
const task5 = createSequenceTask(5);
const task6 = createSequenceTask(6);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
InteractionManager.runAfterInteractions(task6);
setTimeout(() => {
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
expectToBeCalledOnce(task4);
expectToBeCalledOnce(task5);
expectToBeCalledOnce(task6);
resolveTest();
}, 100);
};
itif(!isWindows)(
'resolves async tasks recursively before other queued tasks',
() => {
return new Promise(bigAsyncTest);
},
);
itif(!isWindows)('should also work with a deadline', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
return new Promise(bigAsyncTest);
});
});
@@ -0,0 +1,169 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @noflow
* @format
*/
'use strict';
const Promise = require('promise');
function expectToBeCalledOnce(fn) {
expect(fn.mock.calls.length).toBe(1);
}
function clearTaskQueue(taskQueue) {
do {
jest.runAllTimers();
taskQueue.processNext();
jest.runAllTimers();
} while (taskQueue.hasTasksToProcess());
}
describe('TaskQueue', () => {
let taskQueue;
let onMoreTasks;
let sequenceId;
function createSequenceTask(expectedSequenceId) {
return jest.fn(() => {
expect(++sequenceId).toBe(expectedSequenceId);
});
}
beforeEach(() => {
jest.resetModules();
onMoreTasks = jest.fn();
const TaskQueue = require('../TaskQueue').default;
taskQueue = new TaskQueue({onMoreTasks});
sequenceId = 0;
});
it('should run a basic task', () => {
const task1 = createSequenceTask(1);
taskQueue.enqueue({run: task1, name: 'run1'});
expect(taskQueue.hasTasksToProcess()).toBe(true);
taskQueue.processNext();
expectToBeCalledOnce(task1);
});
it('should handle blocking promise task', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(1);
resolve();
}, 1);
});
});
const task2 = createSequenceTask(2);
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.enqueue({run: task2, name: 'run2'});
taskQueue.processNext();
expectToBeCalledOnce(task1);
expect(task2).not.toBeCalled();
expect(onMoreTasks).not.toBeCalled();
expect(taskQueue.hasTasksToProcess()).toBe(false);
clearTaskQueue(taskQueue);
expectToBeCalledOnce(onMoreTasks);
expectToBeCalledOnce(task2);
});
it('should handle nested simple tasks', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
taskQueue.enqueue({run: task3, name: 'run3'});
});
const task2 = createSequenceTask(2);
const task3 = createSequenceTask(3);
taskQueue.enqueue({run: task1, name: 'run1'});
taskQueue.enqueue({run: task2, name: 'run2'}); // not blocked by task 1
clearTaskQueue(taskQueue);
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
});
it('should handle nested promises', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(1);
taskQueue.enqueue({gen: task2, name: 'gen2'});
taskQueue.enqueue({run: resolve, name: 'resolve1'});
}, 1);
});
});
const task2 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(2);
taskQueue.enqueue({run: task3, name: 'run3'});
taskQueue.enqueue({run: resolve, name: 'resolve2'});
}, 1);
});
});
const task3 = createSequenceTask(3);
const task4 = createSequenceTask(4);
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.enqueue({run: task4, name: 'run4'}); // blocked by task 1 promise
clearTaskQueue(taskQueue);
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
expectToBeCalledOnce(task4);
});
it('should be able to cancel tasks', () => {
const task1 = jest.fn();
const task2 = createSequenceTask(1);
const task3 = jest.fn();
const task4 = createSequenceTask(2);
taskQueue.enqueue(task1);
taskQueue.enqueue(task2);
taskQueue.enqueue(task3);
taskQueue.enqueue(task4);
taskQueue.cancelTasks([task1, task3]);
clearTaskQueue(taskQueue);
expect(task1).not.toBeCalled();
expect(task3).not.toBeCalled();
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task4);
expect(taskQueue.hasTasksToProcess()).toBe(false);
});
it('should not crash when last task is cancelled', () => {
const task1 = jest.fn();
taskQueue.enqueue(task1);
taskQueue.cancelTasks([task1]);
clearTaskQueue(taskQueue);
expect(task1).not.toBeCalled();
expect(taskQueue.hasTasksToProcess()).toBe(false);
});
it('should not crash when task is cancelled between being started and resolved', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1);
});
});
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.processNext();
taskQueue.cancelTasks([task1]);
jest.runAllTimers();
});
});
@@ -400,6 +400,7 @@ class XMLHttpRequest extends EventTarget {
if (XMLHttpRequest._profiling) {
console.timeStamp(
'Incremental Data: ' + this._getMeasureURL(),
// $FlowFixMe[extra-arg] Add correct typing for `console.timeStamp`
start,
undefined,
PERFORMANCE_TRACK_NAME,
@@ -454,6 +455,7 @@ class XMLHttpRequest extends EventTarget {
const start = this._startTime;
console.timeStamp(
this._getMeasureURL(),
// $FlowFixMe[extra-arg] Add correct typing for `console.timeStamp`
start,
undefined,
PERFORMANCE_TRACK_NAME,
@@ -10,21 +10,21 @@
import typeof {enable} from 'promise/setimmediate/rejection-tracking';
import ExceptionsManager from './Core/ExceptionsManager';
import LogBox from './LogBox/LogBox';
let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
allRejections: true,
onUnhandled: (id, rejection) => {
onUnhandled: (id, rejection = {}) => {
let message: string;
let stack: ?string;
if (rejection === undefined) {
message = '';
} else if (
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
Object.prototype.toString.call(rejection) === '[object Error]'
) {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
const stringValue = Object.prototype.toString.call(rejection);
if (stringValue === '[object Error]') {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
message = Error.prototype.toString.call(rejection);
const error: Error = (rejection: $FlowFixMe);
stack = error.stack;
} else {
try {
message = require('pretty-format').format(rejection);
@@ -34,23 +34,37 @@ let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
? rejection
: JSON.stringify((rejection: $FlowFixMe));
}
// It could although this object is not a standard error, it still has stack information to unwind
// $FlowFixMe ignore types just check if stack is there
if (rejection?.stack && typeof rejection.stack === 'string') {
stack = rejection.stack;
}
}
ExceptionsManager.handleException(
new Error(
`Uncaught (in promise, id: ${id})${message ? `: "${message}"` : ''}`,
{
cause: rejection,
const warning = `Possible unhandled promise rejection (id: ${id}):\n${
message ?? ''
}`;
if (__DEV__) {
LogBox.addLog({
level: 'warn',
message: {
content: warning,
substitutions: [],
},
),
false /* isFatal */,
);
componentStack: [],
componentStackType: null,
stack,
category: 'possible_unhandled_promise_rejection',
});
} else {
console.warn(warning);
}
},
onHandled: id => {
const warning =
`Promise rejection handled (id: ${id})\n` +
'This means you can ignore any previous messages of the form ' +
`"Uncaught (in promise, id: ${id})"`;
`"Possible unhandled promise rejection (id: ${id}):"`;
console.warn(warning);
},
};
+3 -3
View File
@@ -710,13 +710,13 @@ NSData *__nullable RCTGzipData(NSData *__nullable input, float level)
void *libz = dlopen("/usr/lib/libz.dylib", RTLD_LAZY);
typedef int (*DeflateInit2_)(z_streamp, int, int, int, int, int, const char *, int);
using DeflateInit2_ = int (*)(z_streamp, int, int, int, int, int, const char *, int);
DeflateInit2_ deflateInit2_ = (DeflateInit2_)dlsym(libz, "deflateInit2_");
typedef int (*Deflate)(z_streamp, int);
using Deflate = int (*)(z_streamp, int);
Deflate deflate = (Deflate)dlsym(libz, "deflate");
typedef int (*DeflateEnd)(z_streamp);
using DeflateEnd = int (*)(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 fun getReactHost ()Lcom/facebook/react/ReactHost;
protected fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
protected final fun getReactHost ()Lcom/facebook/react/ReactHost;
protected final fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
protected fun getTaskConfig (Landroid/content/Intent;)Lcom/facebook/react/jstasks/HeadlessJsTaskConfig;
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
public fun onDestroy ()V
@@ -113,7 +113,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
* somewhere.
*/
@Suppress("DEPRECATION")
protected open val reactNativeHost: ReactNativeHost
protected val reactNativeHost: ReactNativeHost
get() = (application as ReactApplication).reactNativeHost
/**
@@ -121,7 +121,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
* [ReactApplication] and calls [ReactApplication.reactHost]. This method assumes it is called in
* new architecture and returns null if not.
*/
protected open val reactHost: ReactHost?
protected val reactHost: ReactHost?
get() = (application as ReactApplication).reactHost
protected val reactContext: ReactContext?
@@ -135,10 +135,6 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private void init() {
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
setClipChildren(false);
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
}
}
@Override
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c5ed8a904dcda7ade87527233b879b92>>
* @generated SignedSource<<99019349a8f3642c42e6dac5f1476e7f>>
*/
/**
@@ -101,7 +101,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableVirtualViewDebugFeatures(): Boolean = false
override fun enableVirtualViewRenderState(): Boolean = true
override fun enableVirtualViewRenderState(): Boolean = false
override fun enableVirtualViewWindowFocusDetection(): Boolean = false
@@ -19,7 +19,7 @@ import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel
import com.facebook.react.uimanager.events.Event
/** Event used to notify JS component about changes of its position or dimensions. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.WARNING)
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
public class OnLayoutEvent private constructor() : Event<OnLayoutEvent>() {
@VisibleForTesting internal var x: Int = 0
@VisibleForTesting internal var y: Int = 0
@@ -60,7 +60,7 @@ public class OnLayoutEvent private constructor() : Event<OnLayoutEvent>() {
public companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"OnLayoutEvent", LegacyArchitectureLogLevel.WARNING)
"OnLayoutEvent", LegacyArchitectureLogLevel.ERROR)
}
private val EVENTS_POOL: SynchronizedPool<OnLayoutEvent> = SynchronizedPool<OnLayoutEvent>(20)
@@ -377,9 +377,6 @@ public class ReactScrollView extends ScrollView
}
ReactScrollViewHelper.emitLayoutEvent(this);
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
@Override
@@ -489,9 +486,6 @@ public class ReactScrollView extends ScrollView
this,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity());
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT);
@@ -11,7 +11,6 @@ import android.graphics.Rect
import android.view.ViewGroup
import com.facebook.common.logging.FLog
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.views.virtual.VirtualViewMode
import java.util.*
@@ -46,19 +45,22 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
return true
}
internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
ReactScrollViewHelper.ScrollListener {
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
private val prerenderRatio: Int = 1
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
private val emptyRect: Rect = Rect()
private val visibleRect: Rect = Rect()
private val prerenderRect: Rect = Rect()
public fun onChange(virtualView: VirtualView) {
if (virtualViews.add(virtualView)) {
debugLog("add", { "virtualViewID=${virtualView.virtualViewID}" })
} else {
debugLog("update", { "virtualViewID=${virtualView.virtualViewID}" })
init {
ReactScrollViewHelper.addScrollListener(this)
}
public fun add(virtualView: VirtualView) {
assert(virtualViews.add(virtualView)) {
"Attempting to add duplicate VirtualView: ${virtualView.virtualViewID}"
}
updateModes(virtualView)
}
@@ -67,13 +69,35 @@ internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
assert(virtualViews.remove(virtualView)) {
"Attempting to remove non-existent VirtualView: ${virtualView.virtualViewID}"
}
debugLog("remove", { "virtualViewID=${virtualView.virtualViewID}" })
}
// Called on ScrollView onLayout or onScroll
public fun updateState() {
debugLog("VirtualViewContainer.updateState")
updateModes()
// ReactScrollViewHelper.ScrollListener.onLayout
// Emitted from ScrollView's onLayout
override fun onLayout(scrollView: ViewGroup?) {
// ReactScrollViewHelper is global
if (this.scrollView == scrollView) {
debugLog("ReactScrollViewHelper.onLayout")
updateModes()
}
}
// ReactScrollViewHelper.ScrollListener.onScroll
// Emitted from ScrollView's onLayout
override fun onScroll(
scrollView: ViewGroup?,
scrollEventType: ScrollEventType?,
xVelocity: Float,
yVelocity: Float
) {
// ReactScrollViewHelper is global
if (this.scrollView == scrollView) {
debugLog("ReactScrollViewHelper.onScroll")
updateModes()
}
}
public fun update(virtualView: VirtualView) {
updateModes(virtualView)
}
private fun updateModes(virtualView: VirtualView? = null) {
@@ -86,26 +110,17 @@ internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
val virtualViewsIt = if (virtualView != null) listOf(virtualView) else virtualViews
virtualViewsIt.forEach { vv ->
val rect = vv.containerRelativeRect
var mode = VirtualViewMode.Hidden
var thresholdRect = emptyRect
when {
rect.isEmpty -> {}
rectsOverlap(rect, visibleRect) -> {
mode = VirtualViewMode.Visible
thresholdRect = visibleRect
vv.onModeChange(VirtualViewMode.Visible, visibleRect)
}
rectsOverlap(rect, prerenderRect) -> {
mode = VirtualViewMode.Prerender
thresholdRect = prerenderRect
vv.onModeChange(VirtualViewMode.Prerender, prerenderRect)
}
else -> {
vv.onModeChange(VirtualViewMode.Hidden, emptyRect)
}
else -> {}
}
debugLog(
"updateModes",
{ "virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect" })
vv.onModeChange(mode, thresholdRect)
}
}
}
@@ -34,7 +34,6 @@ public class ReactVirtualViewExperimental(context: Context) :
override val containerRelativeRect: Rect = Rect()
private var offsetX: Int = 0
private var offsetY: Int = 0
private var hadLayout: Boolean = false
internal val nativeId: String?
get() = getTag(R.id.view_tag_native_id) as? String
@@ -46,20 +45,14 @@ public class ReactVirtualViewExperimental(context: Context) :
@VisibleForTesting
internal fun doAttachedToWindow() {
scrollView = getScrollView()
// onAttachedToWindow is usually called before layout but there are cases where it's called
// after. If called after, we need to report the updated layout to the VirtualViewContainer
if (hadLayout) {
updateParentOffset()
reportChangeToContainer()
}
// Assuming that layout has been called before this
scrollView = getScrollView()?.also { scrollView?.virtualViewContainerState?.add(this) }
}
/** From [View#onLayout] */
// This is when the view itself has layout changes
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
hadLayout = true
if (changed) {
containerRelativeRect.set(
left + offsetX,
@@ -67,7 +60,7 @@ public class ReactVirtualViewExperimental(context: Context) :
right + offsetX,
bottom + offsetY,
)
reportChangeToContainer()
updateContainer()
}
}
@@ -84,8 +77,24 @@ public class ReactVirtualViewExperimental(context: Context) :
oldBottom: Int
) {
if (oldLeft != left || oldTop != top) {
updateParentOffset()
reportChangeToContainer()
val virtualViewScrollView = scrollView ?: return
offsetX = 0
offsetY = 0
var parent: ViewParent? = parent
while (parent != null && parent != virtualViewScrollView) {
if (parent is View) {
offsetX += parent.left
offsetY += parent.top
}
parent = parent.parent
}
containerRelativeRect.set(
left + offsetX,
top + offsetY,
right + offsetX,
bottom + offsetY,
)
updateContainer()
}
}
@@ -100,8 +109,6 @@ public class ReactVirtualViewExperimental(context: Context) :
scrollView = null
mode = null
modeChangeEmitter = null
hadLayout = false
containerRelativeRect.setEmpty()
}
override val virtualViewID: String
@@ -149,28 +156,8 @@ public class ReactVirtualViewExperimental(context: Context) :
}
}
private fun updateParentOffset() {
val virtualViewScrollView = scrollView ?: return
offsetX = 0
offsetY = 0
var parent: ViewParent? = parent
while (parent != null && parent != virtualViewScrollView) {
if (parent is View) {
offsetX += parent.left
offsetY += parent.top
}
parent = parent.parent
}
containerRelativeRect.set(
left + offsetX,
top + offsetY,
right + offsetX,
bottom + offsetY,
)
}
private fun reportChangeToContainer() {
scrollView?.virtualViewContainerState?.onChange(this)
private fun updateContainer() {
scrollView?.virtualViewContainerState?.update(this)
}
private fun getScrollView(): VirtualViewContainer? = traverseParentStack(true)
@@ -38,7 +38,7 @@ add_library(
target_merge_so(reactnativejni_common)
target_include_directories(reactnativejni_common PUBLIC ../../)
target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact rrc_view)
target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact)
target_compile_reactnative_options(reactnativejni_common PRIVATE)
target_compile_options(reactnativejni_common PRIVATE -Wno-unused-lambda-capture)
@@ -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<<614fe0894f6235d879a0b29e7dab7759>>
* @generated SignedSource<<d7534fe6e3b398e56c179da8233e3b0a>>
*/
/**
@@ -184,7 +184,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
}
bool enableVirtualViewRenderState() override {
return true;
return false;
}
bool enableVirtualViewWindowFocusDetection() override {
@@ -49,6 +49,21 @@ 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) {
@@ -64,6 +64,19 @@ 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,12 +251,11 @@ CommitStatus ShadowTree::commit(
if (status != CommitStatus::Failed) {
return status;
}
attempts++;
}
{
std::unique_lock lock(commitMutex_);
return tryCommit(transaction, commitOptions, true);
return tryCommit(transaction, commitOptions);
}
} else {
while (true) {
@@ -276,8 +275,7 @@ CommitStatus ShadowTree::commit(
CommitStatus ShadowTree::tryCommit(
const ShadowTreeCommitTransaction& transaction,
const CommitOptions& commitOptions,
bool hasLocked) const {
const CommitOptions& commitOptions) const {
TraceSection s("ShadowTree::commit");
auto telemetry = TransactionTelemetry{};
@@ -289,10 +287,7 @@ CommitStatus ShadowTree::tryCommit(
{
// Reading `currentRevision_` in shared manner.
std::shared_lock lock(commitMutex_, std::defer_lock);
if (!hasLocked) {
lock.lock();
}
std::shared_lock lock(commitMutex_);
commitMode = commitMode_;
oldRevision = currentRevision_;
}
@@ -333,10 +328,7 @@ CommitStatus ShadowTree::tryCommit(
{
// Updating `currentRevision_` in unique manner if it hasn't changed.
std::unique_lock lock(commitMutex_, std::defer_lock);
if (!hasLocked) {
lock.lock();
}
std::unique_lock lock(commitMutex_);
if (currentRevision_.number != oldRevision.number) {
return CommitStatus::Failed;
@@ -111,8 +111,7 @@ class ShadowTree final {
*/
CommitStatus tryCommit(
const ShadowTreeCommitTransaction& transaction,
const CommitOptions& commitOptions,
bool hasLocked = false) const;
const CommitOptions& commitOptions) const;
/*
* Calls `tryCommit` in a loop until it finishes successfully.
+6 -5
View File
@@ -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<<a942544e271327a89ec40682d3ec736d>>
* @generated SignedSource<<83d07f37fd6028027511209f02e7f056>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
@@ -257,17 +257,18 @@ 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 InteractionManagerStub_default
declare const InteractionManagerStub_default: {
declare const InteractionManager: typeof InteractionManager_default
declare const InteractionManager_default: typeof InteractionManagerImpl
declare const InteractionManagerImpl: {
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): {
-65
View File
@@ -12,71 +12,6 @@
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;
+1 -2
View File
@@ -21,7 +21,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 20.19.4"
"node": ">= 22.14.0"
},
"bin": {
"react-native": "cli.js"
@@ -106,7 +106,6 @@
"ReactCommon",
"README.md",
"rn-get-polyfills.js",
"scripts/replace-rncore-version.js",
"scripts/bundle.js",
"scripts/cocoapods",
"scripts/codegen",
@@ -462,24 +462,22 @@ const definitions: FeatureFlagDefinitions = {
ossReleaseStage: 'none',
},
enableVirtualViewRenderState: {
defaultValue: true,
defaultValue: false,
metadata: {
dateAdded: '2025-06-25',
description:
'Enables reading render state when dispatching VirtualView events.',
expectedReleaseValue: true,
purpose: 'experimentation',
purpose: 'operational',
},
ossReleaseStage: 'none',
},
enableVirtualViewWindowFocusDetection: {
defaultValue: false,
metadata: {
dateAdded: '2025-06-24',
description:
'Enables window focus detection for prioritizing VirtualView events.',
expectedReleaseValue: true,
purpose: 'experimentation',
purpose: 'operational',
},
ossReleaseStage: 'none',
},
@@ -710,6 +708,17 @@ 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: {
@@ -732,6 +741,16 @@ 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: {
@@ -743,6 +762,16 @@ 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: {
@@ -785,6 +814,16 @@ 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: {
@@ -817,6 +856,16 @@ 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,6 +89,13 @@ 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:
@@ -255,6 +262,44 @@ 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:
@@ -269,7 +314,9 @@ function addAnimatedValuesListenersToProps(
* callbacks may update state, which is unsupported and will force synchronous
* updates.
*/
function useAnimatedPropsLifecycle(node: AnimatedProps): void {
function useAnimatedPropsLifecycleWithCleanupInMicrotask(
node: AnimatedProps,
): void {
const isMounted = useRef<boolean>(false);
useInsertionEffect(() => {
@@ -16,8 +16,9 @@ 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} from 'react';
import {useInsertionEffect, useMemo, useRef, useState} from 'react';
type CompositeKey = {
style?: {[string]: CompositeKeyComponent},
@@ -63,6 +64,19 @@ 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),
@@ -88,7 +102,39 @@ 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,7 +14,6 @@ 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';
@@ -60,16 +59,9 @@ const NotHidden = null;
type State = HiddenHeight | typeof NotHidden;
function createVirtualView(
initialState: State,
experimental: boolean,
): VirtualViewComponent {
function createVirtualView(initialState: State): VirtualViewComponent {
const initialHidden = initialState !== NotHidden;
const NativeComponent = experimental
? VirtualViewExperimentalNativeComponent
: VirtualViewNativeComponent;
component VirtualView(
children?: React.Node,
nativeID?: string,
@@ -120,7 +112,7 @@ function createVirtualView(
};
return (
<NativeComponent
<VirtualViewNativeComponent
initialHidden={initialHidden}
nativeID={nativeID}
ref={ref}
@@ -138,24 +130,16 @@ function createVirtualView(
}
onModeChange={handleModeChange}>
{isHidden ? null : children}
</NativeComponent>
</VirtualViewNativeComponent>
);
}
return VirtualView;
}
export default createVirtualView(NotHidden, false) as VirtualViewComponent;
export default createVirtualView(NotHidden) as VirtualViewComponent;
export const VirtualViewExperimental = createVirtualView(
NotHidden,
true,
) as VirtualViewComponent;
export function createHiddenVirtualView(
height: number,
experimental: boolean,
): VirtualViewComponent {
return createVirtualView(height as HiddenHeight, experimental);
export function createHiddenVirtualView(height: number): VirtualViewComponent {
return createVirtualView(height as HiddenHeight);
}
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<<a5b97c3df370225b7601f7375bb03ce0>>
* @generated SignedSource<<d30fc4e36ad600353c9b9098fd71430a>>
* @flow strict
* @noformat
*/
@@ -29,16 +29,21 @@ 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>;
@@ -115,6 +120,11 @@ 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.
*/
@@ -125,11 +135,21 @@ 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.
*/
@@ -150,6 +170,11 @@ 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.
*/
@@ -165,6 +190,11 @@ 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.
*/
@@ -328,7 +358,7 @@ export const enableVirtualViewDebugFeatures: Getter<boolean> = createNativeFlagG
/**
* Enables reading render state when dispatching VirtualView events.
*/
export const enableVirtualViewRenderState: Getter<boolean> = createNativeFlagGetter('enableVirtualViewRenderState', true);
export const enableVirtualViewRenderState: Getter<boolean> = createNativeFlagGetter('enableVirtualViewRenderState', false);
/**
* Enables window focus detection for prioritizing VirtualView events.
*/
@@ -4,6 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @fantom_flags utilizeTokensInIntersectionObserver:*
* @flow strict-local
* @format
*/
@@ -19,6 +20,7 @@ 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';
@@ -1492,42 +1494,44 @@ describe('IntersectionObserver', () => {
});
});
it('should not retain initial children of observed targets', () => {
const root = Fantom.createRoot();
observer = new IntersectionObserver(() => {});
if (ReactNativeFeatureFlags.utilizeTokensInIntersectionObserver()) {
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>,
);
Fantom.runTask(() => {
root.render(
<Observe>
<View ref={ref} />
</Observe>,
);
});
expect(getReferenceCount()).toBeGreaterThan(0);
Fantom.runTask(() => {
root.render(<Observe />);
});
expect(getReferenceCount()).toBe(0);
});
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({
@@ -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,11 +69,36 @@ 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.
@@ -164,19 +189,34 @@ 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;
}
const token = nullthrows(NativeIntersectionObserver.observeV2)({
intersectionObserverId,
rootShadowNode: rootNativeNodeReference,
targetShadowNode: targetNativeNodeReference,
thresholds: registeredObserver.observer.thresholds,
rootThresholds: registeredObserver.observer.rnRootThresholds,
});
targetToTokenMap.set(target, token);
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);
}
return true;
}
@@ -200,18 +240,33 @@ export function unobserve(
return;
}
const targetToken = targetToTokenMap.get(target);
if (targetToken == null) {
console.error(
'IntersectionObserverManager: could not find registration data for target',
);
return;
}
if (modernNativeIntersectionObserver == null) {
const targetNativeNodeReference = targetToShadowNodeMap.get(target);
if (targetNativeNodeReference == null) {
console.error(
'IntersectionObserverManager: could not find registration data for target',
);
return;
}
nullthrows(NativeIntersectionObserver.unobserveV2)(
intersectionObserverId,
targetToken,
);
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,
);
}
}
/**
@@ -34,6 +34,10 @@ 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,11 +15,9 @@ import type {
PerformanceEntryJSON,
} from './PerformanceEntry';
import {warnNoNativePerformance} from './internals/Utilities';
import {PerformanceEntry} from './PerformanceEntry';
import MaybeNativePerformance from './specs/NativePerformance';
import nullthrows from 'nullthrows';
const NativePerformance = nullthrows(MaybeNativePerformance);
import NativePerformance from './specs/NativePerformance';
export type PerformanceEventTimingJSON = {
...PerformanceEntryJSON,
@@ -88,8 +86,14 @@ 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,9 +35,8 @@ import {
} from './internals/Utilities';
import MemoryInfo from './MemoryInfo';
import ReactNativeStartupTiming from './ReactNativeStartupTiming';
import MaybeNativePerformance from './specs/NativePerformance';
import NativePerformance from './specs/NativePerformance';
import {PerformanceMark, PerformanceMeasure} from './UserTiming';
import nullthrows from 'nullthrows';
export type PerformanceMeasureOptions =
| $ReadOnly<{
@@ -59,13 +58,11 @@ export type PerformanceMeasureOptions =
const ENTRY_TYPES_AVAILABLE_FROM_TIMELINE: $ReadOnlyArray<PerformanceEntryType> =
['mark', 'measure'];
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 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,
@@ -101,46 +98,53 @@ export default class Performance {
// Get the current JS memory information.
get memory(): 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;
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;
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 {
const {
startTime,
endTime,
initializeRuntimeStart,
initializeRuntimeEnd,
executeJavaScriptBundleEntryPointStart,
executeJavaScriptBundleEntryPointEnd,
} = NativePerformance.getReactNativeStartupTiming();
return new ReactNativeStartupTiming({
startTime,
endTime,
initializeRuntimeStart,
initializeRuntimeEnd,
executeJavaScriptBundleEntryPointStart,
executeJavaScriptBundleEntryPointEnd,
});
if (NativePerformance?.getReactNativeStartupTiming) {
const {
startTime,
endTime,
initializeRuntimeStart,
initializeRuntimeEnd,
executeJavaScriptBundleEntryPointStart,
executeJavaScriptBundleEntryPointEnd,
} = NativePerformance.getReactNativeStartupTiming();
return new ReactNativeStartupTiming({
startTime,
endTime,
initializeRuntimeStart,
initializeRuntimeEnd,
executeJavaScriptBundleEntryPointStart,
executeJavaScriptBundleEntryPointEnd,
});
}
return new ReactNativeStartupTiming();
}
mark(
@@ -213,6 +217,11 @@ export default class Performance {
}
clearMarks(markName?: string): void {
if (!cachedNativeClearMarks) {
warnNoNativePerformance();
return;
}
cachedNativeClearMarks(markName);
}
@@ -405,6 +414,11 @@ export default class Performance {
}
clearMeasures(measureName?: string): void {
if (!cachedNativeClearMeasures) {
warnNoNativePerformance();
return;
}
cachedNativeClearMeasures(measureName);
}
@@ -420,6 +434,10 @@ 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);
}
@@ -432,6 +450,11 @@ export default class Performance {
return [];
}
if (!NativePerformance?.getEntriesByType) {
warnNoNativePerformance();
return [];
}
return NativePerformance.getEntriesByType(
performanceEntryTypeToRaw(entryType),
).map(rawToPerformanceEntry);
@@ -449,6 +472,11 @@ export default class Performance {
return [];
}
if (!NativePerformance?.getEntriesByName) {
warnNoNativePerformance();
return [];
}
return NativePerformance.getEntriesByName(
entryName,
entryType != null ? performanceEntryTypeToRaw(entryType) : undefined,
@@ -21,13 +21,12 @@ import {
rawToPerformanceEntry,
rawToPerformanceEntryType,
} from './internals/RawPerformanceEntry';
import MaybeNativePerformance from './specs/NativePerformance';
import {warnNoNativePerformance} from './internals/Utilities';
import NativePerformance from './specs/NativePerformance';
import nullthrows from 'nullthrows';
export {PerformanceEntry} from './PerformanceEntry';
const NativePerformance = nullthrows(MaybeNativePerformance);
export class PerformanceObserverEntryList {
#entries: PerformanceEntryList;
@@ -76,6 +75,13 @@ 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,
@@ -114,6 +120,11 @@ export class PerformanceObserver {
}
observe(options: PerformanceObserverInit): void {
if (!NativePerformance || NativePerformance.observe == null) {
warnNoNativePerformance();
return;
}
this.#validateObserveOptions(options);
if (this.#nativeObserverHandle == null) {
@@ -124,12 +135,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,
@@ -138,7 +149,12 @@ export class PerformanceObserver {
}
disconnect(): void {
if (this.#nativeObserverHandle == null) {
if (!NativePerformance) {
warnNoNativePerformance();
return;
}
if (this.#nativeObserverHandle == null || !NativePerformance.disconnect) {
return;
}
@@ -146,11 +162,16 @@ 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
);
@@ -164,7 +185,7 @@ export class PerformanceObserver {
let droppedEntriesCount = 0;
if (!this.#calledAtLeastOnce) {
droppedEntriesCount =
NativePerformance.getDroppedEntriesCount(observerHandle);
NativePerformance.getDroppedEntriesCount?.(observerHandle) ?? 0;
this.#calledAtLeastOnce = true;
}
@@ -11,19 +11,11 @@
// 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
// 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 DetailType = mixed;
export type PerformanceMarkOptions = $ReadOnly<{
detail?: DetailType,
@@ -1,38 +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
*/
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
}
@@ -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 MaybeNativePerformance from '../specs/NativePerformance';
import NativePerformance from '../specs/NativePerformance';
import * as Fantom from '@react-native/fantom';
import nullthrows from 'nullthrows';
import {useState} from 'react';
@@ -22,8 +22,6 @@ 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;
@@ -195,7 +193,7 @@ describe('Event Timing API', () => {
});
it('reports number of dispatched events via performance.eventCounts', () => {
NativePerformance.clearEventCountsForTesting?.();
NativePerformance?.clearEventCountsForTesting?.();
const root = Fantom.createRoot();
Fantom.runTask(() => {
@@ -18,11 +18,8 @@ import type {
import ensureInstance from '../../../__tests__/utilities/ensureInstance';
import DOMException from '../../errors/DOMException';
import MaybeNativePerformance from '../specs/NativePerformance';
import NativePerformance from '../specs/NativePerformance';
import {PerformanceMark, PerformanceMeasure} from '../UserTiming';
import nullthrows from 'nullthrows';
const NativePerformance = nullthrows(MaybeNativePerformance);
declare var performance: Performance;
@@ -47,7 +44,7 @@ describe('User Timing', () => {
describe('mark', () => {
it('works with default timestamp', () => {
NativePerformance.setCurrentTimeStampForTesting?.(25);
NativePerformance?.setCurrentTimeStampForTesting?.(25);
const mark = performance.mark('mark-now');
@@ -147,7 +144,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', {});
@@ -160,7 +157,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,
@@ -175,7 +172,7 @@ describe('User Timing', () => {
});
it('works with start mark', () => {
NativePerformance.setCurrentTimeStampForTesting?.(25);
NativePerformance?.setCurrentTimeStampForTesting?.(25);
performance.mark('start-mark', {
startTime: 10,
@@ -194,7 +191,7 @@ describe('User Timing', () => {
});
it('works with end mark', () => {
NativePerformance.setCurrentTimeStampForTesting?.(25);
NativePerformance?.setCurrentTimeStampForTesting?.(25);
performance.mark('end-mark', {
startTime: 50,
@@ -213,7 +210,7 @@ describe('User Timing', () => {
});
it('works with start mark and end mark', () => {
NativePerformance.setCurrentTimeStampForTesting?.(25);
NativePerformance?.setCurrentTimeStampForTesting?.(25);
performance.mark('start-mark', {
startTime: 10,
@@ -378,7 +375,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');
@@ -391,7 +388,7 @@ describe('User Timing', () => {
});
it('works with startMark', () => {
NativePerformance.setCurrentTimeStampForTesting?.(25);
NativePerformance?.setCurrentTimeStampForTesting?.(25);
performance.mark('start-mark', {
startTime: 10,
@@ -411,7 +408,7 @@ describe('User Timing', () => {
});
it('works with startMark and endMark', () => {
NativePerformance.setCurrentTimeStampForTesting?.(25);
NativePerformance?.setCurrentTimeStampForTesting?.(25);
performance.mark('start-mark', {
startTime: 10,
@@ -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,25 +67,6 @@ 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 */) {
@@ -0,0 +1,8 @@
<?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,7 +50,6 @@
</queries>
<application
android:usesCleartextTraffic="${usesCleartextTraffic}"
android:name=".RNTesterApplication"
android:allowBackup="true"
android:banner="@drawable/tv_banner"
@@ -10,8 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import RNTesterBlock from '../../components/RNTesterBlock';
import RNTesterPage from '../../components/RNTesterPage';
import RNTesterText from '../../components/RNTesterText';
@@ -284,4 +282,4 @@ exports.examples = [
return <AccessibilityAndroidExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,7 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {EventSubscription, GestureResponderEvent} from 'react-native';
import RNTesterBlock from '../../components/RNTesterBlock';
@@ -2251,4 +2250,4 @@ exports.examples = [
return <LabelCooptingExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,8 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
const {RNTesterThemeContext} = require('../../components/RNTesterTheme');
const React = require('react');
const {Alert, Text, View} = require('react-native');
@@ -85,4 +83,4 @@ exports.examples = [
return <AccessibilityIOSExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,7 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {HostInstance} from 'react-native';
import {RNTesterThemeContext} from '../../components/RNTesterTheme';
@@ -574,4 +573,4 @@ exports.examples = [
return <ShareScreenshotAnchorExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,8 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import AnExSet from './AnExSet';
import React from 'react';
import {
@@ -398,4 +396,4 @@ exports.examples = [
return <AnExApp />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -8,7 +8,6 @@
* @format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {ColorSchemeName} from 'react-native';
import RNTesterText from '../../components/RNTesterText';
@@ -246,4 +245,4 @@ exports.examples = [
return <ToggleNativeAppearance />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -8,8 +8,6 @@
* @format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import * as React from 'react';
import {Image, StyleSheet, View} from 'react-native';
@@ -193,4 +191,4 @@ exports.examples = [
);
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,8 +10,6 @@
'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');
@@ -223,7 +221,7 @@ exports.examples = [
);
},
},
] as Array<RNTesterModuleExample>;
];
const styles = StyleSheet.create({
container: {
@@ -10,8 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import RNTesterBlock from '../../components/RNTesterBlock';
import RNTesterPage from '../../components/RNTesterPage';
import RNTesterText from '../../components/RNTesterText';
@@ -118,4 +116,4 @@ exports.examples = [
return <ContentURLAndroidExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -8,7 +8,6 @@
* @format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {Node} from 'react';
import React from 'react';
@@ -38,4 +37,4 @@ exports.examples = [
);
},
},
] as Array<RNTesterModuleExample>;
];
@@ -8,8 +8,6 @@
* @format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import * as React from 'react';
import {StyleSheet, Text, View} from 'react-native';
@@ -109,4 +107,4 @@ exports.examples = [
description: 'Views with a cursor do not get flattened',
render: CursorExampleViewFlattening,
},
] as Array<RNTesterModuleExample>;
];
@@ -8,8 +8,6 @@
* @format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import * as React from 'react';
import {Alert, Button, DevSettings} from 'react-native';
@@ -46,4 +44,4 @@ exports.examples = [
);
},
},
] as Array<RNTesterModuleExample>;
];
@@ -8,8 +8,6 @@
* @format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import RNTesterText from '../../components/RNTesterText';
import React, {useEffect, useState} from 'react';
import {Dimensions, useWindowDimensions} from 'react-native';
@@ -62,4 +60,4 @@ exports.examples = [
return <DimensionsSubscription dim="screen" />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,7 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {Node} from 'react';
import {RNTesterThemeContext} from '../../components/RNTesterTheme';
@@ -105,4 +104,4 @@ exports.examples = [
return <Drawer />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -9,7 +9,6 @@
*/
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {ViewProps} from 'react-native';
import React, {useState} from 'react';
@@ -156,4 +155,4 @@ exports.examples = [
return <AddChildrenForInteropLayer testID="add-children" />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -8,8 +8,6 @@
* @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';
@@ -186,5 +184,5 @@ export default {
return <PressableExample />;
},
},
] as Array<RNTesterModuleExample>,
],
};
@@ -10,8 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import {useTheme} from '../../components/RNTesterTheme';
import {useState} from 'react';
import {
@@ -113,4 +111,4 @@ exports.examples = [
return <InputAccessoryViewExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,8 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import RNTesterText from '../../components/RNTesterText';
import * as React from 'react';
import {View} from 'react-native';
@@ -21,7 +19,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: Array<RNTesterModuleExample> = [
export const examples = [
{
title: 'View flex',
render(): React.Node {
@@ -8,8 +8,6 @@
* @format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import RNTesterText from '../../components/RNTesterText';
import React from 'react';
import {PanResponder, ScrollView, StyleSheet, View} from 'react-native';
@@ -62,7 +60,7 @@ exports.examples = [
);
},
},
] as Array<RNTesterModuleExample>;
];
const styles = StyleSheet.create({
container: {
@@ -10,8 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import React, {useState} from 'react';
import {
Alert,
@@ -278,4 +276,4 @@ exports.examples = [
return <KeyboardAvoidingContentContainerStyle />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,8 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import RNTesterText from '../../components/RNTesterText';
import React from 'react';
import {
@@ -391,4 +389,4 @@ exports.examples = [
return <LayoutUpdateExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,7 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {
ViewLayout,
ViewLayoutEvent,
@@ -164,4 +163,4 @@ exports.examples = [
return <LayoutEventExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,7 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
import RNTesterBlock from '../../components/RNTesterBlock';
@@ -211,4 +210,4 @@ exports.examples = [
return <LayoutExample />;
},
},
] as Array<RNTesterModuleExample>;
];
@@ -10,7 +10,6 @@
'use strict';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {ViewStyleProp} from 'react-native/Libraries/StyleSheet/StyleSheet';
import RNTesterText from '../../components/RNTesterText';
@@ -306,4 +305,4 @@ exports.examples = [
);
},
},
] as Array<RNTesterModuleExample>;
];
@@ -8,8 +8,6 @@
* @format
*/
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import RNTesterBlock from '../../components/RNTesterBlock';
import RNTesterText from '../../components/RNTesterText';
import React from 'react';
@@ -170,4 +168,4 @@ exports.examples = [
return <OpenSettingsExample />;
},
},
] as Array<RNTesterModuleExample>;
];

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