Compare commits

..
Author SHA1 Message Date
Nicola Corti 5983edd21b Migrate ClipboardModuleTest to use BridgelessReactContext (#53131)
Summary:
This test was still using the old `BridgeReactContext`, I'm migrating it to `BridgelessReactContext`.

## Changelog:

[INTERNAL] -


Test Plan: CI

Reviewed By: mdvacca

Differential Revision: D79801564

Pulled By: cortinico
2025-08-07 09:35:26 -07:00
generatedunixname499836121 bf13ecba7f Apply fixup patch to fbsource
Summary:
This is an automatically generated fixup patch to bring fbsource back into sync with
facebook/react on GitHub. Please land this patch as soon as possible, as the difference
reflected on here is already on GitHub and future changes may depend on these
changes!

<< DO NOT EDIT BELOW THIS LINE >>
diff-train-skip-merge
diff-train-source-id: 7da6014535999d256d2846aadbb7fc1373d76253

Generated by: https://www.internalfb.com/intern/sandcastle/job/18014400563434324/

GitHub Repo: facebook/react

Changelog: [Internal]

Reviewed By: jackpope

Differential Revision: D79754431

fbshipit-source-id: 027428142f36683b07cb66112335f75ecfb5dd12
2025-08-07 09:27:23 -07:00
Andrew Datsenko 5f8807acc2 Add public API test (#53096)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53096

Changelog: [Internal]
As title

Reviewed By: rshest

Differential Revision: D79726634

fbshipit-source-id: 1474cbfb635250e06f4f898338ef0874bc488ed1
2025-08-07 09:04:25 -07:00
Nicola Corti 026e22bb8d Deprecate the DefaultDevSupportManagerFactory.create() method used for Old Arch (#53137)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53137

One of those 2 methods can be deprecated as it was used only for old architecture.
We'll be removing it at some point in the future.

Changelog:
[Android] [Deprecated] - DefaultDevSupportManagerFactory.create() method used for Old Arch

Reviewed By: rshest

Differential Revision: D79806116

fbshipit-source-id: ad2d5515f93bb85e3b7c495b369078f4c66d143b
2025-08-07 08:56:26 -07:00
Mateo Guzmán f1894393ca Initial Kotlin setup and migrate YogaConstants (#53133)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53133

# Changelog:
[Internal] -

As part of the ongoing effort to migrate the React Native codebase to Kotlin, this PR introduces the initial setup required for Kotlin support in Yoga.

- Added initial basic Kotlin configuration to the project.
- Migrated `YogaConstants` as an initial file to try out the first migration steps.

X-link: https://github.com/facebook/yoga/pull/1829

Test Plan:
- Tested the migrated class directly against facebook/react-native, see the PR [here](https://github.com/facebook/react-native/pull/52998).
- Run: `./gradlew :yoga:assembleDebug` & `./gradlew :yoga:compileDebugSources`

I am not able to run the Java tests in this repo (even before the initial Kotlin setup) – not sure if I am missing something there but any pointers are welcome – it seems like there is some missing configuration. Currently trying with `./gradlew :yoga:test`

Reviewed By: cortinico

Differential Revision: D79545992

Pulled By: rshest

fbshipit-source-id: 8257ff53e6b6f2436980be98b6c94e1ac526b207
2025-08-07 08:17:56 -07:00
Ruslan Shestopalyuk 77ee24ddce Print benchmark comparison results in a tabular format (#53097)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53097

# Changelog:
[Internal] -

This makes the benchmark results comparison printout, added in https://github.com/facebook/react-native/pull/52925, be done in a tabular form, so it's easier to read and copy/paste around.

Reviewed By: lenaic

Differential Revision: D79728695

fbshipit-source-id: 4dfc999ad4a9a8c2d67efdfce11aff75383cf645
2025-08-07 08:11:32 -07:00
Eliot Fallon 327057fad5 fix: fix a typo in the react_native_pods.rb file (#53129)
Summary:
- Switches fmt_config to fast_float_config so it matches what is used in the method

When trying to use a forked version of the Pod it is not possible to set a new repo for Fast Float. This was caused by a typo in the method used to update the configuration of where to find the Pod.

## Changelog:

[IOS] [FIXED] - Fixed variable naming error in set_fast_float_config method in react_native_pods.rb

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

Test Plan: Add the line `set_fast_float_config({:git => 'some-git-repo'})` to an application's Podfile

Reviewed By: cortinico

Differential Revision: D79805939

Pulled By: cipolleschi

fbshipit-source-id: 9705e1f63e21b788362ca94b74e32bce0177a729
2025-08-07 07:48:07 -07:00
Nicola Corti cd71c9620c Do not die on collect-results after printing to Discord. (#53132)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53132

This will prevent the Firebase script from running at all. I'm removing it.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D79801691

fbshipit-source-id: 2705dff93fc9dbbcfaf97a1ba29b69d4d0a8143c
2025-08-07 07:07:10 -07:00
generatedunixname537391475639613 9bb53c02dd xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/switch/androidswitch/react/renderer/components/androidswitch/AndroidSwitchShadowNode.cpp (#53126)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53126

Reviewed By: rshest

Differential Revision: D79710434

fbshipit-source-id: da72999b76c0eff73eb62ef47dcf5ec0ed6c9e09
2025-08-07 05:47:35 -07:00
David Vacca f67078df07 Deprecate all LegacyArchitecture classes on LayoutAnimation package (#53101)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53101

Deprecate all LegacyArchitecture classes on LayoutAnimation package

changelog: [Android][Changed] Deprecate LegacyArchitecture classes from LayoutAnimation package

Reviewed By: alanleedev

Differential Revision: D79658935

fbshipit-source-id: 34ab2f674868dbee459f2018e82a7d50d0d7333a
2025-08-07 05:11:31 -07:00
Mathieu Acthernoene 3b185e4bce Fix Dimensions window values on Android < 15 (#52738)
Summary:
This PR (initially created for edge-to-edge opt-in support, rebased multiple times) fixes the `Dimensions` API `window` values on Android < 15, when edge-to-edge is enabled.

Currently the window height doesn't include the status and navigation bar heights (but it does on Android >= 15):

<img width="300" alt="Screenshot 2025-06-27 at 16 23 02" src="https://github.com/user-attachments/assets/c7d11334-9298-4f7f-a75c-590df8cc2d8a" />

Using `WindowMetricsCalculator` from AndroidX:

<img width="300" alt="Screenshot 2025-06-27 at 16 34 01" src="https://github.com/user-attachments/assets/7a4e3dc7-a83b-421b-8f6d-fd1344f5fe81" />

Fixes https://github.com/facebook/react-native/issues/47080

## Changelog:

[Android] [Fixed] Fix `Dimensions` `window` values on Android < 15 when edge-to-edge is enabled

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

Test Plan:
Run the example app on an Android < 15 device.

Rollback Plan:

Reviewed By: cipolleschi, Abbondanzo

Differential Revision: D78738516

Pulled By: alanleedev

fbshipit-source-id: fdb22f3cc76b0bda987db426cb015124bcacdc84
2025-08-07 02:17:14 -07:00
Alan Lee 8b2e309479 replace getWindowDisplayMetrics with getScreenDisplayMetrics (#53041)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53041

update `DisplayMetricsHolder.getWindowDisplayMetrics()` to `getScreenDisplayMetrics()`.

Where window width and height is not needed, prefer to use `screenDisplayMetrics` as with upcoming diff `windowDisplayMetrics` initialization only happen using UiContext and have potential to cause more issues if used unnecessarily.

Changelog: [Internal] Update `DisplayMetricsHolder.getWindowDisplayMetrics()` to use `.getScreenDisplayMetrics()`

Reviewed By: mlord93

Differential Revision: D79571226

fbshipit-source-id: d90fca36c119318e7a2dfa6953fc2148b35e83d4
2025-08-07 02:17:14 -07:00
David Vacca d4bf644e47 Update deprecation message for ReactNativeHost class (#53118)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53118

Update deprecation message for ReactNativeHost class

changelog: [internal] internal

Reviewed By: shwanton

Differential Revision: D79677829

fbshipit-source-id: 5013e0988a1ffbfea49a261fd23ac71af76c3313
2025-08-06 23:58:34 -07:00
Devan Buggay 2697e8aaab Remove mode from metro client-log data (#53117)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53117

Sending the mode is no longer needed as you can't opt out of legacy arch any longer, and was only used for printing a (NOBRIDGE) prefix.

Changelog: [Internal]

Reviewed By: shwanton

Differential Revision: D79762592

fbshipit-source-id: 845aabf2a8365c88808990ea481503b23597a8a0
2025-08-06 23:56:45 -07:00
Tim Yung c861804325 VirtualView: Simplify Window Focus Detection
Summary:
Refactors the window focus detection feature flag logic in `VirtualView` (Android) to eliminate one instance property and instead utilize the existence of the focus listener to determine whether window focus detection is enabled.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D79743782

fbshipit-source-id: d12e70d8e52b72d546ff097c3b1bcfbd29fb9129
2025-08-06 20:49:05 -07:00
Alex Hunt 2768c84445 Implement local connection for perf metrics in HostTarget (#52838)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52838

**Context**

Experimental V2 Performance Monitor prototype, beginning by bringing the [Interaction to Next Paint (INP)](https://web.dev/articles/inp) metric to React Native.

**This diff**

Wires up a client/subscriber for the `"__chromium_devtools_metrics_reporter"` runtime binding (to which we emit live metrics events since D78904748). This will be used to unpack these performance updates to send to the host platform.

- Creates a new `HostRuntimeBinding` helper, which establishes a local/private CDP session.
- Conditionally installs our perf metrics runtime binding in `HostTarget` when `perfMonitorV2Enabled` is set.
- Wires up a new `onPerfMonitorUpdate` event on `HostTargetDelegate` (unimplemented until the next diff).

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D78904766

fbshipit-source-id: 991f17a4cc69f574917750053a5da31bbc6dc0d5
2025-08-06 16:05:28 -07:00
Zeya Peng be6f3c6f77 Add NativeAnimated jest test to verify node creation behavior at update (#53115)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53115

## Changelog:

[Internal] [Added] - Add NativeAnimated jest test to verify node creation behavior at update

Reviewed By: yungsters

Differential Revision: D79729178

fbshipit-source-id: 1c24c6a19ee994ee4ea7a916c47f227e151d65b4
2025-08-06 15:17:33 -07:00
David Vacca 54770cecc4 Mark JSInstance as InteropLegacyArchitecture (#53100)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53100

Mark JSInstance as InteropLegacyArchitecture

changelog: [internal] internal

Reviewed By: mlord93, cortinico

Differential Revision: D79732414

fbshipit-source-id: fc66d6cfef34b4ac66af724bf4336f4f4c38f447
2025-08-06 14:13:32 -07:00
Andrew Datsenko 457190cc4b Add test for width, style, testID and tintColor (#53088)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53088

Changelog: [Internal]
As title

Reviewed By: rshest

Differential Revision: D79642765

fbshipit-source-id: 92862934fc72cfca2ce35c365fa7369d878d58d9
2025-08-06 09:38:29 -07:00
Maciej Jastrzębski 6965d57e75 fix(a11y): TextInput aria-label handling (#53051)
Summary:
The `aria-label` prop was ignored on `TextInput` component. Which resulted in screen reader not able to read it.

This PR forwards `aria-label` to `accessibilityLabel` in a manner similar to e.g. `View` and `Text`

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[GENERAL] [FIXED] - a11y: fix `aria-label` on `TextInput`

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

Test Plan:
Run RNTester => TextInput => Accessibility section under Accessibility Inspector or screen reader.

iOS fixed:
https://github.com/user-attachments/assets/68c3a2ef-7dfe-479c-97fc-cbe72108c45c

iOS baseline:
https://github.com/user-attachments/assets/2e8372ba-10dc-47d2-b6b1-9f664000de7d

Reviewed By: andrewdacenko

Differential Revision: D79635413

Pulled By: rshest

fbshipit-source-id: dd2f583d67c6c6c6393e02c5fe534308e1e2f921
2025-08-06 09:36:35 -07:00
Tommy Nguyen bf2c3af93b fix(codegen): fix missing dependencies (#52884)
Summary:
`react-native/codegen` uses `babel/parser` and `babel/core` but does not declare dependency on them. Depending on how packages are hoisted (and especially in pnpm setups), this causes crashes during codegen.

Resolves https://github.com/facebook/react-native/issues/52883

## Changelog:

[GENERAL] [FIXED] - Add missing Babel dependencies

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

Test Plan: See https://github.com/facebook/react-native/issues/52883

Reviewed By: cortinico, christophpurrer

Differential Revision: D79103092

Pulled By: robhogan

fbshipit-source-id: ecaf690f994393a652ea7f0d4f30bbabeb23a434
2025-08-06 08:19:47 -07:00
generatedunixname537391475639613 a4b958099c xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/text/ParagraphShadowNode.cpp (#53093)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53093

Reviewed By: rshest

Differential Revision: D79709943

fbshipit-source-id: b8d75b6e3b790c94dd3250654459afd782315d9f
2025-08-06 08:18:43 -07:00
Alex Hunt b4164cd97c Move JInspectorNetworkReporter, add missing SoLoader call (#53055)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53055

Moves the C++ part of `InspectorNetworkReporter.kt` into the `react_devsupportjni` JNI library, and adds missing `SoLoader.loadLibrary` call.

Replaces D79568759 / https://github.com/facebook/react-native/pull/53036.

Changelog: [Internal]

Reviewed By: robhogan

Differential Revision: D79638331

fbshipit-source-id: 5df450ad80e4532d6ada1e4dab51a1de6418a4e0
2025-08-06 08:14:31 -07:00
Andrew Datsenko f526e91fda Add tests for src and srcSet (#53062)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53062

Changelog: [Internal]
As title

Reviewed By: rshest

Differential Revision: D79642287

fbshipit-source-id: 59d7dbf7f2562ca550ac48eb9067469e6128e7c6
2025-08-06 08:08:44 -07:00
Andrew Datsenko bb5d0df0e3 Add source tests (#53074)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53074

Changelog: [Internal]
Add tests for source prop of Image.

Reviewed By: rshest

Differential Revision: D79605165

fbshipit-source-id: 400cb37f59966634a2ced3c95425be4dcb1fd1a3
2025-08-06 08:08:44 -07:00
Andrew Datsenko a6ef65e06b Add resizeMode tests (#53061)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53061

Changelog: [Internal]
Add tests for resizeMode prop

Reviewed By: rshest

Differential Revision: D79600137

fbshipit-source-id: d802fb375f2f57f38a51d918d2045763dd79c440
2025-08-06 08:08:44 -07:00
Andrew Datsenko 93694e3f5d Add referrerPolicy tests (#53073)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53073

Changelog: [Internal]
Add referrerPolicty for cross origin requests.

Reviewed By: rshest

Differential Revision: D79598735

fbshipit-source-id: 96a5b092a278993ac728a9413c1a9376bfe7a69a
2025-08-06 08:08:44 -07:00
Andrew Datsenko 96ebf5e969 Add on<Event> props (#53060)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53060

Changelog: [Internal]
Add tests for Image props that notify of image loading progress.

Reviewed By: rshest

Differential Revision: D79596580

fbshipit-source-id: 124ee1b4ac70710c4efa9b6e11a168b61fe29aed
2025-08-06 08:08:44 -07:00
Andrew Datsenko 7bd1254eda Add tests for defaultSource and height (#53058)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53058

Changelog: [Internal]
As title

Reviewed By: rshest

Differential Revision: D79566292

fbshipit-source-id: cd0d39d914d4e17ee6472be766ba18b0c3866d95
2025-08-06 08:08:44 -07:00
Sophie L 4b8dbe7642 fix: turn off build IDs for reproducibility (#53089)
Summary:
these cause issues for apps that want to be "reproducible" (i.e. the same code leads to the same output, which can be helpful for detemining if there's been any tampering or similar). see also https://gitlab.com/IzzyOnDroid/repo/-/wikis/Reproducible-Builds/RB-Hints-for-Developers#no-funny-build-time-generated-ids

I'm not exactly sure why this was set. it seems to have been introduced in https://github.com/facebook/react-native/commit/e3830ddffd9260fe071e0c9f9df40b379d54cf26 without any (public) explanation as to why it was needed?

## Changelog:

[ANDROID] [FIXED] - Turned off build IDs for native libraries, fixing issues with reproducibility

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

Test Plan: I've successfully used similar patches in my own app for a while.

Reviewed By: cipolleschi

Differential Revision: D79718924

Pulled By: cortinico

fbshipit-source-id: 7c609fa0b5b305cb759586fb1c7f332589ca9cc7
2025-08-06 08:07:14 -07:00
Ruslan Shestopalyuk 99f1c30409 Don't populate props with default values for Text component (#53059)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53059

# Changelog:
[Internal] -

This is a re-submit of the partially reverted https://github.com/facebook/react-native/pull/52905, which should work fine now on top of the changes stack that support the RN feature flags being safely used on the modules' top level.

Reviewed By: rubennorte

Differential Revision: D79640965

fbshipit-source-id: d8bda0dba662930d1343ea9d5155791a686b653b
2025-08-06 07:54:26 -07:00
Samuel Susla 288f6d9f48 Back out "Make sure props default value is restored when disconnected from animated" (#53084)
Summary:
changelog: [internal]

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

This is causing glitches

Reviewed By: rozele

Differential Revision: D79705318

fbshipit-source-id: 0e25909837f0db9812af7835ea88f60390c6ae70
2025-08-06 06:13:14 -07:00
Ruslan Lesiutin 421a0c7077 fix: reset maxDuration only after recording TracingStartedInPage event (#53080)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53080

# Changelog: [Internal]

We were accidentally resetting this before calculating the timestampf for the TracingStartedInPage event, which is the left boundary for the timeline window.

Reviewed By: sbuggay

Differential Revision: D79670865

fbshipit-source-id: c6b5f869e185d6d3c80cb7153891c72283c82418
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin 593bdc6762 Keep tracingMode on TraceRecordingState (#53077)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53077

# Changelog: [Internal]

Stores the tracing::Mode on a TraceRecordingState and updates the logic for:
- InstanceTracingAgent to enable PerformanceTracer with a specified window size
- RuntimeTracingAgent to only enable sampling profiler for CDP-initiated sessions

Reviewed By: sbuggay

Differential Revision: D79670864

fbshipit-source-id: 2c4eacb29666b59acbc508848a0a3d859f21a403
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin b358404f44 Override background Trace Recording if user initiated a new one (#53039)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53039

# Changelog: [Internal]

We can now distinguish these 2 different modes.

Reviewed By: sbuggay

Differential Revision: D79565934

fbshipit-source-id: 780678d1f50d4c1d73d55ebc95da0ea328fa2cb3
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin f66ef53962 Create TracingMode (#53035)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53035

# Changelog: [Internal]

This adds an ability to distinguish different trace recordings, based on mode

Reviewed By: sbuggay

Differential Revision: D79557790

fbshipit-source-id: cbb216df86fa1a4692e1b82c8a14a6049b5c45ec
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin 989579c533 Stop potentially running trace recording if CDP session is destroyed (#52960)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52960

# Changelog: [Internal]

We never call `stopTracing()` on a HostTarget, if the Agent was destroyed.

This could only happen if the session was destroyed. There could me multiple sessions, so we have to keep the source of truth for recording status in a session state.

Reviewed By: sbuggay

Differential Revision: D79435494

fbshipit-source-id: db72ffdf6856974e980afa78d59dec4bf45df289
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin 8e97de473e Cleanup legacy tracing endpoints from Agents (#52965)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52965

# Changelog: [Internal]

Reviewed By: sbuggay

Differential Revision: D79434656

fbshipit-source-id: 8c6b690ac6c22df8376651d14b940f74cf23dca3
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin f618ca4872 Use new endpoints in CDP TracingAgent (#52961)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52961

# Changelog: [Internal]

Now that we've implemented all required serializers and TraceRecordedState has everything needed, we can migrate TracingAgent to use this new infra.

Reviewed By: sbuggay

Differential Revision: D79433497

fbshipit-source-id: 8c63f0faa50844786b7af8860c22fc006dd38414
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin b764966c9a Expose start and stop tracing endpoints for HostTargetController (#52962)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52962

# Changelog: [Internal]

CDP Agents should interact with Targets by using Controllers:
https://www.internalfb.com/code/fbsource/[0b20b752c43f]/xplat/js/react-native-github/packages/react-native/ReactCommon/jsinspector-modern/HostTarget.h?lines=300-303

This diff adds public endpoinst for controlling the trace recording on a `HostTargetController` that will be used by `TracingAgent` later.

Reviewed By: sbuggay

Differential Revision: D79433500

fbshipit-source-id: 8ff45b1e2380206f0b72f8ba8b879543cf6b9933
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin b954fbf8b7 Implement serializer for TraceRecordingState (#52964)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52964

# Changelog: [Internal]

Now TraceRecordingState should capture everything we need in order to display a trace on a timeline.

We just need to serialize it properly into collection of serialized Trace Events that would be sent via `Tracing.dataCollected` CDP events.

This is what this serializer is doing.

Reviewed By: sbuggay

Differential Revision: D79434655

fbshipit-source-id: 6b6858db23077eb7305781b9b0d98af8dedd68ca
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin c8f260658d Record startTime on TraceRecordingState (#52967)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52967

# Changelog: [Internal]

We would need this to correctly serialize Runtime Sampling Profiles.

In case of recording the trace in the background, this might not be the value we would want to use, we would probably add another field or calculate it dynamically, depending on the tracing mode.

This is not the case for now, will be solved separately on top of the stack.

Reviewed By: sbuggay

Differential Revision: D79433502

fbshipit-source-id: 172ff19985d325585590794fb2523a24a6860221
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin fa9b195661 Record processId on TraceRecordingState (#52969)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52969

# Changelog: [Internal]

We would need this to correctly serialize Runtime Sampling Profiles.

Reviewed By: sbuggay

Differential Revision: D79433501

fbshipit-source-id: 43d10037351669a87623ffccf2b6da60961c0157
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin 3c637b09e6 Record InstanceTracing profile on TraceRecordingState (#52963)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52963

# Changelog: [Internal]

We are deprecating previous `InstanceTarcingProfile` struct, but not removing yet, since this is still used and we will migrate in one of the diffs at the top of the stack.

The `InstanceTracingProfile` will consist only of Trace Events that were captured by PerformanceTracer, and will not store Runtime Sampling Profiles.

There are multiple reasons for this:
1. As of right now, Runtime Sampling Profiles are completely independant from Instance Profiles and do not require anythings from Instance as a Target to be represented on a timeline.
2. Although PerformanceTracer is a singleton, it should be this way. It captures events for something that can only be dispatched if there is an allocated React Instance, so in the future PerformanceTracer could become a data-member of InstanceTarget.

Reviewed By: sbuggay

Differential Revision: D79433498

fbshipit-source-id: 0fdf09517488bc5c6c8eb604aee66535cdc19ffb
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin 5144062937 Record RuntimeSamplingProfile on TraceRecordingState (#52968)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52968

# Changelog: [Internal]

Now that we've defined all actors and relationship between Agents, we can start recording profiles and store them on a recording state.

Reviewed By: sbuggay

Differential Revision: D79415396

fbshipit-source-id: 9639e59d619268d970b895da2e19dfd92c963a32
2025-08-06 05:48:35 -07:00
Ruslan Lesiutin a9da64fbf7 Create a local session for Tracing (#52966)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52966

# Changelog: [Internal]

The current design for Tracing is flawed. Right now the logic is mostly scattered around CDP Agents, lifetime of which is tied to CDP session.

This diff introduces a new approach:
- The HostTarget will be the only Target that has public entrypoints to startTracing as part of the jsinspector backend.
- It will create and own TraceRecording that acts as a local session. We won't use wording session here, because it is already reserved for CDP case.
- Every Target will implement TracingAgent, lifetime of which will be limited by lifetime of either Target or TraceRecording.
- All these TracingAgent will have a reference to TraceRecordingState, which they can mutate

This approach unblocks:
- Recording traces without active CDP sessions, for example in a background.
- Recording full instance reloads and multiple profiles for Instances and Runtimes.

{F1980838032}

Reviewed By: sbuggay

Differential Revision: D79371359

fbshipit-source-id: 034e984fd7e977457bd41a980e5feb049db3339d
2025-08-06 05:48:35 -07:00
generatedunixname537391475639613 fce9f68f29 xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/safeareaview/SafeAreaViewShadowNode.cpp (#53090)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53090

Reviewed By: rshest

Differential Revision: D79627030

fbshipit-source-id: fe36f901b2573689821ec3eacecc1f364d3c9183
2025-08-06 05:44:42 -07:00
Rubén Norte 0090333296 Add test to ensure baseline memory usage (#53072)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53072

Changelog: [internal]

This creates a test to make sure there are no changes in React Native dramatically increasing memory usage in simple scenarios.

Reviewed By: rshest

Differential Revision: D79646758

fbshipit-source-id: d7a863468adabb75de7ceb123d96131564bd0959
2025-08-06 05:40:02 -07:00
Rubén Norte 789fc57254 Improve API to take JS heap snapshots in Fantom (#53071)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53071

Changelog: [internal]

The current API to take JS heap snapshots has some problems:
1. Ergonomics: it requires you to input the filepath where you want to store the snapshot. This isn't aligned with the behavior we have for JS traces where the output path is provided to you.
2. It doesn't work in optimized builds, as it requires a specific option in Hermes.

For 1), this replaces `Fantom.saveJSMemoryHeapSnapshot(filePath)` with `Fantom.takeJSMemoryHeapSnapshot()` that outputs the snapshot in a predefined path and prints it to the console.

For 2), this adds a new environment variable to force building Hermes with memory instrumentation (`FANTOM_ENABLE_JS_MEMORY_INSTRUMENTATION`). This is exposed as an option and not set by default because it has a performance overhead at runtime that we don't want to pay (especially in benchmarks).

This option only works when using Buck in development, because we want to generate this new binary type on demand when necessary, instead of making it part of the prebuilts we do before running tests in OSS and CI.

Reviewed By: lenaic

Differential Revision: D79642314

fbshipit-source-id: a2980616a495bd6dca29c0709a9581db6fb3f2cc
2025-08-06 05:40:02 -07:00
Rubén Norte 2187f653f6 Improve filenames of JS sampling profiler traces (#53069)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53069

Changelog: [internal]

This changes the names of the JS traces from Fantom from using a unix timestamp in the file name to using the ISO date:

- From: `View-itest.js-1754406329686.cpuprofile`
- To: `View-itest.js-2025-08-05T15:05:29.686Z.cpuprofile`

Reviewed By: rshest

Differential Revision: D79646760

fbshipit-source-id: d8a654724c1abc2d3e285ee658c2d390d3241d82
2025-08-06 05:40:02 -07:00
Ruslan Shestopalyuk 743db63570 Fix logspam when there is no benchmarking results (#53091)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53091

# Changelog:
[Internal] -
This fixes some excessive printing from the benchmarking results, which happened even if there was no benchmarks ran.

Reviewed By: rubennorte

Differential Revision: D79719026

fbshipit-source-id: 0e75d97ae9c762cab25007bfab5ca8a6dc43e148
2025-08-06 05:32:11 -07:00
Vitali Zaidman 93a052c611 Changelog for 0.81.0-rc.5 (#53086)
Summary:
Add changelog entry for 0.81.0-rc.5

## Changelog:

[INTERNAL] Changelog for 0.81.0-rc.5

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

Test Plan: N/A

Reviewed By: cortinico

Differential Revision: D79717506

Pulled By: vzaidman

fbshipit-source-id: 6086894bc5609e5e250939bcc2008a01350c2afe
2025-08-06 05:13:03 -07:00
Riccardo Cipolleschi 7d0bef2f25 Add warning if RCT_NEW_ARCH_ENABLED is set to 0 (#53070)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53070

As per title, this change adds a warning if you call `pod install` with `RCT_NEW_ARCH_ENABLED` set to 0.

## Changelog:
[iOS][Added] -  Add warning if RCT_NEW_ARCH_ENABLED is set to 0

Reviewed By: mdvacca

Differential Revision: D79655716

fbshipit-source-id: 516cc02f9b2dbddaae99c2d74bba249970641d1d
2025-08-06 04:56:19 -07:00
118 changed files with 2999 additions and 908 deletions
@@ -118,7 +118,6 @@ async function collectResults(discordWebHook) {
} else {
console.log('Discord webhook not set');
}
process.exit(1);
}
// Initialize Firebase client
+26 -206
View File
@@ -1,218 +1,40 @@
# Changelog
## v0.81.0-rc.3
### Breaking
- Metro to ^0.83.1 ([e247be793c](https://github.com/facebook/react-native/commit/e247be793c70a374955d798d8cbbc6eba58080ec) by [@motiz88](https://github.com/motiz88))
#### Android specific
#### iOS specific
### Added
#### Android specific
#### iOS specific
### Changed
#### Android specific
#### iOS specific
### Deprecated
#### Android specific
#### iOS specific
### Removed
#### Android specific
#### iOS specific
## v0.81.0-rc.5
### Fixed
#### Android specific
- **Runtime:** Fixed `ReactHostImpl.nativeModules` always returning an empty list ([2f46a49](https://github.com/facebook/react-native/commit/2f46a49b8d8a11d5cf4342eee83c469b545c6779) by [@lukmccall](https://github.com/lukmccall))
## v0.81.0-rc.4 - Burned
## v0.81.0-rc.3
### Changed
- **Metro:** Metro to ^0.83.1 ([e247be793c](https://github.com/facebook/react-native/commit/e247be793c70a374955d798d8cbbc6eba58080ec) by [@motiz88](https://github.com/motiz88))
### Fixed
#### Android specific
- **rngp:** Fix a race condition with codegen libraries missing sources ([9013a9e666](https://github.com/facebook/react-native/commit/9013a9e66629677c47e1b69703f9fc8f4cbc1c2c) by [@cortinico](https://github.com/cortinico))
- Make accessors inside HeadlessJsTaskService open again ([7ef57163cb](https://github.com/facebook/react-native/commit/7ef57163cb016317e43e563da7ea181989f6abca) by [@cortinico](https://github.com/cortinico))
#### iOS specific
### Security
#### Android specific
#### iOS specific
### Unknown
- Release 0.81.0-rc.3 ([0e6009eecf](https://github.com/facebook/react-native/commit/0e6009eecfeac71121a045f0f7e6bae94ffc11b0) by [@react-native-bot](https://github.com/react-native-bot))
- Bump Podfile.lock ([3695258eed](https://github.com/facebook/react-native/commit/3695258eed45fed5fbff5dd6128c88275f91083c) by [@react-native-bot](https://github.com/react-native-bot))
#### Android Unknown
#### iOS Unknown
#### Failed to parse
- **API:** Make accessors inside HeadlessJsTaskService open again ([7ef57163cb](https://github.com/facebook/react-native/commit/7ef57163cb016317e43e563da7ea181989f6abca) by [@cortinico](https://github.com/cortinico))
## v0.81.0-rc.2
### Breaking
#### Android specific
#### iOS specific
### Added
#### Android specific
#### iOS specific
### Changed
- Added support to `react-native/babel-preset` for a `hermesParserOptions` option, that expects an object that enables overriding `hermes-parser` options. ([0508eddfe6](https://github.com/facebook/react-native/commit/0508eddfe60df60cb3bfa4074ae199bd0e492d5f) by [@yungsters](https://github.com/yungsters))
- `NewAppScreen` no longer internally handles device safe area, use optional `safeAreaInsets` prop (aligned in 0.81 template) ([732bd12dc2](https://github.com/facebook/react-native/commit/732bd12dc21460641ef01b23f2eb722f26b060d5) by [@huntie](https://github.com/huntie))
#### Android specific
#### iOS specific
### Deprecated
#### Android specific
#### iOS specific
### Removed
#### Android specific
#### iOS specific
- **API:** `NewAppScreen` no longer internally handles device safe area, use optional `safeAreaInsets` prop (aligned in 0.81 template) ([732bd12dc2](https://github.com/facebook/react-native/commit/732bd12dc21460641ef01b23f2eb722f26b060d5) by [@huntie](https://github.com/huntie))
- **Babel:** Added support to `react-native/babel-preset` for a `hermesParserOptions` option, that expects an object that enables overriding `hermes-parser` options. ([0508eddfe6](https://github.com/facebook/react-native/commit/0508eddfe60df60cb3bfa4074ae199bd0e492d5f) by [@yungsters](https://github.com/yungsters))
### Fixed
#### Android specific
#### iOS specific
- Fixed issue with RNDeps release/debug switch failing ([4ee2b60a1e](https://github.com/facebook/react-native/commit/4ee2b60a1eacca744d58a7ad336ca9d3714289f6) by [@chrfalch](https://github.com/chrfalch))
- Fixed missing script for resolving prebuilt xcframework when switching between release/debug ([2e55241a90](https://github.com/facebook/react-native/commit/2e55241a901b4cd95917de68ce9078928820a208) by [@chrfalch](https://github.com/chrfalch))
### Security
#### Android specific
#### iOS specific
### Unknown
- Release 0.81.0-rc.2 ([68ef746ec5](https://github.com/facebook/react-native/commit/68ef746ec5dd7d2874d190733e75bb8197034c5a) by [@react-native-bot](https://github.com/react-native-bot))
- Fix E2E test script when the ci flag is not specified ([cdd7f99581](https://github.com/facebook/react-native/commit/cdd7f995813727b630ff38abc8a910a0f8f10b37) by [@cipolleschi](https://github.com/cipolleschi))
- Fix E2E script when using CI artifacts ([d8bf94489a](https://github.com/facebook/react-native/commit/d8bf94489ab7498eba7e5f45d09dd2819fe739c3) by [@cipolleschi](https://github.com/cipolleschi))
- Bump Podfile.lock ([10b63c15b6](https://github.com/facebook/react-native/commit/10b63c15b6faaf54d555ede879f82ac565f030a9) by [@react-native-bot](https://github.com/react-native-bot))
- Release 0.81.0-rc.1 ([b06bb89ddd](https://github.com/facebook/react-native/commit/b06bb89ddd3cebddea4716036a4368b87a65f492) by [@react-native-bot](https://github.com/react-native-bot))
#### Android Unknown
#### iOS Unknown
#### Failed to parse
- **Podspec:** Fixed issue with RNDeps release/debug switch failing ([4ee2b60a1e](https://github.com/facebook/react-native/commit/4ee2b60a1eacca744d58a7ad336ca9d3714289f6) by [@chrfalch](https://github.com/chrfalch))
- **Podspec:** Fixed missing script for resolving prebuilt xcframework when switching between release/debug ([2e55241a90](https://github.com/facebook/react-native/commit/2e55241a901b4cd95917de68ce9078928820a208) by [@chrfalch](https://github.com/chrfalch))
## v0.81.0-rc.1
@@ -220,28 +42,27 @@
#### iOS specific
- Add release/debug switch script for React-Core-prebuilt ([42d1a7934c](https://github.com/facebook/react-native/commit/42d1a7934cad4b2c92653e3fa7781c2af8f44df4) by [@chrfalch](https://github.com/chrfalch))
- Added support for using USE_FRAMEWORKS with prebuilt React Native Core ([40e45f5366](https://github.com/facebook/react-native/commit/40e45f53661ce80c3a6fbbf07f52dc900afcad52) by [@chrfalch](https://github.com/chrfalch))
- Add the `ENTERPRISE_REPOSITORY` env variable to cocopaods infra ([23f3bf9239](https://github.com/facebook/react-native/commit/23f3bf9239a849590f1c72b25732d0090780128c) by [@cipolleschi](https://github.com/cipolleschi))
- **CocoaPods** Add the `ENTERPRISE_REPOSITORY` env variable to cocoapods infra ([23f3bf9239](https://github.com/facebook/react-native/commit/23f3bf9239a849590f1c72b25732d0090780128c) by [@cipolleschi](https://github.com/cipolleschi))
- **Prebuild:** Add release/debug switch script for React-Core-prebuilt ([42d1a7934c](https://github.com/facebook/react-native/commit/42d1a7934cad4b2c92653e3fa7781c2af8f44df4) by [@chrfalch](https://github.com/chrfalch))
- **Prebuild:** Added support for using USE_FRAMEWORKS with prebuilt React Native Core ([40e45f5366](https://github.com/facebook/react-native/commit/40e45f53661ce80c3a6fbbf07f52dc900afcad52) by [@chrfalch](https://github.com/chrfalch))
### Changed
- Bump Metro to 0.83.0 ([6b9f5d622f](https://github.com/facebook/react-native/commit/6b9f5d622ffbe79da8f4e7b7d8094504a480425e) by [@robhogan](https://github.com/robhogan))
- **Metro:** Bump Metro to 0.83.0 ([6b9f5d622f](https://github.com/facebook/react-native/commit/6b9f5d622ffbe79da8f4e7b7d8094504a480425e) by [@robhogan](https://github.com/robhogan))
#### Android specific
- Gradle to 8.14.3 ([6892dde363](https://github.com/facebook/react-native/commit/6892dde36373bbef2d0afe535ae818b1a7164f08) by [@cortinico](https://github.com/cortinico))
- Expose `react_renderer_bridging` headers via prefab ([d1730ff960](https://github.com/facebook/react-native/commit/d1730ff960fcb9a01ee94b9e46e5a9fbb7d73f4a) by [@tomekzaw](https://github.com/tomekzaw))
- Introduce more deprecation warnings for Legacy Arch classes ([625f69f284](https://github.com/facebook/react-native/commit/625f69f284ddfd9c6beecaa4052a871d092053ef) by [@cortinico](https://github.com/cortinico))
- **Gradle:** Gradle to 8.14.3 ([6892dde363](https://github.com/facebook/react-native/commit/6892dde36373bbef2d0afe535ae818b1a7164f08) by [@cortinico](https://github.com/cortinico))
- **Gradle:** Expose `react_renderer_bridging` headers via prefab ([d1730ff960](https://github.com/facebook/react-native/commit/d1730ff960fcb9a01ee94b9e46e5a9fbb7d73f4a) by [@tomekzaw](https://github.com/tomekzaw))
- **Legacy Arch:** Introduce more deprecation warnings for Legacy Arch classes ([625f69f284](https://github.com/facebook/react-native/commit/625f69f284ddfd9c6beecaa4052a871d092053ef) by [@cortinico](https://github.com/cortinico))
### Fixed
- Fixed nodes with `display: contents` set being cloned with the wrong owner ([d4b36b0300](https://github.com/facebook/react-native/commit/d4b36b03003eb2de9eaf5b57bb639bae8cc12f20) by [@j-piasecki](https://github.com/j-piasecki))
- **Yoga:** Fixed nodes with `display: contents` set being cloned with the wrong owner ([d4b36b0300](https://github.com/facebook/react-native/commit/d4b36b03003eb2de9eaf5b57bb639bae8cc12f20) by [@j-piasecki](https://github.com/j-piasecki))
#### iOS specific
- Fixed premature return in header file generation from podspec globs ([f2b064c2d4](https://github.com/facebook/react-native/commit/f2b064c2d40c39017ac2a31bf3caf8acef23038c) by [@chrfalch](https://github.com/chrfalch))
- **Podspec:** Fixed premature return in header file generation from podspec globs ([f2b064c2d4](https://github.com/facebook/react-native/commit/f2b064c2d40c39017ac2a31bf3caf8acef23038c) by [@chrfalch](https://github.com/chrfalch))
## v0.81.0-rc.0
@@ -1586,4 +1407,3 @@ See [CHANGELOG-0.5x](./CHANGELOG-0.5x.md#v0530)
## v0.52.0
See [CHANGELOG-0.5x](./CHANGELOG-0.5x.md#v0520)
@@ -29,6 +29,8 @@
"lib"
],
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/parser": "^7.25.3",
"glob": "^7.1.1",
"hermes-parser": "0.30.0",
"invariant": "^2.2.4",
@@ -15,7 +15,7 @@ import type {HostInstance} from 'react-native';
import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance';
import * as Fantom from '@react-native/fantom';
import {createRef, useMemo} from 'react';
import {createRef} from 'react';
import {Animated, View, useAnimatedValue} from 'react-native';
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags';
@@ -747,98 +747,3 @@ test('Animated.sequence', () => {
expect(_isSequenceFinished).toBe(true);
});
test('Props default value is restored when disconnected from animated', () => {
let _animatedOpacity;
const elementRef = createRef<HostInstance>();
function MyApp({
shouldAnimate,
}: $ReadOnly<{
shouldAnimate?: boolean,
}>) {
const animatedOpacity = useAnimatedValue(1, {useNativeDriver: true});
const opacity = useMemo(
() => (shouldAnimate === true ? animatedOpacity : undefined),
[shouldAnimate, animatedOpacity],
);
const scale = useMemo(
() =>
opacity?.interpolate({
inputRange: [0, 1],
outputRange: [0.95, 1],
}) ?? new Animated.Value(1),
[opacity],
);
_animatedOpacity = animatedOpacity;
return (
<Animated.View
ref={elementRef}
style={[
{
opacity,
transform: [{scale}],
height: 100,
width: 100,
},
]}
/>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<MyApp shouldAnimate={true} />);
});
const element = ensureInstance(elementRef.current, ReactNativeElement);
expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
<rn-view />, // default opacity is 1
);
Fantom.runTask(() => {
Animated.timing(_animatedOpacity, {
toValue: 0,
duration: 500,
useNativeDriver: true,
}).start();
});
Fantom.unstable_produceFramesForDuration(500);
Fantom.runWorkLoop();
expect(Fantom.unstable_getDirectManipulationProps(element)).toEqual({
opacity: 0,
transform: [{scale: 0.95}],
});
expect(
root.getRenderedOutput({props: ['opacity', 'transform']}).toJSX(),
).toEqual(<rn-view opacity="0" transform='[{"scale": 0.950000}]' />);
Fantom.runTask(() => {
root.render(<MyApp shouldAnimate={false} />);
});
expect(Fantom.unstable_getDirectManipulationProps(element)).toEqual({
opacity: null,
transform: null,
});
if (ReactNativeFeatureFlags.cxxNativeAnimatedRemoveJsSync()) {
expect(
root.getRenderedOutput({props: ['opacity', 'transform']}).toJSX(),
).toEqual(<rn-view transform='[{"scale": 0.950000}]' />); // TODO: T223344928 scale should be 1
} else {
expect(
root.getRenderedOutput({props: ['opacity', 'transform']}).toJSX(),
).toEqual(<rn-view transform='[{"scale": 1.000000}]' />);
}
Fantom.runWorkLoop();
});
@@ -618,6 +618,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
// so omitting onBlur and onFocus pressability handlers here.
const {onBlur, onFocus, ...eventHandlers} = usePressability(config);
const _accessibilityLabel =
props?.['aria-label'] ?? props?.accessibilityLabel;
let _accessibilityState;
if (
accessibilityState != null ||
@@ -681,6 +684,7 @@ function InternalTextInput(props: TextInputProps): React.Node {
{...otherProps}
{...eventHandlers}
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
accessible={accessible}
submitBehavior={submitBehavior}
@@ -744,8 +748,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
{...otherProps}
{...colorProps}
{...eventHandlers}
accessibilityState={_accessibilityState}
accessibilityLabel={_accessibilityLabel}
accessibilityLabelledBy={_accessibilityLabelledBy}
accessibilityState={_accessibilityState}
accessible={accessible}
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
autoCapitalize={autoCapitalize}
@@ -432,6 +432,7 @@ jest.unmock('../TextInput');
expect(instance.toJSON()).toMatchInlineSnapshot(`
<RCTSinglelineTextInputView
accessibilityLabel="label"
accessibilityState={
Object {
"busy": true,
@@ -169,6 +169,460 @@ describe('<Image>', () => {
);
});
});
describe('defaultSource', () => {
it('can provide a default image to display', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
defaultSource={require('./img/img1.png')}
source={LOGO_SOURCE}
/>,
);
});
expect(
root.getRenderedOutput({props: ['defaultSource']}).toJSX(),
).toEqual(
<rn-image
defaultSource-type="remote"
defaultSource-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
/>,
);
});
});
describe('height', () => {
it('provides height for image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image height={100} source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
<rn-image height="100.000000" />,
);
});
});
describe('width', () => {
it('provides width for image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image width={100} source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['width']}).toJSX()).toEqual(
<rn-image width="100.000000" />,
);
});
});
describe('loading progress', () => {
(
[
['onError', 'fails to load'],
['onLoadStart', 'start loading'],
['onProgress', 'is loading'],
['onLoad', 'loads successfully'],
['onLoadEnd', 'ends loading'],
] as const
).forEach(([onProp, event]) => {
it(`${onProp} is called when image ${event}`, () => {
const onPropCallback = jest.fn();
const ref = createRef<HostInstance>();
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
ref={ref}
source={LOGO_SOURCE}
onError={() => {
onProp === 'onError' && onPropCallback();
}}
onLoad={() => {
onProp === 'onLoad' && onPropCallback();
}}
onLoadStart={() => {
onProp === 'onLoadStart' && onPropCallback();
}}
onLoadEnd={() => {
onProp === 'onLoadEnd' && onPropCallback();
}}
onProgress={() => {
onProp === 'onProgress' && onPropCallback();
}}
/>,
);
});
expect(onPropCallback).toHaveBeenCalledTimes(0);
const image = ensureInstance(ref.current, ReactNativeElement);
Fantom.dispatchNativeEvent(image, onProp, {});
expect(onPropCallback).toHaveBeenCalledTimes(1);
});
});
});
describe('referrerPolicy', () => {
(
[
'no-referrer',
'no-referrer-when-downgrade',
'origin',
'origin-when-cross-origin',
'same-origin',
'strict-origin',
'strict-origin-when-cross-origin',
'unsafe-url',
] as const
).forEach(referrerPolicy => {
it(`${referrerPolicy} sets correct "Referrer-Policy" header`, () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image referrerPolicy={referrerPolicy} src={LOGO_SOURCE.uri} />,
);
});
expect(
root.getRenderedOutput({props: ['source-header']}).toJSX(),
).toEqual(
<rn-image source-header-Referrer-Policy={referrerPolicy} />,
);
});
});
});
describe('resizeMode', () => {
it('is set to "cover" by default', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['resizeMode']}).toJSX()).toEqual(
<rn-image />,
);
Fantom.runTask(() => {
root.render(<Image resizeMode="cover" source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['resizeMode']}).toJSX()).toEqual(
<rn-image />,
);
});
(['stretch', 'contain', 'repeat', 'center'] as const).forEach(
resizeMode => {
it(`can be set to "${resizeMode}"`, () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image resizeMode={resizeMode} source={LOGO_SOURCE} />,
);
});
expect(
root.getRenderedOutput({props: ['resizeMode']}).toJSX(),
).toEqual(<rn-image resizeMode={resizeMode} />);
});
},
);
});
describe('source', () => {
it('can be set to a local image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image source={require('./img/img1.png')} />);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-size="{1, 1}"
source-type="local"
source-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
/>,
);
});
it('can be set to a remote image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
width: 100,
height: 100,
scale: 2,
cache: 'only-if-cached',
method: 'POST',
body: 'name=React+Native',
headers: {
Authorization: 'Basic RandomString',
},
}}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-body="name=React+Native"
source-cache="only-if-cached"
source-header-Authorization="Basic RandomString"
source-method="POST"
source-scale="2"
source-size="{100, 100}"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
it('can be set to a list of remote images', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
source={[
{
uri: 'https://reactnative.dev/img/tiny_logo.png',
scale: 1,
headers: {
Authorization: 'Basic RandomString',
},
},
{
uri: 'https://reactnative.dev/img/medium_logo.png',
scale: 2,
cache: 'only-if-cached',
},
{
uri: 'https://reactnative.dev/img/large_logo.png',
scale: 3,
method: 'POST',
},
]}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-header-Authorization="Basic RandomString"
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-cache="only-if-cached"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/medium_logo.png"
source-3x-method="POST"
source-3x-type="remote"
source-3x-uri="https://reactnative.dev/img/large_logo.png"
/>,
);
});
});
describe('src', () => {
it('can be set to a remote image', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image src="https://reactnative.dev/img/tiny_logo.png" />,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
it('takes precedence over `source` prop', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
src="https://reactnative.dev/img/tiny_logo.png"
source={{uri: 'https://reactnative.dev/img/medium_logo.png'}}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
});
describe('srcSet', () => {
it('can be set to a list of remote images', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
srcSet={
'https://reactnative.dev/img/tiny_logo.png 1x, https://reactnative.dev/img/header_logo.svg 2x'
}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
/>,
);
});
it('defaults to `1x` descriptor', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
srcSet={
'https://reactnative.dev/img/tiny_logo.png, https://reactnative.dev/img/header_logo.svg 2x'
}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
/>,
);
});
it('uses `src` for `1x` descriptor when provided', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
srcSet={
'https://reactnative.dev/img/header_logo.svg 2x, https://reactnative.dev/img/large_logo.svg 3x'
}
src="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
source-3x-type="remote"
source-3x-uri="https://reactnative.dev/img/large_logo.svg"
/>,
);
});
});
describe('style', () => {
it('can be set', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<Image
style={{
width: 100,
height: 100,
resizeMode: 'contain',
}}
source={LOGO_SOURCE}
/>,
);
});
expect(root.getRenderedOutput().toJSX()).toEqual(
<rn-image
height="100.000000"
overflow="hidden"
resizeMode="contain"
width="100.000000"
source-scale="1"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
});
describe('testID', () => {
it('can be set', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image testID="test" source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['testID']}).toJSX()).toEqual(
<rn-image testID="test" />,
);
});
});
describe('tintColor', () => {
it('can be set', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<Image tintColor="red" source={LOGO_SOURCE} />);
});
expect(root.getRenderedOutput({props: ['tintColor']}).toJSX()).toEqual(
<rn-image tintColor="rgba(255, 0, 0, 1)" />,
);
});
});
});
describe('ref', () => {
@@ -0,0 +1,128 @@
/**
* 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
*/
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
import type {HostInstance} from 'react-native';
import * as Fantom from '@react-native/fantom';
import * as React from 'react';
import {createRef} from 'react';
import {ImageBackground} from 'react-native';
import ensureInstance from 'react-native/src/private/__tests__/utilities/ensureInstance';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
describe('<ImageBackground>', () => {
describe('props', () => {
describe('ImageProps', () => {
it('can have local source', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<ImageBackground source={require('./img/img1.png')} />);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-scale="1"
source-size="{1, 1}"
source-type="local"
source-uri="file://drawable-mdpi/packages_reactnative_libraries_image___tests___img_img1.png"
/>,
);
});
it('can have remote source', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<ImageBackground
source={{
uri: 'https://reactnative.dev/img/tiny_logo.png',
width: 100,
height: 100,
scale: 2,
cache: 'only-if-cached',
method: 'POST',
body: 'name=React+Native',
headers: {
Authorization: 'Basic RandomString',
},
}}
/>,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-body="name=React+Native"
source-cache="only-if-cached"
source-header-Authorization="Basic RandomString"
source-method="POST"
source-scale="2"
source-size="{100, 100}"
source-type="remote"
source-uri="https://reactnative.dev/img/tiny_logo.png"
/>,
);
});
it('can have srcSet', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(
<ImageBackground srcSet="https://reactnative.dev/img/tiny_logo.png 1x, https://reactnative.dev/img/header_logo.svg 2x" />,
);
});
expect(root.getRenderedOutput({props: ['source']}).toJSX()).toEqual(
<rn-image
source-1x-scale="1"
source-1x-type="remote"
source-1x-uri="https://reactnative.dev/img/tiny_logo.png"
source-2x-scale="2"
source-2x-type="remote"
source-2x-uri="https://reactnative.dev/img/header_logo.svg"
/>,
);
});
});
describe('style', () => {
it('can be set', () => {
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<ImageBackground style={{width: 100, height: 100}} />);
});
expect(
root.getRenderedOutput({props: ['width|height']}).toJSX(),
).toEqual(<rn-image width="100.000000" height="100.000000" />);
});
});
});
describe('ref', () => {
it('Allows to set a reference to the inner `Image` component', () => {
const elementRef = createRef<HostInstance>();
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<ImageBackground imageRef={elementRef} />);
});
const image = ensureInstance(elementRef.current, ReactNativeElement);
expect(image.tagName).toBe('RN:Image');
});
});
});
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow
* @generated SignedSource<<16b364e89f43b8a47832b0dfb98af11e>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<cf323fc5ca893bab5669c7d321660412>>
*/
'use strict';
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow strict-local
* @generated SignedSource<<1dd9e9c3f20e37ae14e485fc6ee3d9e9>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<908f5fb85384725318e261f40e49d9a6>>
*/
'use strict';
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow
* @generated SignedSource<<e2c46705ed927302dbe9332dafba459d>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<8f46fdc9267fcc4fdc9e76842fe24066>>
*/
'use strict';
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow strict-local
* @generated SignedSource<<e8dce0e82b831c91465d04b49fb48ab2>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<83073425aa3f71ced2c8c51f25a25938>>
*/
'use strict';
@@ -7,9 +7,7 @@
* @noformat
* @nolint
* @flow strict-local
* @generated SignedSource<<556d1487de0b9e4a09cbc67dd130a884>>
*
* This file was sync'd from the facebook/react repository.
* @generated SignedSource<<52163887de05f1cff05388145cf85b3b>>
*/
'use strict';
+517 -243
View File
@@ -14,6 +14,7 @@ import type {GestureResponderEvent} from '../Types/CoreEventTypes';
import type {NativeTextProps} from './TextNativeComponent';
import type {PressRetentionOffset, TextProps} from './TextProps';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import * as PressabilityDebug from '../Pressability/PressabilityDebug';
import usePressability from '../Pressability/usePressability';
import flattenStyle from '../StyleSheet/flattenStyle';
@@ -35,156 +36,495 @@ type TextForwardRef = React.ElementRef<
*
* @see https://reactnative.dev/docs/text
*/
const TextImpl: component(
ref?: React.RefSetter<TextForwardRef>,
...props: TextProps
) = ({
ref: forwardedRef,
accessible,
accessibilityLabel,
accessibilityState,
allowFontScaling,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-label': ariaLabel,
'aria-selected': ariaSelected,
children,
ellipsizeMode,
disabled,
id,
nativeID,
numberOfLines,
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
selectable,
selectionColor,
suppressHighlighting,
style,
...restProps
}: {
ref?: React.RefSetter<TextForwardRef>,
...TextProps,
}) => {
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
let _accessibilityState: ?TextProps['accessibilityState'] =
accessibilityState;
if (
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
if (_accessibilityState != null) {
_accessibilityState = {
busy: ariaBusy ?? _accessibilityState.busy,
checked: ariaChecked ?? _accessibilityState.checked,
disabled: ariaDisabled ?? _accessibilityState.disabled,
expanded: ariaExpanded ?? _accessibilityState.expanded,
selected: ariaSelected ?? _accessibilityState.selected,
};
} else {
_accessibilityState = {
busy: ariaBusy,
checked: ariaChecked,
disabled: ariaDisabled,
expanded: ariaExpanded,
selected: ariaSelected,
};
let _TextImpl;
if (ReactNativeFeatureFlags.reduceDefaultPropsInText()) {
const TextImplNoDefaultProps: component(
ref?: React.RefSetter<TextForwardRef>,
...props: TextProps
) = ({
ref: forwardedRef,
accessible,
accessibilityLabel,
accessibilityState,
allowFontScaling,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-label': ariaLabel,
'aria-selected': ariaSelected,
children,
ellipsizeMode,
disabled,
id,
nativeID,
numberOfLines,
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
selectable,
selectionColor,
suppressHighlighting,
style,
...restProps
}: {
ref?: React.RefSetter<TextForwardRef>,
...TextProps,
}) => {
const processedProps = restProps as {
...NativeTextProps,
};
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
let _accessibilityState: ?TextProps['accessibilityState'] =
accessibilityState;
if (
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
if (_accessibilityState != null) {
_accessibilityState = {
busy: ariaBusy ?? _accessibilityState.busy,
checked: ariaChecked ?? _accessibilityState.checked,
disabled: ariaDisabled ?? _accessibilityState.disabled,
expanded: ariaExpanded ?? _accessibilityState.expanded,
selected: ariaSelected ?? _accessibilityState.selected,
};
} else {
_accessibilityState = {
busy: ariaBusy,
checked: ariaChecked,
disabled: ariaDisabled,
expanded: ariaExpanded,
selected: ariaSelected,
};
}
}
}
const _accessibilityStateDisabled = _accessibilityState?.disabled;
const _disabled = disabled ?? _accessibilityStateDisabled;
const _accessibilityStateDisabled = _accessibilityState?.disabled;
const _disabled = disabled ?? _accessibilityStateDisabled;
const isPressable =
(onPress != null ||
onLongPress != null ||
onStartShouldSetResponder != null) &&
_disabled !== true;
// TODO: Move this processing to the view configuration.
const _selectionColor =
selectionColor != null ? processColor(selectionColor) : undefined;
let _style = style;
if (__DEV__) {
if (PressabilityDebug.isEnabled() && onPress != null) {
_style = [style, {color: 'magenta'}];
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
// falsy states we need to update the accessibilityState object to use the disabled prop.
if (
_accessibilityState != null &&
_disabled !== _accessibilityStateDisabled &&
((_disabled != null && _disabled !== false) ||
(_accessibilityStateDisabled != null &&
_accessibilityStateDisabled !== false))
) {
_accessibilityState.disabled = _disabled;
}
}
let _numberOfLines = numberOfLines;
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
const _accessible = Platform.select({
ios: accessible !== false,
android:
accessible == null
? onPress != null || onLongPress != null
: accessible,
default: accessible,
});
const isPressable =
(onPress != null ||
onLongPress != null ||
onStartShouldSetResponder != null) &&
_disabled !== true;
// TODO: Move this processing to the view configuration.
const _selectionColor =
selectionColor != null ? processColor(selectionColor) : undefined;
let _style = style;
if (__DEV__) {
console.error(
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
if (PressabilityDebug.isEnabled() && onPress != null) {
_style = [style, {color: 'magenta'}];
}
}
let _numberOfLines = numberOfLines;
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
if (__DEV__) {
console.error(
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
);
}
_numberOfLines = 0;
}
let _selectable = selectable;
let processedStyle = flattenStyle<TextStyleProp>(_style);
if (processedStyle != null) {
let overrides: ?{...TextStyleInternal} = null;
if (typeof processedStyle.fontWeight === 'number') {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.fontWeight =
// $FlowFixMe[incompatible-cast]
(String(processedStyle.fontWeight): TextStyleInternal['fontWeight']);
}
if (processedStyle.userSelect != null) {
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
overrides = overrides || ({}: {...TextStyleInternal});
overrides.userSelect = undefined;
}
if (processedStyle.verticalAlign != null) {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.textAlignVertical =
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
overrides.verticalAlign = undefined;
}
if (overrides != null) {
// $FlowFixMe[incompatible-type]
_style = [_style, overrides];
}
}
const _nativeID = id ?? nativeID;
if (_accessibilityLabel !== undefined) {
processedProps.accessibilityLabel = _accessibilityLabel;
}
if (_accessibilityState !== undefined) {
processedProps.accessibilityState = _accessibilityState;
}
if (_nativeID !== undefined) {
processedProps.nativeID = _nativeID;
}
if (_numberOfLines !== undefined) {
processedProps.numberOfLines = _numberOfLines;
}
if (_selectable !== undefined) {
processedProps.selectable = _selectable;
}
if (_style !== undefined) {
processedProps.style = _style;
}
if (_selectionColor !== undefined) {
processedProps.selectionColor = _selectionColor;
}
let textPressabilityProps: ?TextPressabilityProps;
if (isPressable) {
textPressabilityProps = {
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
suppressHighlighting,
};
}
const hasTextAncestor = useContext(TextAncestorContext);
if (hasTextAncestor) {
processedProps.disabled = disabled;
processedProps.children = children;
if (isPressable) {
return (
<NativePressableVirtualText
ref={forwardedRef}
textProps={processedProps}
textPressabilityProps={textPressabilityProps ?? {}}
/>
);
}
return <NativeVirtualText {...processedProps} ref={forwardedRef} />;
}
let nativeText = null;
processedProps.accessible = _accessible;
processedProps.allowFontScaling = allowFontScaling !== false;
processedProps.disabled = _disabled;
processedProps.ellipsizeMode = ellipsizeMode ?? 'tail';
processedProps.children = children;
if (isPressable) {
nativeText = (
<NativePressableText
ref={forwardedRef}
textProps={processedProps}
textPressabilityProps={textPressabilityProps ?? {}}
/>
);
} else {
nativeText = <NativeText {...processedProps} ref={forwardedRef} />;
}
if (children == null) {
return nativeText;
}
// If the children do not contain a JSX element it would not be possible to have a
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
// which has a performance overhead. Since we do this for performance reasons we need
// to keep the check simple to avoid regressing overall perf. For this reason the
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
// to capture the majority of `Text` uses but also not make this check too expensive.
if (Array.isArray(children) && children.length <= 3) {
let hasNonTextChild = false;
for (let child of children) {
if (child != null && typeof child === 'object') {
hasNonTextChild = true;
break;
}
}
if (!hasNonTextChild) {
return nativeText;
}
} else if (typeof children !== 'object') {
return nativeText;
}
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
};
_TextImpl = TextImplNoDefaultProps;
} else {
const TextImplLegacy: component(
ref?: React.RefSetter<TextForwardRef>,
...props: TextProps
) = ({
ref: forwardedRef,
accessible,
accessibilityLabel,
accessibilityState,
allowFontScaling,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
'aria-disabled': ariaDisabled,
'aria-expanded': ariaExpanded,
'aria-label': ariaLabel,
'aria-selected': ariaSelected,
children,
ellipsizeMode,
disabled,
id,
nativeID,
numberOfLines,
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
selectable,
selectionColor,
suppressHighlighting,
style,
...restProps
}: {
ref?: React.RefSetter<TextForwardRef>,
...TextProps,
}) => {
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
let _accessibilityState: ?TextProps['accessibilityState'] =
accessibilityState;
if (
ariaBusy != null ||
ariaChecked != null ||
ariaDisabled != null ||
ariaExpanded != null ||
ariaSelected != null
) {
if (_accessibilityState != null) {
_accessibilityState = {
busy: ariaBusy ?? _accessibilityState.busy,
checked: ariaChecked ?? _accessibilityState.checked,
disabled: ariaDisabled ?? _accessibilityState.disabled,
expanded: ariaExpanded ?? _accessibilityState.expanded,
selected: ariaSelected ?? _accessibilityState.selected,
};
} else {
_accessibilityState = {
busy: ariaBusy,
checked: ariaChecked,
disabled: ariaDisabled,
expanded: ariaExpanded,
selected: ariaSelected,
};
}
}
const _accessibilityStateDisabled = _accessibilityState?.disabled;
const _disabled = disabled ?? _accessibilityStateDisabled;
const isPressable =
(onPress != null ||
onLongPress != null ||
onStartShouldSetResponder != null) &&
_disabled !== true;
// TODO: Move this processing to the view configuration.
const _selectionColor =
selectionColor != null ? processColor(selectionColor) : undefined;
let _style = style;
if (__DEV__) {
if (PressabilityDebug.isEnabled() && onPress != null) {
_style = [style, {color: 'magenta'}];
}
}
let _numberOfLines = numberOfLines;
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
if (__DEV__) {
console.error(
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
);
}
_numberOfLines = 0;
}
let _selectable = selectable;
let processedStyle = flattenStyle<TextStyleProp>(_style);
if (processedStyle != null) {
let overrides: ?{...TextStyleInternal} = null;
if (typeof processedStyle.fontWeight === 'number') {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.fontWeight =
// $FlowFixMe[incompatible-cast]
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
}
if (processedStyle.userSelect != null) {
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
overrides = overrides || ({}: {...TextStyleInternal});
overrides.userSelect = undefined;
}
if (processedStyle.verticalAlign != null) {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.textAlignVertical =
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
overrides.verticalAlign = undefined;
}
if (overrides != null) {
// $FlowFixMe[incompatible-type]
_style = [_style, overrides];
}
}
const _nativeID = id ?? nativeID;
const hasTextAncestor = useContext(TextAncestorContext);
if (hasTextAncestor) {
if (isPressable) {
return (
<NativePressableVirtualText
ref={forwardedRef}
textProps={{
...restProps,
accessibilityLabel: _accessibilityLabel,
accessibilityState: _accessibilityState,
nativeID: _nativeID,
numberOfLines: _numberOfLines,
selectable: _selectable,
selectionColor: _selectionColor,
style: _style,
disabled: disabled,
children,
}}
textPressabilityProps={{
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
suppressHighlighting,
}}
/>
);
}
return (
<NativeVirtualText
{...restProps}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
nativeID={_nativeID}
numberOfLines={_numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={_style}
disabled={disabled}>
{children}
</NativeVirtualText>
);
}
_numberOfLines = 0;
}
let _selectable = selectable;
let processedStyle = flattenStyle<TextStyleProp>(_style);
if (processedStyle != null) {
let overrides: ?{...TextStyleInternal} = null;
if (typeof processedStyle.fontWeight === 'number') {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.fontWeight =
// $FlowFixMe[incompatible-cast]
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
// falsy states we need to update the accessibilityState object to use the disabled prop.
if (
_disabled !== _accessibilityStateDisabled &&
((_disabled != null && _disabled !== false) ||
(_accessibilityStateDisabled != null &&
_accessibilityStateDisabled !== false))
) {
_accessibilityState = {..._accessibilityState, disabled: _disabled};
}
if (processedStyle.userSelect != null) {
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
overrides = overrides || ({}: {...TextStyleInternal});
overrides.userSelect = undefined;
}
const _accessible = Platform.select({
ios: accessible !== false,
android:
accessible == null
? onPress != null || onLongPress != null
: accessible,
default: accessible,
});
if (processedStyle.verticalAlign != null) {
overrides = overrides || ({}: {...TextStyleInternal});
overrides.textAlignVertical =
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
overrides.verticalAlign = undefined;
}
if (overrides != null) {
// $FlowFixMe[incompatible-type]
_style = [_style, overrides];
}
}
const _nativeID = id ?? nativeID;
const hasTextAncestor = useContext(TextAncestorContext);
if (hasTextAncestor) {
let nativeText = null;
if (isPressable) {
return (
<NativePressableVirtualText
nativeText = (
<NativePressableText
ref={forwardedRef}
textProps={{
...restProps,
accessibilityLabel: _accessibilityLabel,
accessibilityState: _accessibilityState,
accessible: _accessible,
allowFontScaling: allowFontScaling !== false,
disabled: _disabled,
ellipsizeMode: ellipsizeMode ?? 'tail',
nativeID: _nativeID,
numberOfLines: _numberOfLines,
selectable: _selectable,
selectionColor: _selectionColor,
style: _style,
disabled: disabled,
children,
}}
textPressabilityProps={{
@@ -203,127 +543,61 @@ const TextImpl: component(
}}
/>
);
} else {
nativeText = (
<NativeText
{...restProps}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
accessible={_accessible}
allowFontScaling={allowFontScaling !== false}
disabled={_disabled}
ellipsizeMode={ellipsizeMode ?? 'tail'}
nativeID={_nativeID}
numberOfLines={_numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={_style}>
{children}
</NativeText>
);
}
return (
<NativeVirtualText
{...restProps}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
nativeID={_nativeID}
numberOfLines={_numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={_style}
disabled={disabled}>
{children}
</NativeVirtualText>
);
}
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
// falsy states we need to update the accessibilityState object to use the disabled prop.
if (
_disabled !== _accessibilityStateDisabled &&
((_disabled != null && _disabled !== false) ||
(_accessibilityStateDisabled != null &&
_accessibilityStateDisabled !== false))
) {
_accessibilityState = {..._accessibilityState, disabled: _disabled};
}
const _accessible = Platform.select({
ios: accessible !== false,
android:
accessible == null ? onPress != null || onLongPress != null : accessible,
default: accessible,
});
let nativeText = null;
if (isPressable) {
nativeText = (
<NativePressableText
ref={forwardedRef}
textProps={{
...restProps,
accessibilityLabel: _accessibilityLabel,
accessibilityState: _accessibilityState,
accessible: _accessible,
allowFontScaling: allowFontScaling !== false,
disabled: _disabled,
ellipsizeMode: ellipsizeMode ?? 'tail',
nativeID: _nativeID,
numberOfLines: _numberOfLines,
selectable: _selectable,
selectionColor: _selectionColor,
style: _style,
children,
}}
textPressabilityProps={{
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
suppressHighlighting,
}}
/>
);
} else {
nativeText = (
<NativeText
{...restProps}
accessibilityLabel={_accessibilityLabel}
accessibilityState={_accessibilityState}
accessible={_accessible}
allowFontScaling={allowFontScaling !== false}
disabled={_disabled}
ellipsizeMode={ellipsizeMode ?? 'tail'}
nativeID={_nativeID}
numberOfLines={_numberOfLines}
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={_style}>
{children}
</NativeText>
);
}
if (children == null) {
return nativeText;
}
// If the children do not contain a JSX element it would not be possible to have a
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
// which has a performance overhead. Since we do this for performance reasons we need
// to keep the check simple to avoid regressing overall perf. For this reason the
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
// to capture the majority of `Text` uses but also not make this check too expensive.
if (Array.isArray(children) && children.length <= 3) {
let hasNonTextChild = false;
for (let child of children) {
if (child != null && typeof child === 'object') {
hasNonTextChild = true;
break;
}
}
if (!hasNonTextChild) {
if (children == null) {
return nativeText;
}
} else if (typeof children !== 'object') {
return nativeText;
}
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
};
// If the children do not contain a JSX element it would not be possible to have a
// nested `Text` component so we can skip adding the `TextAncestorContext` context wrapper
// which has a performance overhead. Since we do this for performance reasons we need
// to keep the check simple to avoid regressing overall perf. For this reason the
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
// to capture the majority of `Text` uses but also not make this check too expensive.
if (Array.isArray(children) && children.length <= 3) {
let hasNonTextChild = false;
for (let child of children) {
if (child != null && typeof child === 'object') {
hasNonTextChild = true;
break;
}
}
if (!hasNonTextChild) {
return nativeText;
}
} else if (typeof children !== 'object') {
return nativeText;
}
return <TextAncestorContext value={true}>{nativeText}</TextAncestorContext>;
};
_TextImpl = TextImplLegacy;
}
const TextImpl: component(
ref?: React.RefSetter<TextForwardRef>,
...props: TextProps
) = _TextImpl;
TextImpl.displayName = 'Text';
@@ -128,7 +128,6 @@ const HMRClient: HMRClientNativeInterface = {
JSON.stringify({
type: 'log',
level,
mode: global.RN$Bridgeless === true ? 'NOBRIDGE' : 'BRIDGE',
data: data.map(item =>
typeof item === 'string'
? item
@@ -96,6 +96,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
@interface RCTModalHostViewComponentView () <RCTFabricModalHostViewControllerDelegate>
@property (nonatomic, weak) UIView *accessibilityFocusedView;
@end
@implementation RCTModalHostViewComponentView {
@@ -148,6 +150,7 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
{
BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window;
if (shouldBePresented) {
[self saveAccessibilityFocusedView];
self.viewController.presentationController.delegate = self;
_isPresented = YES;
@@ -179,6 +182,8 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
if (eventEmitter) {
eventEmitter->onDismiss(ModalHostViewEventEmitter::OnDismiss{});
}
[self restoreAccessibilityFocusedView];
}];
}
}
@@ -207,6 +212,23 @@ static ModalHostViewEventEmitter::OnOrientationChange onOrientationChangeStruct(
[self ensurePresentedOnlyIfNeeded];
}
- (void)saveAccessibilityFocusedView
{
id focusedElement = UIAccessibilityFocusedElement(nil);
if (focusedElement && [focusedElement isKindOfClass:[UIView class]]) {
self.accessibilityFocusedView = (UIView *)focusedElement;
}
}
- (void)restoreAccessibilityFocusedView
{
id viewToFocus = self.accessibilityFocusedView;
if (viewToFocus) {
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, viewToFocus);
self.accessibilityFocusedView = nil;
}
}
#pragma mark - RCTFabricModalHostViewControllerDelegate
- (void)boundsDidChange:(CGRect)newBounds
@@ -3348,8 +3348,10 @@ public final class com/facebook/react/uimanager/DisplayMetricsHolder {
public static final fun getDisplayMetricsWritableMap (D)Lcom/facebook/react/bridge/WritableMap;
public static final fun getScreenDisplayMetrics ()Landroid/util/DisplayMetrics;
public static final fun getWindowDisplayMetrics ()Landroid/util/DisplayMetrics;
public static final fun initDisplayMetrics (Landroid/content/Context;)V
public static final fun initDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
public static final fun initScreenDisplayMetrics (Landroid/content/Context;)V
public static final fun initScreenDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
public static final fun initWindowDisplayMetrics (Landroid/content/Context;)V
public static final fun initWindowDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
public static final fun setScreenDisplayMetrics (Landroid/util/DisplayMetrics;)V
public static final fun setWindowDisplayMetrics (Landroid/util/DisplayMetrics;)V
}
@@ -630,6 +630,7 @@ dependencies {
api(libs.androidx.autofill)
api(libs.androidx.swiperefreshlayout)
api(libs.androidx.tracing)
api(libs.androidx.window)
api(libs.fbjni)
api(libs.fresco)
@@ -229,6 +229,9 @@ public class ReactInstanceManager {
return new ReactInstanceManagerBuilder();
}
/**
* @noinspection deprecation
*/
/* package */ ReactInstanceManager(
Context applicationContext,
@Nullable Activity currentActivity,
@@ -259,7 +262,11 @@ public class ReactInstanceManager {
FLog.d(TAG, "ReactInstanceManager.ctor()");
initializeSoLoaderIfNecessary(applicationContext);
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext);
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(applicationContext);
if (currentActivity != null) {
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
}
// See {@code ReactInstanceManagerBuilder} for description of all flags here.
mApplicationContext = applicationContext;
@@ -924,6 +931,13 @@ public class ReactInstanceManager {
ReactContext currentReactContext = getCurrentReactContext();
if (currentReactContext != null) {
DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext);
Activity currentActivity = currentReactContext.getCurrentActivity();
if (currentActivity != null) {
DisplayMetricsHolder.initWindowDisplayMetrics(currentActivity);
}
AppearanceModule appearanceModule =
currentReactContext.getNativeModule(AppearanceModule.class);
@@ -35,7 +35,8 @@ import java.util.List;
*
* @deprecated This class will be replaced by com.facebook.react.ReactHost in the New Architecture.
*/
@Deprecated
@Deprecated(
since = "This class is part of Legacy Architecture and will be removed in a future release")
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Nullsafe(Nullsafe.Mode.LOCAL)
public abstract class ReactNativeHost {
@@ -136,9 +136,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
setClipChildren(false);
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
}
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
}
@Override
@@ -883,7 +882,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private int mDeviceRotation = 0;
/* package */ CustomGlobalLayoutListener() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(getContext());
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(getContext());
mVisibleViewArea = new Rect();
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
}
@@ -1006,7 +1006,8 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
return;
}
mDeviceRotation = rotation;
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
emitOrientationChanged(rotation);
}
@@ -7,13 +7,13 @@
package com.facebook.react.bridge
import com.facebook.react.common.annotations.internal.LegacyArchitecture
import com.facebook.react.common.annotations.internal.InteropLegacyArchitecture
/**
* This interface includes the methods needed to use a running JS instance, without specifying any
* of the bridge-specific initialization or lifecycle management.
*/
@LegacyArchitecture
@InteropLegacyArchitecture
public interface JSInstance {
public fun invokeCallback(callbackID: Int, arguments: NativeArrayInterface)
@@ -25,6 +25,11 @@ import com.facebook.react.packagerconnection.RequestHandler
*/
internal class DefaultDevSupportManagerFactory : DevSupportManagerFactory {
@Deprecated(
"Use the other create() method with useDevSupport parameter for New Architecture. This method will be removed in a future release.",
replaceWith =
ReplaceWith(
"create(applicationContext, reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, redBoxHandler, devBundleDownloadListener, minNumShakes, customPackagerCommandHandlers, surfaceDelegateFactory, devLoadingViewManager, pausedInDebuggerOverlayManager)"))
override fun create(
applicationContext: Context,
reactInstanceManagerHelper: ReactInstanceDevHelper,
@@ -22,6 +22,12 @@ public interface DevSupportManagerFactory {
* Factory used by the Old Architecture flow to create a [DevSupportManager] and a
* [BridgeDevSupportManager]
*/
@Deprecated(
message =
"Use the other create() method with useDevSupport parameter for New Architecture. This method will be removed in a future release.",
replaceWith =
ReplaceWith(
"create(applicationContext, reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, redBoxHandler, devBundleDownloadListener, minNumShakes, customPackagerCommandHandlers, surfaceDelegateFactory, devLoadingViewManager, pausedInDebuggerOverlayManager)"))
public fun create(
applicationContext: Context,
reactInstanceManagerHelper: ReactInstanceDevHelper,
@@ -15,7 +15,8 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.DisplayMetricsHolder.getDisplayMetricsWritableMap
import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized
import com.facebook.react.uimanager.DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized
import com.facebook.react.uimanager.DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
/** Module that exposes Android Constants to JS. */
@@ -26,7 +27,8 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
private var previousDisplayMetrics: ReadableMap? = null
init {
initDisplayMetricsIfNotInitialized(reactContext)
initScreenDisplayMetricsIfNotInitialized(reactContext)
reactContext.currentActivity?.let { initWindowDisplayMetricsIfNotInitialized(it) }
reactContext.addLifecycleEventListener(this)
}
@@ -8,6 +8,7 @@
package com.facebook.react.modules.network
import com.facebook.proguard.annotations.DoNotStripAny
import com.facebook.soloader.SoLoader
/**
* [Experimental] An interface for reporting network events to the modern debugger server and Web
@@ -19,6 +20,10 @@ import com.facebook.proguard.annotations.DoNotStripAny
*/
@DoNotStripAny
internal object InspectorNetworkReporter {
init {
SoLoader.loadLibrary("react_devsupportjni")
}
@JvmStatic external fun isDebuggingEnabled(): Boolean
/**
@@ -625,9 +625,8 @@ public class ReactHostImpl(
override fun onConfigurationChanged(context: Context) {
val currentReactContext = this.currentReactContext
if (currentReactContext != null) {
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
DisplayMetricsHolder.initDisplayMetrics(currentReactContext)
}
DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext)
currentReactContext.currentActivity?.let { DisplayMetricsHolder.initWindowDisplayMetrics(it) }
val appearanceModule = currentReactContext.getNativeModule(AppearanceModule::class.java)
appearanceModule?.onConfigurationChanged(context)
@@ -918,6 +917,7 @@ public class ReactHostImpl(
val instance =
ReactInstance(
reactContext,
currentActivity,
reactHostDelegate,
componentFactory,
devSupportManager,
@@ -7,6 +7,7 @@
package com.facebook.react.runtime
import android.app.Activity
import android.content.res.AssetManager
import android.view.View
import com.facebook.common.logging.FLog
@@ -88,6 +89,7 @@ import kotlin.jvm.JvmStatic
@UnstableReactNativeAPI
internal class ReactInstance(
private val context: BridgelessReactContext,
private val activity: Activity?,
delegate: ReactHostDelegate,
componentFactory: ComponentFactory,
devSupportManager: DevSupportManager,
@@ -240,7 +242,8 @@ internal class ReactInstance(
FabricUIManager(context, ViewManagerRegistry(viewManagerResolver), eventBeatManager)
// Misc initialization that needs to be done before Fabric init
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(context)
activity?.let { DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(it) }
val binding = FabricUIManagerBinding()
binding.register(
@@ -13,16 +13,20 @@ import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.window.layout.WindowMetricsCalculator
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
/**
* Holds an instance of the current DisplayMetrics so we don't have to thread it through all the
* classes that need it.
*/
public object DisplayMetricsHolder {
private const val INITIALIZATION_MISSING_MESSAGE =
"DisplayMetricsHolder must be initialized with initDisplayMetricsIfNotInitialized or initDisplayMetrics"
private const val SCREEN_INITIALIZATION_MISSING_MESSAGE =
"DisplayMetricsHolder must be initialized with initScreenDisplayMetricsIfNotInitialized or initScreenDisplayMetrics"
private const val WINDOW_INITIALIZATION_MISSING_MESSAGE =
"DisplayMetricsHolder must be initialized with initWindowDisplayMetricsIfNotInitialized or initWindowDisplayMetrics"
@JvmStatic private var windowDisplayMetrics: DisplayMetrics? = null
@JvmStatic private var screenDisplayMetrics: DisplayMetrics? = null
@@ -30,7 +34,7 @@ public object DisplayMetricsHolder {
/** The metrics of the window associated to the Context used to initialize ReactNative */
@JvmStatic
public fun getWindowDisplayMetrics(): DisplayMetrics {
checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
checkNotNull(windowDisplayMetrics) { WINDOW_INITIALIZATION_MISSING_MESSAGE }
return windowDisplayMetrics as DisplayMetrics
}
@@ -42,7 +46,7 @@ public object DisplayMetricsHolder {
/** Screen metrics returns the metrics of the default screen on the device. */
@JvmStatic
public fun getScreenDisplayMetrics(): DisplayMetrics {
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
checkNotNull(screenDisplayMetrics) { SCREEN_INITIALIZATION_MISSING_MESSAGE }
return screenDisplayMetrics as DisplayMetrics
}
@@ -52,33 +56,58 @@ public object DisplayMetricsHolder {
}
@JvmStatic
public fun initDisplayMetricsIfNotInitialized(context: Context) {
if (screenDisplayMetrics != null) {
return
public fun initScreenDisplayMetricsIfNotInitialized(context: Context) {
if (screenDisplayMetrics == null) {
initScreenDisplayMetrics(context)
}
initDisplayMetrics(context)
}
@JvmStatic
public fun initDisplayMetrics(context: Context) {
val displayMetrics = context.resources.displayMetrics
windowDisplayMetrics = displayMetrics
val screenDisplayMetrics = DisplayMetrics()
screenDisplayMetrics.setTo(displayMetrics)
public fun initWindowDisplayMetricsIfNotInitialized(context: Context) {
if (windowDisplayMetrics == null) {
initWindowDisplayMetrics(context)
}
}
@JvmStatic
public fun initScreenDisplayMetrics(context: Context) {
val displayMetrics = DisplayMetrics()
displayMetrics.setTo(context.resources.displayMetrics)
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
// Get the real display metrics if we are using API level 17 or higher.
// The real metrics include system decor elements (e.g. soft menu bar).
//
// See:
// http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
@Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(screenDisplayMetrics)
DisplayMetricsHolder.screenDisplayMetrics = screenDisplayMetrics
@Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(displayMetrics)
screenDisplayMetrics = displayMetrics
}
/*
* NOTE: Unlike [initScreenDisplayMetrics], this method needs a UiContext (Activity of
* InputMethodService) else WindowMetircsCalculator will throw an exception.
*/
@JvmStatic
public fun initWindowDisplayMetrics(context: Context) {
val displayMetrics = DisplayMetrics()
displayMetrics.setTo(context.resources.displayMetrics)
if (isEdgeToEdgeFeatureFlagOn) {
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context).let { windowMetrics
->
displayMetrics.widthPixels = windowMetrics.bounds.width()
displayMetrics.heightPixels = windowMetrics.bounds.height()
}
}
windowDisplayMetrics = displayMetrics
}
@JvmStatic
public fun getDisplayMetricsWritableMap(fontScale: Double): WritableMap {
checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
checkNotNull(windowDisplayMetrics) { WINDOW_INITIALIZATION_MISSING_MESSAGE }
checkNotNull(screenDisplayMetrics) { SCREEN_INITIALIZATION_MISSING_MESSAGE }
return WritableNativeMap().apply {
putMap(
@@ -20,7 +20,7 @@ public object PixelUtil {
}
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, value, DisplayMetricsHolder.getWindowDisplayMetrics())
TypedValue.COMPLEX_UNIT_DIP, value, DisplayMetricsHolder.getScreenDisplayMetrics())
}
/** Convert from DIP to PX */
@@ -37,7 +37,7 @@ public object PixelUtil {
return Float.NaN
}
val displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics()
val displayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics()
val scaledValue = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, displayMetrics)
if (maxFontScale >= 1) {
@@ -60,13 +60,13 @@ public object PixelUtil {
return Float.NaN
}
return value / DisplayMetricsHolder.getWindowDisplayMetrics().density
return value / DisplayMetricsHolder.getScreenDisplayMetrics().density
}
/** @return [Float] that represents the density of the display metrics for device screen. */
@JvmStatic
public fun getDisplayMetricDensity(): Float =
DisplayMetricsHolder.getWindowDisplayMetrics().density
DisplayMetricsHolder.getScreenDisplayMetrics().density
/* Kotlin extensions */
public fun Int.dpToPx(): Float = toPixelFromDIP(this.toFloat())
@@ -12,6 +12,7 @@ import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_M
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
import static com.facebook.react.uimanager.common.UIManagerType.LEGACY;
import android.app.Activity;
import android.content.ComponentCallbacks2;
import android.content.res.Configuration;
import android.view.View;
@@ -126,7 +127,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule
ViewManagerResolver viewManagerResolver,
int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext);
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext);
Activity currentActivity = reactContext.getCurrentActivity();
if (currentActivity != null) {
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
}
mEventDispatcher = new EventDispatcherImpl(reactContext);
mModuleConstants = createConstants(viewManagerResolver);
mCustomDirectEvents = UIManagerModuleConstants.directEventTypeConstants;
@@ -146,7 +151,11 @@ public class UIManagerModule extends ReactContextBaseJavaModule
List<ViewManager> viewManagersList,
int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext);
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext);
Activity currentActivity = reactContext.getCurrentActivity();
if (currentActivity != null) {
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity);
}
mEventDispatcher = new EventDispatcherImpl(reactContext);
mCustomDirectEvents = MapBuilder.newHashMap();
mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents);
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
@@ -27,6 +29,9 @@ import com.facebook.react.uimanager.IllegalViewOperationException
* order to animate layout when a valid configuration has been supplied by the application.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal abstract class AbstractLayoutAnimation {
var interpolator: Interpolator? = null
var delayMs: Int = 0
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
@@ -15,6 +17,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
* creation.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal enum class AnimatedPropertyType {
OPACITY,
SCALE_X,
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
@@ -17,6 +19,9 @@ import com.facebook.react.uimanager.IllegalViewOperationException
/** Class responsible for default layout animation, i.e animation of view creation and deletion. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal abstract class BaseLayoutAnimation : AbstractLayoutAnimation() {
abstract fun isReverse(): Boolean
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
@@ -14,6 +16,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
* Enum representing the different interpolators that can be used in layout animation configuration.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal enum class InterpolatorType {
LINEAR,
EASE_IN,
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.util.SparseArray
@@ -29,6 +31,9 @@ import javax.annotation.concurrent.NotThreadSafe
*/
@NotThreadSafe
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
public open class LayoutAnimationController {
private val layoutCreateAnimation: AbstractLayoutAnimation = LayoutCreateAnimation()
private val layoutUpdateAnimation: AbstractLayoutAnimation = LayoutUpdateAnimation()
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
@@ -12,6 +14,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
/** Listener invoked when a layout animation has completed. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
public fun interface LayoutAnimationListener {
public fun onAnimationEnd()
}
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
@@ -15,6 +17,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
* Enum representing the different animation type that can be specified in layout animation config.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal enum class LayoutAnimationType {
CREATE,
UPDATE,
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
@@ -16,6 +18,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
* config was supplied for the layout animation of DELETE type.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal class LayoutDeleteAnimation : BaseLayoutAnimation() {
override fun isReverse(): Boolean = true
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import com.facebook.react.common.annotations.internal.LegacyArchitecture
@@ -12,6 +14,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
/** Interface for an animation type that takes care of updating the view layout. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal interface LayoutHandlingAnimation {
/**
* Notifies the animation of a layout update in case one occurs during the animation. This avoids
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
@@ -19,6 +21,9 @@ import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
* was supplied for the layout animation of UPDATE type.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal class LayoutUpdateAnimation : AbstractLayoutAnimation() {
override fun isValid(): Boolean = durationMs > 0
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
@@ -21,6 +23,9 @@ import java.lang.ref.WeakReference
* optimize rendering performances.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal class OpacityAnimation(view: View, private val startOpacity: Float, endOpacity: Float) :
Animation() {
private val viewRef = WeakReference(view)
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
@@ -22,6 +24,9 @@ import java.lang.ref.WeakReference
* ScaleAnimation and TranslateAnimation.
*/
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal class PositionAndSizeAnimation(view: View, x: Int, y: Int, width: Int, height: Int) :
Animation(), LayoutHandlingAnimation {
private val viewRef = WeakReference(view)
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.animation.Interpolator
@@ -19,6 +21,9 @@ import kotlin.math.sin
/** Simple spring interpolator */
// TODO(7613736): Improve spring interpolator with friction and damping variable support
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@Deprecated(
message = "This class is part of Legacy Architecture and will be removed in a future release",
level = DeprecationLevel.WARNING)
internal class SimpleSpringInterpolator @JvmOverloads constructor(springDamping: Float = FACTOR) :
Interpolator {
private val _springDamping: Float = springDamping
@@ -50,28 +50,32 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
internal class VirtualViewContainerState {
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
private val detectWindowFocus = ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
private val emptyRect: Rect = Rect()
private val visibleRect: Rect = Rect()
private val prerenderRect: Rect = Rect()
private val onWindowFocusChangeListener =
ViewTreeObserver.OnWindowFocusChangeListener {
debugLog("onWindowFocusChanged")
updateModes()
if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) {
ViewTreeObserver.OnWindowFocusChangeListener {
debugLog("onWindowFocusChanged")
updateModes()
}
} else {
null
}
private val scrollView: ViewGroup
constructor(scrollView: ViewGroup) {
this.scrollView = scrollView
if (detectWindowFocus) {
if (onWindowFocusChangeListener != null) {
scrollView.viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
}
}
public fun cleanup() {
if (detectWindowFocus) {
if (onWindowFocusChangeListener != null) {
scrollView.viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
}
}
@@ -115,7 +119,7 @@ internal class VirtualViewContainerState {
rect.isEmpty -> {}
rectsOverlap(rect, visibleRect) -> {
thresholdRect = visibleRect
if (detectWindowFocus) {
if (onWindowFocusChangeListener != null) {
if (scrollView.hasWindowFocus()) {
mode = VirtualViewMode.Visible
} else {
@@ -40,11 +40,14 @@ public class ReactVirtualView(context: Context) :
internal var modeChangeEmitter: VirtualViewModeChangeEmitter? = null
internal var prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
internal val debugLogEnabled: Boolean = ReactNativeFeatureFlags.enableVirtualViewDebugFeatures()
internal val detectWindowFocus = ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()
private val onWindowFocusChangeListener =
ViewTreeObserver.OnWindowFocusChangeListener {
dispatchOnModeChangeIfNeeded(checkRectChange = false)
if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) {
ViewTreeObserver.OnWindowFocusChangeListener {
dispatchOnModeChangeIfNeeded(checkRectChange = false)
}
} else {
null
}
private var parentScrollView: View? = null
@@ -90,7 +93,7 @@ public class ReactVirtualView(context: Context) :
ReactScrollViewHelper.addLayoutChangeListener(this)
}
debugLog("onAttachedToWindow")
if (detectWindowFocus) {
if (onWindowFocusChangeListener != null) {
viewTreeObserver.addOnWindowFocusChangeListener(onWindowFocusChangeListener)
}
dispatchOnModeChangeIfNeeded(checkRectChange = false)
@@ -100,7 +103,7 @@ public class ReactVirtualView(context: Context) :
super.onDetachedFromWindow()
ReactScrollViewHelper.removeScrollListener(this)
ReactScrollViewHelper.removeLayoutChangeListener(this)
if (detectWindowFocus) {
if (onWindowFocusChangeListener != null) {
viewTreeObserver.removeOnWindowFocusChangeListener(onWindowFocusChangeListener)
}
cleanupLayoutListeners()
@@ -202,7 +205,7 @@ public class ReactVirtualView(context: Context) :
val newMode: VirtualViewMode
if (rectsOverlap(targetRect, thresholdRect)) {
if (detectWindowFocus) {
if (onWindowFocusChangeListener != null) {
if (hasWindowFocus()) {
newMode = VirtualViewMode.Visible
} else {
@@ -1,25 +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.yoga;
public class YogaConstants {
public static final float UNDEFINED = Float.NaN;
public static boolean isUndefined(float value) {
return Float.compare(value, UNDEFINED) == 0;
}
public static boolean isUndefined(YogaValue value) {
return value.unit == YogaUnit.UNDEFINED;
}
public static float getUndefined() {
return UNDEFINED;
}
}
@@ -0,0 +1,18 @@
/*
* 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.yoga
public object YogaConstants {
@JvmField public val UNDEFINED: Float = Float.NaN
@JvmStatic public fun isUndefined(value: Float): Boolean = value.compareTo(UNDEFINED) == 0
@JvmStatic public fun isUndefined(value: YogaValue): Boolean = value.unit == YogaUnit.UNDEFINED
@JvmStatic public fun getUndefined(): Float = UNDEFINED
}
@@ -29,9 +29,6 @@ if(CCACHE_FOUND)
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
endif(CCACHE_FOUND)
# Make sure every shared lib includes a .note.gnu.build-id header
add_link_options(-Wl,--build-id)
function(add_react_android_subdir relative_path)
add_subdirectory(${REACT_ANDROID_DIR}/${relative_path} ReactAndroid/${relative_path})
endfunction()
@@ -19,6 +19,7 @@ target_include_directories(react_devsupportjni PUBLIC .)
target_link_libraries(react_devsupportjni
fbjni
jsinspector)
jsinspector
jsinspector_network)
target_compile_reactnative_options(react_devsupportjni PRIVATE)
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
#include "InspectorNetworkReporter.h"
#include "JInspectorNetworkReporter.h"
#include <jsinspector-modern/network/NetworkReporter.h>
@@ -16,9 +16,8 @@
#endif
using namespace facebook::jni;
using namespace facebook::react::jsinspector_modern;
namespace facebook::react {
namespace facebook::react::jsinspector_modern {
namespace {
@@ -59,12 +58,12 @@ static std::unordered_map<int, std::string> responseBuffers;
#endif
/* static */ jboolean InspectorNetworkReporter::isDebuggingEnabled(
/* static */ jboolean JInspectorNetworkReporter::isDebuggingEnabled(
jni::alias_ref<jclass> /*unused*/) {
return NetworkReporter::getInstance().isDebuggingEnabled();
}
/* static */ void InspectorNetworkReporter::reportRequestStart(
/* static */ void JInspectorNetworkReporter::reportRequestStart(
jni::alias_ref<jclass> /*unused*/,
jint requestId,
jni::alias_ref<jstring> requestUrl,
@@ -82,7 +81,7 @@ static std::unordered_map<int, std::string> responseBuffers;
std::to_string(requestId), requestInfo, encodedDataLength, std::nullopt);
}
/* static */ void InspectorNetworkReporter::reportConnectionTiming(
/* static */ void JInspectorNetworkReporter::reportConnectionTiming(
jni::alias_ref<jclass> /*unused*/,
jint requestId,
jni::alias_ref<jni::JMap<jstring, jstring>> headers) {
@@ -90,7 +89,7 @@ static std::unordered_map<int, std::string> responseBuffers;
std::to_string(requestId), convertJavaMapToHeaders(headers));
}
/* static */ void InspectorNetworkReporter::reportResponseStart(
/* static */ void JInspectorNetworkReporter::reportResponseStart(
jni::alias_ref<jclass> /*unused*/,
jint requestId,
jni::alias_ref<jstring> requestUrl,
@@ -108,7 +107,7 @@ static std::unordered_map<int, std::string> responseBuffers;
static_cast<std::int64_t>(encodedDataLength));
}
/* static */ void InspectorNetworkReporter::reportDataReceivedImpl(
/* static */ void JInspectorNetworkReporter::reportDataReceivedImpl(
jni::alias_ref<jclass> /*unused*/,
jint requestId,
jint dataLength) {
@@ -116,7 +115,7 @@ static std::unordered_map<int, std::string> responseBuffers;
std::to_string(requestId), dataLength, std::nullopt);
}
/* static */ void InspectorNetworkReporter::reportResponseEnd(
/* static */ void JInspectorNetworkReporter::reportResponseEnd(
jni::alias_ref<jclass> /*unused*/,
jint requestId,
jlong encodedDataLength) {
@@ -134,7 +133,7 @@ static std::unordered_map<int, std::string> responseBuffers;
#endif
}
/* static */ void InspectorNetworkReporter::reportRequestFailed(
/* static */ void JInspectorNetworkReporter::reportRequestFailed(
jni::alias_ref<jclass> /*unused*/,
jint requestId,
jboolean cancelled) {
@@ -142,7 +141,7 @@ static std::unordered_map<int, std::string> responseBuffers;
std::to_string(requestId), cancelled);
}
/* static */ void InspectorNetworkReporter::maybeStoreResponseBodyImpl(
/* static */ void JInspectorNetworkReporter::maybeStoreResponseBodyImpl(
jni::alias_ref<jclass> /*unused*/,
jint requestId,
jni::alias_ref<jstring> body,
@@ -160,7 +159,7 @@ static std::unordered_map<int, std::string> responseBuffers;
}
/* static */ void
InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
JInspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
jni::alias_ref<jclass> /*unused*/,
jint requestId,
jni::alias_ref<jstring> data) {
@@ -176,31 +175,33 @@ InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl(
#endif
}
/* static */ void InspectorNetworkReporter::registerNatives() {
/* static */ void JInspectorNetworkReporter::registerNatives() {
javaClassLocal()->registerNatives({
makeNativeMethod(
"isDebuggingEnabled", InspectorNetworkReporter::isDebuggingEnabled),
"isDebuggingEnabled", JInspectorNetworkReporter::isDebuggingEnabled),
makeNativeMethod(
"reportRequestStart", InspectorNetworkReporter::reportRequestStart),
"reportRequestStart", JInspectorNetworkReporter::reportRequestStart),
makeNativeMethod(
"reportResponseStart", InspectorNetworkReporter::reportResponseStart),
"reportResponseStart",
JInspectorNetworkReporter::reportResponseStart),
makeNativeMethod(
"reportConnectionTiming",
InspectorNetworkReporter::reportConnectionTiming),
JInspectorNetworkReporter::reportConnectionTiming),
makeNativeMethod(
"reportDataReceivedImpl",
InspectorNetworkReporter::reportDataReceivedImpl),
JInspectorNetworkReporter::reportDataReceivedImpl),
makeNativeMethod(
"reportResponseEnd", InspectorNetworkReporter::reportResponseEnd),
"reportResponseEnd", JInspectorNetworkReporter::reportResponseEnd),
makeNativeMethod(
"reportRequestFailed", InspectorNetworkReporter::reportRequestFailed),
"reportRequestFailed",
JInspectorNetworkReporter::reportRequestFailed),
makeNativeMethod(
"maybeStoreResponseBodyImpl",
InspectorNetworkReporter::maybeStoreResponseBodyImpl),
JInspectorNetworkReporter::maybeStoreResponseBodyImpl),
makeNativeMethod(
"maybeStoreResponseBodyIncrementalImpl",
InspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl),
JInspectorNetworkReporter::maybeStoreResponseBodyIncrementalImpl),
});
}
} // namespace facebook::react
} // namespace facebook::react::jsinspector_modern
@@ -9,10 +9,10 @@
#include <fbjni/fbjni.h>
namespace facebook::react {
namespace facebook::react::jsinspector_modern {
class InspectorNetworkReporter
: public jni::HybridClass<InspectorNetworkReporter> {
class JInspectorNetworkReporter
: public jni::HybridClass<JInspectorNetworkReporter> {
public:
static constexpr auto kJavaDescriptor =
"Lcom/facebook/react/modules/network/InspectorNetworkReporter;";
@@ -70,7 +70,7 @@ class InspectorNetworkReporter
static void registerNatives();
private:
InspectorNetworkReporter() = delete;
JInspectorNetworkReporter() = delete;
};
} // namespace facebook::react
} // namespace facebook::react::jsinspector_modern
@@ -8,6 +8,7 @@
#include "JCxxInspectorPackagerConnection.h"
#include "JCxxInspectorPackagerConnectionWebSocketDelegate.h"
#include "JInspectorFlags.h"
#include "JInspectorNetworkReporter.h"
#include <fbjni/fbjni.h>
@@ -18,5 +19,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*unused*/) {
facebook::react::jsinspector_modern::
JCxxInspectorPackagerConnectionWebSocketDelegate::registerNatives();
facebook::react::jsinspector_modern::JInspectorFlags::registerNatives();
facebook::react::jsinspector_modern::JInspectorNetworkReporter::
registerNatives();
});
}
@@ -53,7 +53,6 @@ add_library(
OBJECT
CatalystInstanceImpl.cpp
InspectorNetworkRequestListener.cpp
InspectorNetworkReporter.cpp
JExecutor.cpp
JInspector.cpp
JMessageQueueThread.cpp
@@ -82,10 +81,8 @@ target_link_libraries(reactnativejni
fbjni
folly_runtime
glog_init
jsinspector_network
logger
react_cxxreact
react_featureflags
react_renderer_runtimescheduler
reactnativejni_common
runtimeexecutor
@@ -12,7 +12,6 @@
#include "CatalystInstanceImpl.h"
#include "CxxModuleWrapperBase.h"
#include "InspectorNetworkReporter.h"
#include "InspectorNetworkRequestListener.h"
#include "JInspector.h"
#include "JavaScriptExecutorHolder.h"
@@ -45,7 +44,6 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JInspector::registerNatives();
ReactInstanceManagerInspectorTarget::registerNatives();
InspectorNetworkRequestListener::registerNatives();
InspectorNetworkReporter::registerNatives();
});
}
@@ -127,6 +127,9 @@ void JReactHostInspectorTarget::onSetPausedInDebuggerMessage(
}
}
void JReactHostInspectorTarget::unstable_onPerfMonitorUpdate(
const PerfMonitorUpdateRequest& /* unused */) {}
void JReactHostInspectorTarget::loadNetworkResource(
const jsinspector_modern::LoadNetworkResourceRequest& params,
jsinspector_modern::ScopedExecutor<
@@ -84,6 +84,8 @@ class JReactHostInspectorTarget
void onReload(const PageReloadRequest& request) override;
void onSetPausedInDebuggerMessage(
const OverlaySetPausedInDebuggerMessageRequest&) override;
void unstable_onPerfMonitorUpdate(
const PerfMonitorUpdateRequest& /* unused */) override;
void loadNetworkResource(
const jsinspector_modern::LoadNetworkResourceRequest& params,
jsinspector_modern::ScopedExecutor<
@@ -71,7 +71,8 @@ class RootViewTest {
reactContext = spy(BridgeReactContext(RuntimeEnvironment.getApplication()))
reactContext.initializeWithInstance(catalystInstanceMock)
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext)
DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext)
DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(reactContext)
val uiManagerModuleMock: UIManagerModule = mock()
whenever(catalystInstanceMock.getNativeModule(UIManagerModule::class.java))
.thenReturn(uiManagerModuleMock)
@@ -11,12 +11,18 @@
package com.facebook.react.bridge
import android.app.Application
import com.facebook.react.bridge.queue.MessageQueueThreadSpec
import com.facebook.react.bridge.queue.ReactQueueConfiguration
import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl
import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.runtime.BridgelessReactContext
import com.facebook.react.runtime.ReactHostImpl
import com.facebook.react.runtime.internal.bolts.Task
import com.facebook.react.uimanager.UIManagerModule
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
import org.robolectric.RuntimeEnvironment
@@ -49,4 +55,20 @@ object ReactTestHelper {
whenever(reactInstance.isDestroyed).thenReturn(false)
return reactInstance
}
@OptIn(UnstableReactNativeAPI::class)
fun createTestReactApplicationContext(application: Application): ReactApplicationContext {
val reactHost =
spy(
ReactHostImpl(
RuntimeEnvironment.getApplication(),
mock(),
mock(),
Task.Companion.IMMEDIATE_EXECUTOR,
Task.Companion.IMMEDIATE_EXECUTOR,
false /* allowPackagerServerAccess */,
false /* useDevSupport */,
))
return BridgelessReactContext(application, reactHost)
}
}
@@ -467,7 +467,7 @@ class TouchEventDispatchTest {
metrics.xdpi = 1f
metrics.ydpi = 1f
metrics.density = 1f
DisplayMetricsHolder.setWindowDisplayMetrics(metrics)
DisplayMetricsHolder.setScreenDisplayMetrics(metrics)
val reactContext = ReactTestHelper.createCatalystContextForTest()
@@ -5,36 +5,45 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.modules.clipboard
import android.annotation.SuppressLint
import android.content.ClipboardManager
import android.content.Context
import com.facebook.react.bridge.BridgeReactContext
import com.facebook.react.bridge.ReactTestHelper.createTestReactApplicationContext
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
import com.facebook.testutils.shadows.ShadowSoLoader
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
@Suppress("DEPRECATION")
@SuppressLint("ClipboardManager", "DeprecatedClass")
@RunWith(RobolectricTestRunner::class)
@Config(shadows = [ShadowSoLoader::class])
class ClipboardModuleTest {
private lateinit var clipboardModule: ClipboardModule
private lateinit var clipboardManager: ClipboardManager
@Before
fun setUp() {
clipboardModule = ClipboardModule(BridgeReactContext(RuntimeEnvironment.getApplication()))
ReactNativeFeatureFlagsForTests.setUp()
clipboardModule =
ClipboardModule(createTestReactApplicationContext(RuntimeEnvironment.getApplication()))
clipboardManager =
RuntimeEnvironment.getApplication().getSystemService(Context.CLIPBOARD_SERVICE)
as ClipboardManager
}
@After
fun tearDown() {
ReactNativeFeatureFlags.dangerouslyReset()
}
@Suppress("DEPRECATION")
@Test
fun testSetString() {
clipboardModule.setString(TEST_CONTENT)
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import java.util.Locale
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.layoutanimation
import android.view.View
@@ -25,7 +25,7 @@ class ColorStopTest {
fun setUp() {
val metrics = DisplayMetrics()
metrics.density = 1f
DisplayMetricsHolder.setWindowDisplayMetrics(metrics)
DisplayMetricsHolder.setScreenDisplayMetrics(metrics)
}
@Test
@@ -75,14 +75,14 @@ class ReactImagePropertyTest {
context.initializeWithInstance(catalystInstanceMock)
themeContext = ThemedReactContext(context, context, null, -1)
Fresco.initialize(context)
DisplayMetricsHolder.setWindowDisplayMetrics(DisplayMetrics())
DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics())
ReactNativeFeatureFlagsForTests.setUp()
}
@After
fun teardown() {
DisplayMetricsHolder.setWindowDisplayMetrics(null)
DisplayMetricsHolder.setScreenDisplayMetrics(null)
rnLog.close()
flogMock.close()
}
@@ -65,7 +65,7 @@ class ReactTextInputPropertyTest {
context.initializeWithInstance(catalystInstanceMock)
themedContext = ThemedReactContext(context, context.baseContext, null, ID_NULL)
manager = ReactTextInputManager()
DisplayMetricsHolder.setWindowDisplayMetrics(DisplayMetrics())
DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics())
view = manager.createViewInstance(themedContext)
}
@@ -47,7 +47,7 @@ class ReactVirtualViewTest {
val displayMetricsHolder = mockStatic(DisplayMetricsHolder::class.java)
displayMetricsHolder
.`when`<DisplayMetrics> { DisplayMetricsHolder.getWindowDisplayMetrics() }
.`when`<DisplayMetrics> { DisplayMetricsHolder.getScreenDisplayMetrics() }
.thenAnswer { DisplayMetrics().apply { density = 1f } }
}
@@ -47,7 +47,8 @@ class HostAgent::Impl final {
hostMetadata_(std::move(hostMetadata)),
sessionState_(sessionState),
networkIOAgent_(NetworkIOAgent(frontendChannel, std::move(executor))),
tracingAgent_(TracingAgent(frontendChannel, sessionState)) {}
tracingAgent_(
TracingAgent(frontendChannel, sessionState, targetController)) {}
~Impl() {
if (isPausedInDebuggerOverlayVisible_) {
@@ -310,8 +311,6 @@ class HostAgent::Impl final {
}
void setCurrentInstanceAgent(std::shared_ptr<InstanceAgent> instanceAgent) {
tracingAgent_.setCurrentInstanceAgent(instanceAgent);
auto previousInstanceAgent = std::move(instanceAgent_);
instanceAgent_ = std::move(instanceAgent);
@@ -462,4 +461,17 @@ void HostAgent::setCurrentInstanceAgent(
impl_->setCurrentInstanceAgent(std::move(instanceAgent));
}
#pragma mark - Tracing
HostTracingAgent::HostTracingAgent(tracing::TraceRecordingState& state)
: tracing::TargetTracingAgent(state) {}
void HostTracingAgent::setTracedInstance(InstanceTarget* instanceTarget) {
if (instanceTarget != nullptr) {
instanceTracingAgent_ = instanceTarget->createTracingAgent(state_);
} else {
instanceTracingAgent_ = nullptr;
}
}
} // namespace facebook::react::jsinspector_modern
@@ -12,6 +12,7 @@
#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/InstanceAgent.h>
#include <jsinspector-modern/cdp/CdpJson.h>
#include <jsinspector-modern/tracing/TargetTracingAgent.h>
namespace facebook::react::jsinspector_modern {
@@ -75,4 +76,25 @@ class HostAgent final {
std::unique_ptr<Impl> impl_;
};
#pragma mark - Tracing
/**
* An Agent that handles Tracing events for a particular InstanceTarget.
*
* Lifetime of this agent is bound to the lifetime of the Tracing session -
* HostTargetTraceRecording.
*/
class HostTracingAgent : tracing::TargetTracingAgent {
public:
explicit HostTracingAgent(tracing::TraceRecordingState& state);
/**
* Registers the InstanceTarget with this tracing agent.
*/
void setTracedInstance(InstanceTarget* instanceTarget);
private:
std::shared_ptr<InstanceTracingAgent> instanceTracingAgent_{nullptr};
};
} // namespace facebook::react::jsinspector_modern
@@ -7,6 +7,7 @@
#include "HostTarget.h"
#include "HostAgent.h"
#include "HostTargetTraceRecording.h"
#include "InspectorInterfaces.h"
#include "InspectorUtilities.h"
#include "InstanceTarget.h"
@@ -145,11 +146,53 @@ class HostCommandSender {
std::unique_ptr<ILocalConnection> connection_;
};
/**
* Enables the caller to install and subscribe to a named CDP runtime binding
* on the HostTarget via a callback. Note: Per CDP spec, this does not need to
* check if the `Runtime` domain is enabled.
*/
class HostRuntimeBinding {
public:
explicit HostRuntimeBinding(
HostTarget& target,
std::string name,
std::function<void(std::string)> callback)
: connection_(target.connect(std::make_unique<CallbackRemoteConnection>(
[callback = std::move(callback)](const std::string& message) {
auto parsedMessage = folly::parseJson(message);
// Ignore initial Runtime.addBinding response
if (parsedMessage["id"] == 0 &&
parsedMessage["result"].isObject() &&
parsedMessage["result"].empty()) {
return;
}
// Assert that we only intercept bindingCalled responses
assert(
parsedMessage["method"].asString() ==
"Runtime.bindingCalled");
callback(parsedMessage["params"]["payload"].asString());
}))) {
// Install runtime binding
connection_->sendMessage(cdp::jsonRequest(
0,
"Runtime.addBinding",
folly::dynamic::object("name", std::move(name))));
}
private:
std::unique_ptr<ILocalConnection> connection_;
};
std::shared_ptr<HostTarget> HostTarget::create(
HostTargetDelegate& delegate,
VoidExecutor executor) {
std::shared_ptr<HostTarget> hostTarget{new HostTarget(delegate)};
hostTarget->setExecutor(std::move(executor));
if (InspectorFlags::getInstance().getPerfMonitorV2Enabled()) {
hostTarget->installPerfMetricsBinding();
}
return hostTarget;
}
@@ -179,6 +222,8 @@ HostTarget::~HostTarget() {
assert(
sessions_.empty() &&
"HostTargetSession objects must be destroyed before their HostTarget. Did you call getInspectorInstance().removePage()?");
// Trace Recording object (traceRecording_) doesn't create an actual session,
// so we don't need to reset it explicitly here.
}
HostTargetDelegate::~HostTargetDelegate() = default;
@@ -191,6 +236,13 @@ InstanceTarget& HostTarget::registerInstance(InstanceTargetDelegate& delegate) {
[currentInstance = &*currentInstance_](HostTargetSession& session) {
session.setCurrentInstance(currentInstance);
});
if (traceRecording_) {
// Registers the Instance for tracing, if a Trace is currently being
// recorded.
traceRecording_->setTracedInstance(currentInstance_.get());
}
return *currentInstance_;
}
@@ -200,6 +252,13 @@ void HostTarget::unregisterInstance(InstanceTarget& instance) {
"Invalid unregistration");
sessions_.forEach(
[](HostTargetSession& session) { session.setCurrentInstance(nullptr); });
if (traceRecording_) {
// Unregisters the Instance for tracing, if a Trace is currently being
// recorded.
traceRecording_->setTracedInstance(nullptr);
}
currentInstance_.reset();
}
@@ -212,6 +271,19 @@ void HostTarget::sendCommand(HostCommand command) {
});
}
void HostTarget::installPerfMetricsBinding() {
perfMetricsBinding_ = std::make_unique<HostRuntimeBinding>(
*this, // Used immediately
"__chromium_devtools_metrics_reporter",
[this](const std::string& message) {
auto payload = folly::parseJson(message);
HostTargetDelegate::PerfMonitorUpdateRequest request{
.interactionName = payload["eventName"].asString(),
.durationMs = static_cast<uint16_t>(payload["duration"].asInt())};
delegate_.unstable_onPerfMonitorUpdate(request);
});
}
HostTargetController::HostTargetController(HostTarget& target)
: target_(target) {}
@@ -18,6 +18,8 @@
#include <optional>
#include <string>
#include <jsinspector-modern/tracing/TracingMode.h>
#ifndef JSINSPECTOR_EXPORT
#ifdef _MSC_VER
#ifdef CREATE_SHARED_LIBRARY
@@ -34,8 +36,11 @@ namespace facebook::react::jsinspector_modern {
class HostTargetSession;
class HostAgent;
class HostTracingAgent;
class HostCommandSender;
class HostRuntimeBinding;
class HostTarget;
class HostTargetTraceRecording;
struct HostTargetMetadata {
std::optional<std::string> appDisplayName;
@@ -93,6 +98,11 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
}
};
struct PerfMonitorUpdateRequest {
std::string interactionName;
uint16_t durationMs;
};
virtual ~HostTargetDelegate() override;
/**
@@ -121,6 +131,13 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
virtual void onSetPausedInDebuggerMessage(
const OverlaySetPausedInDebuggerMessageRequest& request) = 0;
/**
* [Experimental] Called when the runtime has new data for the V2 Perf
* Monitor overlay. This is called on the inspector thread.
*/
virtual void unstable_onPerfMonitorUpdate(
const PerfMonitorUpdateRequest& /*request*/) {}
/**
* Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP
* request. Platform implementations should override this to perform a
@@ -166,6 +183,19 @@ class HostTargetController final {
*/
bool decrementPauseOverlayCounter();
/**
* Starts trace recording for this HostTarget.
*
* \param mode In which mode to start the trace recording.
* \return false if already tracing, true otherwise.
*/
bool startTracing(tracing::Mode mode);
/**
* Stops previously started trace recording.
*/
tracing::TraceRecordingState stopTracing();
private:
HostTarget& target_;
size_t pauseOverlayCounter_{0};
@@ -237,6 +267,29 @@ class JSINSPECTOR_EXPORT HostTarget
*/
void sendCommand(HostCommand command);
/**
* Creates a new HostTracingAgent.
* This Agent is not owned by the HostTarget. The Agent will be destroyed at
* the end of the tracing session.
*
* \param state A reference to the state of the active trace recording.
*/
std::shared_ptr<HostTracingAgent> createTracingAgent(
tracing::TraceRecordingState& state);
/**
* Starts trace recording for this HostTarget.
*
* \param mode In which mode to start the trace recording.
* \return false if already tracing, true otherwise.
*/
bool startTracing(tracing::Mode mode);
/**
* Stops previously started trace recording.
*/
tracing::TraceRecordingState stopTracing();
private:
/**
* Constructs a new HostTarget.
@@ -256,6 +309,15 @@ class JSINSPECTOR_EXPORT HostTarget
std::shared_ptr<ExecutionContextManager> executionContextManager_;
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
std::unique_ptr<HostCommandSender> commandSender_;
std::unique_ptr<HostRuntimeBinding> perfMetricsBinding_;
/**
* Current pending trace recording, which encapsulates the configuration of
* the tracing session and the state.
*
* Should only be allocated when there is an active tracing session.
*/
std::unique_ptr<HostTargetTraceRecording> traceRecording_{nullptr};
inline HostTargetDelegate& getDelegate() {
return delegate_;
@@ -265,6 +327,13 @@ class JSINSPECTOR_EXPORT HostTarget
return currentInstance_ != nullptr;
}
/**
* Install a runtime binding subscribing to the Interaction to Next Paint
* (INP) live metric, which we broadcast to the V2 Perf Monitor overlay
* via \ref HostTargetDelegate::unstable_onPerfMonitorUpdate.
*/
void installPerfMetricsBinding();
// Necessary to allow HostAgent to access HostTarget's internals in a
// controlled way (i.e. only HostTargetController gets friend access, while
// HostAgent itself doesn't).
@@ -0,0 +1,54 @@
/*
* 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.
*/
#include "HostTargetTraceRecording.h"
#include "HostTarget.h"
namespace facebook::react::jsinspector_modern {
HostTargetTraceRecording::HostTargetTraceRecording(
tracing::Mode tracingMode,
HostTarget& hostTarget)
: tracingMode_(tracingMode), hostTarget_(hostTarget) {}
void HostTargetTraceRecording::setTracedInstance(
InstanceTarget* instanceTarget) {
// If HostTracingAgent is allocated, it means that there is an active tracing
// recording session.
if (hostTracingAgent_ != nullptr) {
hostTracingAgent_->setTracedInstance(instanceTarget);
}
}
void HostTargetTraceRecording::start() {
assert(
hostTracingAgent_ == nullptr &&
"Tracing Agent for the HostTarget was already initialized.");
state_ = tracing::TraceRecordingState{
.mode = tracingMode_,
.startTime = HighResTimeStamp::now(),
};
hostTracingAgent_ = hostTarget_.createTracingAgent(*state_);
}
tracing::TraceRecordingState HostTargetTraceRecording::stop() {
assert(
hostTracingAgent_ != nullptr &&
"TracingAgent for the HostTarget has not been initialized.");
hostTracingAgent_.reset();
assert(
state_.has_value() &&
"The state for this tracing session has not been initialized.");
auto state = std::move(*state_);
state_.reset();
return state;
}
} // namespace facebook::react::jsinspector_modern
@@ -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.
*/
#pragma once
#include "HostAgent.h"
#include "HostTarget.h"
#include "InstanceTarget.h"
#include <jsinspector-modern/tracing/TraceRecordingState.h>
#include <optional>
namespace facebook::react::jsinspector_modern {
/**
* A local representation of the Tracing "session".
*
* Owned by the HostTarget and should only be allocated during an active
* recording.
*
* Owns all allocated Tracing Agents. A single Target can have a single active
* Tracing Agent, but only as a std::weak_ptr.
*/
class HostTargetTraceRecording {
public:
explicit HostTargetTraceRecording(
tracing::Mode tracingMode,
HostTarget& hostTarget);
inline bool isBackgroundInitiated() const {
return tracingMode_ == tracing::Mode::Background;
}
inline bool isUserInitiated() const {
return tracingMode_ == tracing::Mode::CDP;
}
/**
* Updates the current traced Instance for this recording.
*/
void setTracedInstance(InstanceTarget* instanceTarget);
/**
* Starts the recording.
*
* Will allocate all Tracing Agents for all currently registered Targets.
*/
void start();
/**
* Stops the recording and drops the recording state.
*
* Will deallocate all Tracing Agents.
*/
tracing::TraceRecordingState stop();
private:
/**
* The mode in which this trace recording was initialized.
*/
tracing::Mode tracingMode_;
/**
* The Host for which this Trace Recording is going to happen.
*/
HostTarget& hostTarget_;
/**
* The state of the current Trace Recording.
* Only allocated if the recording is enabled.
*/
std::optional<tracing::TraceRecordingState> state_;
/**
* The TracingAgent of the targeted Host.
* Only allocated if the recording is enabled.
*/
std::shared_ptr<HostTracingAgent> hostTracingAgent_;
};
} // namespace facebook::react::jsinspector_modern
@@ -0,0 +1,55 @@
/*
* 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.
*/
#include "HostTarget.h"
#include "HostTargetTraceRecording.h"
namespace facebook::react::jsinspector_modern {
bool HostTargetController::startTracing(tracing::Mode tracingMode) {
return target_.startTracing(tracingMode);
}
tracing::TraceRecordingState HostTargetController::stopTracing() {
return target_.stopTracing();
}
std::shared_ptr<HostTracingAgent> HostTarget::createTracingAgent(
tracing::TraceRecordingState& state) {
auto agent = std::make_shared<HostTracingAgent>(state);
agent->setTracedInstance(currentInstance_.get());
return agent;
}
bool HostTarget::startTracing(tracing::Mode tracingMode) {
if (traceRecording_ != nullptr) {
if (traceRecording_->isBackgroundInitiated() &&
tracingMode == tracing::Mode::CDP) {
traceRecording_.reset();
} else {
return false;
}
}
traceRecording_ =
std::make_unique<HostTargetTraceRecording>(tracingMode, *this);
traceRecording_->setTracedInstance(currentInstance_.get());
traceRecording_->start();
return true;
}
tracing::TraceRecordingState HostTarget::stopTracing() {
assert(traceRecording_ != nullptr && "No tracing in progress");
auto state = traceRecording_->stop();
traceRecording_.reset();
return state;
}
} // namespace facebook::react::jsinspector_modern
@@ -33,6 +33,10 @@ bool InspectorFlags::getNetworkInspectionEnabled() const {
return loadFlagsAndAssertUnchanged().networkInspectionEnabled;
}
bool InspectorFlags::getPerfMonitorV2Enabled() const {
return loadFlagsAndAssertUnchanged().perfMonitorV2Enabled;
}
void InspectorFlags::dangerouslyResetFlags() {
*this = InspectorFlags{};
}
@@ -59,6 +63,9 @@ const InspectorFlags::Values& InspectorFlags::loadFlagsAndAssertUnchanged()
.networkInspectionEnabled =
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled(),
.perfMonitorV2Enabled =
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
ReactNativeFeatureFlags::perfMonitorV2Enabled(),
};
if (cachedValues_.has_value() && !inconsistentFlagsStateLogged_) {
@@ -35,6 +35,11 @@ class InspectorFlags {
*/
bool getNetworkInspectionEnabled() const;
/**
* Flag determining if the V2 in-app Performance Monitor is enabled.
*/
bool getPerfMonitorV2Enabled() const;
/**
* Forcibly disable the main `getFuseboxEnabled()` flag. This should ONLY be
* used by `ReactInstanceIntegrationTest`.
@@ -52,6 +57,7 @@ class InspectorFlags {
bool fuseboxEnabled;
bool isProfilingBuild;
bool networkInspectionEnabled;
bool perfMonitorV2Enabled;
bool operator==(const Values&) const = default;
};
@@ -24,6 +24,14 @@ void CallbackLocalConnection::disconnect() {
handler_ = nullptr;
}
CallbackRemoteConnection::CallbackRemoteConnection(
std::function<void(std::string)> handler)
: handler_(std::move(handler)) {}
void CallbackRemoteConnection::onMessage(std::string message) {
handler_(std::move(message));
}
RAIIRemoteConnection::RAIIRemoteConnection(
std::unique_ptr<IRemoteConnection> remote)
: remote_(std::move(remote)) {}
@@ -33,6 +33,25 @@ class CallbackLocalConnection : public ILocalConnection {
std::function<void(std::string)> handler_;
};
/**
* Wraps a callback function in IRemoteConnection.
*/
class CallbackRemoteConnection : public IRemoteConnection {
public:
/**
* Creates a new Connection that uses the given callback to receive messages
* from the backend.
*/
explicit CallbackRemoteConnection(std::function<void(std::string)> handler);
void onMessage(std::string message) override;
void onDisconnect() override {}
private:
std::function<void(std::string)> handler_;
};
/**
* Wraps an IRemoteConnection in a simpler interface that calls `onDisconnect`
* implicitly upon destruction.
@@ -9,11 +9,21 @@
#include "RuntimeTarget.h"
#include <jsinspector-modern/cdp/CdpJson.h>
#include <jsinspector-modern/tracing/PerformanceTracer.h>
#include <utility>
namespace facebook::react::jsinspector_modern {
namespace {
// The size of the timeline for the trace recording that happened in the
// background.
constexpr HighResDuration kBackgroundTracePerformanceTracerWindowSize =
HighResDuration::fromMilliseconds(20000);
} // namespace
InstanceAgent::InstanceAgent(
FrontendChannel frontendChannel,
InstanceTarget& target,
@@ -155,25 +165,34 @@ void InstanceAgent::maybeSendPendingConsoleMessages() {
}
}
void InstanceAgent::startTracing() {
if (runtimeAgent_) {
runtimeAgent_->enableSamplingProfiler();
#pragma mark - Tracing
InstanceTracingAgent::InstanceTracingAgent(tracing::TraceRecordingState& state)
: tracing::TargetTracingAgent(state) {
auto& performanceTracer = tracing::PerformanceTracer::getInstance();
if (state.mode == tracing::Mode::Background) {
performanceTracer.startTracing(kBackgroundTracePerformanceTracerWindowSize);
} else {
performanceTracer.startTracing();
}
}
void InstanceAgent::stopTracing() {
if (runtimeAgent_) {
runtimeAgent_->disableSamplingProfiler();
InstanceTracingAgent::~InstanceTracingAgent() {
auto& performanceTracer = tracing::PerformanceTracer::getInstance();
auto performanceTraceEvents = performanceTracer.stopTracing();
if (performanceTraceEvents) {
state_.instanceTracingProfiles.emplace_back(tracing::InstanceTracingProfile{
.performanceTraceEvents = std::move(*performanceTraceEvents),
});
}
}
tracing::InstanceTracingProfile InstanceAgent::collectTracingProfile() {
tracing::RuntimeSamplingProfile runtimeSamplingProfile =
runtimeAgent_->collectSamplingProfile();
return tracing::InstanceTracingProfile{
.runtimeSamplingProfile = std::move(runtimeSamplingProfile),
};
void InstanceTracingAgent::setTracedRuntime(RuntimeTarget* runtimeTarget) {
if (runtimeTarget != nullptr) {
runtimeTracingAgent_ = runtimeTarget->createTracingAgent(state_);
} else {
runtimeTracingAgent_ = nullptr;
}
}
} // namespace facebook::react::jsinspector_modern
@@ -15,6 +15,7 @@
#include <jsinspector-modern/RuntimeAgent.h>
#include <jsinspector-modern/cdp/CdpJson.h>
#include <jsinspector-modern/tracing/InstanceTracingProfile.h>
#include <jsinspector-modern/tracing/TargetTracingAgent.h>
#include <functional>
@@ -59,23 +60,6 @@ class InstanceAgent final {
*/
void sendConsoleMessage(SimpleConsoleMessage message);
/**
* Notify Instance about started Tracing session. Should be initiated by
* TracingAgent on Tracing.start CDP method.
*/
void startTracing();
/**
* Notify Instance about stopped Tracing session. Should be initiated by
* TracingAgent on Tracing.end CDP method.
*/
void stopTracing();
/**
* Return recorded profile for the previous tracing session.
*/
tracing::InstanceTracingProfile collectTracingProfile();
private:
void maybeSendExecutionContextCreatedNotification();
void sendConsoleMessageImmediately(SimpleConsoleMessage message);
@@ -87,4 +71,27 @@ class InstanceAgent final {
SessionState& sessionState_;
};
#pragma mark - Tracing
/**
* An Agent that handles Tracing events for a particular InstanceTarget.
*
* Lifetime of this agent is bound to the lifetime of the Tracing session -
* HostTargetTraceRecording and to the lifetime of the InstanceTarget.
*/
class InstanceTracingAgent : tracing::TargetTracingAgent {
public:
explicit InstanceTracingAgent(tracing::TraceRecordingState& state);
~InstanceTracingAgent();
/**
* Registers the RuntimeTarget with this tracing agent.
*/
void setTracedRuntime(RuntimeTarget* runtimeTarget);
private:
std::shared_ptr<RuntimeTracingAgent> runtimeTracingAgent_;
};
} // namespace facebook::react::jsinspector_modern
@@ -44,12 +44,25 @@ std::shared_ptr<InstanceAgent> InstanceTarget::createAgent(
return instanceAgent;
}
std::shared_ptr<InstanceTracingAgent> InstanceTarget::createTracingAgent(
tracing::TraceRecordingState& state) {
auto agent = std::make_shared<InstanceTracingAgent>(state);
agent->setTracedRuntime(currentRuntime_.get());
tracingAgent_ = agent;
return agent;
}
InstanceTarget::~InstanceTarget() {
// Agents are owned by the session, not by InstanceTarget, but
// they hold an InstanceTarget& that we must guarantee is valid.
assert(
agents_.empty() &&
"InstanceAgent objects must be destroyed before their InstanceTarget. Did you call HostTarget::unregisterInstance()?");
// Tracing Agents are owned by the HostTargetTraceRecording.
assert(
tracingAgent_.expired() &&
"InstanceTracingAgent must be destroyed before their InstanceTarget. Did you call HostTarget::unregisterInstance()?");
}
RuntimeTarget& InstanceTarget::registerRuntime(
@@ -69,6 +82,13 @@ RuntimeTarget& InstanceTarget::registerRuntime(
agents_.forEach([currentRuntime = &*currentRuntime_](InstanceAgent& agent) {
agent.setCurrentRuntime(currentRuntime);
});
if (auto tracingAgent = tracingAgent_.lock()) {
// Registers the Runtime for tracing, if a Trace is currently being
// recorded.
tracingAgent->setTracedRuntime(currentRuntime_.get());
}
return *currentRuntime_;
}
@@ -78,6 +98,13 @@ void InstanceTarget::unregisterRuntime(RuntimeTarget& Runtime) {
"Invalid unregistration");
agents_.forEach(
[](InstanceAgent& agent) { agent.setCurrentRuntime(nullptr); });
if (auto tracingAgent = tracingAgent_.lock()) {
// Unregisters the Runtime for tracing, if a Trace is currently being
// recorded.
tracingAgent->setTracedRuntime(nullptr);
}
currentRuntime_.reset();
}
@@ -15,12 +15,15 @@
#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/RuntimeAgent.h>
#include <jsinspector-modern/tracing/TraceRecordingState.h>
#include <memory>
namespace facebook::react::jsinspector_modern {
class InstanceAgent;
class InstanceTracingAgent;
class HostTargetTraceRecording;
/**
* Receives events from an InstanceTarget. This is a shared interface that
@@ -68,6 +71,18 @@ class InstanceTarget : public EnableExecutorFromThis<InstanceTarget> {
const FrontendChannel& channel,
SessionState& sessionState);
/**
* Creates a new InstanceTracingAgent.
* This Agent is not owned by the InstanceTarget. The Agent will be destroyed
* either before the InstanceTarget is destroyed, as part of the
* InstanceTarget unregistration in HostTarget, or at the end of the tracing
* session.
*
* \param state A reference to the state of the active trace recording.
*/
std::shared_ptr<InstanceTracingAgent> createTracingAgent(
tracing::TraceRecordingState& state);
/**
* Registers a JS runtime with this InstanceTarget. \returns a reference to
* the created RuntimeTarget, which is owned by the \c InstanceTarget. All the
@@ -103,6 +118,13 @@ class InstanceTarget : public EnableExecutorFromThis<InstanceTarget> {
std::shared_ptr<RuntimeTarget> currentRuntime_{nullptr};
WeakList<InstanceAgent> agents_;
std::shared_ptr<ExecutionContextManager> executionContextManager_;
/**
* This TracingAgent is owned by the HostTracingAgent, both are bound to
* the lifetime of their corresponding targets and the lifetime of the tracing
* session - HostTargetTraceRecording.
*/
std::weak_ptr<InstanceTracingAgent> tracingAgent_;
};
} // namespace facebook::react::jsinspector_modern
@@ -128,4 +128,24 @@ tracing::RuntimeSamplingProfile RuntimeAgent::collectSamplingProfile() {
return targetController_.collectSamplingProfile();
}
#pragma mark - Tracing
RuntimeTracingAgent::RuntimeTracingAgent(
tracing::TraceRecordingState& state,
RuntimeTargetController& targetController)
: tracing::TargetTracingAgent(state), targetController_(targetController) {
if (state.mode == tracing::Mode::CDP) {
targetController_.enableSamplingProfiler();
}
}
RuntimeTracingAgent::~RuntimeTracingAgent() {
if (state_.mode == tracing::Mode::CDP) {
targetController_.disableSamplingProfiler();
auto profile = targetController_.collectSamplingProfile();
state_.runtimeSamplingProfiles.emplace_back(std::move(profile));
}
}
} // namespace facebook::react::jsinspector_modern
@@ -13,6 +13,8 @@
#include <jsinspector-modern/cdp/CdpJson.h>
#include <jsinspector-modern/tracing/RuntimeSamplingProfile.h>
#include <jsinspector-modern/tracing/TargetTracingAgent.h>
#include <jsinspector-modern/tracing/TraceRecordingState.h>
namespace facebook::react::jsinspector_modern {
@@ -106,4 +108,24 @@ class RuntimeAgent final {
const ExecutionContextDescription executionContextDescription_;
};
#pragma mark - Tracing
/**
* An Agent that handles Tracing events for a particular RuntimeTarget.
*
* Lifetime of this agent is bound to the lifetime of the Tracing session -
* HostTargetTraceRecording and to the lifetime of the RuntimeTarget.
*/
class RuntimeTracingAgent : tracing::TargetTracingAgent {
public:
explicit RuntimeTracingAgent(
tracing::TraceRecordingState& state,
RuntimeTargetController& targetController);
~RuntimeTracingAgent();
private:
RuntimeTargetController& targetController_;
};
} // namespace facebook::react::jsinspector_modern
@@ -77,12 +77,24 @@ std::shared_ptr<RuntimeAgent> RuntimeTarget::createAgent(
return runtimeAgent;
}
std::shared_ptr<RuntimeTracingAgent> RuntimeTarget::createTracingAgent(
tracing::TraceRecordingState& state) {
auto agent = std::make_shared<RuntimeTracingAgent>(state, controller_);
tracingAgent_ = agent;
return agent;
}
RuntimeTarget::~RuntimeTarget() {
// Agents are owned by the session, not by RuntimeTarget, but
// they hold a RuntimeTarget& that we must guarantee is valid.
assert(
agents_.empty() &&
"RuntimeAgent objects must be destroyed before their RuntimeTarget. Did you call InstanceTarget::unregisterRuntime()?");
// Tracing Agents are owned by the HostTargetTraceRecording.
assert(
tracingAgent_.expired() &&
"RuntimeTracingAgent must be destroyed before their InstanceTarget. Did you call InstanceTarget::unregisterRuntime()?");
}
void RuntimeTarget::installBindingHandler(const std::string& bindingName) {
@@ -17,6 +17,7 @@
#include <ReactCommon/RuntimeExecutor.h>
#include <jsinspector-modern/tracing/RuntimeSamplingProfile.h>
#include <jsinspector-modern/tracing/TraceRecordingState.h>
#include <memory>
@@ -35,6 +36,7 @@
namespace facebook::react::jsinspector_modern {
class RuntimeAgent;
class RuntimeTracingAgent;
class RuntimeAgentDelegate;
class RuntimeTarget;
struct SessionState;
@@ -202,6 +204,17 @@ class JSINSPECTOR_EXPORT RuntimeTarget
const FrontendChannel& channel,
SessionState& sessionState);
/**
* Creates a new RuntimeTracingAgent.
* This Agent is not owned by the RuntimeTarget. The Agent will be destroyed
* either before the RuntimeTarget is destroyed, as part of the RuntimeTarget
* unregistration in InstanceTarget, or at the end of the tracing session.
*
* \param state A reference to the state of the active trace recording.
*/
std::shared_ptr<RuntimeTracingAgent> createTracingAgent(
tracing::TraceRecordingState& state);
/**
* Start sampling profiler for a particular JavaScript runtime.
*/
@@ -246,6 +259,13 @@ class JSINSPECTOR_EXPORT RuntimeTarget
WeakList<RuntimeAgent> agents_;
RuntimeTargetController controller_{*this};
/**
* This TracingAgent is owned by the InstanceTracingAgent, both are bound to
* the lifetime of their corresponding targets and the lifetime of the tracing
* session - HostTargetTraceRecording.
*/
std::weak_ptr<RuntimeTracingAgent> tracingAgent_;
/**
* Adds a function with the given name on the runtime's global object, that
* when called will send a Runtime.bindingCalled event through all connected
@@ -25,6 +25,12 @@ struct SessionState {
bool isReactNativeApplicationDomainEnabled{false};
bool isRuntimeDomainEnabled{false};
/**
* Whether the Trace Recording was initialized via CDP Tracing.start method
* and not finished yet with Tracing.stop.
*/
bool hasPendingTraceRecording{false};
/**
* A map from binding names (registered during this session using @cdp
* Runtime.addBinding) to execution context selectors.
@@ -10,6 +10,8 @@
#include <jsinspector-modern/tracing/PerformanceTracer.h>
#include <jsinspector-modern/tracing/RuntimeSamplingProfileTraceEventSerializer.h>
#include <jsinspector-modern/tracing/TraceEventSerializer.h>
#include <jsinspector-modern/tracing/TraceRecordingStateSerializer.h>
#include <jsinspector-modern/tracing/TracingMode.h>
namespace facebook::react::jsinspector_modern {
@@ -24,39 +26,30 @@ const uint16_t TRACE_EVENT_CHUNK_SIZE = 1000;
/**
* The maximum number of ProfileChunk trace events
* that will be sent in a single CDP Tracing.dataCollected message.
* TODO(T219394401): Increase the size once we manage the queue on OkHTTP side
* TODO(T219394401): Increase the size once we manage the queue on OkHTTP
side
* properly and avoid WebSocket disconnections when sending a message larger
* than 16MB.
*/
const uint16_t PROFILE_TRACE_EVENT_CHUNK_SIZE = 1;
void serializeTraceEventsInChunks(
std::vector<tracing::TraceEvent>&& traceEvents,
uint16_t chunkSize,
const std::function<void(folly::dynamic&& eventsChunk)>& resultCallback) {
auto serializedTraceEvents = folly::dynamic::array();
for (auto&& traceEvent : traceEvents) {
// Emit trace events
serializedTraceEvents.push_back(
tracing::TraceEventSerializer::serialize(std::move(traceEvent)));
if (serializedTraceEvents.size() == chunkSize) {
resultCallback(std::move(serializedTraceEvents));
serializedTraceEvents = folly::dynamic::array();
}
}
if (!serializedTraceEvents.empty()) {
resultCallback(std::move(serializedTraceEvents));
}
}
} // namespace
TracingAgent::TracingAgent(
FrontendChannel frontendChannel,
const SessionState& sessionState)
SessionState& sessionState,
HostTargetController& hostTargetController)
: frontendChannel_(std::move(frontendChannel)),
sessionState_(sessionState) {}
sessionState_(sessionState),
hostTargetController_(hostTargetController) {}
TracingAgent::~TracingAgent() {
// Agents are owned by the session. If the agent is destroyed, it means that
// the session was destroyed. We should stop pending recording.
if (sessionState_.hasPendingTraceRecording) {
hostTargetController_.stopTracing();
}
}
bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
if (req.method == "Tracing.start") {
@@ -69,57 +62,27 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
return true;
}
if (!instanceAgent_) {
bool didNotHaveAlreadyRunningRecording =
hostTargetController_.startTracing(tracing::Mode::CDP);
if (!didNotHaveAlreadyRunningRecording) {
frontendChannel_(cdp::jsonError(
req.id,
cdp::ErrorCode::InternalError,
"Couldn't find instance available for Tracing"));
cdp::ErrorCode::InvalidRequest,
"Tracing has already been started"));
return true;
}
bool correctlyStartedPerformanceTracer =
tracing::PerformanceTracer::getInstance().startTracing();
if (!correctlyStartedPerformanceTracer) {
frontendChannel_(cdp::jsonError(
req.id,
cdp::ErrorCode::InternalError,
"Tracing session already started"));
return true;
}
instanceAgent_->startTracing();
instanceTracingStartTimestamp_ = HighResTimeStamp::now();
sessionState_.hasPendingTraceRecording = true;
frontendChannel_(cdp::jsonResult(req.id));
return true;
} else if (req.method == "Tracing.end") {
// @cdp Tracing.end support is experimental.
if (!instanceAgent_) {
frontendChannel_(cdp::jsonError(
req.id,
cdp::ErrorCode::InternalError,
"Couldn't find instance available for Tracing"));
return true;
}
instanceAgent_->stopTracing();
tracing::PerformanceTracer& performanceTracer =
tracing::PerformanceTracer::getInstance();
auto collectedEvents = performanceTracer.stopTracing();
if (!collectedEvents) {
frontendChannel_(cdp::jsonError(
req.id,
cdp::ErrorCode::InternalError,
"Tracing session not started"));
return true;
}
auto state = hostTargetController_.stopTracing();
sessionState_.hasPendingTraceRecording = false;
// Send response to Tracing.end request.
frontendChannel_(cdp::jsonResult(req.id));
@@ -128,19 +91,10 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
"Tracing.dataCollected",
folly::dynamic::object("value", std::move(eventsChunk))));
};
serializeTraceEventsInChunks(
std::move(*collectedEvents),
TRACE_EVENT_CHUNK_SIZE,
dataCollectedCallback);
auto tracingProfile = instanceAgent_->collectTracingProfile();
tracing::IdGenerator profileIdGenerator;
tracing::RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
std::move(tracingProfile.runtimeSamplingProfile),
profileIdGenerator,
instanceTracingStartTimestamp_,
tracing::TraceRecordingStateSerializer::emitAsDataCollectedChunks(
std::move(state),
dataCollectedCallback,
TRACE_EVENT_CHUNK_SIZE,
PROFILE_TRACE_EVENT_CHUNK_SIZE);
frontendChannel_(cdp::jsonNotification(
@@ -153,9 +107,4 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
return false;
}
void TracingAgent::setCurrentInstanceAgent(
std::shared_ptr<InstanceAgent> instanceAgent) {
instanceAgent_ = std::move(instanceAgent);
}
} // namespace facebook::react::jsinspector_modern
@@ -7,8 +7,8 @@
#pragma once
#include "HostTarget.h"
#include "InspectorInterfaces.h"
#include "InstanceAgent.h"
#include <jsinspector-modern/cdp/CdpJson.h>
#include <jsinspector-modern/tracing/Timing.h>
@@ -27,7 +27,10 @@ class TracingAgent {
*/
TracingAgent(
FrontendChannel frontendChannel,
const SessionState& sessionState);
SessionState& sessionState,
HostTargetController& hostTargetController);
~TracingAgent();
/**
* Handle a CDP request. The response will be sent over the provided
@@ -36,33 +39,15 @@ class TracingAgent {
*/
bool handleRequest(const cdp::PreparsedRequest& req);
/**
* Replace the current InstanceAgent with the given one.
* \param agent The new InstanceAgent. May be null to signify that there is
* currently no active instance.
*/
void setCurrentInstanceAgent(std::shared_ptr<InstanceAgent> agent);
private:
/**
* A channel used to send responses and events to the frontend.
*/
FrontendChannel frontendChannel_;
/**
* Current InstanceAgent. May be null to signify that there is
* currently no active instance.
*/
std::shared_ptr<InstanceAgent> instanceAgent_;
SessionState& sessionState_;
/**
* Timestamp of when we started tracing of an Instance, will be used as a
* a start of JavaScript samples recording and as a time origin for the events
* in this trace.
*/
HighResTimeStamp instanceTracingStartTimestamp_;
const SessionState& sessionState_;
HostTargetController& hostTargetController_;
};
} // namespace facebook::react::jsinspector_modern
@@ -7,13 +7,12 @@
#pragma once
#include "RuntimeSamplingProfile.h"
#include "TraceEvent.h"
namespace facebook::react::jsinspector_modern::tracing {
struct InstanceTracingProfile {
public:
RuntimeSamplingProfile runtimeSamplingProfile;
std::vector<TraceEvent> performanceTraceEvents;
};
} // namespace facebook::react::jsinspector_modern::tracing
@@ -77,7 +77,6 @@ std::optional<std::vector<TraceEvent>> PerformanceTracer::stopTracing() {
tracingAtomic_ = false;
performanceMeasureCount_ = 0;
currentTraceMaxDuration_ = std::nullopt;
events = collectEventsAndClearBuffers(currentTraceEndTime);
}
@@ -114,6 +113,7 @@ std::optional<std::vector<TraceEvent>> PerformanceTracer::stopTracing() {
.tid = oscompat::getCurrentThreadId(),
});
currentTraceMaxDuration_ = std::nullopt;
return events;
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "TraceRecordingState.h"
namespace facebook::react::jsinspector_modern::tracing {
/**
* An interface for a tracing agent of a target.
* Tracing Agents are only allocated during an active tracing session.
*
* Construction of a TracingAgent means that either the recording has just
* started or the target was just created during an active recording.
* Destruction of a TracingAgent means that either the recording has stopped or
* the target is about to be destroyed.
*/
class TargetTracingAgent {
public:
explicit TargetTracingAgent(TraceRecordingState& state) : state_(state) {
(void)state_;
}
protected:
TraceRecordingState& state_;
};
} // namespace facebook::react::jsinspector_modern::tracing
@@ -0,0 +1,39 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "InstanceTracingProfile.h"
#include "RuntimeSamplingProfile.h"
#include "TracingMode.h"
#include <oscompat/OSCompat.h>
#include <react/timing/primitives.h>
#include <vector>
namespace facebook::react::jsinspector_modern::tracing {
struct TraceRecordingState {
// The mode of this Trace Recording.
tracing::Mode mode;
// The ID of the OS-level process that this Trace Recording is associated
// with.
ProcessId processId = oscompat::getCurrentProcessId();
// The timestamp at which this Trace Recording started.
HighResTimeStamp startTime;
// All captured Runtime Sampling Profiles during this Trace Recording.
std::vector<RuntimeSamplingProfile> runtimeSamplingProfiles{};
// All captures Instance Tracing Profiles during this Trace Recording.
std::vector<InstanceTracingProfile> instanceTracingProfiles{};
};
} // namespace facebook::react::jsinspector_modern::tracing
@@ -0,0 +1,68 @@
/*
* 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.
*/
#include "TraceRecordingStateSerializer.h"
#include "RuntimeSamplingProfileTraceEventSerializer.h"
#include "TraceEventSerializer.h"
namespace facebook::react::jsinspector_modern::tracing {
namespace {
folly::dynamic generateNewChunk(uint16_t chunkSize) {
folly::dynamic chunk = folly::dynamic::array();
chunk.reserve(chunkSize);
return chunk;
}
} // namespace
/* static */ void TraceRecordingStateSerializer::emitAsDataCollectedChunks(
TraceRecordingState&& recording,
const std::function<void(folly::dynamic&&)>& chunkCallback,
uint16_t performanceTraceEventsChunkSize,
uint16_t profileTraceEventsChunkSize) {
auto instancesProfiles = std::move(recording.instanceTracingProfiles);
IdGenerator profileIdGenerator;
for (auto& instanceProfile : instancesProfiles) {
emitPerformanceTraceEvents(
std::move(instanceProfile.performanceTraceEvents),
chunkCallback,
performanceTraceEventsChunkSize);
}
RuntimeSamplingProfileTraceEventSerializer::serializeAndDispatch(
std::move(recording.runtimeSamplingProfiles),
profileIdGenerator,
recording.startTime,
chunkCallback,
profileTraceEventsChunkSize);
}
/* static */ void TraceRecordingStateSerializer::emitPerformanceTraceEvents(
std::vector<TraceEvent>&& events,
const std::function<void(folly::dynamic&&)>& chunkCallback,
uint16_t chunkSize) {
folly::dynamic chunk = generateNewChunk(chunkSize);
for (auto& event : events) {
if (chunk.size() == chunkSize) {
chunkCallback(std::move(chunk));
chunk = generateNewChunk(chunkSize);
}
chunk.push_back(TraceEventSerializer::serialize(std::move(event)));
}
if (!chunk.empty()) {
chunkCallback(std::move(chunk));
}
}
} // namespace facebook::react::jsinspector_modern::tracing
@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "TraceEvent.h"
#include "TraceRecordingState.h"
#include <folly/dynamic.h>
#include <vector>
namespace facebook::react::jsinspector_modern::tracing {
/**
* A serializer for TraceRecordingState that can be used for tranforming the
* recording into sequence of serialized Trace Events.
*/
class TraceRecordingStateSerializer {
public:
/**
* Transforms the recording into a sequence of serialized Trace Events, which
* is split in chunks of sizes \p performanceTraceEventsChunkSize or
* \p profileTraceEventsChunkSize, depending on type, and sent with \p
* chunkCallback.
*/
static void emitAsDataCollectedChunks(
TraceRecordingState&& recording,
const std::function<void(folly::dynamic&& chunk)>& chunkCallback,
uint16_t performanceTraceEventsChunkSize,
uint16_t profileTraceEventsChunkSize);
static void emitPerformanceTraceEvents(
std::vector<TraceEvent>&& events,
const std::function<void(folly::dynamic&& chunk)>& chunkCallback,
uint16_t chunkSize);
};
} // namespace facebook::react::jsinspector_modern::tracing
@@ -0,0 +1,17 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
namespace facebook::react::jsinspector_modern::tracing {
enum class Mode {
CDP, // Initiated by the user via Chrome DevTools Frontend.
Background, // Initiated by the host, doesn't require active CDP session.
};
}
@@ -292,7 +292,7 @@ SharedDebugStringConvertibleList ImageProps::getDebugProps() const {
sourcesList = sources[0].getDebugProps("source");
} else if (sources.size() > 1) {
for (const auto& source : sources) {
std::string sourceName = "source@" + react::toString(source.scale) + "x";
std::string sourceName = "source-" + react::toString(source.scale) + "x";
auto debugProps = source.getDebugProps(sourceName);
sourcesList.insert(
sourcesList.end(), debugProps.begin(), debugProps.end());
@@ -304,8 +304,32 @@ SharedDebugStringConvertibleList ImageProps::getDebugProps() const {
SharedDebugStringConvertibleList{
debugStringConvertibleItem(
"blurRadius", blurRadius, imageProps.blurRadius),
debugStringConvertibleItem(
"resizeMode",
toString(resizeMode),
toString(imageProps.resizeMode)),
debugStringConvertibleItem(
"tintColor", toString(tintColor), toString(imageProps.tintColor)),
};
}
inline std::string toString(ImageResizeMode resizeMode) {
switch (resizeMode) {
case ImageResizeMode::Cover:
return "cover";
case ImageResizeMode::Contain:
return "contain";
case ImageResizeMode::Stretch:
return "stretch";
case ImageResizeMode::Center:
return "center";
case ImageResizeMode::Repeat:
return "repeat";
case ImageResizeMode::None:
return "none";
}
}
#endif
} // namespace facebook::react
@@ -34,7 +34,7 @@ class ImageProps final : public ViewProps {
ImageSources sources{};
ImageSource defaultSource{};
ImageSource loadingIndicatorSource{};
ImageResizeMode resizeMode{ImageResizeMode::Stretch};
ImageResizeMode resizeMode{ImageResizeMode::Cover};
Float blurRadius{};
EdgeInsets capInsets{};
SharedColor tintColor{};

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