Compare commits

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

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

## Changelog:

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


Test Plan:
Tested locally with RNTester by doing:

```
./gradlew installDebugOptimized
```

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

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

Rollback Plan:

Reviewed By: cipolleschi

Differential Revision: D78351347

Pulled By: cortinico
2025-07-18 07:43:44 -07:00
Jakub Piasecki 5cda3065ce Update font scale on Android when recreating RootView (#52595)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52595

Changelog: [ANDROID][FIXED] Update font scale when recreating `RootView`

At the moment `enableFontScaleChangesUpdatingLayout` flag only works when the activity is configured to handle font scale changes by itself (`configChanges="fontScale"` in the manifest).

When that configuration is missing, the OS handles the font scale changes by recreating the activity, but in this case the path responsible for updating internally kept font size isn't executed.

This diff updates the RootView, so that the display metrics are also updated when it's created. Alternative approach would be to do that on the Activity, but that assumes usage of `ReactActivity`.

Reviewed By: NickGerleman

Differential Revision: D78323174

fbshipit-source-id: e48583091767497b5dfd4f2d938329530de4068d
2025-07-18 06:50:29 -07:00
Rubén Norte 532b415960 Throw an error when using an unrecognized @fantom_ prefixed pragma (#52701)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52701

Changelog: [internal]

This adds validation for Fantom test file pragmas to avoid ignoring configurations accidentally when introducing typos.

Reviewed By: rshest

Differential Revision: D78550866

fbshipit-source-id: 7123bfb39573adbb1adf417c232cf7d4cae4cd25
2025-07-18 04:19:28 -07:00
Rubén Norte adef486333 Assume NativePerformance will be available if the Performance module is loaded (#52671)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52671

Changelog: [internal]

When setting up the global `performance` value, we check if the native module exists to decide what version to setup: the legacy "barebones" version or the modern version that's mostly spec compliant.

As we do that check and we shouldn't be requiring the module from anywhere else, it should be safe to always assume the module will be defined if we load the `Performance` class, so we can avoid checks in all methods.

NOTE: I've kept some checks for a few methods that are still not fully propagated (new methods like `reportMark`, `reportMeasure` and `getMarkTime`).

This improves performance slightly for `performance` methods:

* Before

| (index) | Task name                                                 | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples |
| ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- |
| 0       | 'mark (default)'                                          | '1803.32 ± 0.71%'    | '1763.00'           | '563849 ± 0.01%'           | '567215'                  | 554908  |
| 1       | 'mark (with custom startTime)'                            | '1762.39 ± 1.67%'    | '1692.00'           | '587832 ± 0.01%'           | '591017'                  | 567414  |
| 2       | 'measure (default)'                                       | '1879.47 ± 1.44%'    | '1813.00'           | '548962 ± 0.01%'           | '551572'                  | 532064  |
| 3       | 'measure (with start and end timestamps)'                 | '1916.65 ± 0.84%'    | '1873.00'           | '531258 ± 0.01%'           | '533903'                  | 521743  |
| 4       | 'measure (with mark names)'                               | '2049.84 ± 0.44%'    | '2013.00'           | '492799 ± 0.01%'           | '496771'                  | 487844  |
| 5       | 'clearMarks'                                              | '719.87 ± 0.04%'     | '711.00'            | '1403602 ± 0.01%'          | '1406470'                 | 1389136 |
| 6       | 'clearMeasures'                                           | '710.04 ± 0.04%'     | '701.00'            | '1421610 ± 0.01%'          | '1426534'                 | 1408373 |
| 7       | 'mark + clearMarks'                                       | '2256.56 ± 1.10%'    | '2143.00'           | '460721 ± 0.02%'           | '466636'                  | 443152  |
| 8       | 'measure + clearMeasures (with start and end timestamps)' | '2345.44 ± 1.11%'    | '2244.00'           | '442395 ± 0.02%'           | '445633'                  | 426360  |
| 9       | 'measure + clearMeasures (with mark names)'               | '2349.55 ± 0.61%'    | '2283.00'           | '434370 ± 0.02%'           | '438020'                  | 425613  |

* After

| (index) | Task name                                                 | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples |
| ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- |
| 0       | 'mark (default)'                                          | '1791.47 ± 1.08%'    | '1732.00'           | '573787 ± 0.01%'           | '577367'                  | 558202  |
| 1       | 'mark (with custom startTime)'                            | '1699.41 ± 1.27%'    | '1642.00'           | '605188 ± 0.01%'           | '609013'                  | 588441  |
| 2       | 'measure (default)'                                       | '1820.92 ± 1.39%'    | '1763.00'           | '563437 ± 0.01%'           | '567215'                  | 549173  |
| 3       | 'measure (with start and end timestamps)'                 | '1923.57 ± 1.65%'    | '1852.00'           | '537112 ± 0.01%'           | '539957'                  | 519867  |
| 4       | 'measure (with mark names)'                               | '2036.09 ± 1.05%'    | '1983.00'           | '500406 ± 0.01%'           | '504286'                  | 491139  |
| 5       | 'clearMarks'                                              | '657.49 ± 0.07%'     | '641.00'            | '1543793 ± 0.01%'          | '1560062'                 | 1520939 |
| 6       | 'clearMeasures'                                           | '669.02 ± 0.09%'     | '651.00'            | '1520386 ± 0.01%'          | '1536098'                 | 1494730 |
| 7       | 'mark + clearMarks'                                       | '2213.09 ± 1.53%'    | '2103.00'           | '470008 ± 0.02%'           | '475511'                  | 451858  |
| 8       | 'measure + clearMeasures (with start and end timestamps)' | '2353.15 ± 1.27%'    | '2214.00'           | '448563 ± 0.02%'           | '451671'                  | 424962  |
| 9       | 'measure + clearMeasures (with mark names)'               | '2298.28 ± 0.62%'    | '2243.00'           | '441703 ± 0.02%'           | '445831'                  | 435108  |

Reviewed By: hoxyq

Differential Revision: D78412931

fbshipit-source-id: 07390722bb00a51847cb5625b2adb4e26f89e5d2
2025-07-18 03:44:21 -07:00
Rubén Norte c9367a8d86 Remove optionality for stable methods of the NativePerformance module (#52668)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52668

Changelog: [internal]

These methods have been available for months so no need to consider backwards compatibility with older binaries.

Reviewed By: huntie

Differential Revision: D78412932

fbshipit-source-id: 0f1d541c36c98a4cc8c847d1ee7a08a9d0f4b849
2025-07-18 03:44:21 -07:00
Alex Hunt e247be793c Lower minimum Node.js version to 20.19.4 (#52678)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52678

From partner feedback, there's still appetite to support Node 20.x for the next <1y of life. Lower min version to `20.19.4` (Jul 2025) and widen test matrix in CI.

Changelog:
[General][Breaking] - Our new minimum Node version is Node.js 20 (Overrides #51840)

Reviewed By: cortinico

Differential Revision: D78494491

fbshipit-source-id: c8d9dc6250cb11f8a12ca7e761b65f4a8dae9265
2025-07-18 03:32:13 -07:00
Nick Gerleman 3f9b19eb89 Fix build_android GHA Job (#52694)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52694

build_android job is always failing, with this assertion. Seems like find/replace in D78484060 gone wrong?

Changelog: [Internal]

Reviewed By: sbuggay

Differential Revision: D78534334

fbshipit-source-id: 291bdd01b41fa6efea00ed63a0dee8bdb14cbc3a
2025-07-17 20:42:02 -07:00
40 changed files with 170 additions and 172 deletions
+1 -1
View File
@@ -583,7 +583,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: ["24", "22"]
node-version: ["24", "22", "20"]
steps:
- name: Checkout
uses: actions/checkout@v4
+1 -1
View File
@@ -17,7 +17,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"files": [
"path-support.js",
+1 -1
View File
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"files": [
"index.js"
+1 -1
View File
@@ -43,6 +43,6 @@
}
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"files": [
"dist"
+1 -1
View File
@@ -20,6 +20,6 @@
"BUILD_INFO"
],
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -26,7 +26,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 22.14.0",
"node": ">= 20.19.4",
"electron": ">=36.3.0"
},
"dependencies": {
+1 -1
View File
@@ -35,7 +35,7 @@
"ws": "^6.2.3"
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"devDependencies": {
"selfsigned": "^2.4.1",
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"main": "index.js",
"dependencies": {
@@ -22,6 +22,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -36,6 +36,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"scripts": {
"build": "./gradlew build",
@@ -18,6 +18,7 @@ import com.facebook.react.tasks.GenerateEntryPointTask
import com.facebook.react.tasks.GeneratePackageListTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildTypesForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevServerLocation
import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
@@ -84,6 +85,7 @@ class ReactPlugin : Plugin<Project> {
configureAutolinking(project, extension)
configureCodegen(project, extension, rootExtension, isLibrary = false)
configureResources(project, extension)
configureBuildTypesForApp(project)
}
// Library Only Configuration
@@ -19,6 +19,7 @@ import java.net.Inet4Address
import java.net.NetworkInterface
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.plus
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
@@ -27,6 +28,26 @@ import org.w3c.dom.Element
@Suppress("UnstableApiUsage")
internal object AgpConfiguratorUtils {
fun configureBuildTypesForApp(project: Project) {
val action =
Action<AppliedPlugin> {
project.extensions
.getByType(ApplicationAndroidComponentsExtension::class.java)
.finalizeDsl { ext ->
ext.buildTypes {
val debug =
getByName("debug").apply {
manifestPlaceholders["usesCleartextTraffic"] = "true"
}
getByName("release").apply {
manifestPlaceholders["usesCleartextTraffic"] = "false"
}
}
}
}
project.pluginManager.withPlugin("com.android.application", action)
}
fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
val action =
Action<AppliedPlugin> {
@@ -55,7 +55,7 @@ class OsTest {
@Test
fun unixifyPath_withAWindowsPath_convertsItCorrectly() {
val aWindowsPath = "C:\\just\\a\\windows\\path\\"
val aWindowsPath = "D:\\just\\a\\windows\\path\\"
assertThat("/D/just/a/windows/path/").isEqualTo(aWindowsPath.unixifyPath())
}
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"exports": {
".": "./src/index.js",
+1 -1
View File
@@ -30,6 +30,6 @@
}
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"files": [
"console.js",
@@ -13,7 +13,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"main": "src/index.js",
"files": [
@@ -14,7 +14,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"main": "src/index.js",
"files": [
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"scripts": {
"build": "yarn clean && node scripts/build.js --verbose",
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"exports": {
".": "./src/index.js",
+2 -1
View File
@@ -8,12 +8,13 @@
* @format
*/
import Performance from '../../src/private/webapis/performance/Performance';
import NativePerformance from '../../src/private/webapis/performance/specs/NativePerformance';
// In case if the native implementation of the Performance API is available, use it,
// otherwise fall back to the legacy/default one, which only defines 'Performance.now()'
if (NativePerformance) {
const Performance =
require('../../src/private/webapis/performance/Performance').default;
// $FlowExpectedError[cannot-write]
global.performance = new Performance();
} else {
@@ -135,6 +135,10 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private void init() {
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
setClipChildren(false);
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
}
}
@Override
@@ -113,8 +113,8 @@ void UIManagerBinding::dispatchEventToJS(
}
auto instanceHandle = eventTarget != nullptr ? [&]() {
auto retrievedInstanceHandle = eventTarget->getInstanceHandle(runtime);
if (retrievedInstanceHandle.isUndefined()) {
auto instanceHandle = eventTarget->getInstanceHandle(runtime);
if (instanceHandle.isUndefined()) {
return jsi::Value::null();
}
@@ -126,7 +126,7 @@ void UIManagerBinding::dispatchEventToJS(
react_native_assert(payload.isObject());
payload.asObject(runtime).setProperty(
runtime, "target", eventTarget->getTag());
return retrievedInstanceHandle;
return instanceHandle;
}()
: jsi::Value::null();
+1 -1
View File
@@ -21,7 +21,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"bin": {
"react-native": "cli.js"
@@ -15,9 +15,11 @@ import type {
PerformanceEntryJSON,
} from './PerformanceEntry';
import {warnNoNativePerformance} from './internals/Utilities';
import {PerformanceEntry} from './PerformanceEntry';
import NativePerformance from './specs/NativePerformance';
import MaybeNativePerformance from './specs/NativePerformance';
import nullthrows from 'nullthrows';
const NativePerformance = nullthrows(MaybeNativePerformance);
export type PerformanceEventTimingJSON = {
...PerformanceEntryJSON,
@@ -86,14 +88,8 @@ function getCachedEventCounts(): Map<string, number> {
return cachedEventCounts;
}
if (!NativePerformance || !NativePerformance?.getEventCounts) {
warnNoNativePerformance();
cachedEventCounts = new Map();
return cachedEventCounts;
}
const eventCounts = new Map<string, number>(
NativePerformance.getEventCounts?.() ?? [],
NativePerformance.getEventCounts() ?? [],
);
cachedEventCounts = eventCounts;
@@ -35,8 +35,9 @@ import {
} from './internals/Utilities';
import MemoryInfo from './MemoryInfo';
import ReactNativeStartupTiming from './ReactNativeStartupTiming';
import NativePerformance from './specs/NativePerformance';
import MaybeNativePerformance from './specs/NativePerformance';
import {PerformanceMark, PerformanceMeasure} from './UserTiming';
import nullthrows from 'nullthrows';
export type PerformanceMeasureOptions =
| $ReadOnly<{
@@ -58,11 +59,13 @@ export type PerformanceMeasureOptions =
const ENTRY_TYPES_AVAILABLE_FROM_TIMELINE: $ReadOnlyArray<PerformanceEntryType> =
['mark', 'measure'];
const cachedReportMark = NativePerformance?.reportMark;
const cachedReportMeasure = NativePerformance?.reportMeasure;
const cachedGetMarkTime = NativePerformance?.getMarkTime;
const cachedNativeClearMarks = NativePerformance?.clearMarks;
const cachedNativeClearMeasures = NativePerformance?.clearMeasures;
const NativePerformance = nullthrows(MaybeNativePerformance);
const cachedReportMark = NativePerformance.reportMark;
const cachedReportMeasure = NativePerformance.reportMeasure;
const cachedGetMarkTime = NativePerformance.getMarkTime;
const cachedNativeClearMarks = NativePerformance.clearMarks;
const cachedNativeClearMeasures = NativePerformance.clearMeasures;
const MARK_OPTIONS_REUSABLE_OBJECT: {...PerformanceMarkOptions} = {
startTime: 0,
@@ -98,53 +101,46 @@ export default class Performance {
// Get the current JS memory information.
get memory(): MemoryInfo {
if (NativePerformance?.getSimpleMemoryInfo) {
// JSI API implementations may have different variants of names for the JS
// heap information we need here. We will parse the result based on our
// guess of the implementation for now.
const memoryInfo = NativePerformance.getSimpleMemoryInfo();
if (memoryInfo.hasOwnProperty('hermes_heapSize')) {
// We got memory information from Hermes
const {
hermes_heapSize: totalJSHeapSize,
hermes_allocatedBytes: usedJSHeapSize,
} = memoryInfo;
// JSI API implementations may have different variants of names for the JS
// heap information we need here. We will parse the result based on our
// guess of the implementation for now.
const memoryInfo = NativePerformance.getSimpleMemoryInfo();
if (memoryInfo.hasOwnProperty('hermes_heapSize')) {
// We got memory information from Hermes
const {
hermes_heapSize: totalJSHeapSize,
hermes_allocatedBytes: usedJSHeapSize,
} = memoryInfo;
return new MemoryInfo({
jsHeapSizeLimit: null, // We don't know the heap size limit from Hermes.
totalJSHeapSize,
usedJSHeapSize,
});
} else {
// JSC and V8 has no native implementations for memory information in JSI::Instrumentation
return new MemoryInfo();
}
return new MemoryInfo({
jsHeapSizeLimit: null, // We don't know the heap size limit from Hermes.
totalJSHeapSize,
usedJSHeapSize,
});
} else {
// JSC and V8 has no native implementations for memory information in JSI::Instrumentation
return new MemoryInfo();
}
return new MemoryInfo();
}
// Startup metrics is not used in web, but only in React Native.
get rnStartupTiming(): ReactNativeStartupTiming {
if (NativePerformance?.getReactNativeStartupTiming) {
const {
startTime,
endTime,
initializeRuntimeStart,
initializeRuntimeEnd,
executeJavaScriptBundleEntryPointStart,
executeJavaScriptBundleEntryPointEnd,
} = NativePerformance.getReactNativeStartupTiming();
return new ReactNativeStartupTiming({
startTime,
endTime,
initializeRuntimeStart,
initializeRuntimeEnd,
executeJavaScriptBundleEntryPointStart,
executeJavaScriptBundleEntryPointEnd,
});
}
return new ReactNativeStartupTiming();
const {
startTime,
endTime,
initializeRuntimeStart,
initializeRuntimeEnd,
executeJavaScriptBundleEntryPointStart,
executeJavaScriptBundleEntryPointEnd,
} = NativePerformance.getReactNativeStartupTiming();
return new ReactNativeStartupTiming({
startTime,
endTime,
initializeRuntimeStart,
initializeRuntimeEnd,
executeJavaScriptBundleEntryPointStart,
executeJavaScriptBundleEntryPointEnd,
});
}
mark(
@@ -217,11 +213,6 @@ export default class Performance {
}
clearMarks(markName?: string): void {
if (!cachedNativeClearMarks) {
warnNoNativePerformance();
return;
}
cachedNativeClearMarks(markName);
}
@@ -414,11 +405,6 @@ export default class Performance {
}
clearMeasures(measureName?: string): void {
if (!cachedNativeClearMeasures) {
warnNoNativePerformance();
return;
}
cachedNativeClearMeasures(measureName);
}
@@ -434,10 +420,6 @@ export default class Performance {
* https://www.w3.org/TR/performance-timeline/#extensions-to-the-performance-interface
*/
getEntries(): PerformanceEntryList {
if (!NativePerformance?.getEntries) {
warnNoNativePerformance();
return [];
}
return NativePerformance.getEntries().map(rawToPerformanceEntry);
}
@@ -450,11 +432,6 @@ export default class Performance {
return [];
}
if (!NativePerformance?.getEntriesByType) {
warnNoNativePerformance();
return [];
}
return NativePerformance.getEntriesByType(
performanceEntryTypeToRaw(entryType),
).map(rawToPerformanceEntry);
@@ -472,11 +449,6 @@ export default class Performance {
return [];
}
if (!NativePerformance?.getEntriesByName) {
warnNoNativePerformance();
return [];
}
return NativePerformance.getEntriesByName(
entryName,
entryType != null ? performanceEntryTypeToRaw(entryType) : undefined,
@@ -21,12 +21,13 @@ import {
rawToPerformanceEntry,
rawToPerformanceEntryType,
} from './internals/RawPerformanceEntry';
import {warnNoNativePerformance} from './internals/Utilities';
import NativePerformance from './specs/NativePerformance';
import MaybeNativePerformance from './specs/NativePerformance';
import nullthrows from 'nullthrows';
export {PerformanceEntry} from './PerformanceEntry';
const NativePerformance = nullthrows(MaybeNativePerformance);
export class PerformanceObserverEntryList {
#entries: PerformanceEntryList;
@@ -75,13 +76,6 @@ export interface PerformanceObserverInit {
}
function getSupportedPerformanceEntryTypes(): $ReadOnlyArray<PerformanceEntryType> {
if (!NativePerformance) {
return Object.freeze([]);
}
if (!NativePerformance.getSupportedPerformanceEntryTypes) {
// fallback if getSupportedPerformanceEntryTypes is not defined on native side
return Object.freeze(['mark', 'measure', 'event']);
}
return Object.freeze(
NativePerformance.getSupportedPerformanceEntryTypes().map(
rawToPerformanceEntryType,
@@ -120,11 +114,6 @@ export class PerformanceObserver {
}
observe(options: PerformanceObserverInit): void {
if (!NativePerformance || NativePerformance.observe == null) {
warnNoNativePerformance();
return;
}
this.#validateObserveOptions(options);
if (this.#nativeObserverHandle == null) {
@@ -135,12 +124,12 @@ export class PerformanceObserver {
if (options.entryTypes) {
this.#type = 'multiple';
NativePerformance.observe?.(observerHandle, {
NativePerformance.observe(observerHandle, {
entryTypes: options.entryTypes.map(performanceEntryTypeToRaw),
});
} else if (options.type) {
this.#type = 'single';
NativePerformance.observe?.(observerHandle, {
NativePerformance.observe(observerHandle, {
type: performanceEntryTypeToRaw(options.type),
buffered: options.buffered,
durationThreshold: options.durationThreshold,
@@ -149,12 +138,7 @@ export class PerformanceObserver {
}
disconnect(): void {
if (!NativePerformance) {
warnNoNativePerformance();
return;
}
if (this.#nativeObserverHandle == null || !NativePerformance.disconnect) {
if (this.#nativeObserverHandle == null) {
return;
}
@@ -162,16 +146,11 @@ export class PerformanceObserver {
}
#createNativeObserver(): OpaqueNativeObserverHandle | null {
if (!NativePerformance || !NativePerformance.createObserver) {
warnNoNativePerformance();
return null;
}
this.#calledAtLeastOnce = false;
const observerHandle: OpaqueNativeObserverHandle =
NativePerformance.createObserver(() => {
const rawEntries = NativePerformance.takeRecords?.(
const rawEntries = NativePerformance.takeRecords(
observerHandle,
true, // sort records
);
@@ -185,7 +164,7 @@ export class PerformanceObserver {
let droppedEntriesCount = 0;
if (!this.#calledAtLeastOnce) {
droppedEntriesCount =
NativePerformance.getDroppedEntriesCount?.(observerHandle) ?? 0;
NativePerformance.getDroppedEntriesCount(observerHandle);
this.#calledAtLeastOnce = true;
}
@@ -13,7 +13,7 @@ import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type Performance from 'react-native/src/private/webapis/performance/Performance';
import type {PerformanceObserverEntryList} from 'react-native/src/private/webapis/performance/PerformanceObserver';
import NativePerformance from '../specs/NativePerformance';
import MaybeNativePerformance from '../specs/NativePerformance';
import * as Fantom from '@react-native/fantom';
import nullthrows from 'nullthrows';
import {useState} from 'react';
@@ -22,6 +22,8 @@ import setUpPerformanceObserver from 'react-native/src/private/setup/setUpPerfor
import {PerformanceEventTiming} from 'react-native/src/private/webapis/performance/EventTiming';
import {PerformanceObserver} from 'react-native/src/private/webapis/performance/PerformanceObserver';
const NativePerformance = nullthrows(MaybeNativePerformance);
setUpPerformanceObserver();
declare var performance: Performance;
@@ -193,7 +195,7 @@ describe('Event Timing API', () => {
});
it('reports number of dispatched events via performance.eventCounts', () => {
NativePerformance?.clearEventCountsForTesting?.();
NativePerformance.clearEventCountsForTesting?.();
const root = Fantom.createRoot();
Fantom.runTask(() => {
@@ -18,8 +18,11 @@ import type {
import ensureInstance from '../../../__tests__/utilities/ensureInstance';
import DOMException from '../../errors/DOMException';
import NativePerformance from '../specs/NativePerformance';
import MaybeNativePerformance from '../specs/NativePerformance';
import {PerformanceMark, PerformanceMeasure} from '../UserTiming';
import nullthrows from 'nullthrows';
const NativePerformance = nullthrows(MaybeNativePerformance);
declare var performance: Performance;
@@ -44,7 +47,7 @@ describe('User Timing', () => {
describe('mark', () => {
it('works with default timestamp', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
const mark = performance.mark('mark-now');
@@ -144,7 +147,7 @@ describe('User Timing', () => {
describe('measure', () => {
describe('with measureOptions', () => {
it('uses 0 as default start and now as default end', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
const measure = performance.measure('measure-with-defaults', {});
@@ -157,7 +160,7 @@ describe('User Timing', () => {
});
it('works with a start timestamp', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
const measure = performance.measure('measure-with-start-timestamp', {
start: 10,
@@ -172,7 +175,7 @@ describe('User Timing', () => {
});
it('works with start mark', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
performance.mark('start-mark', {
startTime: 10,
@@ -191,7 +194,7 @@ describe('User Timing', () => {
});
it('works with end mark', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
performance.mark('end-mark', {
startTime: 50,
@@ -210,7 +213,7 @@ describe('User Timing', () => {
});
it('works with start mark and end mark', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
performance.mark('start-mark', {
startTime: 10,
@@ -375,7 +378,7 @@ describe('User Timing', () => {
describe('with startMark / endMark', () => {
it('uses 0 as default start and now as default end', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
const measure = performance.measure('measure-with-defaults');
@@ -388,7 +391,7 @@ describe('User Timing', () => {
});
it('works with startMark', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
performance.mark('start-mark', {
startTime: 10,
@@ -408,7 +411,7 @@ describe('User Timing', () => {
});
it('works with startMark and endMark', () => {
NativePerformance?.setCurrentTimeStampForTesting?.(25);
NativePerformance.setCurrentTimeStampForTesting?.(25);
performance.mark('start-mark', {
startTime: 10,
@@ -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;
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
</manifest>
@@ -50,6 +50,7 @@
</queries>
<application
android:usesCleartextTraffic="${usesCleartextTraffic}"
android:name=".RNTesterApplication"
android:allowBackup="true"
android:banner="@drawable/tv_banner"
+1 -1
View File
@@ -10,7 +10,7 @@
"directory": "packages/rn-tester"
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"scripts": {
"start": "react-native start",
+1 -1
View File
@@ -17,7 +17,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"exports": {
".": {
+1 -1
View File
@@ -33,6 +33,6 @@
"rxjs": "^7.8.1"
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
"private": true,
"license": "MIT",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"devDependencies": {
"@rnx-kit/rn-changelog-generator": "^0.4.0",
@@ -5,7 +5,7 @@
"description": "TypeScript related unit test for @react-native/codegen",
"license": "MIT",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"scripts": {
"build": "yarn clean && node scripts/build.js --verbose && tsc",
@@ -82,6 +82,13 @@ const FANTOM_BENCHMARK_DEFAULT_MODE: FantomTestConfigMode =
const MAX_FANTOM_CONFIGURATION_VARIATIONS = 12;
const VALID_FANTOM_PRAGMAS = [
'fantom_mode',
'fantom_flags',
'fantom_hermes_variant',
'fantom_react_fb_flags',
];
/**
* Extracts the Fantom configurations from the test file, specified as part of
* the docblock comment. E.g.:
@@ -118,6 +125,8 @@ export default function getFantomTestConfigs(
const docblock = extract(testContents);
const pragmas = parse(docblock) as DocblockPragmas;
verifyFantomPragmas(pragmas);
const config: FantomTestConfig = {
mode: DEFAULT_MODE,
hermesVariant: DEFAULT_HERMES_VARIANT,
@@ -401,3 +410,19 @@ function parseFeatureFlagValue<T: boolean | number | string>(
throw new Error(`Unsupported feature flag type: ${typeof defaultValue}`);
}
}
function verifyFantomPragmas(pragmas: DocblockPragmas): void {
for (const pragma of Object.keys(pragmas)) {
if (
pragma.startsWith('fantom_') &&
!VALID_FANTOM_PRAGMAS.includes(pragma)
) {
const validFantomPragmas = VALID_FANTOM_PRAGMAS.map(p => `@${p}`).join(
', ',
);
throw new Error(
`Unrecognized Fantom pragma @${pragma}. Valid pragmas are ${validFantomPragmas}.`,
);
}
}
}