Compare commits

...
Author SHA1 Message Date
Devmate Bot 0ec20e0c90 xplat/js/react-native-github/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/internal/PrepareBoostTaskTest.kt
Reviewed By: cortinico

Differential Revision: D82206808
2025-09-11 03:15:23 -07:00
Riccardo Cipolleschi dab8a45623 cipolleschi/fix rctnetworking not building (#53713)
Summary:
Fix Cocoapods builds with static libraries and dynamic frameworks

## Changelog:
[Internal] -

Pull Request resolved: https://github.com/facebook/react-native/pull/53713

Test Plan: Tested locally on RNTester

Reviewed By: cortinico

Differential Revision: D82118127

Pulled By: cipolleschi

fbshipit-source-id: 73736c2382be5f6b0770bb7154780e6dcc4aca1c
2025-09-10 12:14:14 -07:00
generatedunixname537391475639613 40c60adacf xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java (#53704)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53704

Reviewed By: cortinico

Differential Revision: D82098400

fbshipit-source-id: e692b8e2bad976805657c2f406831c346198554f
2025-09-10 07:23:21 -07:00
Riccardo Cipolleschi a7cd3cc08f Make codegen generate a Package.swift file for Codegen'd target (#53619)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53619

With this change, we are making Codegen generate a Package.swift file so that we can integrate the `ReactCodegen` and the `ReactAppDependencyProvider` in apps only using SwiftPM

## Changelog
[iOS][Added] - Make codegen generate PAckage.swift file for the codegen targets

Reviewed By: cortinico

Differential Revision: D81769543

fbshipit-source-id: 1f1a1b9f41126e142931d5eda6e75109a69f828c
2025-09-10 05:25:54 -07:00
Riccardo Cipolleschi 87f8e85b7f Move generated code to ReactCodegen and ReactappDependencyProvider folder (#53618)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53618

This change makes sure that we generate the codegen files in the ReactCodegen and ReactAppDependencyProvider folder.

This is necessary as Swift PM needs the source code of packages to be grouped in folders that are children of where the Package.swift is located.

This is not a breaking change, because Cocoapods has been updated accordingly, import/include paths are not changed and the folder layout should not be accessed by anybody directly

## Changelog:
[Internal] -

Reviewed By: cortinico

Differential Revision: D81769522

fbshipit-source-id: 7c70f96a9aa503c4faaf173b94c8ee0e326094a1
2025-09-10 05:25:54 -07:00
Phil Pluckthun f6f5ea0b2e Remove outdated artifacts codegen early return (#53690)
Summary:
Follow-up to https://github.com/facebook/react-native/issues/53503 for a regression

When no React Native module is present this bail condition stops us from generating the artifacts podspec that's needed to complete build.

## Changelog:

[IOS] [FIXED] - Fix regression that skips artifacts code generation

Pull Request resolved: https://github.com/facebook/react-native/pull/53690

Test Plan:
- Create an app **without** any React Native modules, run `pod install`; without this fix the podspec will be missing and the build will fail
  - With expo this can be reproduced using `create-expo-app --template blank-typescript@next` on `react-native@0.81.2`
  - With the community CLI this can be reproduced using `npx react-native-community/cli@latest init test --skip-install --version 0.81.2` and uninstalling `react-native-safe-area-context`

Reviewed By: javache

Differential Revision: D82103491

Pulled By: cipolleschi

fbshipit-source-id: 3d9619b5a935ca920220824b3963a9a107f926ca
2025-09-10 05:24:28 -07:00
Riccardo Cipolleschi 6c49572ee4 Make sure we don't run RN CI on forks (#53707)
Summary:
We had reports from the Community of the RN CI running on forks and causing high costs and bills for them
This change should make sure that the most impactful jobs only runs on the React Native CI and not on forks.

## Changelog:
[Internal] -

Pull Request resolved: https://github.com/facebook/react-native/pull/53707

Test Plan: GHA

Reviewed By: cortinico

Differential Revision: D82107313

Pulled By: cipolleschi

fbshipit-source-id: ff7f418344975e7bb8306a6356d774c26bea3db1
2025-09-10 04:49:14 -07:00
Rubén Norte 529f55c8c9 Implement performance.timeOrigin (#53660)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53660

Changelog: [internal]

(This is internal because these APIs aren't enabled in OSS yet)

Implements `performance.timeOrigin` to allow converting timestamps from `performance.now()` to be based on the Unix epoch.

This implementation isn't fully spec-compliant to align with the current implementation of `performance.now()`, where the base of the clock is system boot time instead of app startup / navigation time.

Reviewed By: huntie

Differential Revision: D82016724

fbshipit-source-id: e3a066721cecf41e2fd963beb94a0a2f1c5d6493
2025-09-10 04:48:55 -07:00
generatedunixname537391475639613 481fb612a2 xplat/js/react-native-github/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/internal/PrepareGflagsTaskTest.kt (#53702)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53702

Reviewed By: cortinico

Differential Revision: D82093601

fbshipit-source-id: 133936f59d155fa1d68358abe4015c7b7b1613a6
2025-09-10 04:41:29 -07:00
Oskar Kwaśniewski 3ce6a05aac fix: add conditional checks for facebook/react-native repo for nightly (#53700)
Summary:
This PR adds additional checks to run nightly jobs only on the main repo.

I noticed pretty heavy usage on my fork of React Native:

<img width="951" height="179" alt="Screenshot 2025-09-10 at 11 47 29" src="https://github.com/user-attachments/assets/91cb9e4a-8658-42bd-bbfe-ffba01b0b3b3" />

I also noticed a typo in this file with output instead of outputs

## Changelog:

[INTERNAL] [FIXED] - add conditional checks for facebook/react-native repo for nightly workflow

Pull Request resolved: https://github.com/facebook/react-native/pull/53700

Test Plan: Check if the syntax is correct.

Reviewed By: cortinico

Differential Revision: D82104958

Pulled By: cipolleschi

fbshipit-source-id: fc2e6e0299345ebd115c7a574a5a8161f2b0ca5c
2025-09-10 03:57:54 -07:00
Jakub Piasecki 813b9441ca Read Hermes V1 opt-in flag from the app's properties (#53665)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53665

Changelog: [ANDROID][FIXED] - Read the Hermes V1 opt-in flag from the apps properties when building from source

Reviewed By: cortinico

Differential Revision: D82018545

fbshipit-source-id: f3c6fdbac190f47b6bf6836105d9e0909d8b86ba
2025-09-10 00:51:41 -07:00
Zeya Peng cf5040b4f8 fix array type parsing in DynamicEventPayload::extractValue (#53689)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53689

## Changelog:

[General] [Fixed] - fix array type parsing in DynamicEventPayload::extractValue

When unwrapping event mapping like `e.nativeEvent.touches[0].locationX` e.g. for Animated.event like below,

```
onTouchMove={Animated.event(
  [
    {
      nativeEvent: {
        touches: {
          0: {locationX: animatedValue},
        },
      },
    },
  ],
  {useNativeDriver: true},
)}
```
here it'll throw exception `terminating due to uncaught exception of type folly::TypeError: TypeError: expected dynamic type 'object', but had type 'array'` when getting into folly dynamic array, because array index in the event path is string instead of integer

Reviewed By: rozele

Differential Revision: D82050538

fbshipit-source-id: ed25c8917b90190c995d1fcd6d60af207e72e270
2025-09-09 21:12:26 -07:00
Luna Wei 019a553ea4 Remove enable_eager_alternate_state_node_cleanup param (#53693)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53693

Changelog: [Internal] - Remove usage of `enableEagerAlternateStateNodeCleanup`  as it is default true now

Reviewed By: yungsters

Differential Revision: D81962955

fbshipit-source-id: 80b0d9321b1162dd79d052b05acb3dc9459e37d2
2025-09-09 18:46:39 -07:00
generatedunixname89002005287564 2ae5a74f06 Fix CQS signal modernize-use-using in xplat/js/react-native-github/packages (#53661)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53661

Reviewed By: javache

Differential Revision: D81764229

fbshipit-source-id: 617716ddb5cf17d41a4927c3a6cce6074278d8b0
2025-09-09 11:37:24 -07:00
generatedunixname537391475639613 3d4a4caac8 xplat/js/react-native-github/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GeneratePackageListTaskTest.kt (#53614)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53614

Reviewed By: javache

Differential Revision: D81764947

fbshipit-source-id: de19d7d89f3a7e19285928f9541b0b47b503d160
2025-09-09 11:34:37 -07:00
Alex Hunt cdc4a8ead4 Wire up background tracing and actions in Perf Monitor (#53460)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53460

Completes the first pass of adding background performance trace controls in the V2 Perf Monitor UI.

Key changes:
- Initiates background trace on `PerfMonitorOverlayManager::init`.
- Wires up background trace recording states in button dialog, and connects `pauseAndAnalyzeBackgroundTrace` and `resumeBackgroundTrace` actions.
- Moves UI manager/view classes into `perfmonitor` subpackage.
- Fixes `responsivenessScore` determination/UI coloring.
- Adds tooltip UI to the overlay view.

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D80807554

fbshipit-source-id: 53360c2d454adfbba40fd795d400b28d90ff9e61
2025-09-09 10:31:04 -07:00
43 changed files with 658 additions and 295 deletions
+1
View File
@@ -19,6 +19,7 @@ on:
jobs:
create_release:
if: github.repository == 'facebook/react-native'
runs-on: ubuntu-latest
steps:
- name: Checkout
+6 -3
View File
@@ -23,6 +23,7 @@ jobs:
prepare_hermes_workspace:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
env:
HERMES_WS_DIR: /tmp/hermes
HERMES_VERSION_FILE: packages/react-native/sdks/.hermesversion
@@ -50,8 +51,8 @@ jobs:
- name: Build HermesC Apple
uses: ./.github/actions/build-hermesc-apple
with:
hermes-version: ${{ needs.prepare_hermes_workspace.output.hermes-version }}
react-native-version: ${{ needs.prepare_hermes_workspace.output.react-native-version }}
hermes-version: ${{ needs.prepare_hermes_workspace.outputs.hermes-version }}
react-native-version: ${{ needs.prepare_hermes_workspace.outputs.react-native-version }}
build_apple_slices_hermes:
runs-on: macos-14
@@ -75,7 +76,7 @@ jobs:
uses: ./.github/actions/build-apple-slices-hermes
with:
flavor: ${{ matrix.flavor }}
slice: ${{ matrix.slice}}
slice: ${{ matrix.slice }}
hermes-version: ${{ needs.prepare_hermes_workspace.outputs.hermes-version }}
react-native-version: ${{ needs.prepare_hermes_workspace.outputs.react-native-version }}
@@ -101,6 +102,7 @@ jobs:
flavor: ${{ matrix.flavor }}
prebuild_apple_dependencies:
if: github.repository == 'facebook/react-native'
uses: ./.github/workflows/prebuild-ios-dependencies.yml
secrets: inherit
@@ -145,6 +147,7 @@ jobs:
build_android:
runs-on: 8-core-ubuntu
if: github.repository == 'facebook/react-native'
needs: [set_release_type]
container:
image: reactnativecommunity/react-native-android:latest
@@ -9,6 +9,7 @@ on:
jobs:
publish_bumped_packages:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
env:
GHA_NPM_TOKEN: ${{ secrets.GHA_NPM_TOKEN }}
steps:
+2
View File
@@ -21,6 +21,7 @@ jobs:
prepare_hermes_workspace:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
env:
HERMES_WS_DIR: /tmp/hermes
HERMES_VERSION_FILE: packages/react-native/sdks/.hermesversion
@@ -98,6 +99,7 @@ jobs:
flavor: ${{ matrix.flavor }}
prebuild_apple_dependencies:
if: github.repository == 'facebook/react-native'
uses: ./.github/workflows/prebuild-ios-dependencies.yml
secrets: inherit
+1
View File
@@ -9,6 +9,7 @@ on:
jobs:
rerun:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
steps:
- name: rerun ${{ inputs.run_id }}
env:
+5
View File
@@ -11,6 +11,7 @@ on:
jobs:
set_release_type:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
outputs:
RELEASE_TYPE: ${{ steps.set_release_type.outputs.RELEASE_TYPE }}
env:
@@ -34,6 +35,7 @@ jobs:
prepare_hermes_workspace:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
env:
HERMES_WS_DIR: /tmp/hermes
HERMES_VERSION_FILE: packages/react-native/sdks/.hermesversion
@@ -112,6 +114,7 @@ jobs:
flavor: ${{ matrix.flavor }}
prebuild_apple_dependencies:
if: github.repository == 'facebook/react-native'
uses: ./.github/workflows/prebuild-ios-dependencies.yml
secrets: inherit
@@ -594,6 +597,7 @@ jobs:
test_js:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
strategy:
fail-fast: false
matrix:
@@ -608,6 +612,7 @@ jobs:
lint:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
@@ -27,6 +27,7 @@ on:
jobs:
validate-dotslash-artifacts:
runs-on: ubuntu-latest
if: github.repository == 'facebook/react-native'
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -23,6 +23,7 @@ import com.facebook.react.utils.PropertyUtils.SCOPED_USE_THIRD_PARTY_JSC
import com.facebook.react.utils.PropertyUtils.USE_THIRD_PARTY_JSC
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.jetbrains.kotlin.gradle.plugin.extraProperties
internal object ProjectUtils {
@@ -75,7 +76,11 @@ internal object ProjectUtils {
(project.hasProperty(HERMES_V1_ENABLED) &&
project.property(HERMES_V1_ENABLED).toString().toBoolean()) ||
(project.hasProperty(SCOPED_HERMES_V1_ENABLED) &&
project.property(SCOPED_HERMES_V1_ENABLED).toString().toBoolean())
project.property(SCOPED_HERMES_V1_ENABLED).toString().toBoolean()) ||
(project.extraProperties.has(HERMES_V1_ENABLED) &&
project.extraProperties.get(HERMES_V1_ENABLED).toString().toBoolean()) ||
(project.extraProperties.has(SCOPED_HERMES_V1_ENABLED) &&
project.extraProperties.get(SCOPED_HERMES_V1_ENABLED).toString().toBoolean())
internal fun Project.needsCodegenFromPackageJson(rootProperty: DirectoryProperty): Boolean {
val parsedPackageJson = readPackageJsonFile(this, rootProperty)
@@ -33,9 +33,9 @@ class GeneratePackageListTaskTest {
val inputFile = tempFolder.newFile("config.json")
val task =
createTestTask<GeneratePackageListTask> {
it.generatedOutputDirectory.set(outputFolder)
it.autolinkInputFile.set(inputFile)
createTestTask<GeneratePackageListTask> { testTask ->
testTask.generatedOutputDirectory.set(outputFolder)
testTask.autolinkInputFile.set(inputFile)
}
assertThat(task.inputs.files.singleFile).isEqualTo(inputFile)
@@ -37,11 +37,11 @@ class PrepareBoostTaskTest {
val project = createProject()
val boostThirdPartyJniPath = File(project.projectDir, "src/main/jni/third-party/boost/")
val task =
createTestTask<PrepareBoostTask>(project = project) {
it.boostPath.setFrom(boostpath)
it.boostThirdPartyJniPath.set(boostThirdPartyJniPath)
it.boostVersion.set("1.0.0")
it.outputDir.set(output)
createTestTask<PrepareBoostTask>(project = project) { task ->
task.boostPath.setFrom(boostpath)
task.boostThirdPartyJniPath.set(boostThirdPartyJniPath)
task.boostVersion.set("1.0.0")
task.outputDir.set(output)
}
File(boostThirdPartyJniPath, "CMakeLists.txt").apply {
parentFile.mkdirs()
@@ -82,11 +82,11 @@ class PrepareGflagsTaskTest {
val gflagsThirdPartyPath = tempFolder.newFolder("gflagspath/jni")
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareGflagsTask> {
it.gflagsPath.setFrom(gflagspath)
it.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
it.gflagsVersion.set("1.0.0")
it.outputDir.set(output)
createTestTask<PrepareGflagsTask> { taskConfig ->
taskConfig.gflagsPath.setFrom(gflagspath)
taskConfig.gflagsThirdPartyPath.set(gflagsThirdPartyPath)
taskConfig.gflagsVersion.set("1.0.0")
taskConfig.outputDir.set(output)
}
File(gflagspath, "gflags-1.0.0/src/gflags_declare.h.in").apply {
parentFile.mkdirs()
@@ -50,6 +50,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"])
add_dependency(s, "React-networking", :framework_name => 'React_networking')
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
@@ -67,7 +67,7 @@ template <typename T>
facebook::react::LazyVector<T> RCTBridgingToVec(id value, T (^ctor)(id element))
{
NSArray *array = RCTBridgingToArray(value);
typedef typename facebook::react::LazyVector<T>::size_type _size_t;
using _size_t = typename facebook::react::LazyVector<T>::size_type;
_size_t size = static_cast<_size_t>(array.count);
return facebook::react::LazyVector<T>::fromUnsafeRawValue(array, size, ctor);
}
+3 -3
View File
@@ -721,13 +721,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;
@@ -96,7 +96,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
private final TraceListener mTraceListener;
private final JavaScriptModuleRegistry mJSModuleRegistry;
private final JSBundleLoader mJSBundleLoader;
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<PendingJSCall>();
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<>();
private final Object mJSCallsPendingInitLock = new Object();
private final NativeModuleRegistry mNativeModuleRegistry;
@@ -83,6 +83,6 @@ internal class BridgelessDevSupportManager(
}
fun tracingState(): TracingState {
return TracingState.DISABLED
return TracingState.ENABLEDINCDPMODE
}
}
@@ -68,6 +68,7 @@ import com.facebook.react.devsupport.interfaces.StackFrame
import com.facebook.react.devsupport.interfaces.TracingState
import com.facebook.react.devsupport.interfaces.TracingStateProvider
import com.facebook.react.devsupport.perfmonitor.PerfMonitorDevHelper
import com.facebook.react.devsupport.perfmonitor.PerfMonitorOverlayManager
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.internal.featureflags.ReactNativeNewArchitectureFeatureFlags
import com.facebook.react.modules.core.RCTNativeAppEventEmitter
@@ -77,6 +78,7 @@ import java.io.File
import java.net.MalformedURLException
import java.net.URL
import java.util.Locale
import javax.inject.Provider
public abstract class DevSupportManagerBase(
protected val applicationContext: Context,
@@ -182,7 +184,7 @@ public abstract class DevSupportManagerBase(
null
}
private var perfMonitorOverlayManager: PerfMonitorOverlayViewManager? = null
private var perfMonitorOverlayManager: PerfMonitorOverlayManager? = null
private var tracingStateProvider: TracingStateProvider? = null
init {
@@ -215,19 +217,19 @@ public abstract class DevSupportManagerBase(
if (
ReactNativeNewArchitectureFeatureFlags.enableBridgelessArchitecture() &&
ReactNativeFeatureFlags.perfMonitorV2Enabled() &&
reactInstanceDevHelper is PerfMonitorDevHelper &&
perfMonitorOverlayManager == null
reactInstanceDevHelper is PerfMonitorDevHelper
) {
perfMonitorOverlayManager =
PerfMonitorOverlayViewManager(
Supplier {
PerfMonitorOverlayManager(
reactInstanceDevHelper,
Provider {
val context = reactInstanceDevHelper.currentActivity
if (context == null || context.isFinishing) {
return@Supplier null
return@Provider null
}
context
},
reactInstanceDevHelper.inspectorTarget,
{ openDebugger(DebuggerFrontendPanelName.PERFORMANCE.toString()) },
)
}
}
@@ -548,10 +550,11 @@ public abstract class DevSupportManagerBase(
override fun onNewReactContextCreated(reactContext: ReactContext) {
resetCurrentContext(reactContext)
if (perfMonitorOverlayManager != null && reactInstanceDevHelper is PerfMonitorDevHelper) {
if (reactInstanceDevHelper is PerfMonitorDevHelper) {
perfMonitorOverlayManager?.let { manager ->
reactInstanceDevHelper.inspectorTarget?.addPerfMonitorListener(manager)
}
perfMonitorOverlayManager?.enable()
}
}
@@ -882,6 +885,7 @@ public abstract class DevSupportManagerBase(
override fun onPackagerDisconnected() {
isPackagerConnected = false
perfMonitorOverlayManager?.disable()
}
override fun onPackagerReloadCommand() {
@@ -1,20 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.devsupport.interfaces
/** [Experimental] Interface to manage the V2 Perf Monitor overlay. */
internal interface PerfMonitorOverlayManager {
/** Enable the Perf Monitor overlay. Will be shown when updates are received. */
public fun enable()
/** Disable the Perf Monitor overlay. Will remain hidden when updates are received. */
public fun disable()
/** Reset the Perf Monitor overlay, e.g. after a reload. */
public fun reset()
}
@@ -7,11 +7,16 @@
package com.facebook.react.devsupport.perfmonitor
import com.facebook.react.devsupport.interfaces.TracingState
/**
* [Experimental] Interface implemented by [com.facebook.react.runtime.ReactHostInspectorTarget]
* exposing actions for the V2 Perf Monitor.
*/
internal interface PerfMonitorInspectorTargetBinding {
/** Get the current CDP or background performance tracing state. */
public fun getTracingState(): TracingState
/** Attempt to pause the current background performance trace, and open in DevTools. */
public fun pauseAndAnalyzeBackgroundTrace()
@@ -0,0 +1,86 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.devsupport.perfmonitor
import android.content.Context
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.devsupport.interfaces.TracingState
import javax.inject.Provider
internal class PerfMonitorOverlayManager(
private val devHelper: PerfMonitorDevHelper,
private val contextProvider: Provider<Context?>,
private val onRequestOpenDevTools: () -> Unit,
) : PerfMonitorUpdateListener {
private var enabled: Boolean = false
private var initialized: Boolean = false
private var view: PerfMonitorOverlayView? = null
private var tracingState: TracingState = TracingState.ENABLEDINCDPMODE
private fun init() {
if (initialized || !enabled) {
return
}
UiThreadUtil.runOnUiThread {
val context = contextProvider.get() ?: return@runOnUiThread
view = PerfMonitorOverlayView(context, ::handleRecordingButtonPress)
// Start background tracing
devHelper.inspectorTarget?.resumeBackgroundTrace()
view?.show()
initialized = true
}
}
/** Enable the Perf Monitor overlay. Will be shown when updates are received. */
fun enable() {
enabled = true
init()
UiThreadUtil.runOnUiThread { view?.show() }
}
/** Disable the Perf Monitor overlay. Will remain hidden when updates are received. */
fun disable() {
UiThreadUtil.runOnUiThread { view?.hide() }
enabled = false
}
/** Reset the Perf Monitor overlay, e.g. after a reload. */
fun reset() {
UiThreadUtil.runOnUiThread { view?.resetState() }
// Update with current recording state
onRecordingStateChanged(
devHelper.inspectorTarget?.getTracingState() ?: TracingState.ENABLEDINCDPMODE
)
}
override fun onNewFocusedEvent(data: PerfMonitorUpdateListener.LongTaskEventData) {
view?.updateFocusedEvent(data)
}
override fun onRecordingStateChanged(state: TracingState) {
tracingState = state
view?.updateRecordingState(state)
}
private fun handleRecordingButtonPress() {
when (tracingState) {
TracingState.ENABLEDINBACKGROUNDMODE -> {
devHelper.inspectorTarget?.pauseAndAnalyzeBackgroundTrace()
onRequestOpenDevTools()
}
TracingState.DISABLED -> {
devHelper.inspectorTarget?.resumeBackgroundTrace()
}
TracingState.ENABLEDINCDPMODE -> Unit
}
}
}
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.devsupport
package com.facebook.react.devsupport.perfmonitor
import android.app.Dialog
import android.content.Context
@@ -20,107 +20,110 @@ import android.view.Window
import android.view.WindowManager
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.util.Supplier
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.react.R
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.devsupport.interfaces.PerfMonitorOverlayManager
import com.facebook.react.devsupport.interfaces.TracingState
import com.facebook.react.devsupport.perfmonitor.PerfMonitorInspectorTargetBinding
import com.facebook.react.devsupport.perfmonitor.PerfMonitorUpdateListener
import com.facebook.react.uimanager.DisplayMetricsHolder
import com.facebook.react.uimanager.PixelUtil
import java.util.Locale
internal class PerfMonitorOverlayViewManager(
private val contextSupplier: Supplier<Context?>,
private val inspectorTarget: PerfMonitorInspectorTargetBinding?,
) : PerfMonitorOverlayManager, PerfMonitorUpdateListener {
private var initialized: Boolean = false
private var enabled: Boolean = false
private var hasInteractionData: Boolean = false
private var interactionDialog: Dialog? = null
private var buttonDialog: Dialog? = null
private var durationLabel: TextView? = null
internal class PerfMonitorOverlayView(
private val context: Context,
private val onButtonPress: () -> Unit,
) {
private var hidden: Boolean = true
private var hasEventData: Boolean = false
private val metricsDialog: Dialog
private val toolbarDialog: Dialog
private val tooltipDialog: Dialog
private lateinit var buttonLabel: TextView
private lateinit var recordingStateLabel: TextView
private lateinit var durationLabel: TextView
private lateinit var tooltipLabel: TextView
private var ttl: Int = 0
private var hideAfterTimeoutHandler: Handler? = null
override fun enable() {
UiThreadUtil.runOnUiThread {
enabled = true
if (hasInteractionData) {
showOverlay()
}
}
}
override fun disable() {
UiThreadUtil.runOnUiThread {
enabled = false
hideOverlay()
}
}
override fun reset() {
UiThreadUtil.runOnUiThread {
hasInteractionData = false
hideOverlay()
}
}
override fun onRecordingStateChanged(state: TracingState) {
// recordingState = state
// view?.updateRecordingState(state)
}
override fun onNewFocusedEvent(data: PerfMonitorUpdateListener.LongTaskEventData) {
UiThreadUtil.runOnUiThread {
ensureInitialized()
durationLabel?.text = String.format(Locale.US, "%d ms", data.durationMs)
durationLabel?.setTextColor(getDurationHighlightColor(data.responsivenessScore))
hasInteractionData = true
ttl = data.ttl
hideAfterTimeoutHandler?.removeCallbacksAndMessages(null)
if (enabled) {
showOverlay()
// Schedule hiding overlay after ttl milliseconds
if (ttl > 0) {
if (hideAfterTimeoutHandler == null) {
hideAfterTimeoutHandler = Handler(Looper.getMainLooper())
}
hideAfterTimeoutHandler?.postDelayed({ hideOverlay() }, ttl.toLong())
}
}
}
}
private fun ensureInitialized() {
if (initialized) {
return
}
val context = contextSupplier.get() ?: return
init {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)
createDialog(context)
createButton(context)
initialized = true
tooltipDialog = createTooltipDialog()
metricsDialog = createMetricsDialog()
toolbarDialog = createToolbarDialog()
}
private fun showOverlay() {
interactionDialog?.show()
buttonDialog?.show()
fun show() {
toolbarDialog.show()
tooltipDialog.show()
if (hasEventData) {
toolbarDialog.window?.decorView?.post { updateMetricsDialogPosition() }
metricsDialog.show()
}
hidden = false
}
private fun hideOverlay() {
interactionDialog?.hide()
buttonDialog?.hide()
fun hide() {
metricsDialog.hide()
toolbarDialog.hide()
tooltipDialog.hide()
hidden = true
}
private fun createDialog(context: Context) {
val containerLayout = createInnerLayout(context)
fun resetState() {
hasEventData = false
metricsDialog.hide()
}
fun updateFocusedEvent(data: PerfMonitorUpdateListener.LongTaskEventData) {
durationLabel.text = String.format(Locale.US, "%d ms", data.durationMs)
durationLabel.setTextColor(getDurationHighlightColor(data.responsivenessScore))
hasEventData = true
ttl = data.ttl
hideAfterTimeoutHandler?.removeCallbacksAndMessages(null)
if (!hidden) {
metricsDialog.show()
// Schedule hiding metrics overlay after ttl milliseconds
if (ttl > 0) {
if (hideAfterTimeoutHandler == null) {
hideAfterTimeoutHandler = Handler(Looper.getMainLooper())
}
hideAfterTimeoutHandler?.postDelayed({ metricsDialog.hide() }, ttl.toLong())
}
}
}
fun updateRecordingState(state: TracingState) {
recordingStateLabel.text =
when (state) {
TracingState.ENABLEDINBACKGROUNDMODE -> "Profiling: ON"
TracingState.DISABLED -> "Profiling: OFF"
TracingState.ENABLEDINCDPMODE -> "Profiling: DISABLED"
}
buttonLabel.text =
when (state) {
TracingState.ENABLEDINBACKGROUNDMODE -> "Open ↗️"
TracingState.DISABLED -> "Start"
TracingState.ENABLEDINCDPMODE -> ""
}
tooltipLabel.text =
when (state) {
TracingState.ENABLEDINBACKGROUNDMODE -> "Dev Menu > Finish performance trace"
TracingState.DISABLED -> "Dev Menu > Start performance trace"
TracingState.ENABLEDINCDPMODE -> ""
}
if (state == TracingState.ENABLEDINCDPMODE) {
tooltipDialog.hide()
} else {
tooltipDialog.show()
}
toolbarDialog.window?.decorView?.post { updateMetricsDialogPosition() }
}
private fun createMetricsDialog(): Dialog {
val containerLayout = createInnerLayout()
val longTaskLabel =
TextView(context).apply {
textSize = TEXT_SIZE_PRIMARY
@@ -138,7 +141,7 @@ internal class PerfMonitorOverlayViewManager(
containerLayout.addView(durationLabel)
val dialog =
createAnchoredDialog(context, dpToPx(140f), dpToPx(16f)).apply {
createAnchoredDialog(getMetricsDialogOffsetX(), dpToPx(16f)).apply {
setContentView(containerLayout)
}
dialog.window?.apply {
@@ -151,30 +154,25 @@ internal class PerfMonitorOverlayViewManager(
}
}
this.interactionDialog = dialog
return dialog
}
fun updateRecordingState(state: TracingState) {}
private fun createButton(context: Context) {
val buttonInner = createInnerLayout(context)
buttonInner.addView(
private fun createToolbarDialog(): Dialog {
val buttonInner = createInnerLayout()
recordingStateLabel =
TextView(context).apply {
text = "Analyze"
textSize = TEXT_SIZE_PRIMARY
setTextColor(Color.WHITE)
typeface = TYPEFACE_BOLD
}
)
buttonInner.addView(
buttonInner.addView(recordingStateLabel)
buttonLabel =
TextView(context).apply {
text = "cmd + A"
textSize = TEXT_SIZE_ACCESSORY
setTextColor(Color.WHITE)
alpha = 0.7f
textSize = TEXT_SIZE_PRIMARY
setTextColor(COLOR_TEXT_BLUE)
typeface = TYPEFACE_BOLD
}
)
buttonInner.addView(buttonLabel)
val buttonView =
LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
@@ -185,19 +183,43 @@ internal class PerfMonitorOverlayViewManager(
dpToPx(8f).toInt(),
)
addView(buttonInner)
setOnClickListener { inspectorTarget?.pauseAndAnalyzeBackgroundTrace() }
setOnClickListener { onButtonPress() }
}
val dialog =
createAnchoredDialog(context, dpToPx(0f), dpToPx(0f)).apply { setContentView(buttonView) }
val dialog = createAnchoredDialog(dpToPx(0f), dpToPx(0f)).apply { setContentView(buttonView) }
dialog.window?.apply {
attributes =
attributes?.apply { flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE }
}
this.buttonDialog = dialog
return dialog
}
private fun createAnchoredDialog(context: Context, offsetX: Float, offsetY: Float): Dialog {
private fun createTooltipDialog(): Dialog {
val containerLayout = createInnerLayout()
tooltipLabel =
TextView(context).apply {
textSize = TEXT_SIZE_ACCESSORY
setTextColor(Color.WHITE)
}
containerLayout.addView(tooltipLabel)
val dialog =
createAnchoredDialog(dpToPx(16f), dpToPx(52f)).apply { setContentView(containerLayout) }
dialog.window?.apply {
attributes =
attributes?.apply {
flags =
flags or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
}
}
return dialog
}
private fun createAnchoredDialog(offsetX: Float, offsetY: Float): Dialog {
val dialog =
Dialog(context, R.style.NoAnimationDialog).apply {
requestWindowFeature(Window.FEATURE_NO_TITLE)
@@ -228,12 +250,12 @@ internal class PerfMonitorOverlayViewManager(
return dialog
}
private fun createInnerLayout(context: Context): LinearLayout {
private fun createInnerLayout(): LinearLayout {
return LinearLayout(context).apply {
orientation = LinearLayout.HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
val paddingHorizontal = dpToPx(14f).toInt()
val paddingVertical = dpToPx(8f).toInt()
val paddingVertical = dpToPx(7f).toInt()
setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical)
layoutParams =
LinearLayout.LayoutParams(
@@ -256,10 +278,21 @@ internal class PerfMonitorOverlayViewManager(
}
}
private fun getMetricsDialogOffsetX(): Float {
val toolbarWidth = toolbarDialog?.window?.decorView?.width ?: 0
return toolbarWidth.toFloat()
}
private fun updateMetricsDialogPosition() {
metricsDialog?.window?.apply {
attributes = attributes?.apply { x = getMetricsDialogOffsetX().toInt() }
}
}
private fun getDurationHighlightColor(responsivenessScore: Int): Int {
return when (responsivenessScore) {
3 -> COLOR_TEXT_RED
2 -> COLOR_TEXT_YELLOW
2 -> COLOR_TEXT_RED
1 -> COLOR_TEXT_YELLOW
else -> COLOR_TEXT_GREEN
}
}
@@ -270,6 +303,7 @@ internal class PerfMonitorOverlayViewManager(
private val COLOR_TEXT_GREEN = Color.parseColor("#4AEB2F")
private val COLOR_TEXT_YELLOW = Color.parseColor("#FFAA00")
private val COLOR_TEXT_RED = Color.parseColor("#FF0000")
private val COLOR_TEXT_BLUE = Color.parseColor("#00B0FF")
private val COLOR_OVERLAY_BORDER = Color.parseColor("#6C6C6C")
private val TEXT_SIZE_PRIMARY = 13f
private val TEXT_SIZE_ACCESSORY = 9f
@@ -81,6 +81,6 @@ internal class ReactHostImplDevHelper(private val delegate: ReactHostImpl) :
delegate.loadBundle(bundleLoader)
override fun getTracingState(): TracingState {
return delegate.reactHostInspectorTarget?.tracingState() ?: TracingState.DISABLED
return delegate.reactHostInspectorTarget?.getTracingState() ?: TracingState.ENABLEDINCDPMODE
}
}
@@ -43,7 +43,7 @@ internal class ReactHostInspectorTarget(reactHostImpl: ReactHostImpl) :
external fun tracingStateAsInt(): Int
fun tracingState(): TracingState {
override fun getTracingState(): TracingState {
return TracingState.entries[tracingStateAsInt()]
}
@@ -50,7 +50,8 @@ void PerfMonitorUpdateHandler::handlePerfMetricsUpdate(
}
auto responsivenessScore = getInteractionScore(duration);
auto ttl = responsivenessScore == InteractionResponsivenessScore::Poor
auto ttl =
responsivenessScore == InteractionResponsivenessScore::NeedsImprovement
? BAD_EVENT_TTL
: DEFAULT_TTL;
@@ -47,9 +47,10 @@ Pod::Spec.new do |s|
add_rncore_dependency(s)
s.dependency "React-domnativemodule"
s.dependency "React-featureflagsnativemodule"
s.dependency "React-microtasksnativemodule"
s.dependency "React-idlecallbacksnativemodule"
s.dependency "React-webperformancenativemodule"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-featureflags")
add_dependency(s, "React-featureflagsnativemodule")
end
@@ -131,9 +131,18 @@ NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
: NativePerformanceCxxSpec(std::move(jsInvoker)) {}
HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) {
// This is not spec-compliant, as this is the duration from system boot to
// now, instead of from app startup to now.
// This should be carefully changed eventually.
return HighResTimeStamp::now();
}
HighResDuration NativePerformance::timeOrigin(jsi::Runtime& /*rt*/) {
// This is not spec-compliant, as this is an approximation from Unix epoch to
// system boot, instead of a precise duration from Unix epoch to app startup.
return HighResTimeStamp::unsafeOriginFromUnixTimeStamp();
}
void NativePerformance::reportMark(
jsi::Runtime& rt,
const std::string& name,
@@ -88,6 +88,9 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
// https://www.w3.org/TR/hr-time-3/#now-method
HighResTimeStamp now(jsi::Runtime& rt);
// https://www.w3.org/TR/hr-time-3/#timeorigin-attribute
HighResDuration timeOrigin(jsi::Runtime& rt);
#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/)
void reportMark(
@@ -45,12 +45,14 @@ Pod::Spec.new do |s|
s.dependency "React-jsi"
s.dependency "React-jsiexecutor"
s.dependency "React-cxxreact"
depend_on_js_engine(s)
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
s.dependency "ReactCommon/turbomodule/core"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "React-performancetimeline")
add_dependency(s, "React-runtimeexecutor", :additional_framework_paths => ["platform/ios"])
@@ -27,10 +27,10 @@ std::optional<double> DynamicEventPayload::extractValue(
auto dynamic = payload_;
for (auto& key : path) {
auto type = dynamic.type();
if ((type == folly::dynamic::Type::OBJECT ||
type == folly::dynamic::Type::ARRAY) &&
!dynamic.empty()) {
if (type == folly::dynamic::Type::OBJECT && !dynamic.empty()) {
dynamic = folly::dynamic(dynamic[key]);
} else if (type == folly::dynamic::Type::ARRAY && !dynamic.empty()) {
dynamic = folly::dynamic(dynamic[std::stoi(key)]);
}
}
if (dynamic.type() == folly::dynamic::Type::DOUBLE) {
@@ -201,6 +201,11 @@ class HighResTimeStamp {
return HighResTimeStamp(chronoNow());
}
static HighResDuration unsafeOriginFromUnixTimeStamp() noexcept {
static auto origin = computeUnsafeOriginFromUnixTimeStamp();
return origin;
}
static constexpr HighResTimeStamp min() noexcept {
return HighResTimeStamp(std::chrono::steady_clock::time_point::min());
}
@@ -285,6 +290,13 @@ class HighResTimeStamp {
std::chrono::steady_clock::time_point chronoTimePoint_;
static HighResDuration computeUnsafeOriginFromUnixTimeStamp() noexcept {
auto systemNow = std::chrono::system_clock::now();
auto steadyNow = std::chrono::steady_clock::now();
return HighResDuration(
systemNow.time_since_epoch() - steadyNow.time_since_epoch());
}
#ifdef REACT_NATIVE_DEBUG
static std::function<std::chrono::steady_clock::time_point()>&
getTimeStampProvider() {
+4 -4
View File
@@ -199,11 +199,11 @@ declare interface EventCounts {
}
declare class Performance {
+eventCounts: EventCounts;
+timeOrigin: DOMHighResTimeStamp;
clearMarks(name?: string): void;
clearMeasures(name?: string): void;
eventCounts: EventCounts;
getEntries: (
options?: PerformanceEntryFilterOptions,
) => Array<PerformanceEntry>;
@@ -215,7 +215,7 @@ declare class Performance {
startMarkOrOptions?: string | PerformanceMeasureOptions,
endMark?: string,
): PerformanceMeasure;
now: () => DOMHighResTimeStamp;
now(): DOMHighResTimeStamp;
toJSON(): string;
}
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`execute test-app "RCTAppDependencyProvider.h" should match snapshot 1`] = `
exports[`execute test-app "ReactAppDependencyProvider/RCTAppDependencyProvider.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_END
"
`;
exports[`execute test-app "RCTAppDependencyProvider.mm" should match snapshot 1`] = `
exports[`execute test-app "ReactAppDependencyProvider/RCTAppDependencyProvider.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -73,7 +73,45 @@ exports[`execute test-app "RCTAppDependencyProvider.mm" should match snapshot 1`
"
`;
exports[`execute test-app "RCTModuleProviders.h" should match snapshot 1`] = `
exports[`execute test-app "ReactAppDependencyProvider/ReactAppDependencyProvider.podspec" should match snapshot 1`] = `
"# 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.
version = \\"1000.0.0\\"
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = \`git rev-parse HEAD\`.strip if system(\\"git rev-parse --git-dir > /dev/null 2>&1\\")
else
source[:tag] = \\"v#{version}\\"
end
Pod::Spec.new do |s|
s.name = \\"ReactAppDependencyProvider\\"
s.version = version
s.summary = \\"The third party dependency provider for the app\\"
s.homepage = \\"https://reactnative.dev/\\"
s.documentation_url = \\"https://reactnative.dev/\\"
s.license = \\"MIT\\"
s.author = \\"Meta Platforms, Inc. and its affiliates\\"
s.platforms = min_supported_versions
s.source = source
s.source_files = \\"**/RCTAppDependencyProvider.{h,mm}\\"
# This guard prevent to install the dependencies when we run \`pod install\` in the old architecture.
s.pod_target_xcconfig = {
\\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(),
\\"DEFINES_MODULE\\" => \\"YES\\"
}
s.dependency \\"ReactCodegen\\"
end
"
`;
exports[`execute test-app "ReactCodegen/RCTModuleProviders.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -93,7 +131,7 @@ exports[`execute test-app "RCTModuleProviders.h" should match snapshot 1`] = `
"
`;
exports[`execute test-app "RCTModuleProviders.mm" should match snapshot 1`] = `
exports[`execute test-app "ReactCodegen/RCTModuleProviders.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -159,7 +197,7 @@ exports[`execute test-app "RCTModuleProviders.mm" should match snapshot 1`] = `
"
`;
exports[`execute test-app "RCTModulesConformingToProtocolsProvider.h" should match snapshot 1`] = `
exports[`execute test-app "ReactCodegen/RCTModulesConformingToProtocolsProvider.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -181,7 +219,7 @@ exports[`execute test-app "RCTModulesConformingToProtocolsProvider.h" should mat
"
`;
exports[`execute test-app "RCTModulesConformingToProtocolsProvider.mm" should match snapshot 1`] = `
exports[`execute test-app "ReactCodegen/RCTModulesConformingToProtocolsProvider.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -248,7 +286,7 @@ exports[`execute test-app "RCTModulesConformingToProtocolsProvider.mm" should ma
"
`;
exports[`execute test-app "RCTThirdPartyComponentsProvider.h" should match snapshot 1`] = `
exports[`execute test-app "ReactCodegen/RCTThirdPartyComponentsProvider.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -268,7 +306,7 @@ exports[`execute test-app "RCTThirdPartyComponentsProvider.h" should match snaps
"
`;
exports[`execute test-app "RCTThirdPartyComponentsProvider.mm" should match snapshot 1`] = `
exports[`execute test-app "ReactCodegen/RCTThirdPartyComponentsProvider.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -303,7 +341,7 @@ exports[`execute test-app "RCTThirdPartyComponentsProvider.mm" should match snap
"
`;
exports[`execute test-app "RCTUnstableModulesRequiringMainQueueSetupProvider.h" should match snapshot 1`] = `
exports[`execute test-app "ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -321,7 +359,7 @@ exports[`execute test-app "RCTUnstableModulesRequiringMainQueueSetupProvider.h"
"
`;
exports[`execute test-app "RCTUnstableModulesRequiringMainQueueSetupProvider.mm" should match snapshot 1`] = `
exports[`execute test-app "ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -355,45 +393,7 @@ exports[`execute test-app "RCTUnstableModulesRequiringMainQueueSetupProvider.mm"
"
`;
exports[`execute test-app "ReactAppDependencyProvider.podspec" should match snapshot 1`] = `
"# 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.
version = \\"1000.0.0\\"
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = \`git rev-parse HEAD\`.strip if system(\\"git rev-parse --git-dir > /dev/null 2>&1\\")
else
source[:tag] = \\"v#{version}\\"
end
Pod::Spec.new do |s|
s.name = \\"ReactAppDependencyProvider\\"
s.version = version
s.summary = \\"The third party dependency provider for the app\\"
s.homepage = \\"https://reactnative.dev/\\"
s.documentation_url = \\"https://reactnative.dev/\\"
s.license = \\"MIT\\"
s.author = \\"Meta Platforms, Inc. and its affiliates\\"
s.platforms = min_supported_versions
s.source = source
s.source_files = \\"**/RCTAppDependencyProvider.{h,mm}\\"
# This guard prevent to install the dependencies when we run \`pod install\` in the old architecture.
s.pod_target_xcconfig = {
\\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(),
\\"DEFINES_MODULE\\" => \\"YES\\"
}
s.dependency \\"ReactCodegen\\"
end
"
`;
exports[`execute test-app "ReactCodegen.podspec" should match snapshot 1`] = `
exports[`execute test-app "ReactCodegen/ReactCodegen.podspec" should match snapshot 1`] = `
"# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
@@ -507,7 +507,7 @@ end
"
`;
exports[`execute test-app-legacy "RCTAppDependencyProvider.h" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactAppDependencyProvider/RCTAppDependencyProvider.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -536,7 +536,7 @@ NS_ASSUME_NONNULL_END
"
`;
exports[`execute test-app-legacy "RCTAppDependencyProvider.mm" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactAppDependencyProvider/RCTAppDependencyProvider.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -580,7 +580,45 @@ exports[`execute test-app-legacy "RCTAppDependencyProvider.mm" should match snap
"
`;
exports[`execute test-app-legacy "RCTModuleProviders.h" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactAppDependencyProvider/ReactAppDependencyProvider.podspec" should match snapshot 1`] = `
"# 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.
version = \\"1000.0.0\\"
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = \`git rev-parse HEAD\`.strip if system(\\"git rev-parse --git-dir > /dev/null 2>&1\\")
else
source[:tag] = \\"v#{version}\\"
end
Pod::Spec.new do |s|
s.name = \\"ReactAppDependencyProvider\\"
s.version = version
s.summary = \\"The third party dependency provider for the app\\"
s.homepage = \\"https://reactnative.dev/\\"
s.documentation_url = \\"https://reactnative.dev/\\"
s.license = \\"MIT\\"
s.author = \\"Meta Platforms, Inc. and its affiliates\\"
s.platforms = min_supported_versions
s.source = source
s.source_files = \\"**/RCTAppDependencyProvider.{h,mm}\\"
# This guard prevent to install the dependencies when we run \`pod install\` in the old architecture.
s.pod_target_xcconfig = {
\\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(),
\\"DEFINES_MODULE\\" => \\"YES\\"
}
s.dependency \\"ReactCodegen\\"
end
"
`;
exports[`execute test-app-legacy "ReactCodegen/RCTModuleProviders.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -600,7 +638,7 @@ exports[`execute test-app-legacy "RCTModuleProviders.h" should match snapshot 1`
"
`;
exports[`execute test-app-legacy "RCTModuleProviders.mm" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactCodegen/RCTModuleProviders.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -657,7 +695,7 @@ exports[`execute test-app-legacy "RCTModuleProviders.mm" should match snapshot 1
"
`;
exports[`execute test-app-legacy "RCTModulesConformingToProtocolsProvider.h" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactCodegen/RCTModulesConformingToProtocolsProvider.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -679,7 +717,7 @@ exports[`execute test-app-legacy "RCTModulesConformingToProtocolsProvider.h" sho
"
`;
exports[`execute test-app-legacy "RCTModulesConformingToProtocolsProvider.mm" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactCodegen/RCTModulesConformingToProtocolsProvider.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -737,7 +775,7 @@ exports[`execute test-app-legacy "RCTModulesConformingToProtocolsProvider.mm" sh
"
`;
exports[`execute test-app-legacy "RCTThirdPartyComponentsProvider.h" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactCodegen/RCTThirdPartyComponentsProvider.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -757,7 +795,7 @@ exports[`execute test-app-legacy "RCTThirdPartyComponentsProvider.h" should matc
"
`;
exports[`execute test-app-legacy "RCTThirdPartyComponentsProvider.mm" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactCodegen/RCTThirdPartyComponentsProvider.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -791,7 +829,7 @@ exports[`execute test-app-legacy "RCTThirdPartyComponentsProvider.mm" should mat
"
`;
exports[`execute test-app-legacy "RCTUnstableModulesRequiringMainQueueSetupProvider.h" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.h" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -809,7 +847,7 @@ exports[`execute test-app-legacy "RCTUnstableModulesRequiringMainQueueSetupProvi
"
`;
exports[`execute test-app-legacy "RCTUnstableModulesRequiringMainQueueSetupProvider.mm" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.mm" should match snapshot 1`] = `
"/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
@@ -834,45 +872,7 @@ exports[`execute test-app-legacy "RCTUnstableModulesRequiringMainQueueSetupProvi
"
`;
exports[`execute test-app-legacy "ReactAppDependencyProvider.podspec" should match snapshot 1`] = `
"# 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.
version = \\"1000.0.0\\"
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = \`git rev-parse HEAD\`.strip if system(\\"git rev-parse --git-dir > /dev/null 2>&1\\")
else
source[:tag] = \\"v#{version}\\"
end
Pod::Spec.new do |s|
s.name = \\"ReactAppDependencyProvider\\"
s.version = version
s.summary = \\"The third party dependency provider for the app\\"
s.homepage = \\"https://reactnative.dev/\\"
s.documentation_url = \\"https://reactnative.dev/\\"
s.license = \\"MIT\\"
s.author = \\"Meta Platforms, Inc. and its affiliates\\"
s.platforms = min_supported_versions
s.source = source
s.source_files = \\"**/RCTAppDependencyProvider.{h,mm}\\"
# This guard prevent to install the dependencies when we run \`pod install\` in the old architecture.
s.pod_target_xcconfig = {
\\"CLANG_CXX_LANGUAGE_STANDARD\\" => rct_cxx_language_standard(),
\\"DEFINES_MODULE\\" => \\"YES\\"
}
s.dependency \\"ReactCodegen\\"
end
"
`;
exports[`execute test-app-legacy "ReactCodegen.podspec" should match snapshot 1`] = `
exports[`execute test-app-legacy "ReactCodegen/ReactCodegen.podspec" should match snapshot 1`] = `
"# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
@@ -42,18 +42,18 @@ const packageJson = JSON.stringify({
});
[
'RCTAppDependencyProvider.h',
'RCTAppDependencyProvider.mm',
'RCTModuleProviders.h',
'RCTModuleProviders.mm',
'RCTModulesConformingToProtocolsProvider.h',
'RCTModulesConformingToProtocolsProvider.mm',
'RCTThirdPartyComponentsProvider.h',
'RCTThirdPartyComponentsProvider.mm',
'ReactAppDependencyProvider.podspec',
'ReactCodegen.podspec',
'RCTUnstableModulesRequiringMainQueueSetupProvider.h',
'RCTUnstableModulesRequiringMainQueueSetupProvider.mm',
'ReactAppDependencyProvider/RCTAppDependencyProvider.h',
'ReactAppDependencyProvider/RCTAppDependencyProvider.mm',
'ReactCodegen/RCTModuleProviders.h',
'ReactCodegen/RCTModuleProviders.mm',
'ReactCodegen/RCTModulesConformingToProtocolsProvider.h',
'ReactCodegen/RCTModulesConformingToProtocolsProvider.mm',
'ReactCodegen/RCTThirdPartyComponentsProvider.h',
'ReactCodegen/RCTThirdPartyComponentsProvider.mm',
'ReactAppDependencyProvider/ReactAppDependencyProvider.podspec',
'ReactCodegen/ReactCodegen.podspec',
'ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.h',
'ReactCodegen/RCTUnstableModulesRequiringMainQueueSetupProvider.mm',
].forEach(file => {
it(`"${file}" should match snapshot`, () => {
const generatedFileDir = path.join(outputDir, 'build/generated/ios');
@@ -0,0 +1,44 @@
/**
* 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-local
* @format
*/
'use strict';
const {TEMPLATES_FOLDER_PATH} = require('./constants');
const {codegenLog} = require('./utils');
const fs = require('fs');
const path = require('path');
const PACKAGE_SWIFT_TEMPLATE_PATH = path.join(
TEMPLATES_FOLDER_PATH,
'Package.swift.template',
);
function generatePackageSwift(
projectRoot /*: string */,
outputDir /*: string */,
reactNativePath /*: string */,
) {
const fullOutputPath = path.join(projectRoot, outputDir);
fs.mkdirSync(outputDir, {recursive: true});
// Generate PAckage.swift File
codegenLog('Generating Package.swift');
const templateH = fs
.readFileSync(PACKAGE_SWIFT_TEMPLATE_PATH, 'utf8')
.replace(
/{reactNativePath}/,
path.relative(fullOutputPath, reactNativePath),
);
const finalPathH = path.join(outputDir, 'Package.swift');
fs.writeFileSync(finalPathH, templateH);
codegenLog(`Generated artifact: ${finalPathH}`);
}
module.exports = {
generatePackageSwift,
};
@@ -22,6 +22,7 @@ const {
} = require('./generateAppDependencyProvider');
const {generateCustomURLHandlers} = require('./generateCustomURLHandlers');
const {generateNativeCode} = require('./generateNativeCode');
const {generatePackageSwift} = require('./generatePackageSwift');
const {generateRCTModuleProviders} = require('./generateRCTModuleProviders');
const {
generateRCTThirdPartyComponents,
@@ -37,6 +38,7 @@ const {
codegenLog,
findCodegenEnabledLibraries,
findDisabledLibrariesByPlatform,
findReactNativeRootPath,
pkgJsonIncludesGeneratedCode,
readPkgJsonInDirectory,
readReactNativeConfig,
@@ -99,7 +101,6 @@ function execute(
if (codegenEnabledLibraries.length === 0) {
codegenLog('No codegen-enabled libraries found.', true);
return;
}
let platforms =
@@ -114,10 +115,6 @@ function execute(
({name}) => !disabledLibraries.includes(name),
);
if (!libraries.length) {
continue;
}
const outputPath = computeOutputPath(
projectRoot,
baseOutputPath,
@@ -125,10 +122,15 @@ function execute(
platform,
);
const reactCodegenOutputPath =
platform === 'android'
? outputPath
: path.join(outputPath, 'ReactCodegen');
if (runReactNativeCodegen) {
const schemaInfos = generateSchemaInfos(libraries);
generateNativeCode(
outputPath,
reactCodegenOutputPath,
schemaInfos.filter(schemaInfo =>
mustGenerateNativeCode(projectRoot, schemaInfo),
),
@@ -139,20 +141,32 @@ function execute(
if (source === 'app' && platform !== 'android') {
// These components are only required by apps, not by libraries and are Apple specific.
generateRCTThirdPartyComponents(libraries, outputPath);
generateRCTModuleProviders(projectRoot, pkgJson, libraries, outputPath);
generateCustomURLHandlers(libraries, outputPath);
generateRCTThirdPartyComponents(libraries, reactCodegenOutputPath);
generateRCTModuleProviders(
projectRoot,
pkgJson,
libraries,
reactCodegenOutputPath,
);
generateCustomURLHandlers(libraries, reactCodegenOutputPath);
generateUnstableModulesRequiringMainQueueSetupProvider(
libraries,
outputPath,
reactCodegenOutputPath,
);
generateAppDependencyProvider(
path.join(outputPath, 'ReactAppDependencyProvider'),
);
generateAppDependencyProvider(outputPath);
generateReactCodegenPodspec(
projectRoot,
pkgJson,
outputPath,
reactCodegenOutputPath,
baseOutputPath,
);
generatePackageSwift(
projectRoot,
outputPath,
findReactNativeRootPath(projectRoot),
);
}
cleanupEmptyFilesAndFolders(outputPath);
@@ -461,6 +461,17 @@ function findDisabledLibrariesByPlatform(
);
}
function findReactNativeRootPath(projectRoot /* : string */) /* : string */ {
const reactNativePackageJsonPath = require.resolve(
path.join('react-native', 'package.json'),
{
paths: [projectRoot],
},
);
return path.dirname(reactNativePackageJsonPath);
}
module.exports = {
buildCodegenIfNeeded,
pkgJsonIncludesGeneratedCode,
@@ -474,4 +485,5 @@ module.exports = {
parseiOSAnnotations,
readReactNativeConfig,
findDisabledLibrariesByPlatform,
findReactNativeRootPath,
};
@@ -0,0 +1,59 @@
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "React-GeneratedCode",
platforms: [.iOS(.v15), .macCatalyst(SupportedPlatform.MacCatalystVersion.v13)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "ReactCodegen",
targets: ["ReactCodegen"]),
.library(
name: "ReactAppDependencyProvider",
targets: ["ReactAppDependencyProvider"]),
],
dependencies: [
.package(name: "React", path: "{reactNativePath}")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: "ReactCodegen",
dependencies: ["React"],
path: "ReactCodegen",
exclude: ["ReactCodegen.podspec"],
publicHeadersPath: ".",
cSettings: [
.headerSearchPath("headers")
],
cxxSettings: [
.headerSearchPath("headers"),
.unsafeFlags(["-std=c++20"]),
],
linkerSettings: [
.linkedFramework("Foundation")
]
),
.target(
name: "ReactAppDependencyProvider",
dependencies: ["ReactCodegen"],
path: "ReactAppDependencyProvider",
exclude: ["ReactAppDependencyProvider.podspec"],
publicHeadersPath: ".",
cSettings: [
.headerSearchPath("headers"),
],
cxxSettings: [
.headerSearchPath("headers"),
.unsafeFlags(["-std=c++20"]),
],
linkerSettings: [
.linkedFramework("Foundation")
]
)
]
)
@@ -24,7 +24,8 @@ require_relative './cocoapods/rncore.rb'
# Importing to expose use_native_modules!
require_relative './cocoapods/autolinking.rb'
$CODEGEN_OUTPUT_DIR = 'build/generated/ios'
$CODEGEN_OUTPUT_DIR = 'build/generated/ios/ReactCodegen'
$APP_DEPENDENCY_PROVIDER_OUTPUT_DIR = 'build/generated/ios/ReactAppDependencyProvider'
$CODEGEN_COMPONENT_DIR = 'react/renderer/components'
$CODEGEN_MODULE_DIR = '.'
@@ -200,7 +201,7 @@ def use_react_native! (
end
pod 'ReactCodegen', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true
pod 'ReactAppDependencyProvider', :path => $CODEGEN_OUTPUT_DIR, :modular_headers => true
pod 'ReactAppDependencyProvider', :path => $APP_DEPENDENCY_PROVIDER_OUTPUT_DIR, :modular_headers => true
# Not needed, but run_codegen expects this to be set.
folly_config = get_folly_config()
run_codegen!(
@@ -4,7 +4,6 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @fantom_react_fb_flags enableEagerAlternateStateNodeCleanup:true
* @flow strict-local
* @format
*/
@@ -63,6 +63,7 @@ const cachedReportMeasure = NativePerformance.reportMeasure;
const cachedGetMarkTime = NativePerformance.getMarkTime;
const cachedNativeClearMarks = NativePerformance.clearMarks;
const cachedNativeClearMeasures = NativePerformance.clearMeasures;
let cachedTimeOrigin: ?DOMHighResTimeStamp;
const MARK_OPTIONS_REUSABLE_OBJECT: PerformanceMarkOptions = {
startTime: 0,
@@ -139,6 +140,24 @@ export default class Performance {
});
}
/**
* Returns the high resolution timestamp that is used as the baseline for
* performance-related timestamps.
* https://developer.mozilla.org/en-US/docs/Web/API/Performance/timeOrigin
*/
get timeOrigin(): DOMHighResTimeStamp {
if (cachedTimeOrigin == null) {
if (NativePerformance.timeOrigin) {
cachedTimeOrigin = NativePerformance?.timeOrigin();
} else {
// Very naive polyfill.
cachedTimeOrigin = Date.now() - getCurrentTimeStamp();
}
}
return cachedTimeOrigin;
}
mark(
markName: string,
markOptions?: PerformanceMarkOptions,
@@ -22,4 +22,37 @@ describe('Performance', () => {
return new PerformanceEntry();
}).toThrow("Failed to construct 'PerformanceEntry': Illegal constructor");
});
describe('now', () => {
it('provides increasing timestamps since boot time', () => {
const first = performance.now();
const second = performance.now();
const third = performance.now();
expect(typeof first).toBe('number');
expect(typeof second).toBe('number');
expect(typeof third).toBe('number');
expect(first).toBeGreaterThan(0);
expect(second).toBeGreaterThan(first);
expect(third).toBeGreaterThan(second);
});
});
describe('timeOrigin', () => {
it('allows moving timestamps to Unix epoch', () => {
// We need to truncate timestamps because `Date.now()` only provides
// integer millisecond precision.
const adjustedMonotonicTime = Math.trunc(
performance.now() + performance.timeOrigin,
);
const wallTime = Date.now();
const adjustedMonotonicTimeAfter = Math.trunc(
performance.now() + performance.timeOrigin,
);
expect(wallTime).toBeGreaterThanOrEqual(adjustedMonotonicTime);
expect(adjustedMonotonicTimeAfter).toBeGreaterThanOrEqual(wallTime);
});
});
});
@@ -54,6 +54,8 @@ export type PerformanceObserverInit = {
export interface Spec extends TurboModule {
+now: () => number;
+timeOrigin?: () => number;
+reportMark: (name: string, startTime: number, entry: mixed) => void;
+reportMeasure: (
name: string,
+23
View File
@@ -44,3 +44,26 @@ configure<com.facebook.react.ReactSettingsExtension> {
lockFiles = files("yarn.lock"),
)
}
// Gradle properties defined in `gradle.properties` are not inherited by
// included builds, see https://github.com/gradle/gradle/issues/2534.
// This is a workaround to read the configuration from the consuming project,
// and apply relevant properties to the :react-native project.
buildscript {
val properties = java.util.Properties()
val propertiesToInherit = listOf("hermesV1Enabled", "react.hermesV1Enabled")
try {
file("../../android/gradle.properties").inputStream().use { properties.load(it) }
gradle.rootProject {
propertiesToInherit.forEach { property ->
if (properties.containsKey(property)) {
gradle.rootProject.extra.set(property, properties.getProperty(property))
}
}
}
} catch (e: Exception) {
// fail silently
}
}