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
Ruslan Lesiutin aa25ad22a2 Update debugger-frontend from 7dcbddd...9215667 (#53082)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53082

Changelog: [Internal] - Update `react-native/debugger-frontend` from 7dcbddd...9215667

Resyncs `react-native/debugger-frontend` from GitHub - see `rn-chrome-devtools-frontend` [changelog](https://github.com/facebook/react-native-devtools-frontend/compare/7dcbddd636137a9604d69a99cc69221216cd4be6...921566790e9e16d0ecace6e49b3cfaace205958c).

### Changelog

| Commit | Author | Date/Time | Subject |
| ------ | ------ | --------- | ------- |
| [921566790](https://github.com/facebook/react-native-devtools-frontend/commit/921566790) | Ruslan Lesiutin (28902667+hoxyq@users.noreply.github.com) | 2025-08-05T18:09:32+01:00 | [feat: add React Native-only event for pre-enabling multiple layers of Tracing (#199)](https://github.com/facebook/react-native-devtools-frontend/commit/921566790) |

Reviewed By: sbuggay

Differential Revision: D79661411

fbshipit-source-id: 63c13de4c6f24bd669aed6397e6d08f00510237c
2025-08-06 03:41:09 -07:00
Nicola Corti 00175bd096 Compare nightly results up to 7 days in the back (#53065)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53065

Instead of checking results from the previous day, allow to go back up to 7
days in the past to check for previous runs.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D79568067

fbshipit-source-id: 5d6fac6a06b4e26e52de6fbf6187ec8f8e44e10e
2025-08-06 03:04:02 -07:00
Nicola Corti f806851875 Store nightly outcome and compute daily broken/recovered from Firebase (#53066)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53066

This diff introduces the mechanism for us to store the result of nightly 3p
library integration on Firebase.
Having the result store, we can now query the result from the previous day and
report if the build is newly broken or recovered overnight.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D79565536

fbshipit-source-id: ba839b2950462a7ca6186a163f93f062719304fb
2025-08-06 03:04:02 -07:00
Riccardo Cipolleschi aa4555eaf1 Fix dynamic framework build (#53075)
Summary:
Pure cocoapods build with dyn frameworks was broken due to some missing dependencies with the new performance metrics of cdp

## Changelog:
[iOS][Fixed] - Fix pure cocoapods dynamic framework build

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

Test Plan:
Tested locally in RNTester with
```
USE_FRAMEWORKS=dynamic bundle exec pod install
```
and then building from Xcode

Reviewed By: cortinico

Differential Revision: D79676186

Pulled By: cipolleschi

fbshipit-source-id: ae6b81138fcae66c67bedadb1e1ad9cd6c4b6c35
2025-08-06 03:02:22 -07:00
nishan (o^▽^o) 138d0eb01d Add filter to native animated allowlist (#52920)
Summary:
Add filter to native animated allowlist.

## Changelog:

[GENERAL] [ADDED] - Allow filter usage with native animated driver.

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

Pick one each for the category and type tags:

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

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

Test Plan: Tested on RNTester

Reviewed By: joevilches

Differential Revision: D79473929

Pulled By: lunaleaps

fbshipit-source-id: de6fcc0f18a1d656688dce513d2cf48a3c9d4f09
2025-08-05 20:27:16 -07:00
Peter Abbondanzo 4503068117 Make ReactAccessibilityDelegate nullsafe (#53068)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53068

Adds some assertion annotations and small null checks to `ReactAccessibilityDelegate` to comply with FB's Nullsafe annotation

Changelog: [Internal]

Reviewed By: arushikesarwani94

Differential Revision: D79645332

fbshipit-source-id: 707e4e6d4a5f09232af168c2f7c57c2fdbb1f08d
2025-08-05 16:11:34 -07:00
Sam Zhou af1bcb6d44 Mass replace $FlowIgnore with $FlowFixMe in react-native (#53076)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53076

Changelog: [Internal]

Reviewed By: marcoww6

Differential Revision: D79672242

fbshipit-source-id: 560f057d8658ed602cf7241e584bade70d8f3a99
2025-08-05 15:44:41 -07:00
Rubén Norte 5936f29d6a Add test to ensure setUpDefaultReactNativeEnvironment does not access feature flags (#53057)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53057

Changelog: [internal]

This adds a Fantom test to ensure that `setUpDefaultReactNativeEnvironment` doesn't read any feature flags. This prevents catching this as a runtime issue when the feature flag system complains that feature flags were accessed before being overridden, which always would happen if this module read any flags (as it runs before any product code that sets overrides).

Reviewed By: rshest

Differential Revision: D79639890

fbshipit-source-id: 6997609b7bf84947a6da53b58e68f9edd5654912
2025-08-05 13:58:32 -07:00
Rubén Norte 527e308a90 Add private function in feature flags to reset internal JS state for testing (#53056)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53056

Changelog: [internal]

This exposes a utility method in `ReactNativeFeatureFlagsBase` to reset its internal state for testing purposes.

This is intentionally not exposed through `ReactNativeFeatureFlags` to avoid it being used at runtime.

Reviewed By: rshest

Differential Revision: D79639889

fbshipit-source-id: adfb6125d991994c9706d5952d309915fec8f815
2025-08-05 13:58:32 -07:00
192 changed files with 4523 additions and 1095 deletions
@@ -0,0 +1,897 @@
/**
* 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.
*
* @format
*/
'use strict';
const {
FirebaseClient,
compareResults,
getYesterdayDate,
getTodayDate,
} = require('../firebaseUtils');
describe('FirebaseClient', () => {
const originalFetch = global.fetch;
const originalEnv = process.env;
beforeEach(() => {
global.fetch = jest.fn();
process.env = {
...originalEnv,
FIREBASE_APP_EMAIL: 'test@example.com',
FIREBASE_APP_PASS: 'testpassword',
FIREBASE_APP_APIKEY: 'test-api-key',
FIREBASE_APP_PROJECTNAME: 'test-project',
};
jest.spyOn(console, 'log').mockImplementation(() => {});
jest.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
global.fetch = originalFetch;
process.env = originalEnv;
jest.restoreAllMocks();
});
describe('constructor', () => {
it('should initialize with environment variables', () => {
const client = new FirebaseClient();
expect(client.email).toBe('test@example.com');
expect(client.password).toBe('testpassword');
expect(client.apiKey).toBe('test-api-key');
expect(client.projectId).toBe('test-project');
expect(client.databaseUrl).toBe(
'test-project-default-rtdb.firebaseio.com',
);
expect(client.idToken).toBeNull();
});
});
describe('authenticate', () => {
it('should authenticate successfully', async () => {
const mockResponse = {
idToken: 'mock-id-token',
refreshToken: 'mock-refresh-token',
};
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResponse)),
});
const client = new FirebaseClient();
await client.authenticate();
expect(client.idToken).toBe('mock-id-token');
expect(global.fetch).toHaveBeenCalledWith(
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=test-api-key',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'test@example.com',
password: 'testpassword',
returnSecureToken: true,
}),
},
);
});
it('should throw error when email is missing', async () => {
delete process.env.FIREBASE_APP_EMAIL;
const client = new FirebaseClient();
await expect(client.authenticate()).rejects.toThrow(
'Firebase credentials not found in environment variables',
);
});
it('should throw error when password is missing', async () => {
delete process.env.FIREBASE_APP_PASS;
const client = new FirebaseClient();
await expect(client.authenticate()).rejects.toThrow(
'Firebase credentials not found in environment variables',
);
});
it('should handle authentication failure', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 400,
text: jest.fn().mockResolvedValueOnce(
JSON.stringify({
error: {message: 'Invalid credentials'},
}),
),
});
const client = new FirebaseClient();
await expect(client.authenticate()).rejects.toThrow(
'HTTP 400: Invalid credentials',
);
});
});
describe('makeRequest', () => {
it('should make successful GET request', async () => {
const mockData = {test: 'data'};
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
});
const client = new FirebaseClient();
const result = await client.makeRequest('example.com', '/test', 'GET');
expect(result).toEqual(mockData);
expect(global.fetch).toHaveBeenCalledWith('https://example.com/test', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
});
it('should make successful POST request with data', async () => {
const mockData = {success: true};
const postData = {test: 'post data'};
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
});
const client = new FirebaseClient();
const result = await client.makeRequest(
'example.com',
'/test',
'POST',
postData,
);
expect(result).toEqual(mockData);
expect(global.fetch).toHaveBeenCalledWith('https://example.com/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData),
});
});
it('should handle non-JSON response', async () => {
const textResponse = 'plain text response';
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(textResponse),
});
const client = new FirebaseClient();
const result = await client.makeRequest('example.com', '/test', 'GET');
expect(result).toBe(textResponse);
});
it('should handle HTTP error with JSON error message', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce(
JSON.stringify({
error: {message: 'Not found'},
}),
),
});
const client = new FirebaseClient();
await expect(
client.makeRequest('example.com', '/test', 'GET'),
).rejects.toThrow('HTTP 404: Not found');
});
it('should handle HTTP error with plain text error message', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 500,
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
});
const client = new FirebaseClient();
await expect(
client.makeRequest('example.com', '/test', 'GET'),
).rejects.toThrow('HTTP 500: Internal Server Error');
});
});
describe('makeDatabaseRequest', () => {
it('should make database request with existing token', async () => {
const mockData = {test: 'data'};
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockData)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.makeDatabaseRequest('2023-12-01', 'GET');
expect(result).toEqual(mockData);
expect(global.fetch).toHaveBeenCalledWith(
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
);
});
it('should authenticate before making request if no token exists', async () => {
const authResponse = {idToken: 'new-token'};
const dataResponse = {test: 'data'};
global.fetch
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(dataResponse)),
});
const client = new FirebaseClient();
const result = await client.makeDatabaseRequest('2023-12-01', 'GET');
expect(result).toEqual(dataResponse);
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(client.idToken).toBe('new-token');
});
it('should make PUT request with data', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce('null'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const testData = [{library: 'test', status: 'success'}];
await client.makeDatabaseRequest('2023-12-01', 'PUT', testData);
expect(global.fetch).toHaveBeenCalledWith(
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(testData),
},
);
});
});
describe('storeResults', () => {
it('should store results successfully', async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce('null'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const results = [{library: 'test', status: 'success'}];
await client.storeResults('2023-12-01', results);
expect(global.fetch).toHaveBeenCalledWith(
'https://test-project-default-rtdb.firebaseio.com/nightly-results/2023-12-01.json?auth=existing-token',
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(results),
},
);
expect(console.log).toHaveBeenCalledWith(
'Successfully stored results for 2023-12-01',
);
});
});
describe('getResults', () => {
it('should retrieve results successfully', async () => {
const mockResults = [{library: 'test', status: 'success'}];
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const results = await client.getResults('2023-12-01');
expect(results).toEqual(mockResults);
});
it('should return null for 404 errors', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce('Not Found'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const results = await client.getResults('2023-12-01');
expect(results).toBeNull();
});
it('should throw error for non-404 HTTP errors', async () => {
global.fetch.mockResolvedValueOnce({
ok: false,
status: 500,
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
await expect(client.getResults('2023-12-01')).rejects.toThrow(
'HTTP 500: Internal Server Error',
);
});
});
describe('getLatestResults', () => {
it('should authenticate before making requests if no token exists', async () => {
const authResponse = {idToken: 'new-token'};
const mockResults = [{library: 'test', status: 'success'}];
global.fetch
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(authResponse)),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
const result = await client.getLatestResults('2023-12-15', 1);
expect(result).toEqual({
results: mockResults,
date: '2023-12-14',
});
expect(client.idToken).toBe('new-token');
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-14 (1 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-14 (1 days back)',
);
});
it('should find results from the previous day', async () => {
const mockResults = [{library: 'test', status: 'success'}];
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-14',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-14 (1 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-14 (1 days back)',
);
});
it('should find results from several days back', async () => {
const mockResults = [{library: 'test', status: 'success'}];
// Mock 404 responses for first 2 days, then success on 3rd day
global.fetch
.mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce('Not Found'),
})
.mockResolvedValueOnce({
ok: false,
status: 404,
text: jest.fn().mockResolvedValueOnce('Not Found'),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-12',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-14 (1 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-13 (2 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-12 (3 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-12 (3 days back)',
);
});
it('should skip empty results and continue searching', async () => {
const mockResults = [{library: 'test', status: 'success'}];
// Mock empty array for first day, then valid results on second day
global.fetch
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify([])),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-13',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-14 (1 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-13 (2 days back)...',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-13 (2 days back)',
);
});
it('should return null when no results found within maxDaysBack', async () => {
// Mock 404 responses for all days
global.fetch.mockResolvedValue({
ok: false,
status: 404,
text: jest.fn().mockResolvedValue('Not Found'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 3);
expect(result).toEqual({
results: null,
date: null,
});
expect(console.log).toHaveBeenCalledWith(
'No previous results found within the last 3 days',
);
expect(global.fetch).toHaveBeenCalledTimes(3);
});
it('should use default maxDaysBack of 7 when not specified', async () => {
// Mock 404 responses for all days
global.fetch.mockResolvedValue({
ok: false,
status: 404,
text: jest.fn().mockResolvedValue('Not Found'),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15');
expect(result).toEqual({
results: null,
date: null,
});
expect(console.log).toHaveBeenCalledWith(
'No previous results found within the last 7 days',
);
expect(global.fetch).toHaveBeenCalledTimes(7);
});
it('should handle non-404 errors and continue searching', async () => {
const mockResults = [{library: 'test', status: 'success'}];
// Mock 500 error for first day, then success on second day
global.fetch
.mockResolvedValueOnce({
ok: false,
status: 500,
text: jest.fn().mockResolvedValueOnce('Internal Server Error'),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-13',
});
expect(console.log).toHaveBeenCalledWith(
'No results found for 2023-12-14: HTTP 500: Internal Server Error',
);
expect(console.log).toHaveBeenCalledWith(
'Found results from 2023-12-13 (2 days back)',
);
});
it('should handle date boundaries correctly', async () => {
const mockResults = [{library: 'test', status: 'success'}];
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
// Test month boundary
const result = await client.getLatestResults('2023-12-01', 1);
expect(result).toEqual({
results: mockResults,
date: '2023-11-30',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-11-30 (1 days back)...',
);
});
it('should handle year boundary correctly', async () => {
const mockResults = [{library: 'test', status: 'success'}];
global.fetch.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
// Test year boundary
const result = await client.getLatestResults('2024-01-01', 1);
expect(result).toEqual({
results: mockResults,
date: '2023-12-31',
});
expect(console.log).toHaveBeenCalledWith(
'Checking for results on 2023-12-31 (1 days back)...',
);
});
it('should handle null results and continue searching', async () => {
const mockResults = [{library: 'test', status: 'success'}];
// Mock null for first day, then valid results on second day
global.fetch
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce('null'),
})
.mockResolvedValueOnce({
ok: true,
text: jest.fn().mockResolvedValueOnce(JSON.stringify(mockResults)),
});
const client = new FirebaseClient();
client.idToken = 'existing-token';
const result = await client.getLatestResults('2023-12-15', 7);
expect(result).toEqual({
results: mockResults,
date: '2023-12-13',
});
});
});
});
describe('compareResults', () => {
it('should handle null previous results', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const result = compareResults(currentResults, null);
expect(result).toEqual({
broken: [],
recovered: [],
newFailures: [{library: 'lib1', platform: 'iOS', status: 'failed'}],
});
});
it('should handle undefined previous results', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
];
const result = compareResults(currentResults, undefined);
expect(result).toEqual({
broken: [],
recovered: [],
newFailures: [{library: 'lib1', platform: 'iOS', status: 'failed'}],
});
});
it('should identify broken tests', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'success',
currentStatus: 'failed',
},
]);
expect(result.recovered).toEqual([]);
});
it('should identify recovered tests', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([]);
expect(result.recovered).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'failed',
currentStatus: 'success',
},
]);
});
it('should identify both broken and recovered tests', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
{library: 'lib3', platform: 'iOS', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
{library: 'lib2', platform: 'Android', status: 'failed'},
{library: 'lib3', platform: 'iOS', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'success',
currentStatus: 'failed',
},
]);
expect(result.recovered).toEqual([
{
library: 'lib2',
platform: 'Android',
previousStatus: 'failed',
currentStatus: 'success',
},
]);
});
it('should handle tests that are not in previous results', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'success',
currentStatus: 'failed',
},
]);
expect(result.recovered).toEqual([]);
});
it('should handle empty current results', () => {
const currentResults = [];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([]);
expect(result.recovered).toEqual([]);
});
it('should handle empty previous results', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'failed'},
];
const previousResults = [];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([]);
expect(result.recovered).toEqual([]);
// When previousResults is an empty array (not null/undefined),
// the function doesn't return newFailures property
expect(result.newFailures).toBeUndefined();
});
it('should handle different status values', () => {
const currentResults = [
{library: 'lib1', platform: 'iOS', status: 'timeout'},
{library: 'lib2', platform: 'Android', status: 'success'},
];
const previousResults = [
{library: 'lib1', platform: 'iOS', status: 'success'},
{library: 'lib2', platform: 'Android', status: 'error'},
];
const result = compareResults(currentResults, previousResults);
expect(result.broken).toEqual([
{
library: 'lib1',
platform: 'iOS',
previousStatus: 'success',
currentStatus: 'timeout',
},
]);
expect(result.recovered).toEqual([
{
library: 'lib2',
platform: 'Android',
previousStatus: 'error',
currentStatus: 'success',
},
]);
});
});
describe('getYesterdayDate', () => {
it("should return yesterday's date in YYYY-MM-DD format", () => {
const mockDate = new Date('2023-12-15T10:30:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getYesterdayDate();
expect(result).toBe('2023-12-14');
global.Date.mockRestore();
});
it('should handle month boundary correctly', () => {
const mockDate = new Date('2023-12-01T10:30:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getYesterdayDate();
expect(result).toBe('2023-11-30');
global.Date.mockRestore();
});
it('should handle year boundary correctly', () => {
const mockDate = new Date('2024-01-01T10:30:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getYesterdayDate();
expect(result).toBe('2023-12-31');
global.Date.mockRestore();
});
});
describe('getTodayDate', () => {
it("should return today's date in YYYY-MM-DD format", () => {
const mockDate = new Date('2023-12-15T10:30:00Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getTodayDate();
expect(result).toBe('2023-12-15');
global.Date.mockRestore();
});
it('should handle different times of day correctly', () => {
const mockDate = new Date('2023-12-15T23:59:59Z');
jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
const result = getTodayDate();
expect(result).toBe('2023-12-15');
global.Date.mockRestore();
});
});
@@ -11,8 +11,15 @@ const fs = require('fs');
const path = require('path');
const {
prepareFailurePayload,
prepareComparisonPayload,
sendMessageToDiscord,
} = require('./notifyDiscord');
const {
FirebaseClient,
compareResults,
getYesterdayDate,
getTodayDate,
} = require('./firebaseUtils');
function readOutcomes() {
const baseDir = '/tmp';
@@ -103,16 +110,62 @@ async function collectResults(discordWebHook) {
const outcomes = readOutcomes();
const failures = printFailures(outcomes);
// Send failure notification if there are current failures
if (failures.length > 0) {
if (discordWebHook) {
console.log('Sending to discord');
console.log('Sending current failures to Discord...');
await notifyDiscord(discordWebHook, failures);
} else {
console.log('Web hook not set');
console.log('Discord webhook not set');
}
process.exit(1);
}
console.log('✅ All tests passed!');
// Initialize Firebase client
const firebaseClient = new FirebaseClient();
const today = getTodayDate();
try {
// Store today's results in Firebase
console.log(`Storing results for ${today} in Firebase...`);
await firebaseClient.storeResults(today, outcomes);
// Get the most recent previous results for comparison
console.log(`Looking for most recent previous results before ${today}...`);
const {results: previousResults, date: previousDate} =
await firebaseClient.getLatestResults(today);
let broken = [];
let recovered = [];
if (previousResults) {
console.log(`Comparing with results from ${previousDate}`);
// Compare results and identify broken/recovered jobs
const comparison = compareResults(outcomes, previousResults);
broken = comparison.broken;
recovered = comparison.recovered;
console.log(
`Found ${broken.length} newly broken jobs and ${recovered.length} recovered jobs compared to ${previousDate}`,
);
} else {
console.log(
'No previous results found for comparison - this might be the first run or no recent data available',
);
}
// Send comparison message to Discord if there are changes
if (discordWebHook && (broken.length > 0 || recovered.length > 0)) {
console.log('Sending comparison results to Discord...');
const comparisonMessage = prepareComparisonPayload(broken, recovered);
await sendMessageToDiscord(discordWebHook, comparisonMessage);
}
console.log('✅ All tests passed!');
} catch (error) {
console.error('Error in collectResults:', error);
// If Firebase fails but there are no test failures, don't fail the workflow
console.log('⚠️ Firebase operations failed, but all tests passed');
}
}
module.exports = {
+261
View File
@@ -0,0 +1,261 @@
/**
* 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.
*
* @format
*/
// We connect to firebase using a plain HTTP request because we don't want to
// add yet another devDependency to the react-native monorepo.
class FirebaseClient {
constructor() {
this.email = process.env.FIREBASE_APP_EMAIL;
this.password = process.env.FIREBASE_APP_PASS;
this.apiKey = process.env.FIREBASE_APP_APIKEY;
this.projectId = process.env.FIREBASE_APP_PROJECTNAME;
this.databaseUrl = `${this.projectId}-default-rtdb.firebaseio.com`;
this.idToken = null;
}
async authenticate() {
if (!this.email || !this.password) {
throw new Error(
'Firebase credentials not found in environment variables',
);
}
const authData = {
email: this.email,
password: this.password,
returnSecureToken: true,
};
const response = await this.makeRequest(
'identitytoolkit.googleapis.com',
`/v1/accounts:signInWithPassword?key=${this.apiKey}`,
'POST',
authData,
);
this.idToken = response.idToken;
return;
}
/**
* Make a database request for a specific date
* @param {string} date - Date in YYYY-MM-DD format
* @param {string} method - HTTP method
* @param {*} data - Data to send (optional)
* @returns {Promise<*>} - Response data
*/
async makeDatabaseRequest(date, method, data = null) {
if (!this.idToken) {
await this.authenticate();
}
const path = `/nightly-results/${date}.json?auth=${this.idToken}`;
return this.makeRequest(this.databaseUrl, path, method, data);
}
/**
* Store test results for a specific date
* @param {string} date - Date in YYYY-MM-DD format
* @param {Array<Object>} results - Array of test results
* @returns {Promise<void>}
*/
async storeResults(date, results) {
await this.makeDatabaseRequest(date, 'PUT', results);
console.log(`Successfully stored results for ${date}`);
}
/**
* Retrieve test results for a specific date
* @param {string} date - Date in YYYY-MM-DD format
* @returns {Promise<Array<Object>|null>} - Array of test results or null if not found
*/
async getResults(date) {
try {
return await this.makeDatabaseRequest(date, 'GET');
} catch (error) {
if (error.message.includes('404')) {
return null; // No results found for this specific date.
}
throw error;
}
}
/**
* Find the most recent available job results before the given date
* @param {string} currentDate - Current date in YYYY-MM-DD format
* @param {number} maxDaysBack - Maximum number of days to look back (default: 7)
* @returns {Promise<{results: Array<Object>|null, date: string|null}>} - Most recent results and their date
*/
async getLatestResults(currentDate, maxDaysBack = 7) {
if (!this.idToken) {
await this.authenticate();
}
const currentDateObj = new Date(currentDate);
for (let daysBack = 1; daysBack <= maxDaysBack; daysBack++) {
const checkDate = new Date(currentDateObj);
checkDate.setDate(checkDate.getDate() - daysBack);
const checkDateStr = checkDate.toISOString().split('T')[0];
console.log(
`Checking for results on ${checkDateStr} (${daysBack} days back)...`,
);
try {
const results = await this.getResults(checkDateStr);
if (results && results.length > 0) {
console.log(
`Found results from ${checkDateStr} (${daysBack} days back)`,
);
return {results, date: checkDateStr};
}
} catch (error) {
console.log(`No results found for ${checkDateStr}: ${error.message}`);
continue;
}
}
console.log(
`No previous results found within the last ${maxDaysBack} days`,
);
return {results: null, date: null};
}
async makeRequest(hostname, path, method, data = null) {
const url = `https://${hostname}${path}`;
const options = {
method,
headers: {
'Content-Type': 'application/json',
},
};
if (data) {
options.body = JSON.stringify(data);
}
const response = await fetch(url, options);
const responseText = await response.text();
if (!response.ok) {
let errorMessage;
try {
const parsedError = JSON.parse(responseText);
errorMessage = parsedError.error?.message || responseText;
} catch {
errorMessage = responseText;
}
throw new Error(`HTTP ${response.status}: ${errorMessage}`);
}
try {
return JSON.parse(responseText);
} catch {
return responseText;
}
}
}
/**
* Compare current results with previous day's results
* @param {Array<Object>} currentResults - Today's test results
* @param {Array<Object>} previousResults - Yesterday's test results
* @returns {Object} - Object containing broken and recovered tests
*/
function compareResults(currentResults, previousResults) {
if (!previousResults) {
return {
broken: [],
recovered: [],
newFailures: currentResults.filter(result => result.status !== 'success'),
};
}
// Create maps for easier lookup
const currentMap = new Map();
const previousMap = new Map();
currentResults.forEach(result => {
const key = `${result.library}-${result.platform}`;
currentMap.set(key, result);
});
previousResults.forEach(result => {
const key = `${result.library}-${result.platform}`;
previousMap.set(key, result);
});
const broken = [];
const recovered = [];
// Check for broken tests (was success, now failed)
for (const [key, currentResult] of currentMap) {
const previousResult = previousMap.get(key);
if (previousResult) {
if (
previousResult.status === 'success' &&
currentResult.status !== 'success'
) {
broken.push({
library: currentResult.library,
platform: currentResult.platform,
previousStatus: previousResult.status,
currentStatus: currentResult.status,
});
}
}
}
// Check for recovered tests (was failed, now success)
for (const [key, currentResult] of currentMap) {
const previousResult = previousMap.get(key);
if (previousResult) {
if (
previousResult.status !== 'success' &&
currentResult.status === 'success'
) {
recovered.push({
library: currentResult.library,
platform: currentResult.platform,
previousStatus: previousResult.status,
currentStatus: currentResult.status,
});
}
}
}
return {broken, recovered};
}
/**
* Get yesterday's date in YYYY-MM-DD format
* @returns {string} - Yesterday's date
*/
function getYesterdayDate() {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
return yesterday.toISOString().split('T')[0];
}
/**
* Get today's date in YYYY-MM-DD format
* @returns {string} - Today's date
*/
function getTodayDate() {
const today = new Date();
return today.toISOString().split('T')[0];
}
module.exports = {
FirebaseClient,
compareResults,
getYesterdayDate,
getTodayDate,
};
+59 -13
View File
@@ -41,20 +41,12 @@ async function sendMessageToDiscord(webHook, message) {
}
/**
* Prepares a formatted Discord message payload from a list of failures.
* @param {Array<Object>} failures - List of failures to format
* @returns {Object} - The formatted Discord message payload
* Sorts jobs by platform first, then by library name.
* @param {Array<Object>} jobs - Array of jobs with platform and library properties
* @returns {Array<Object>} - Sorted array of jobs
*/
function prepareFailurePayload(failures) {
if (!failures || failures.length === 0) {
return {
content:
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
};
}
// Sort failures by platform and then by library name
const sortedFailures = [...failures].sort((a, b) => {
function sortResultsByPlatformAndLibrary(jobs) {
return [...jobs].sort((a, b) => {
// First sort by platform
const platformA = a.platform || 'Unknown';
const platformB = b.platform || 'Unknown';
@@ -68,6 +60,23 @@ function prepareFailurePayload(failures) {
const libraryB = b.library || 'Unknown';
return libraryA.localeCompare(libraryB);
});
}
/**
* Prepares a formatted Discord message payload from a list of failures.
* @param {Array<Object>} failures - List of failures to format
* @returns {Object} - The formatted Discord message payload
*/
function prepareFailurePayload(failures) {
if (!failures || failures.length === 0) {
return {
content:
'⚠️ **React Native Nightly Integration Failures** ⚠️\n\nNo failures to report.',
};
}
// Sort failures by platform and then by library name
const sortedFailures = sortResultsByPlatformAndLibrary(failures);
// Format the failures into a message
const formattedFailures = sortedFailures
@@ -83,8 +92,45 @@ function prepareFailurePayload(failures) {
};
}
/**
* Prepares a formatted Discord message payload for broken and recovered nightly jobs.
* @param {Array<Object>} broken - List of newly broken jobs
* @param {Array<Object>} recovered - List of recovered jobs
* @returns {Object} - The formatted Discord message payload
*/
function prepareComparisonPayload(broken, recovered) {
let content = '📊 **React Native Nightly Integration Status Update** 📊\n\n';
if (broken.length === 0 && recovered.length === 0) {
content +=
'No changes from yesterday - all nightly jobs maintained their previous status.';
} else {
if (broken.length > 0) {
content += '🔴 **Newly Broken Jobs:**\n';
const sortedBroken = sortResultsByPlatformAndLibrary(broken);
sortedBroken.forEach(job => {
content += `❌ [${job.platform}] ${job.library} (was ${job.previousStatus}, now ${job.currentStatus})\n`;
});
content += '\n';
}
if (recovered.length > 0) {
content += '🟢 **Recovered Jobs:**\n';
const sortedRecovered = sortResultsByPlatformAndLibrary(recovered);
sortedRecovered.forEach(job => {
content += `✅ [${job.platform}] ${job.library} (was ${job.previousStatus}, now ${job.currentStatus})\n`;
});
}
}
return {content};
}
// Export the functions using CommonJS syntax
module.exports = {
prepareFailurePayload,
prepareComparisonPayload,
sendMessageToDiscord,
};
+4
View File
@@ -32,3 +32,7 @@ jobs:
needs: check-nightly
secrets:
discord_webhook_url: ${{ secrets.NIGHTLY_DISCORD_WEBHOOK }}
firebase_app_email: ${{ secrets.FIREBASE_APP_EMAIL }}
firebase_app_pass: ${{ secrets.FIREBASE_APP_PASS }}
firebase_app_projectname: ${{ secrets.FIREBASE_APP_PROJECTNAME }}
firebase_app_apikey: ${{ secrets.FIREBASE_APP_APIKEY }}
@@ -5,6 +5,14 @@ on:
secrets:
discord_webhook_url:
required: true
firebase_app_email:
required: true
firebase_app_pass:
required: true
firebase_app_apikey:
required: true
firebase_app_projectname:
required: true
# We use the matrix.library entry to specify the dependency we want to use
@@ -94,6 +102,11 @@ jobs:
path: /tmp
- name: Collect failures
uses: actions/github-script@v6
env:
FIREBASE_APP_EMAIL: ${{ secrets.firebase_app_email }}
FIREBASE_APP_PASS: ${{ secrets.firebase_app_pass }}
FIREBASE_APP_APIKEY: ${{ secrets.firebase_app_apikey }}
FIREBASE_APP_PROJECTNAME: ${{ secrets.firebase_app_projectname }}
with:
script: |
const {collectResults} = require('./.github/workflow-scripts/collectNightlyOutcomes.js');
+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)
+3 -3
View File
@@ -24,12 +24,12 @@ try {
} catch (e) {
// Fallback to lib when source doesn't exit (e.g. when installed as a dev dependency)
FlowParser =
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
require('@react-native/codegen/lib/parsers/flow/parser').FlowParser;
TypeScriptParser =
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
require('@react-native/codegen/lib/parsers/typescript/parser').TypeScriptParser;
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
}
@@ -80,7 +80,7 @@ try {
'@react-native-community/cli-server-api',
{paths: [communityCliPath]},
);
// $FlowIgnore[unsupported-syntax] dynamic import
// $FlowFixMe[unsupported-syntax] dynamic import
communityMiddlewareFallback.createDevServerMiddleware = require(
communityCliServerApiPath,
).createDevServerMiddleware as CreateDevServerMiddleware;
@@ -92,14 +92,14 @@ async function runServer(
console.info(`Starting dev server on ${devServerUrl}\n`);
if (args.assetPlugins) {
// $FlowIgnore[cannot-write] Assigning to readonly property
// $FlowFixMe[cannot-write] Assigning to readonly property
metroConfig.transformer.assetPlugins = args.assetPlugins.map(plugin =>
require.resolve(plugin),
);
}
// TODO(T214991636): Remove legacy Metro log forwarding
if (!args.clientLogs) {
// $FlowIgnore[cannot-write] Assigning to readonly property
// $FlowFixMe[cannot-write] Assigning to readonly property
metroConfig.server.forwardClientLogs = false;
}
@@ -146,7 +146,7 @@ async function runServer(
}
},
};
// $FlowIgnore[cannot-write] Assigning to readonly property
// $FlowFixMe[cannot-write] Assigning to readonly property
metroConfig.reporter = reporter;
await Metro.runServer(metroConfig, {
@@ -175,7 +175,7 @@ function getReporterImpl(
try {
// First we let require resolve it, so we can require packages in node_modules
// as expected. eg: require('my-package/reporter');
// $FlowIgnore[unsupported-syntax]
// $FlowFixMe[unsupported-syntax]
return require(customLogReporterPath);
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
@@ -183,7 +183,7 @@ function getReporterImpl(
}
// If that doesn't work, then we next try relative to the cwd, eg:
// require('./reporter');
// $FlowIgnore[unsupported-syntax]
// $FlowFixMe[unsupported-syntax]
return require(path.resolve(customLogReporterPath));
}
}
+1 -1
View File
@@ -71,7 +71,7 @@ const FIRST = 1,
FOURTH = 4;
function getNodePackagePath(packageName: string): string {
// $FlowIgnore[prop-missing] type definition is incomplete
// $FlowFixMe[prop-missing] type definition is incomplete
return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]});
}
+1 -1
View File
@@ -75,7 +75,7 @@ const FIRST = 1,
FIFTH = 5;
function getNodePackagePath(packageName: string): string {
// $FlowIgnore[prop-missing] type definition is incomplete
// $FlowFixMe[prop-missing] type definition is incomplete
return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]});
}
+2 -2
View File
@@ -1,5 +1,5 @@
@generated SignedSource<<46a90cb884e816d8836dca52c2e13e50>>
Git revision: 7dcbddd636137a9604d69a99cc69221216cd4be6
@generated SignedSource<<9252db36d4b1db907a38c08935ceeb38>>
Git revision: 921566790e9e16d0ecace6e49b3cfaace205958c
Built with --nohooks: false
Is local checkout: false
Remote URL: https://github.com/facebook/react-native-devtools-frontend
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ contextBridge.executeInMainWorld({
let didDecorateInspectorFrontendHostInstance = false;
// reactNativeDecorateInspectorFrontendHostInstance was introduced in
// https://github.com/facebook/react-native-devtools-frontend/pull/168
// $FlowIgnore[prop-missing]
// $FlowFixMe[prop-missing]
globalThis.reactNativeDecorateInspectorFrontendHostInstance = (
InspectorFrontendHostInstance: $FlowFixMe,
) => {
+2 -2
View File
@@ -18,9 +18,9 @@ declare module.exports: typeof Node;
// Because Electron doesn't support package.json `exports`, we need to
// switch at runtime.
if ('electron' in process.versions) {
// $FlowIgnore[invalid-export]
// $FlowFixMe[invalid-export]
module.exports = require('./electron');
} else {
// $FlowIgnore[invalid-export]
// $FlowFixMe[invalid-export]
module.exports = require('./node');
}
@@ -70,7 +70,7 @@ export class DebuggerAgent {
this.#ws = null;
}
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get socket(): WebSocket {
return nullthrows(this.#ws);
}
@@ -103,11 +103,11 @@ export class DebuggerMock extends DebuggerAgent {
originalHandleCallsArray === this.handle.mock.calls
? this.handle.mock.calls.slice(originalHandleCallCount)
: this.handle.mock.calls;
// $FlowIgnore[incompatible-use]
// $FlowIgnore[prop-missing]
// $FlowFixMe[incompatible-use]
// $FlowFixMe[prop-missing]
const [response] = newHandleCalls.find(args => args[0].id === message.id);
// $FlowIgnore[incompatible-return]
// $FlowIgnore[incompatible-indexer]
// $FlowFixMe[incompatible-return]
// $FlowFixMe[incompatible-indexer]
return response;
}
}
@@ -90,7 +90,7 @@ export class DeviceAgent {
});
}
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get socket(): WebSocket {
return nullthrows(this.#ws);
}
@@ -64,9 +64,9 @@ export async function sendFromTargetToDebugger<Message: CdpMessageFromTarget>(
originalHandleCallsArray === debugger_.handle.mock.calls
? debugger_.handle.mock.calls.slice(originalHandleCallCount)
: debugger_.handle.mock.calls;
// $FlowIgnore[incompatible-type]
// $FlowFixMe[incompatible-type]
const [receivedMessage]: [Message] = newHandleCalls.find(
// $FlowIgnore[incompatible-call]
// $FlowFixMe[incompatible-call]
(call: [Message]) => call[0].method === message.method,
);
return receivedMessage;
@@ -97,13 +97,13 @@ export async function sendFromDebuggerToTarget<Message: CdpMessageToTarget>(
originalEventCallsArray === device.wrappedEventParsed.mock.calls
? device.wrappedEventParsed.mock.calls.slice(originalEventCallCount)
: device.wrappedEventParsed.mock.calls;
// $FlowIgnore[incompatible-use]
// $FlowFixMe[incompatible-use]
const [receivedMessage] = newEventCalls.find(
// $FlowIgnore[prop-missing]
// $FlowIgnore[incompatible-use]
// $FlowFixMe[prop-missing]
// $FlowFixMe[incompatible-use]
call => call[0].wrappedEvent.id === message.id,
);
// $FlowIgnore[incompatible-return]
// $FlowFixMe[incompatible-return]
return receivedMessage.wrappedEvent;
}
@@ -141,7 +141,7 @@ export async function createAndConnectTarget(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -60,7 +60,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -119,7 +119,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -187,7 +187,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -288,7 +288,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -338,7 +338,7 @@ describe.each(['HTTP', 'HTTPS'])(
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toHaveLength(1);
});
@@ -108,7 +108,7 @@ describe('inspector proxy device message middleware', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -314,7 +314,7 @@ describe('inspector-proxy device socket handoff', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toEqual(
expect.arrayContaining(
@@ -55,7 +55,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -113,7 +113,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toContainEqual(
expect.objectContaining({
@@ -169,7 +169,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -222,7 +222,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toContainEqual(
expect.objectContaining({
@@ -273,7 +273,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -423,7 +423,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList.length).toBeGreaterThan(0);
});
@@ -459,7 +459,7 @@ describe('inspector proxy React Native reloads', () => {
await until(async () => {
pageList = (await fetchJson(
`${serverRef.serverBaseUrl}/json`,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
): any);
expect(pageList).toContainEqual(
expect.objectContaining({
@@ -10,7 +10,7 @@
export function withAbortSignalForEachTest(): $ReadOnly<{signal: AbortSignal}> {
const ref: {signal: AbortSignal} = {
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get signal() {
throw new Error(
'The return value of withAbortSignalForEachTest is lazily initialized and can only be accessed in tests.',
@@ -39,19 +39,19 @@ export function withServerForEachTest(options: CreateServerOptions): $ReadOnly<{
app: ConnectApp,
port: number,
} = {
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get serverBaseUrl() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get serverBaseWsUrl() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get app() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
// $FlowIgnore[unsafe-getters-setters]
// $FlowFixMe[unsafe-getters-setters]
get port() {
throw new Error(EAGER_ACCESS_ERROR_MESSAGE);
},
@@ -40,7 +40,7 @@ function makeRequest(
host: ?string,
encrypted: boolean,
): http$IncomingMessage<> | http$IncomingMessage<tls$TLSSocket> {
// $FlowIgnore[incompatible-return] Partial mock of request
// $FlowFixMe[incompatible-return] Partial mock of request
return {
socket: encrypted ? {encrypted: true} : {},
headers: host != null ? {host} : {},
@@ -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",
@@ -487,7 +487,7 @@ describe('buildSchemaFromConfigType', () => {
describe('when buildModuleSchema returns null', () => {
it('throws an error', () => {
// $FlowIgnore[incompatible-call] - This is to test an invariant
// $FlowFixMe[incompatible-call] - This is to test an invariant
buildModuleSchemaMock.mockReturnValueOnce(null);
expect(() =>
@@ -10,9 +10,9 @@
'use strict';
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
const flowSnaps = require('../../../../src/parsers/flow/components/__tests__/__snapshots__/component-parser-test.js.snap');
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
const tsSnaps = require('../../../../src/parsers/typescript/components/__tests__/__snapshots__/typescript-component-parser-test.js.snap');
const flowFixtures = require('../../flow/components/__test_fixtures__/fixtures.js');
const tsFixtures = require('../../typescript/components/__test_fixtures__/fixtures.js');
@@ -10,9 +10,9 @@
'use strict';
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
const flowSnaps = require('../../../../src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap');
// $FlowIgnore[cannot-resolve-module]
// $FlowFixMe[cannot-resolve-module]
const tsSnaps = require('../../../../src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap');
const flowFixtures = require('../../flow/modules/__test_fixtures__/fixtures.js');
const tsFixtures = require('../../typescript/modules/__test_fixtures__/fixtures.js');
@@ -48,6 +48,7 @@ const SUPPORTED_STYLES: {[string]: true} = {
borderStartStartRadius: true,
elevation: true,
opacity: true,
filter: true,
transform: true,
zIndex: true,
/* ios styles */
@@ -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();
});
@@ -21,11 +21,11 @@ function mockQueueMicrotask() {
let queueMicrotask;
beforeEach(() => {
queueMicrotask = global.queueMicrotask;
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.queueMicrotask = process.nextTick;
});
afterEach(() => {
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.queueMicrotask = queueMicrotask;
});
}
@@ -60,12 +60,12 @@ function processColor(
}
if (isRgbaValue(color)) {
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
return (color: RgbaValue);
}
let normalizedColor: ?ProcessedColorValue = normalizeColor(
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
(color: ColorValue),
);
if (normalizedColor === undefined || normalizedColor === null) {
@@ -125,7 +125,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
let value: RgbaValue | RgbaAnimatedValue | ColorValue =
valueIn ?? defaultColor;
if (isRgbaAnimatedValue(value)) {
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
const rgbaAnimatedValue: RgbaAnimatedValue = (value: RgbaAnimatedValue);
this.r = rgbaAnimatedValue.r;
this.g = rgbaAnimatedValue.g;
@@ -133,14 +133,14 @@ export default class AnimatedColor extends AnimatedWithChildren {
this.a = rgbaAnimatedValue.a;
} else {
const processedColor: RgbaValue | NativeColorValue =
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
processColor((value: ColorValue | RgbaValue)) ?? defaultColor;
let initColor: RgbaValue = defaultColor;
if (isRgbaValue(processedColor)) {
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
initColor = (processedColor: RgbaValue);
} else {
// $FlowIgnore[incompatible-cast] - Type is verified above
// $FlowFixMe[incompatible-cast] - Type is verified above
this.nativeColor = (processedColor: NativeColorValue);
}
@@ -170,7 +170,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
processColor(value) ?? defaultColor;
this._withSuspendedCallbacks(() => {
if (isRgbaValue(processedColor)) {
// $FlowIgnore[incompatible-type] - Type is verified above
// $FlowFixMe[incompatible-type] - Type is verified above
const rgbaValue: RgbaValue = processedColor;
this.r.setValue(rgbaValue.r);
this.g.setValue(rgbaValue.g);
@@ -181,7 +181,7 @@ export default class AnimatedColor extends AnimatedWithChildren {
shouldUpdateNodeConfig = true;
}
} else {
// $FlowIgnore[incompatible-type] - Type is verified above
// $FlowFixMe[incompatible-type] - Type is verified above
const nativeColor: NativeColorValue = processedColor;
if (this.nativeColor !== nativeColor) {
this.nativeColor = nativeColor;
@@ -224,7 +224,7 @@ function createStringInterpolation(
outputRange.every(output =>
output.components.every(
(component, i) =>
// $FlowIgnoreMe[invalid-compare]
// $FlowFixMe[invalid-compare]
typeof component === 'number' || component === firstOutput[i],
),
),
@@ -235,9 +235,9 @@ function createStringInterpolation(
const numericComponents: $ReadOnlyArray<$ReadOnlyArray<number>> =
outputRange.map(output =>
isColor
? // $FlowIgnoreMe[incompatible-call]
? // $FlowFixMe[incompatible-type]
output.components
: // $FlowIgnoreMe[incompatible-call]
: // $FlowFixMe[incompatible-call]
output.components.filter(c => typeof c === 'number'),
);
const interpolations = numericComponents[0].map((_, i) =>
@@ -393,7 +393,7 @@ export default class AnimatedInterpolation<
let outputRange = this._config.outputRange;
let outputType = null;
if (typeof outputRange[0] === 'string') {
// $FlowIgnoreMe[incompatible-cast]
// $FlowFixMe[incompatible-cast]
outputRange = ((outputRange: $ReadOnlyArray<string>).map(value => {
const processedColor = processColor(value);
if (typeof processedColor === 'number') {
@@ -325,8 +325,8 @@ export default class AnimatedProps extends AnimatedNode {
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
// this shim when they do.
// $FlowIgnore[method-unbinding]
// $FlowFixMe[method-unbinding]
const _hasOwnProp = Object.prototype.hasOwnProperty;
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
// $FlowIgnore[method-unbinding]
// $FlowFixMe[method-unbinding]
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
@@ -123,7 +123,7 @@ export default class AnimatedStyle extends AnimatedWithChildren {
this._style = style;
if ((Platform.OS as string) === 'web') {
// $FlowIgnore[cannot-write] - Intentional shadowing.
// $FlowFixMe[cannot-write] - Intentional shadowing.
this.__getValueForStyle = resultStyle => [
originalStyleForWeb,
resultStyle,
@@ -249,8 +249,8 @@ export default class AnimatedStyle extends AnimatedWithChildren {
// Supported versions of JSC do not implement the newer Object.hasOwn. Remove
// this shim when they do.
// $FlowIgnore[method-unbinding]
// $FlowFixMe[method-unbinding]
const _hasOwnProp = Object.prototype.hasOwnProperty;
const hasOwn: (obj: $ReadOnly<{...}>, prop: string) => boolean =
// $FlowIgnore[method-unbinding]
// $FlowFixMe[method-unbinding]
Object.hasOwn ?? ((obj, prop) => _hasOwnProp.call(obj, prop));
@@ -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,
@@ -477,7 +477,7 @@ function runExceptionsManagerTests() {
expect(nativeReportException).not.toBeCalled();
expect(logBoxAddConsoleLog).toBeCalledTimes(1);
expect(logBoxAddConsoleLog.mock.calls[0][0]).toBe('error');
// $FlowIgnore[incompatible-call]
// $FlowFixMe[incompatible-call]
expect(logBoxAddConsoleLog.mock.calls[0][1]).toBe(...args);
} else {
expect(logBoxAddException).not.toBeCalled();
@@ -554,7 +554,7 @@ function runExceptionsManagerTests() {
const object = {
toString: () => 'Warning: Some error may have happened',
};
// $FlowIgnore[prop-missing]
// $FlowFixMe[prop-missing]
object.cycle = object;
const args = [object];
@@ -27,12 +27,12 @@ function _setDevelopmentModeForTests(dev: mixed) {
beforeAll(() => {
originalDev = global.__DEV__;
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = dev;
});
afterAll(() => {
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = originalDev;
});
}
@@ -12,14 +12,14 @@ import {getUrlCacheBreaker, setUrlCacheBreaker} from '../AssetUtils';
describe('AssetUtils', () => {
afterEach(() => {
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = true;
jest.clearAllMocks();
});
it('should return empty string and warn once if no cacheBreaker set (DEV)', () => {
const mockWarn = jest.spyOn(console, 'warn').mockReturnValue(undefined);
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = true;
expect(getUrlCacheBreaker()).toEqual('');
expect(getUrlCacheBreaker()).toEqual('');
@@ -28,7 +28,7 @@ describe('AssetUtils', () => {
it('should return empty string if no cacheBreaker set in prod', () => {
const mockWarn = jest.spyOn(console, 'warn');
// $FlowIgnore[cannot-write]
// $FlowFixMe[cannot-write]
global.__DEV__ = false;
expect(getUrlCacheBreaker()).toEqual('');
expect(mockWarn).not.toHaveBeenCalled();
@@ -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');
});
});
});
@@ -164,7 +164,7 @@ const InteractionManagerStub = {
*/
addListener(
eventType: string,
// $FlowIgnore[unclear-type]
// $FlowFixMe[unclear-type]
listener: (...args: any) => mixed,
context: mixed,
): EventSubscription {
@@ -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';
@@ -18,9 +18,9 @@ export default function splitLayoutProps(props: ?____ViewStyle_Internal): {
let inner: ?____ViewStyle_Internal = null;
if (props != null) {
// $FlowIgnore[incompatible-exact] Will contain a subset of keys from `props`.
// $FlowFixMe[incompatible-exact] Will contain a subset of keys from `props`.
outer = {};
// $FlowIgnore[incompatible-exact] Will contain a subset of keys from `props`.
// $FlowFixMe[incompatible-exact] Will contain a subset of keys from `props`.
inner = {};
for (const prop of Object.keys(props)) {
+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
@@ -43,7 +43,7 @@ function TestComponent(
}
function id(instance: HostInstance | null): string | null {
// $FlowIgnore[prop-missing] - Intentional.
// $FlowFixMe[prop-missing] - Intentional.
return instance?.props?.id ?? null;
}
@@ -108,7 +108,7 @@ test('accepts a ref object', () => {
const ledger: Array<{[string]: string | null}> = [];
const ref = {
// $FlowIgnore[unsafe-getters-setters] - Intentional.
// $FlowFixMe[unsafe-getters-setters] - Intentional.
set current(current: HostInstance | null) {
ledger.push({ref: id(current)});
},
@@ -140,7 +140,7 @@ test('invokes refs in order', () => {
ledger.push({refA: id(current)});
};
const refB = {
// $FlowIgnore[unsafe-getters-setters] - Intentional.
// $FlowFixMe[unsafe-getters-setters] - Intentional.
set current(current: HostInstance | null) {
ledger.push({refB: id(current)});
},
@@ -149,7 +149,7 @@ test('invokes refs in order', () => {
ledger.push({refC: id(current)});
};
const refD = {
// $FlowIgnore[unsafe-getters-setters] - Intentional.
// $FlowFixMe[unsafe-getters-setters] - Intentional.
set current(current: HostInstance | null) {
ledger.push({refD: id(current)});
},
@@ -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())
@@ -7,6 +7,8 @@
package com.facebook.react.uimanager;
import static com.facebook.infer.annotation.Assertions.assertNotNull;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
@@ -25,6 +27,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.Accessibilit
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat;
import androidx.core.view.accessibility.AccessibilityNodeProviderCompat;
import androidx.customview.widget.ExploreByTouchHelper;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.R;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Dynamic;
@@ -49,6 +52,7 @@ import java.util.Map;
* Utility class that handles the addition of a "role" for accessibility to either a View or
* AccessibilityNodeInfo.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
public static final String TOP_ACCESSIBILITY_ACTION_EVENT = "topAccessibilityAction";
@@ -73,12 +77,15 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
@Nullable View mAccessibilityLabelledBy;
static {
sActionIdMap.put("activate", AccessibilityActionCompat.ACTION_CLICK.getId());
sActionIdMap.put("longpress", AccessibilityActionCompat.ACTION_LONG_CLICK.getId());
sActionIdMap.put("increment", AccessibilityActionCompat.ACTION_SCROLL_FORWARD.getId());
sActionIdMap.put("decrement", AccessibilityActionCompat.ACTION_SCROLL_BACKWARD.getId());
sActionIdMap.put("expand", AccessibilityActionCompat.ACTION_EXPAND.getId());
sActionIdMap.put("collapse", AccessibilityActionCompat.ACTION_COLLAPSE.getId());
sActionIdMap.put("activate", assertNotNull(AccessibilityActionCompat.ACTION_CLICK).getId());
sActionIdMap.put(
"longpress", assertNotNull(AccessibilityActionCompat.ACTION_LONG_CLICK).getId());
sActionIdMap.put(
"increment", assertNotNull(AccessibilityActionCompat.ACTION_SCROLL_FORWARD).getId());
sActionIdMap.put(
"decrement", assertNotNull(AccessibilityActionCompat.ACTION_SCROLL_BACKWARD).getId());
sActionIdMap.put("expand", assertNotNull(AccessibilityActionCompat.ACTION_EXPAND).getId());
sActionIdMap.put("collapse", assertNotNull(AccessibilityActionCompat.ACTION_COLLAPSE).getId());
}
public ReactAccessibilityDelegate(
@@ -91,7 +98,9 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
@Override
public void handleMessage(Message msg) {
View host = (View) msg.obj;
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
if (host != null) {
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
}
}
};
@@ -190,13 +199,14 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
if (accessibilityActions != null) {
for (int i = 0; i < accessibilityActions.size(); i++) {
final ReadableMap action = accessibilityActions.getMap(i);
if (!action.hasKey("name")) {
@Nullable final ReadableMap action = accessibilityActions.getMap(i);
if (action == null || !action.hasKey("name")) {
throw new IllegalArgumentException("Unknown accessibility action.");
}
String actionName = action.getString("name");
String actionLabel = action.hasKey("label") ? action.getString("label") : null;
// AccessibilityActionCompat actionLabel must be non-null
String actionLabel = action.hasKey("label") ? assertNotNull(action.getString("label")) : "";
int actionId;
if (sActionIdMap.containsKey(actionName)) {
@@ -336,8 +346,9 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
(AccessibilityRole) host.getTag(R.id.accessibility_role);
final ReadableMap accessibilityValue = (ReadableMap) host.getTag(R.id.accessibility_value);
if (accessibilityRole == AccessibilityRole.ADJUSTABLE
&& (action == AccessibilityActionCompat.ACTION_SCROLL_FORWARD.getId()
|| action == AccessibilityActionCompat.ACTION_SCROLL_BACKWARD.getId())) {
&& (action == assertNotNull(AccessibilityActionCompat.ACTION_SCROLL_FORWARD).getId()
|| action
== assertNotNull(AccessibilityActionCompat.ACTION_SCROLL_BACKWARD).getId())) {
if (accessibilityValue != null && !accessibilityValue.hasKey("text")) {
scheduleAccessibilityEventSender(host);
}
@@ -631,7 +642,8 @@ public class ReactAccessibilityDelegate extends ExploreByTouchHelper {
return true;
}
final List actionList = node.getActionList();
final List<AccessibilityNodeInfoCompat.AccessibilityActionCompat> actionList =
assertNotNull(node.getActionList());
return actionList.contains(AccessibilityNodeInfoCompat.ACTION_CLICK)
|| actionList.contains(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK)
|| actionList.contains(AccessibilityNodeInfoCompat.ACTION_FOCUS);
@@ -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<

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