Compare commits

..
Author SHA1 Message Date
Simek 04ae39e603 fix C++ in-code docs comments to silence warnings 2025-09-18 10:50:03 +02:00
Alex Hunt fae11d58bd Replace trivial uses of string_view (#53775)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53775

A refactor to align our C++ code style within `jsinpector-modern`.

We prefer `std::string` and `const std::string&` everywhere (see [C++ Core Guidelines F.15](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-conventional)), except for when we are handing potentially very large strings — in which case we must use `string_view` all the way down.

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D82446939

fbshipit-source-id: 4b1c43068d1339f4b4a4c7eb06b392d0b0f624e1
2025-09-16 03:17:57 -07:00
Alex Hunt 6eb456bc4e Fix data race condition in NetworkHandler (#53788)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53788

Fix thread safety issue due to member variable  in `NetworkHandler` singleton without mutex.

Also intend to un-singleton this class in the imminent future.

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D82460574

fbshipit-source-id: c0c614f8f1bb5ffba22872ae5717fbd2ca01f2e9
2025-09-16 03:03:06 -07:00
Nicola Corti 27630d3106 Remove unused --otp property from release infrastructure (#53779)
Summary:
The `--otp` flag is completely unused now, therefore it can be removed.
We don't pass the `NPM_CONFIG_OTP` env variable either as this was done back in the days of CircleCI so I'm cleaning this up.

## Changelog:

[INTERNAL] - Remove unused --otp property from release infrastructure

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

Test Plan: CI

Reviewed By: lunaleaps

Differential Revision: D82453539

Pulled By: cortinico

fbshipit-source-id: 84a6b82a037c754165c21e17976dc534d9a7ba4c
2025-09-16 02:52:58 -07:00
Pieter De Baets 96c33a8b0b Update ws to 7.5.10 (#53781)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53781

Fixes deprecation warning printed when starting various `js1` tools

Changelog: [Internal]

Reviewed By: robhogan

Differential Revision: D82449982

fbshipit-source-id: 852b5a6980badef146b4e194434f2f9c6dd9a1d5
2025-09-16 02:51:06 -07:00
Zeya Peng c9dcd64ed5 Clean up batchingControlledByJS in NativeAnimated kotlin (#53793)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53793

## Changelog:

[Android] [Deprecated] - Clean up batchingControlledByJS in NativeAnimated kotlin

`start/finishOperationBatch` will no longer be called on kotlin NativeAnimated since D78005971 (https://github.com/facebook/react-native/pull/52521), so `batchingControlledByJS` will remain false. Cleaning up some logic and TODO comments there

this feature was added in D23010844

Reviewed By: christophpurrer

Differential Revision: D82461457

fbshipit-source-id: a1208720b83e614c2a5f994ec1a5005189c5f197
2025-09-16 00:44:23 -07:00
generatedunixname537391475639613 15daa27871 xplat/js/react-native-github/packages/react-native/ReactCommon/react/renderer/components/view/LayoutConformanceShadowNode.h (#53772)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53772

Reviewed By: javache

Differential Revision: D82292073

fbshipit-source-id: 3a3f8e54b4217c8ce9432520e04c4f232b538687
2025-09-15 16:11:00 -07:00
nishan (o^▽^o) b365e26593 feat(iOS) - blur filter using SwiftUI (#52495)
Summary:
As per the discussion on the previous [PR thread](https://github.com/facebook/react-native/pull/52028#issuecomment-2979481948), this PR uses `SwiftUI` to implement blur filter on iOS.

## Approach:

To implement blur filter on iOS, we have two options:

1. Use `CAFilter` (private API, app can get rejected/API can break). Earlier [PR](https://github.com/facebook/react-native/pull/52028) was using that approach. Thanks to Nick for suggesting SwiftUI API.

2. Use `SwiftUI`. Wrap the view in a SwiftUI view and apply [blur](https://developer.apple.com/documentation/swiftui/view/blur(radius:opaque:)). This PR builds on top of that approach. This also enables a way to add `SwiftUI` only features like this one. Additional filters (grayscale, saturate, contrast, hueRotate) can also be added.

There are a few ways we can implement the SwiftUI approach:

1. Create a new `RCTSwiftUIComponentView` -> do style flattening in View -> check if `filter` is present and conditionally render the `RCTSwiftUIComponentView` on iOS, wrap children with a `SwiftUI` view. Tradeoff with this approach is that it adds `StyleSheet.flatten` overhead on JS side.
2. Add a `SwiftUI` container view inside of `RCTViewComponentView`. Tradeoff with this approach is that it complicates `RCTViewComponentView` a bit.

I decided to go with **2** to avoid the flattening tradeoff and try to minimize complicating `RCTViewComponentView`. it only adds the wrapper if it's required and removes if not (in this PR, blur filter style will add the wrapper, it will get removed if blur filter styling gets removed). It uses the existing container view pattern.

## Changelog:
[IOS][ADDED] - Filter blur

<!-- 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

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

Test Plan:
Test filter blur example on iOS. SwiftUI view should be added to the hierarchy.

<img src="https://github.com/user-attachments/assets/742539f4-a96d-45f4-94ba-5eb588d0ad5a" width="300px" />

## Aside:

- This PR also adds a new swift podspec. Creating a new podspec felt the right approach as adding swift in existing ones were adding some complexity. But open for changes here. Also, need some eyes on the podspec configs. cc - chrfalch  🙏   this might also affect the SPM migration.
- Unrelated: Existing brightness filter has some inconsistency compared to android and web, it uses [self.layer.opacity](https://github.com/facebook/react-native/blob/6892dde36373bbef2d0afe535ae818b1a7164f08/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L1008) so transparent background color do not blend well unless the view has an opacity. One solution would be to calculate true background color by using brightness or else use the `SwiftUI`'s [brightness](https://developer.apple.com/documentation/swiftui/view/brightness(_:)), which would be cleaner imo (tested and it works).

Reviewed By: cipolleschi

Differential Revision: D79666764

Pulled By: joevilches

fbshipit-source-id: 05e43d75ce7b6f25b67b4eed632524a559ea1c2e
2025-09-15 15:04:54 -07:00
generatedunixname537391475639613 7e85653c18 xplat/js/react-native-github/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/internal/PrepareGlogTaskTest.kt (#53750)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53750

Reviewed By: cortinico

Differential Revision: D82293987

fbshipit-source-id: 46bf93c7b8a2bf48a2246d9f5fc4ab8c1163badf
2025-09-15 13:04:17 -07:00
Ruslan Lesiutin 01b4749f6a Emit stashed trace to an active Fusebox client (#53771)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53771

# Changelog: [Internal]

Instead of opening DevTools every time we emit a background trace, we are going to check if there is an active session with Fusebox client and will send it to the first one registered.

Reviewed By: huntie

Differential Revision: D82321146

fbshipit-source-id: 46b4d090ae9a6f8b4fc98181b303ff552c561eb8
2025-09-15 12:45:53 -07:00
Ruslan Lesiutin 1b6d3c9ce1 refactor: emit the trace recoding on ReactNativeApplication domain initialization (#53760)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53760

# Changelog: [Internal]

This is a different approach from the one that I've introduced initially in [1].

This saves us from the scenario, where any local session could snatch the stashed trace recording. For example, if some session was created for a Runtime binding right after we've stashed the trace and before initializing real CDP session with the Frontend.

Reviewed By: huntie

Differential Revision: D82316584

fbshipit-source-id: 806a0f6dbdb4e4e928ce33af228cae86d43772e9
2025-09-15 12:45:53 -07:00
generatedunixname1395667395051502 fedad125bf Update React Native DevTools binaries
Summary:
Automated update of React Native DevTools binaries
bypass-github-export-checks
Changelog: [Internal]

Reviewed By: motiz88

Differential Revision: D82459089

fbshipit-source-id: 3ccf7a1f07da7e79732c785afda1b938ee585ea6
2025-09-15 11:54:30 -07:00
Pieter De Baets 64b30a9376 Fix newarch ignoring defaultfonthandler (#53755)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53755

Changelog: [iOS][Fixed] Make `RCTSetDefaultFontHandler` compatible with the new arch, and add a more powerful version as `RCTSetDefaultFontResolver`

Reviewed By: fkgozali

Differential Revision: D82207676

fbshipit-source-id: eeeaf708491de9156ef4f1e045864e4322213902
2025-09-15 11:31:30 -07:00
ismarbesic 07da2ff3e1 feat(ios): support condensed system font on fabric (#52259)
Summary:
This PR adds support for using the condensed system font on iOS when passing "SystemCondensed" as fontFamily. This behavior existed in the old architecture but was never ported to the new one, see [RCTFont.mm](https://github.com/facebook/react-native/blob/main/packages/react-native/React/Views/RCTFont.mm#L434) as reference. Fixes https://github.com/facebook/react-native/issues/52258.

## Changelog:

[IOS] [ADDED] - Add support for condensed system font when using the new react native architecture.

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

Test Plan:
Before:
<img width="275" src="https://github.com/user-attachments/assets/8744a5ae-252c-46db-b5f9-b803f3e1c671" />

After:
<img width="275" src="https://github.com/user-attachments/assets/69ec27a3-5c9a-46e3-a80a-0e02b76d8813" />

Reviewed By: cortinico

Differential Revision: D82208140

Pulled By: javache

fbshipit-source-id: b23a97c94bf45144c3f0860c30e35cae88c7dc2f
2025-09-15 11:31:30 -07:00
Alex Hunt 90ac3ac7bd Expose unstable_NativeText and unstable_NativeView components (#53777)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53777

Expose `unstable_NativeText` and `unstable_NativeView` components as root exports of the `react-native` package.

These are exposed as `unstable_` APIs which have no semver guarantee.

**Motivation**

There is significant community interest / dependance on the currently private `TextNativeComponent` and `ViewNativeComponent` deep imports, to access the faster-performing inner versions of these UI components.

Using `<Text>` and `<View>`, while recommended and stable, has led to measurable performance overhead in some apps when compared with these `<Native*>` counterparts.

Notably, these APIs are also referenced by low-level libraries such as React Strict DOM.

I am proposing this change in order to:
- Unblock libraries which safely use these.
- Meet users where they are at.
- Unblock us from enabling the Strict TypeScript API (no deep imports).

References:

- https://github.com/react-native-community/discussions-and-proposals/discussions/893#discussioncomment-13452047
- https://javascript.plainenglish.io/optimizing-text-component-rendering-in-react-native-b9d3565659d9
- https://github.com/search?type=code&q=react-native%2FLibraries%2FText%2FTextNativeComponent

**Ideal future state**

We are exposing these as unstable APIs because they should not be part of React Native's final API. The ideal end state is we improve the regular `<Text>` and `<View>` components to eliminate performance overhead and the need to access any lower level API.

Changelog:
[General][Added] - `unstable_NativeText` and `unstable_NativeView` are now exported from the `react-native` package

Reviewed By: javache

Differential Revision: D81588145

fbshipit-source-id: 2ea9b7f822286de85f49607944c6a484d1fcf242
2025-09-15 10:55:55 -07:00
Alex Hunt dba8dbdeeb Implement support for Network trace events (#53761)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53761

Updates `NetworkReporter` and `PerformanceEntryReporter` to populate (minimal) `"ResourceSendRequest"` and `"ResourceFinished"` events when a CDP performance trace is active. This allows the Chrome DevTools Performance panel to display the "Network" track.

**Notes**

- The trace events that Chrome requires need extra fields which aren't present on `PerformanceResourceTiming`, hence the new + optional `devtoolsRequestId`, `requestMethod`, `resourceType` params. We only populate these in debug builds.

**Limitations**

- We emit a *complete trace event set* within `reportResourceTiming`, implementing basic initial support in the Performance panel Network track. This means 1/ either all/no events are sent for a given request (rather than incrementally), 2/ we aren't yet handling failed/cancelled requests in this pipeline.

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D82212362

fbshipit-source-id: 4c6d5d2510cc98ddc819a2778222b835411295c8
2025-09-15 07:45:32 -07:00
Jakub Piasecki 0caf8e70d5 Add dependency on hermes-compiler (#53773)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53773

Changelog: [GENERAL][ADDED] - Added a dependency on hermes-compiler

Reviewed By: cortinico

Differential Revision: D82437752

fbshipit-source-id: 3b2d92b765f3a5ba949363ce7656e936403a1155
2025-09-15 06:16:25 -07:00
Luna Wei 56e5dff73f Hysteresis window for VirtualViewExperimental (#53765)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53765

Changelog: [Internal] Introduce hysteresis window for experimental VirtualView

Reviewed By: yungsters

Differential Revision: D82354142

fbshipit-source-id: 13ec0e3a46930d3bea0ea67060c5f0e1c137dec1
2025-09-13 21:41:49 -07:00
Luna Wei 4f20e4d0fc Subview clipping for VirtualViewExperiment (#53759)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53759

Changelog: [Internal] - Enable subview clipping for experimental version of VirtualView

Reviewed By: mdvacca

Differential Revision: D82313841

fbshipit-source-id: c6726fd5f443a55ec47c93ef13092fe8bb9297e0
2025-09-13 21:41:49 -07:00
Alex Hunt 09a7e0a0fc Restore NOT_FOCUSABLE flag on Perf Monitor overlay (#53754)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53754

Fix following D82302063.

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D82311593

fbshipit-source-id: 07305c8d3fdbd9199662e20a813ccfbf46e6491b
2025-09-13 06:23:56 -07:00
David Vacca 4fb42c84d8 Delete ScreenshotTestsManagerModule (#53746)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53746

ScreenshotTestsManagerModule and ReactAppScreenshotTestActivity are not in use anymore, let's delete them

changelog: [internal] internal

Reviewed By: javache

Differential Revision: D82249453

fbshipit-source-id: 73b0f2ef2e9a5370057c07c3bee03f9c0793d61a
2025-09-12 11:43:51 -07:00
generatedunixname89002005287564 ebe51d6ce8 Fix CQS signal readability-static-accessed-through-instance in xplat/js/react-native-github/packages (#53756)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53756

Reviewed By: javache

Differential Revision: D82289100

fbshipit-source-id: 432d9cdf4116aadbd6f21e8fdb994a91a163bfd5
2025-09-12 10:18:49 -07:00
vlad 81bbbe3c45 Fix deprecation message (#53751)
Summary:
This pull request fixes a small error in the deprecation message for `ReactContextBaseJavaModule#getCurrentActivity()`, where the reference to `getReactApplicationContext().getCurrentActivity()` contained a syntax error.

## Changelog:

[ANDROID] [FIXED] - Correct deprecation message for `ReactContextBaseJavaModule#getCurrentActivity()`

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

Reviewed By: javache

Differential Revision: D82302032

Pulled By: cortinico

fbshipit-source-id: 130991ef514663223165c30fccb920ce87403148
2025-09-12 09:11:04 -07:00
Alex Hunt 35f6ed1146 Update SectionList to accept React elements for separators (#53599)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53599

Runtime and types fix for the `ItemSeparatorComponent` prop on `virtualized-list` components (`FlatList`, `SectionList`, `VirtualizedList`).

- Docs source, where this prop (adjacent to other `*Component` props) is documented as accepting elements: https://reactnative.dev/docs/virtualizedlist#itemseparatorcomponent.
- Existing runtime behaviour matching this definition: https://github.com/facebook/react-native/blob/8d33e1c205b12fe27f4319e6566bb0c088197810/packages/virtualized-lists/Lists/VirtualizedListCellRenderer.js#L197-L203

**Changes**

- Update Flow, manual TS type defs.
- Align runtime behaviour in `VirtualizedSectionList` to add matching `React.isValidElement()` behaviour.

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

Changelog:
[General][Fixed] - The `ItemSeparatorComponent` prop on list components now accepts a React element as well as a component function.

Reviewed By: cortinico

Differential Revision: D81675423

fbshipit-source-id: 3eed93b1bea89554988d6e20fa61b72e17be55df
2025-09-12 07:54:04 -07:00
Nicola Corti f59a6f9508 Do not crash inside getEncodedScreenSizeWithoutVerticalInsets if SurfaceMountingManager is null (#53752)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53752

Currently we `Objects.requireNotNull` on the `SurfaceMountingManager` inside the `getEncodedScreenSizeWithoutVerticalInsets`
function. However the `SurfaceMountingManager` could be null.
In that scenario, I'm returning 0 here (that will restore the old broken behavior, with the modal rendering on the top left corner for the first frame), instead of letting the app crash.

Changelog:
[Android] [Fixed] - Do not crash inside getEncodedScreenSizeWithoutVerticalInsets if SurfaceMountingManager is null

Reviewed By: javache

Differential Revision: D82225855

fbshipit-source-id: df84db612e77b6b981bc28afc0d293867b5d3b2e
2025-09-12 07:44:00 -07:00
Riccardo Cipolleschi 91d427fe52 Fix Switch layout with iOS26 (#53326)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53326

Apple changed the sizes of the UISwitchComponent and now, if you build an iOs app using the <Switch> component, the layout of the app will be broken because of wrong layout measurements.
This has been reported also by [https://github.com/facebook/react-native/issues/52823](https://github.com/facebook/react-native/issues/52823).

The `<Switch>` component was using hardcoded values for its size.
This change fixes the problem by:
- Using codegen for interface only
- Implementing a custom Sadow Node to ask the platform for the Switch measurements
- Updating the JS layout to wrap the size around the native component.

## Changelog:

[iOS][Fixed] - Fix Switch layout to work with iOS26

Reviewed By: sammy-SC

Differential Revision: D80454350

fbshipit-source-id: 1d468910276f7fde4559d2ae87cf60c8494caceb
2025-09-12 07:16:20 -07:00
Alex Hunt d28ee162e3 Restore background tracing touch target (#53753)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53753

Changelog: [Internal]

Reviewed By: rubennorte

Differential Revision: D82302063

fbshipit-source-id: 25c99c2449fd72b81ce3ac25473c41304993cbc1
2025-09-12 06:51:53 -07:00
Alex Hunt 136d795c22 Implement saved window positioning per target (#53743)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53743

As titled. A long awaited quality of life improvement that we can now deliver with our desktop shell.

Window arrangements are saved per [`windowKey`](https://github.com/facebook/react-native/blob/da7bf9c54567aae62ff355b79e66f511cb382065/packages/dev-middleware/src/middleware/openDebuggerMiddleware.js#L193), mapping to each previously opened debugger target.

**Limitations**

- Does not save/restore macOS fullscreen app state.

Changelog: [Internal]

Reviewed By: vzaidman

Differential Revision: D82236159

fbshipit-source-id: e3c2f9c0cb05a3b8ef2208eb4a288e0be064489a
2025-09-12 05:26:27 -07:00
Alex Hunt 88f3452992 Add website links to Help menu (#53741)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53741

Adds a couple of web link options to populate the "Help" menu in the RNDT desktop app. The default menu is otherwise unchanged.

Changelog: [Internal]

Reviewed By: vzaidman

Differential Revision: D82231524

fbshipit-source-id: 9a57e6067854716691dc35938d6f27735b8c8448
2025-09-12 04:13:25 -07:00
Alex Hunt 408abf66d7 Fix user facing display name for debugger-shell (#53740)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53740

Adjusts user facing app name used by Electron, which previously used `package.json#name` and led to this leaking into unwanted parts of the UI — e.g. "[About|Hide|Quit] react-native/debugger-shell" in the macOS menu bar.

**Changes**

- Set the `productName` field in `package.json` ([docs](https://www.electronjs.org/docs/latest/api/app#appname:~:text=npm%20modules%20spec.-,You%20should%20usually%20also%20specify%20a%20productName%20field%2C%20which%20is%20your%20application%27s%20full%20capitalized%20name%2C%20and%20which%20will%20be%20preferred%20over%20name%20by%20Electron.,-app.userAgentFallback%E2%80%8B)).

**Notes**

- This **changes** the `User-Agent` header sent by the app, now of the form `"... ReactNativeDevTools/0.82.0-main-dev ..."`.

Changelog: [Internal]

Reviewed By: vzaidman

Differential Revision: D82228307

fbshipit-source-id: dd316ce5580a3ddf4138c7c3bf76aff99e4f4801
2025-09-12 04:13:25 -07:00
generatedunixname89002005287564 82708c775e Fix CQS signal readability-isolate-declaration in xplat/js/react-native-github/packages (#53747)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53747

Reviewed By: javache

Differential Revision: D82197859

fbshipit-source-id: e76e29754188ff15c7b4ebde8060c04f89aa3b52
2025-09-12 03:52:25 -07:00
Gabriel Donadel bcf9d5850a Add changelog for v0.81.3 (#53694)
Summary:
Add Changelog for 0.81.3

## Changelog:
[Internal] - Add Changelog for 0.81.3

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

Test Plan: N/A

Reviewed By: fabriziocucci

Differential Revision: D82111771

Pulled By: cortinico

fbshipit-source-id: a67f64e81afa794eb4ba260701a187cac9ca55bd
2025-09-12 03:49:52 -07:00
Vojtech Novak bc3503452f chore: improve codegen error when native project not found (#53726)
Summary:
While working on a somewhat non-standard library setup I ran into:

`[Codegen] TypeError [ERR_INVALID_ARG_TYPE]: The "from" argument must be of type string. Received undefined`

which was caused by `xcodeproj` file not found. The issue was on the project side rather than codegen, but the error message was rather unhelpful, so this improves it.

## Changelog:

[General][Changed] - improve codegen error when ios native project not found

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

Test Plan:
tested locally, received the improved error message:

`[Codegen] Error: Cannot find .xcodeproj file inside /Users/some_project. This is required to determine codegen spec paths relative to native project.`

Reviewed By: cortinico

Differential Revision: D82226931

Pulled By: cipolleschi

fbshipit-source-id: dd851205655048fc35ed9f5266cefdbfb067d211
2025-09-12 03:41:50 -07:00
Nicola Corti c4d2ac6401 Temporarily disable prebuilds for template e2e tests on main branch (#53715)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53715

This is a backport of https://github.com/facebook/react-native/commit/ee08261123ac513941189cf7566337156576d4dd
on `main` as otherwise 0.83 will also be affected by the same problem (CI broken for iOS on the release branch).

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D82119125

fbshipit-source-id: 6fbdcd42b446bd49ac71bbe834181ba91cc67990
2025-09-12 03:03:28 -07:00
Zeya Peng 54ebfafb21 ensure animatedNodes collection is only accessed on render thread (#53734)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53734

## Changelog:

[Internal] [Changed] - ensure animatedNodes collection is only accessed on render thread

now `createAnimatedNode` can be called async from js thread (since https://github.com/facebook/react-native/pull/53476), `animatedNodes_` will be written on both threads, there was no proper locking mechanism for read

we can simply add locks wherever we read/write animatedNodes_; but there's way to use fewer locking - since AnimatedNode is created async, but will not be R/W async anywhere else, we can add a new collection to temporarily hold nodes created async and flush it on render thread

Reviewed By: lenaic

Differential Revision: D82119554

fbshipit-source-id: 7f29e9e046cdf2e233c548442d70f1ff5b931cdd
2025-09-11 20:29:00 -07:00
Yury Dymov a7dc5051d3 React Native (#53651)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53651

## Stack
We aim to remove +load methods from the codebase to reduce pre-main startup time and to unblock enabling startup optimizations

## Diff
Diff removes `+load` API from `RCT_EXPORT_MODULE` macro.
It introduces new parameter for `react_native_module_provider` function `eager`, which adds legacy RN modules to newly created socket `REACT_MODULE_EAGER_REGISTRATION_SOCKET`. This socket is invoked right before the RCTBridge is being initialized.

Impact: 137 static loaders are removed from the startup path

## RN
Changelog: [Internal]

Reviewed By: RSNara

Differential Revision: D81727845

fbshipit-source-id: 4904499f2e8587717b26579364ed48ffed934774
2025-09-11 18:55:40 -07:00
Alex Hunt 6e011c98c1 Strip back V2 Perf Monitor to status only (#53742)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53742

Simplify scope for the internal V2 Perf Monitor prototype, and clean up the current perf metrics approach.

Changelog: [Internal]

Reviewed By: hoxyq

Differential Revision: D82208400

fbshipit-source-id: a03eb5f493064fc4b554d56a36a48df889f276b2
2025-09-11 14:21:00 -07:00
Peter Abbondanzo 0ee665ce12 Add API 26 check to ReactScrollViewHelper (#53688)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53688

Support for the `removeIf` method was [added to CopyOnWriteArrayList with AOSP in API 26](https://android-review.googlesource.com/c/platform/libcore/+/304056). On devices with API 24 and 25, invocations of either `ReactScrollViewHelper#removeScrollListener` or `ReactScrollViewHelper#removeLayoutChangeListener` would cause a crash. Rather than bump the required API version and lock out apps targeting API 24/25, this adds a separate code path to bulk remove items from the array list.

Changelog: [Internal]

Differential Revision: D82039300

fbshipit-source-id: 6509dc637534b8e546f84447dbcdce1c5bca42f0
2025-09-11 11:34:09 -07:00
Peter Abbondanzo 2dada2193a Apply LINEAR_TEXT setting with custom typefaces set (#53692)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53692

With some custom typefaces, font hinting causes "jittery" artifacts upon scaling the text. We already set the subpixel text flag for views with custom or modified typefaces and this change also sets the linear text flag. Per [Android documentation](https://developer.android.com/reference/android/graphics/Paint#SUBPIXEL_TEXT_FLAG), it's recommended that both of these flags are set together to avoid this exact artifacting. This change is being gated behind a feature flag to evaluate the performance impact of disabling glyph caches for all text, and may drive the need to introduce a prop that controls this setting in the future.

Changelog: [Internal]

Reviewed By: rozele

Differential Revision: D82050029

fbshipit-source-id: 9e6e023ff723641f663935b6cd7aae07045834bc
2025-09-11 11:32:18 -07:00
Panos Vekris 3d4f0f48fe fix flow typing of mockComponent (#53739)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53739

The previous condition `typeof isESModule extends true` in the definition of `mockComponent` is always `true` (this is expected by semantics of conditional types).

See [try-Flow](https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkUEKTwJEJ+DAkMiUFSwkyZCC3dbdAC+-EgGiSAB1YA9lHBoAACXmICrcAAUAEpZcAJbLtbKYFL4VBdVBlWhkLK0MRnlBVWaVsYIDBzbKFAsoGwSLKpDJZQB+WUARllZoATBrZSwJEJKIbgwBuWUCiVanW2eyy+SBgC8RuVXuwqvjAHpC7LM2XZcHk9qM7LWQGiyWTJReVX04G63Gk4aU9A0-JQ9mYMayfmG6Xy5Xu9XQ3X-WOmy2p+mZx7O1BE1AuxK9Y8DbriqU1RrWzvpXKhwAeABCZotECtAD4TWarzbZVfnfJXe7PfpfQGgwrMMIyjQ1lQABlrD1gVoAtZULAAqWUHjtVg0DaWUEJLDdW1TCQ21LHM8zg4tx2zf1WxrWdY0o9tV3nahF1w3t8P7Qih2VEcSJLMts0nHVlyg+t4MbRjKEolcK3jCUBVyEAGhMEgZSgJIGnAqxgwADn9KxwJAAUgA)

A bug in Flow made it so that when this type was exported `typeof isESModule` became `any`, which made it so that the conditional always evaluated to the first part. The bug is fixed in D82193099.

This diff uses a type parameter, which can be correctly instantiated at the call site.

Changelog: [internal]

Reviewed By: SamChou19815

Differential Revision: D82226987

fbshipit-source-id: 411047c10ad88be867fe9b4c5a1672775b3c9322
2025-09-11 11:22:41 -07:00
Gabriel Donadel da7bf9c545 Add changelog for v0.81.2 (#53691)
Summary:
Add Changelog for 0.81.2

## Changelog:
[Internal] - Add Changelog for 0.81.2

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

Test Plan: N/A

Reviewed By: fabriziocucci

Differential Revision: D82111764

Pulled By: cortinico

fbshipit-source-id: 90a140b7114861afecfa1c65cd69adbeff178e14
2025-09-11 10:35:54 -07:00
Nicola Corti 29c1f72801 Do not invoke project. inside BundleHermesCTask (#53718)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53718

I've just realized we ended up invoking `project.` inside the execution of `BundleHermesCTask`.
This is an anti-pattern and is breaking Gradle Configuration caching.

Instead we should be checking if hermesV1Enabled is set during the Task registration and pass over this information
to the task.

Changelog:
[Internal] [Changed] -

Reviewed By: j-piasecki

Differential Revision: D82130643

fbshipit-source-id: d2026711666867b3767824381cc5be0af3b476cc
2025-09-11 07:20:25 -07:00
Jakub Piasecki ae89022f9a Use artifacts published from Hermes repository when using Hermes V1 (#53725)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53725

Changelog: [GENERAL][CHANGED] - Changed the coordinates of hermes artifacts when using Hermes V1

Adds a new `version.properties` file to keep which hermes versions should be consumed from Maven once the versions of Hermes and React Native are decoupled. This diff only implements changes necessary for consuming Hermes V1, as we don't want to migrate everything quite yet (0.82).

Reviewed By: cortinico

Differential Revision: D82204203

fbshipit-source-id: d712257a73f7ba54612a55c1b312416376f28b56
2025-09-11 06:36:23 -07:00
Nicola Corti 7438fcd5d2 Unblock run_fantom_tests by pinning react-native-android to v18.0 (#53732)
Summary:
This temporarly unblocks `run_fantom_tests` till we find a solution for the docker image bump. See:
- https://github.com/react-native-community/docker-android/pull/242#issuecomment-3280029122

## Changelog:

[INTERNAL] -

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

Test Plan: CI

Reviewed By: cipolleschi

Differential Revision: D82212022

Pulled By: cortinico

fbshipit-source-id: 652926addf12cc2d88ac2139d3ec58a266ced9ef
2025-09-11 06:21:39 -07:00
Rubén Norte 5ae5a120a7 Ship Web Performance APIs in canary (#53712)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53712

Changelog: [internal]

This enables the new Web Performance APIs in the canary channel.

Reviewed By: cortinico

Differential Revision: D82117694

fbshipit-source-id: 370b8397eeec350be8434728ab9d8ce1f5926117
2025-09-11 06:09:29 -07:00
Rubén Norte 9962add377 Remove redudant fields from ReactNativeStartupTiming (#53711)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53711

Changelog: [internal]

This removes some fields that contain the same time as `endTime`, which is confusing when documenting them.

Reviewed By: christophpurrer

Differential Revision: D82112473

fbshipit-source-id: 461e2b4b495ae641dcb3233874360a4f7b90dabf
2025-09-11 06:09:29 -07:00
Rubén Norte c015f626a8 Make eventCounts a getter (#53710)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53710

Changelog: [internal]

This should be a getter according to the spec.

Reviewed By: hoxyq

Differential Revision: D82111779

fbshipit-source-id: 614bb4848907bacd80ef228aa747ae685cf2c1f7
2025-09-11 06:09:29 -07:00
Vitali Zaidman cda0ad8c5f Update debugger-frontend from e87564a...5a792db (#53714)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/53714

Changelog: [Internal] - Update `react-native/debugger-frontend` from e87564a...5a792db

Resyncs `react-native/debugger-frontend` from GitHub - see `rn-chrome-devtools-frontend` [changelog](https://github.com/facebook/react-native-devtools-frontend/compare/e87564a24cf233c60aaebee8c418ec85724f7214...5a792db1225adda206313e9bf751198a1ca7851a).

### Changelog

| Commit | Author | Date/Time | Subject |
| ------ | ------ | --------- | ------- |
| [5a792db12](https://github.com/facebook/react-native-devtools-frontend/commit/5a792db12) | Vitali Zaidman (vzaidman@gmail.com) | 2025-09-01T11:06:46+01:00 | [track clicks on script links (#204)](https://github.com/facebook/react-native-devtools-frontend/commit/5a792db12) |
| [7edec7c48](https://github.com/facebook/react-native-devtools-frontend/commit/7edec7c48) | Vitali Zaidman (vzaidman@gmail.com) | 2025-08-28T10:24:49+01:00 | [track the duration between user setting a breakpoint and the breakpoint being painted (#201)](https://github.com/facebook/react-native-devtools-frontend/commit/7edec7c48) |
| [273f02681](https://github.com/facebook/react-native-devtools-frontend/commit/273f02681) | Ruslan Lesiutin (28902667+hoxyq@users.noreply.github.com) | 2025-08-27T19:33:26+01:00 | [Revert "Add landingView query param enabling view focus on launch (#197)" (#202)](https://github.com/facebook/react-native-devtools-frontend/commit/273f02681) |
| [1e19c4454](https://github.com/facebook/react-native-devtools-frontend/commit/1e19c4454) | Vitali Zaidman (vzaidman@gmail.com) | 2025-08-21T16:23:44+01:00 | [Fixed Protocol Manager displaying events with no params as "(pending)". (#200)](https://github.com/facebook/react-native-devtools-frontend/commit/1e19c4454) |

Reviewed By: robhogan

Differential Revision: D82117068

fbshipit-source-id: 4112be5e8a4f643dffc46e7956c821041dad3b26
2025-09-11 03:49:25 -07:00
Riccardo Cipolleschi dab8a45623 cipolleschi/fix rctnetworking not building (#53713)
Summary:
Fix Cocoapods builds with static libraries and dynamic frameworks

## Changelog:
[Internal] -

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

Test Plan: Tested locally on RNTester

Reviewed By: cortinico

Differential Revision: D82118127

Pulled By: cipolleschi

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

Reviewed By: cortinico

Differential Revision: D82098400

fbshipit-source-id: e692b8e2bad976805657c2f406831c346198554f
2025-09-10 07:23:21 -07:00
159 changed files with 2584 additions and 1140 deletions
+4 -2
View File
@@ -276,7 +276,8 @@ jobs:
NEW_ARCH_ENABLED=1
export RCT_USE_LOCAL_RN_DEP=/tmp/third-party/ReactNativeDependencies${{ matrix.flavor }}.xcframework.tar.gz
export RCT_TESTONLY_RNCORE_TARBALL_PATH="/tmp/ReactCore/ReactCore${{ matrix.flavor }}.xcframework.tar.gz"
# Disable prebuilds for now, as they are causing issues with E2E tests for 0.82-stable branch
# export RCT_TESTONLY_RNCORE_TARBALL_PATH="/tmp/ReactCore/ReactCore${{ matrix.flavor }}.xcframework.tar.gz"
HERMES_ENGINE_TARBALL_PATH=$HERMES_PATH RCT_NEW_ARCH_ENABLED=$NEW_ARCH_ENABLED bundle exec pod install
xcodebuild \
@@ -382,7 +383,8 @@ jobs:
runs-on: 8-core-ubuntu
needs: [set_release_type]
container:
image: reactnativecommunity/react-native-android:latest
# Version is pinned to v18.0 to unblock `run_fantom_tests` - see https://github.com/react-native-community/docker-android/pull/242#issuecomment-3280029122
image: reactnativecommunity/react-native-android:v18.0
env:
TERM: "dumb"
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
+24
View File
@@ -1,5 +1,29 @@
# Changelog
## v0.81.3
### Fixed
#### iOS specific
- Reverted "Use autolinking-generated react-native-config output in second step of cocoapods linking that generates artifacts and generated source" ([537e3ad930](https://github.com/facebook/react-native/commit/537e3ad93041c0cef959f0d20586fe97818900a6) by [@gabrieldonadel](https://github.com/gabrieldonadel))
## v0.81.2
### Added
#### Android specific
- Create a debugOptimized buildType for Android ([5e3edafec6](https://github.com/facebook/react-native/commit/5e3edafec6c69558521061dced7a6bcd046576b0) by [@cortinico](https://github.com/cortinico))
- Add `useNativeEqualsInNativeReadableArrayAndroid` and `useNativeTransformHelperAndroid` feature flag to the experimental channel. This should alleviate some of the perf issue users are seeing on Android + Reanimated + NewArch on 0.81([a346096da8](https://github.com/facebook/react-native/commit/a346096da81fccf5fbc82d83bfc128695e6ec3a5) by [@cortinico](https://github.com/cortinico))
### Fixed
#### iOS specific
- Fix Node scripts related to prebuilt tarball extraction for paths containing whitespaces ([366f2ad505](https://github.com/facebook/react-native/commit/366f2ad5057ffecc1f5b211f6aae29567ae6b7e5) by [@kitten](https://github.com/kitten))
- Use autolinking-generated react-native-config output in second step of cocoapods linking that generates artifacts and generated source ([a2eb29e5e7](https://github.com/facebook/react-native/commit/a2eb29e5e7aef8bbdf4c647c8467b5292b013b20) by [@kitten](https://github.com/kitten))
## v0.82.0-rc.1
### Added
+1
View File
@@ -135,6 +135,7 @@ if (project.findProperty("react.internal.useHermesNightly")?.toString()?.toBoole
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute(project(":packages:react-native:ReactAndroid:hermes-engine"))
// TODO: T237406039 update coordinates
.using(module("com.facebook.react:hermes-android:0.+"))
.because("Users opted to use hermes from nightly")
}
+41
View File
@@ -0,0 +1,41 @@
/**
* 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
* @format
*/
declare module 'electron-store' {
declare export type Schema = any;
declare export type Options = {
+name?: string,
defaults?: Object,
schema?: any,
migrations?: any,
beforeEachMigration?: any,
clearInvalidConfig?: boolean,
serialize?: any,
deserialize?: any,
accessPropertiesByDotNotation?: boolean,
watch?: boolean,
encryptionKey?: string | Buffer | $ReadOnlyArray<number>,
...
};
declare class ElectronStore {
constructor(options?: Options): this;
get(key: string): any;
get(key: string, defaultValue: any): any;
set(key: string, value: any): void;
set(object: Object): void;
has(key: string): boolean;
delete(key: string): void;
clear(): void;
}
declare module.exports: Class<ElectronStore>;
}
+1 -1
View File
@@ -117,7 +117,7 @@
"temp-dir": "^2.0.0",
"tinybench": "^4.1.0",
"typescript": "5.8.3",
"ws": "^6.2.3"
"ws": "^7.5.10"
},
"resolutions": {
"eslint-plugin-react-hooks": "6.1.0-canary-12bc60f5-20250613",
+2 -2
View File
@@ -1,5 +1,5 @@
@generated SignedSource<<b6a779e724ecce5e727ee674dc20f809>>
Git revision: e87564a24cf233c60aaebee8c418ec85724f7214
@generated SignedSource<<0b54f75686e4893a5444839bf621a317>>
Git revision: 5a792db1225adda206313e9bf751198a1ca7851a
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
@@ -238,7 +238,7 @@ import"../../ui/components/icon_button/icon_button.js";import"../../ui/component
<td>${e.method}</td>
<td>${e.params?d`<code>${JSON.stringify(e.params)}</code>`:""}</td>
<td>
${e.result?d`<code>${JSON.stringify(e.result)}</code>`:e.error?d`<code>${JSON.stringify(e.error)}</code>`:"(pending)"}
${e.result?d`<code>${JSON.stringify(e.result)}</code>`:e.error?d`<code>${JSON.stringify(e.error)}</code>`:"id"in e?"(pending)":""}
</td>
<td data-value=${e.elapsedTime||0}>
${"id"in e?e.elapsedTime?U(A.sMs,{PH1:String(e.elapsedTime)}):"(pending)":""}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,71 +1,71 @@
#!/usr/bin/env dotslash
// @generated SignedSource<<e93d55b5e28943e44271f5d3738083e0>>
// @generated SignedSource<<9df662721aea6e3774a8677e2a6065e4>>
{
"name": "React Native DevTools",
"platforms": {
"linux-aarch64": {
"size": 116060647,
"size": 116056584,
"hash": "sha256",
"digest": "4352f1c9848ca919101ec628bd08b87a72a828d1ab55fa43a02098329fa452fa",
"digest": "fac3912f10e3c373c874be6c4696f11e05cbaa754c116db6ea72189afae5efe6",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQM24LW0nPZRk0ypuIG9prz_72YjBJNcVlGSIEpO4zdlLXgw4dFNodH7MKg9eKNTnx7wrVDDBNACrnEPt_OfXOjZyZsV9Oaqu0-vNFRdlEyis4YqmpqGLtz3LvD-9R6fzcWxJI9zrhdPvOvlXP-3Syt1UNITaxXDVqIwAAYpCaAh0oLpupAFCc2yvYw"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQPdlYV_w4y_wxd5Rs8c_F-soLMbY1zwXJjD-JM4TkOtOELyxZGyreTb6dTehp_X6e3qVFZUT05ETRs0MbvqYr4mqdb6dkG9DqNQEVv3TBoHwl7TYSygtHJEs3gXTAtMLYEKW9xXODJlVF-9II7fdTdHU7x7Pyf6NR6S3nv7sKUVD-zKpxF50L2TwUQ"
}
],
"format": "tar.gz",
"path": "React Native DevTools-linux-arm64/React Native DevTools"
},
"linux-x86_64": {
"size": 115930333,
"size": 115929093,
"hash": "sha256",
"digest": "11c7b07942928a6301b07fbf2bc77ce1229b2a52891f23541cdd9858b5250e64",
"digest": "06bbaeb62ae2e0081d184eba42b9f15be0d0b3f4901142b08a8a1430ff83e722",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQNDwm7HZRhtNxHqMr1FfSb0afHFGrn1OHxH0gOiggrLrht9QRUgJ3GG5jj7huhQzMRogE-LCMsnxh1ioOZks-YYX4KRt6Kj1-whdWsGFc7lBhPOpk1ssbYFGN1NNyuyFRmH-3nCY3lBC4AmbCUkbDTUeCi9DidCtJeyc73CZJEu7M62rIzxR2yV"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQPENbrxrg0RF_TiyxSY_YVYqC1UfRKj_bMfKb3qHUsIhBlya8n3FkbfJqTXTNdqL4riFRulXS5ecXZprtvk_9kao-zY59r3kiTRwobzF0jnjM507_9UOnEHWzG5ZJYzQyOtS3kQmT0HwZabVj9qw38OB9mk-MPVCZIPZay2PRnIThBzpKDoOOP9"
}
],
"format": "tar.gz",
"path": "React Native DevTools-linux-x64/React Native DevTools"
},
"macos-aarch64": {
"size": 110891041,
"size": 110891603,
"hash": "sha256",
"digest": "3cbe8b1b3d17e433347f1601435bb9a6cb758528a5c176a66fc52d9977223175",
"digest": "eeb9cc1399c0c38c429848dbf622f1b46e88d7d97788dcdc4c30a9fcce37705c",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQOmC2cqqSv4OrSJKJroYVg_NE8OE4O73AXqY7wXiYqiWQVkDt0Xnyw3ZeUpQT_Qb0-OoT5F8REKoFrB6eqwat8Ovkyina30peYTTwNUzmwnnGQEg7J0fOHNxLF4dkmU1FagXtsoWgex4dKgsK_VpcMsHj3Vp7diomkYvWBVTf_gPVEseYSN9oKq92qa"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQN9bd7nbXi-uF_MooJEiUL2jJVifMqgQxZ236aqbSdiJe5Uzjud5ANf0LDAl8GDVVRShd6x4B-gsZ-qRowH0qJikoGatQIkBgyzp4i8ors52etGOIIwdAxdNIng5Vtp441j2_N__btkJqlHcMMbbqvO8fg9oeYTlhMpOx3MLLWXIlG7ix5IFUDiYChf"
}
],
"format": "tar.gz",
"path": "React Native DevTools.app/Contents/MacOS/React Native DevTools"
},
"macos-x86_64": {
"size": 117766158,
"size": 117770388,
"hash": "sha256",
"digest": "6fb79bc2ba3008401b4c9c128248657b95b98581ccde60f8fadb622163779775",
"digest": "a65e446e526502b267cbe6800ae031e4e1b5b0aca21412152a66ffe1d3a29410",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQMMtGn-YGdfLfTVWC8zbQkQx65Asq6iArKt1t__cjZ8UY_s6-sX5XBHr8k1SaexAO21dFZENQVZ1jW_wn_gJ9ENvosQDG1KfWMViKsHli0xRzZ1HVsgPIj_KVXe907QZwwtJf2XhgH0HT8dfH-AQdDcd0_TB5DFUwOsHzhH0nBrHet7YFkbJtPTaA"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQN29ukKLZS48QcCU-gms06YFnG-fxxDfrWTup-Z6KoIAP-QEu3sFzrpqeZz4g-jFsh3o-IIoHJsdbu4a2U7ROjPfWGguGJrPK8fdc3iV67Qw1_VAPU13dm0dI1DbSY50ah05wh43jdBG3LGCAYYJ-W8_mZMC3HqM2v-8KVd-DoPT4hOh1s9CCFgEQ"
}
],
"format": "tar.gz",
"path": "React Native DevTools.app/Contents/MacOS/React Native DevTools"
},
"windows-x86_64": {
"size": 125527537,
"size": 125526370,
"hash": "sha256",
"digest": "579a5b0944c51c3b1b541ad5af66c1ffedf93cae2a891ecdf88cb7219fd9b096",
"digest": "26b190c0f85249dee91999e020b8fe7ffd5c007458a2103ed3822558861dbe87",
"providers": [
{
"type": "http",
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQOQ3E8lBXqdVHbDPyVb5AOQMrDWjFrFV8fnLLBsygQvLWdpu6ixyG9PgWdwpi5jM-XcDdCHkhBdhaq-5dwT_tgRWKCAMsEBoAIUk0Xg77mGyHG2VF7bNfQ2qFBMuObrsTmrKy1nJ-UFDDm29pJD4GkFQW5NesiBwndJj8t3B8Ur8cczh_XR8rF5"
"url": "https://scontent.xx.fbcdn.net/mci_ab/uap/?ab_b=m&ab_page=react_native_devtools_binaries&ab_entry=AQO0mdIJx1eYiSDJNiAZ2soaeECILayWnvOeiey-fpS4ydDTreAMfwL4QZ5GZo4-AsGwOXCSdF3hBMM2ufnvw6BDFg5UEiTc4DALJu7o6YBBG4wlVZbI-2kkaXd7u8RhBQPjmhsdcX3sDmCGmSr3WK9EkTRKStE2lb6ilOn-969h4dYWekwUUCSX"
}
],
"format": "tar.gz",
+2
View File
@@ -1,5 +1,6 @@
{
"name": "@react-native/debugger-shell",
"productName": "React Native DevTools",
"version": "0.82.0-main",
"description": "Experimental debugger shell for React Native for use with @react-native/debugger-frontend",
"keywords": [
@@ -34,6 +35,7 @@
},
"devDependencies": {
"electron": "37.2.6",
"electron-store": "^8.2.0",
"semver": "^7.1.3"
},
"files": [
@@ -9,10 +9,12 @@
*/
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
const {BrowserWindow, app, shell, ipcMain} = require('electron') as any;
const {BrowserWindow, Menu, app, shell, ipcMain} = require('electron') as any;
const Store = require('electron-store');
const path = require('path');
const util = require('util');
const appSettings = new Store();
const windowMetadata = new WeakMap<
typeof BrowserWindow,
$ReadOnly<{
@@ -53,10 +55,11 @@ function handleLaunchArgs(argv: string[]) {
}, 1000);
}
} else {
// Create the browser window.
frontendWindow = new BrowserWindow({
width: 1200,
height: 600,
...(getSavedWindowPosition(windowKey) ?? {
width: 1200,
height: 600,
}),
webPreferences: {
partition: 'persist:react-native-devtools',
preload: require.resolve('./preload.js'),
@@ -66,6 +69,8 @@ function handleLaunchArgs(argv: string[]) {
});
// Auto-hide the Windows/Linux menu bar
frontendWindow.setMenuBarVisibility(false);
// Observe and update saved window position
setupWindowResizeListeners(frontendWindow, windowKey);
}
// Open links in the default browser instead of in new Electron windows.
@@ -91,8 +96,68 @@ function handleLaunchArgs(argv: string[]) {
frontendWindow.focus();
}
function configureAppMenu() {
const template = [
...(process.platform === 'darwin' ? [{role: 'appMenu'}] : []),
{role: 'fileMenu'},
{role: 'editMenu'},
{role: 'viewMenu'},
{role: 'windowMenu'},
{
role: 'help',
submenu: [
{
label: 'React Native Website',
click: () => shell.openExternal('https://reactnative.dev'),
},
{
label: 'Release Notes',
click: () =>
shell.openExternal(
'https://github.com/facebook/react-native/releases',
),
},
],
},
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
function getSavedWindowPosition(
windowKey: string,
): ?{width: number, height: number, x?: number, y?: number} {
return appSettings.get('windowArrangements', {})[windowKey];
}
function saveWindowPosition(
windowKey: string,
position: {x: number, y: number, width: number, height: number},
) {
const windowArrangements = appSettings.get('windowArrangements', {});
windowArrangements[windowKey] = position;
appSettings.set('windowArrangements', windowArrangements);
}
function setupWindowResizeListeners(
browserWindow: typeof BrowserWindow,
windowKey: string,
) {
const savePosition = () => {
if (!browserWindow.isDestroyed()) {
const [x, y] = browserWindow.getPosition();
const [width, height] = browserWindow.getSize();
saveWindowPosition(windowKey, {x, y, width, height});
}
};
browserWindow.on('moved', savePosition);
browserWindow.on('resized', savePosition);
browserWindow.on('closed', savePosition);
}
app.whenReady().then(() => {
handleLaunchArgs(process.argv.slice(app.isPackaged ? 1 : 2));
configureAppMenu();
app.on(
'second-instance',
@@ -16,9 +16,8 @@ const util = require('util');
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
const {app} = require('electron') as any;
// Set the app name and version early - these are used in --version as well as
// in the User-Agent string.
app.setName(pkg.name);
// Set the application name and version
app.setName(pkg.productName ?? pkg.name);
app.setVersion(pkg.version + '-' + buildInfo.revision);
// Handle global command line arguments which don't require a window
@@ -31,7 +30,7 @@ const {
strict: false,
});
if (version) {
console.log(`${app.getName()} v${app.getVersion()}`);
console.log(`${pkg.name} v${app.getVersion()}`);
// Not app.quit() - we want to exit immediately without initialising the graphical subsystem.
app.exit(0);
}
+1 -1
View File
@@ -33,7 +33,7 @@
"nullthrows": "^1.1.1",
"open": "^7.0.3",
"serve-static": "^1.16.2",
"ws": "^6.2.3"
"ws": "^7.5.10"
},
"engines": {
"node": ">= 20.19.4"
@@ -55,8 +55,8 @@ class ReactPlugin : Plugin<Project> {
project,
)
if (project.rootProject.isHermesV1Enabled != rootExtension.hermesV1Enabled.get()) {
rootExtension.hermesV1Enabled.set(project.rootProject.isHermesV1Enabled)
if (project.rootProject.isHermesV1Enabled) {
rootExtension.hermesV1Enabled.set(true)
}
// App Only Configuration
@@ -71,11 +71,11 @@ class ReactPlugin : Plugin<Project> {
project.afterEvaluate {
val reactNativeDir = extension.reactNativeDir.get().asFile
val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")
val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)
val hermesV1Enabled =
if (project.rootProject.hasProperty("hermesV1Enabled"))
project.rootProject.findProperty("hermesV1Enabled") == "true"
else false
val hermesVersionPropertiesFile =
File(reactNativeDir, "sdks/hermes-engine/version.properties")
val versionAndGroupStrings =
readVersionAndGroupStrings(propertiesFile, hermesVersionPropertiesFile)
val hermesV1Enabled = rootExtension.hermesV1Enabled.get()
configureDependencies(project, versionAndGroupStrings, hermesV1Enabled)
configureRepositories(project)
}
@@ -14,6 +14,7 @@ import com.facebook.react.utils.KotlinStdlibCompatUtils.capitalizeCompat
import com.facebook.react.utils.NdkConfiguratorUtils.configureJsEnginePackagingOptions
import com.facebook.react.utils.NdkConfiguratorUtils.configureNewArchPackagingOptions
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
import com.facebook.react.utils.ProjectUtils.isHermesV1Enabled
import com.facebook.react.utils.ProjectUtils.useThirdPartyJSC
import com.facebook.react.utils.detectedCliFile
import com.facebook.react.utils.detectedEntryFile
@@ -48,6 +49,7 @@ internal fun Project.configureReactTasks(variant: Variant, config: ReactExtensio
} else {
isHermesEnabledInProject
}
val isHermesV1Enabled = project.isHermesV1Enabled || rootProject.isHermesV1Enabled
val isDebuggableVariant =
config.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) }
val useThirdPartyJSC = project.useThirdPartyJSC
@@ -78,6 +80,7 @@ internal fun Project.configureReactTasks(variant: Variant, config: ReactExtensio
task.jsBundleDir.set(jsBundleDir)
task.resourcesDir.set(resourcesDir)
task.hermesEnabled.set(isHermesEnabledInThisVariant)
task.hermesV1Enabled.set(isHermesV1Enabled)
task.minifyEnabled.set(!isHermesEnabledInThisVariant)
task.devEnabled.set(false)
task.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir)
@@ -63,6 +63,8 @@ abstract class BundleHermesCTask : DefaultTask() {
@get:Input abstract val hermesEnabled: Property<Boolean>
@get:Input abstract val hermesV1Enabled: Property<Boolean>
@get:Input abstract val devEnabled: Property<Boolean>
@get:Input abstract val extraPackagerArgs: ListProperty<String>
@@ -94,12 +96,8 @@ abstract class BundleHermesCTask : DefaultTask() {
runCommand(bundleCommand)
if (hermesEnabled.get()) {
val hermesV1Enabled =
if (project.rootProject.hasProperty("hermesV1Enabled"))
project.rootProject.findProperty("hermesV1Enabled") == "true"
else false
val detectedHermesCommand =
detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get(), hermesV1Enabled)
detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get(), hermesV1Enabled.get())
val bytecodeFile = File("${bundleFile}.hbc")
val outputSourceMap = resolveOutputSourceMap(bundleAssetFilename)
val compilerSourceMap = resolveCompilerSourceMap(bundleAssetFilename)
@@ -13,7 +13,7 @@ import com.facebook.react.utils.PropertyUtils.EXCLUSIVE_ENTEPRISE_REPOSITORY
import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY
import com.facebook.react.utils.PropertyUtils.INCLUDE_JITPACK_REPOSITORY_DEFAULT
import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_VERSION_NAME
import com.facebook.react.utils.PropertyUtils.INTERNAL_HERMES_V1_VERSION_NAME
import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO
import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_USE_HERMES_NIGHTLY
@@ -31,6 +31,7 @@ internal object DependencyUtils {
internal data class Coordinates(
val versionString: String,
val hermesVersionString: String,
val hermesV1VersionString: String,
val reactGroupString: String = DEFAULT_INTERNAL_REACT_PUBLISHING_GROUP,
val hermesGroupString: String = DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP,
)
@@ -113,7 +114,12 @@ internal object DependencyUtils {
coordinates: Coordinates,
hermesV1Enabled: Boolean = false,
) {
if (coordinates.versionString.isBlank() || coordinates.hermesVersionString.isBlank()) return
if (
coordinates.versionString.isBlank() ||
(!hermesV1Enabled && coordinates.hermesVersionString.isBlank()) ||
(hermesV1Enabled && coordinates.hermesV1VersionString.isBlank())
)
return
project.rootProject.allprojects { eachProject ->
eachProject.configurations.all { configuration ->
// Here we set a dependencySubstitution for both react-native and hermes-engine as those
@@ -133,7 +139,11 @@ internal object DependencyUtils {
// Contributors only: The hermes-engine version is forced only if the user has
// not opted into using nightlies for local development.
configuration.resolutionStrategy.force(
"${coordinates.reactGroupString}:hermes-android:${coordinates.versionString}"
// TODO: T237406039 update coordinates
if (hermesV1Enabled)
"${coordinates.hermesGroupString}:hermes-android:${coordinates.hermesV1VersionString}"
else
"${coordinates.reactGroupString}:hermes-android:${coordinates.hermesVersionString}"
)
}
}
@@ -146,10 +156,11 @@ internal object DependencyUtils {
): List<Triple<String, String, String>> {
// TODO: T231755027 update coordinates and versioning
val dependencySubstitution = mutableListOf<Triple<String, String, String>>()
// TODO: T237406039 update coordinates
val hermesVersionString =
if (hermesV1Enabled)
"${coordinates.hermesGroupString}:hermes-android:${coordinates.versionString}"
else "${coordinates.reactGroupString}:hermes-android:${coordinates.versionString}"
"${coordinates.hermesGroupString}:hermes-android:${coordinates.hermesV1VersionString}"
else "${coordinates.reactGroupString}:hermes-android:${coordinates.hermesVersionString}"
dependencySubstitution.add(
Triple(
"com.facebook.react:react-native",
@@ -172,6 +183,7 @@ internal object DependencyUtils {
"The react-android dependency was modified to use the correct Maven group.",
)
)
// TODO: T237406039 update coordinates
dependencySubstitution.add(
Triple(
"com.facebook.react:hermes-android",
@@ -183,14 +195,10 @@ internal object DependencyUtils {
return dependencySubstitution
}
fun readVersionAndGroupStrings(propertiesFile: File): Coordinates {
fun readVersionAndGroupStrings(propertiesFile: File, hermesVersionFile: File): Coordinates {
val reactAndroidProperties = Properties()
propertiesFile.inputStream().use { reactAndroidProperties.load(it) }
val versionStringFromFile = (reactAndroidProperties[INTERNAL_VERSION_NAME] as? String).orEmpty()
// TODO: T231755027 update HERMES_VERSION_NAME in gradle.properties to point to the correct
// hermes version
val hermesVersionStringFromFile =
(reactAndroidProperties[INTERNAL_HERMES_VERSION_NAME] as? String).orEmpty()
// If on a nightly, we need to fetch the -SNAPSHOT artifact from Sonatype.
val versionString =
if (versionStringFromFile.startsWith("0.0.0") || "-nightly-" in versionStringFromFile) {
@@ -205,9 +213,18 @@ internal object DependencyUtils {
val hermesGroupString =
reactAndroidProperties[INTERNAL_HERMES_PUBLISHING_GROUP] as? String
?: DEFAULT_INTERNAL_HERMES_PUBLISHING_GROUP
// TODO: T237406039 read both versions from the same file
val hermesVersionProperties = Properties()
hermesVersionFile.inputStream().use { hermesVersionProperties.load(it) }
val hermesVersion = versionString
val hermesV1Version =
(hermesVersionProperties[INTERNAL_HERMES_V1_VERSION_NAME] as? String).orEmpty()
return Coordinates(
versionString,
hermesVersionStringFromFile,
hermesVersion,
hermesV1Version,
reactGroupString,
hermesGroupString,
)
@@ -153,6 +153,7 @@ internal fun detectOSAwareHermesCommand(
// 3. If Hermes V1 is enabled, use hermes-compiler from npm, otherwise, if the
// react-native contains a pre-built hermesc, use it.
// TODO: T237406039 use hermes-compiler from npm for both
val hermesCPath = if (hermesV1Enabled) HERMES_COMPILER_NPM_DIR else HERMESC_IN_REACT_NATIVE_DIR
val prebuiltHermesPath =
hermesCPath
@@ -79,6 +79,10 @@ object PropertyUtils {
/** Internal property used to control the version name of React Native */
const val INTERNAL_VERSION_NAME = "VERSION_NAME"
/** Internal property used to control the version name of Hermes Engine */
const val INTERNAL_HERMES_VERSION_NAME = "HERMES_VERSION_NAME"
/**
* Internal property, shared with iOS, used to control the version name of Hermes Engine. This is
* stored in sdks/hermes-engine/version.properties
*/
const val INTERNAL_HERMES_V1_VERSION_NAME = "HERMES_V1_VERSION_NAME"
}
@@ -98,11 +98,11 @@ class PrepareGlogTaskTest {
val glogThirdPartyJniPath = tempFolder.newFolder("glogpath/jni")
val output = tempFolder.newFolder("output")
val task =
createTestTask<PrepareGlogTask> {
it.glogPath.setFrom(glogpath)
it.glogThirdPartyJniPath.set(glogThirdPartyJniPath)
it.glogVersion.set("1.0.0")
it.outputDir.set(output)
createTestTask<PrepareGlogTask> { task ->
task.glogPath.setFrom(glogpath)
task.glogThirdPartyJniPath.set(glogThirdPartyJniPath)
task.glogVersion.set("1.0.0")
task.outputDir.set(output)
}
File(glogpath, "glog-1.0.0/src/glog.h.in").apply {
parentFile.mkdirs()
@@ -271,54 +271,69 @@ class DependencyUtilsTest {
.isEqualTo(2)
}
// TODO: T236767053
@Test
fun configureDependencies_withEmptyVersion_doesNothing() {
val project = createProject()
configureDependencies(project, DependencyUtils.Coordinates("", ""))
configureDependencies(project, DependencyUtils.Coordinates("", "", ""))
assertThat(project.configurations.first().resolutionStrategy.forcedModules.isEmpty()).isTrue()
}
@Test
fun configureDependencies_withVersionString_appliesResolutionStrategy() {
fun configureDependencies_withVersionString_appliesResolutionStrategy_withClassicHermes() {
val project = createProject()
configureDependencies(project, DependencyUtils.Coordinates("1.2.3", "1.2.3"))
configureDependencies(project, DependencyUtils.Coordinates("1.2.3", "4.5.6", "7.8.9"))
val forcedModules = project.configurations.first().resolutionStrategy.forcedModules
assertThat(forcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" })
.isTrue()
assertThat(forcedModules.any { it.toString() == "com.facebook.react:hermes-android:1.2.3" })
assertThat(forcedModules.any { it.toString() == "com.facebook.react:hermes-android:4.5.6" })
.isTrue()
}
@Test
fun configureDependencies_withVersionString_appliesOnAllProjects() {
fun configureDependencies_withVersionString_appliesResolutionStrategy_withHermesV1() {
val project = createProject()
configureDependencies(
project,
DependencyUtils.Coordinates("1.2.3", "4.5.6", "7.8.9"),
hermesV1Enabled = true,
)
val forcedModules = project.configurations.first().resolutionStrategy.forcedModules
assertThat(forcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" })
.isTrue()
assertThat(forcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" })
.isTrue()
}
@Test
fun configureDependencies_withVersionString_appliesOnAllProjects_withClassicHermes() {
val rootProject = ProjectBuilder.builder().build()
val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build()
val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build()
appProject.plugins.apply("com.android.application")
libProject.plugins.apply("com.android.library")
configureDependencies(appProject, DependencyUtils.Coordinates("1.2.3", "1.2.3"))
configureDependencies(appProject, DependencyUtils.Coordinates("1.2.3", "4.5.6", "7.8.9"))
val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules
val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules
assertThat(appForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" })
.isTrue()
assertThat(appForcedModules.any { it.toString() == "com.facebook.react:hermes-android:1.2.3" })
assertThat(appForcedModules.any { it.toString() == "com.facebook.react:hermes-android:4.5.6" })
.isTrue()
assertThat(libForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" })
.isTrue()
assertThat(libForcedModules.any { it.toString() == "com.facebook.react:hermes-android:1.2.3" })
assertThat(libForcedModules.any { it.toString() == "com.facebook.react:hermes-android:4.5.6" })
.isTrue()
}
@Test
fun configureDependencies_withVersionStringAndGroupString_appliesOnAllProjects() {
fun configureDependencies_withVersionString_appliesOnAllProjects_withHermesV1() {
val rootProject = ProjectBuilder.builder().build()
val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build()
val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build()
@@ -327,25 +342,93 @@ class DependencyUtilsTest {
configureDependencies(
appProject,
DependencyUtils.Coordinates("1.2.3", "1.2.3", "io.github.test"),
DependencyUtils.Coordinates("1.2.3", "4.5.6", "7.8.9"),
hermesV1Enabled = true,
)
val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules
val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules
assertThat(appForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" })
.isTrue()
assertThat(appForcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" })
.isTrue()
assertThat(libForcedModules.any { it.toString() == "com.facebook.react:react-android:1.2.3" })
.isTrue()
assertThat(libForcedModules.any { it.toString() == "com.facebook.hermes:hermes-android:7.8.9" })
.isTrue()
}
@Test
fun configureDependencies_withVersionStringAndGroupString_appliesOnAllProjects_withClassicHermes() {
val rootProject = ProjectBuilder.builder().build()
val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build()
val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build()
appProject.plugins.apply("com.android.application")
libProject.plugins.apply("com.android.library")
configureDependencies(
appProject,
DependencyUtils.Coordinates(
"1.2.3",
"4.5.6",
"7.8.9",
"io.github.test",
"io.github.test.hermes",
),
)
val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules
val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules
assertThat(appForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" })
.isTrue()
assertThat(appForcedModules.any { it.toString() == "io.github.test:hermes-android:1.2.3" })
assertThat(appForcedModules.any { it.toString() == "io.github.test:hermes-android:4.5.6" })
.isTrue()
assertThat(libForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" })
.isTrue()
assertThat(libForcedModules.any { it.toString() == "io.github.test:hermes-android:1.2.3" })
assertThat(libForcedModules.any { it.toString() == "io.github.test:hermes-android:4.5.6" })
.isTrue()
}
@Test
fun getDependencySubstitutions_withDefaultGroup_substitutesCorrectly() {
fun configureDependencies_withVersionStringAndGroupString_appliesOnAllProjects_withHermesV1() {
val rootProject = ProjectBuilder.builder().build()
val appProject = ProjectBuilder.builder().withName("app").withParent(rootProject).build()
val libProject = ProjectBuilder.builder().withName("lib").withParent(rootProject).build()
appProject.plugins.apply("com.android.application")
libProject.plugins.apply("com.android.library")
configureDependencies(
appProject,
DependencyUtils.Coordinates(
"1.2.3",
"4.5.6",
"7.8.9",
"io.github.test",
"io.github.test.hermes",
),
hermesV1Enabled = true,
)
val appForcedModules = appProject.configurations.first().resolutionStrategy.forcedModules
val libForcedModules = libProject.configurations.first().resolutionStrategy.forcedModules
assertThat(appForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" })
.isTrue()
assertThat(
appForcedModules.any { it.toString() == "io.github.test.hermes:hermes-android:7.8.9" }
)
.isTrue()
assertThat(libForcedModules.any { it.toString() == "io.github.test:react-android:1.2.3" })
.isTrue()
assertThat(
libForcedModules.any { it.toString() == "io.github.test.hermes:hermes-android:7.8.9" }
)
.isTrue()
}
@Test
fun getDependencySubstitutions_withDefaultGroup_substitutesCorrectly_withClassicHermes() {
val dependencySubstitutions =
getDependencySubstitutions(DependencyUtils.Coordinates("0.42.0", "0.42.0"))
getDependencySubstitutions(DependencyUtils.Coordinates("0.42.0", "0.42.0", "0.43.0"))
assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first)
assertThat("com.facebook.react:react-android:0.42.0")
@@ -364,10 +447,40 @@ class DependencyUtilsTest {
}
@Test
fun getDependencySubstitutions_withCustomGroup_substitutesCorrectly() {
fun getDependencySubstitutions_withDefaultGroup_substitutesCorrectly_withHermesV1() {
val dependencySubstitutions =
getDependencySubstitutions(
DependencyUtils.Coordinates("0.42.0", "0.42.0", "io.github.test")
DependencyUtils.Coordinates("0.42.0", "0.42.0", "0.43.0"),
hermesV1Enabled = true,
)
assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first)
assertThat("com.facebook.react:react-android:0.42.0")
.isEqualTo(dependencySubstitutions[0].second)
assertThat(
"The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210."
)
.isEqualTo(dependencySubstitutions[0].third)
assertThat("com.facebook.react:hermes-engine").isEqualTo(dependencySubstitutions[1].first)
assertThat("com.facebook.hermes:hermes-android:0.43.0")
.isEqualTo(dependencySubstitutions[1].second)
assertThat(
"The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210."
)
.isEqualTo(dependencySubstitutions[1].third)
}
@Test
fun getDependencySubstitutions_withCustomGroup_substitutesCorrectly_withClassicHermes() {
val dependencySubstitutions =
getDependencySubstitutions(
DependencyUtils.Coordinates(
"0.42.0",
"0.42.0",
"0.43.0",
"io.github.test",
"io.github.test.hermes",
)
)
assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first)
@@ -392,6 +505,44 @@ class DependencyUtilsTest {
.isEqualTo(dependencySubstitutions[3].third)
}
@Test
fun getDependencySubstitutions_withCustomGroup_substitutesCorrectly_withHermesV1() {
val dependencySubstitutions =
getDependencySubstitutions(
DependencyUtils.Coordinates(
"0.42.0",
"0.42.0",
"0.43.0",
"io.github.test",
"io.github.test.hermes",
),
hermesV1Enabled = true,
)
assertThat("com.facebook.react:react-native").isEqualTo(dependencySubstitutions[0].first)
assertThat("io.github.test:react-android:0.42.0").isEqualTo(dependencySubstitutions[0].second)
assertThat(
"The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210."
)
.isEqualTo(dependencySubstitutions[0].third)
assertThat("com.facebook.react:hermes-engine").isEqualTo(dependencySubstitutions[1].first)
assertThat("io.github.test.hermes:hermes-android:0.43.0")
.isEqualTo(dependencySubstitutions[1].second)
assertThat(
"The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210."
)
.isEqualTo(dependencySubstitutions[1].third)
assertThat("com.facebook.react:react-android").isEqualTo(dependencySubstitutions[2].first)
assertThat("io.github.test:react-android:0.42.0").isEqualTo(dependencySubstitutions[2].second)
assertThat("The react-android dependency was modified to use the correct Maven group.")
.isEqualTo(dependencySubstitutions[2].third)
assertThat("com.facebook.react:hermes-android").isEqualTo(dependencySubstitutions[3].first)
assertThat("io.github.test.hermes:hermes-android:0.43.0")
.isEqualTo(dependencySubstitutions[3].second)
assertThat("The hermes-android dependency was modified to use the correct Maven group.")
.isEqualTo(dependencySubstitutions[3].third)
}
@Test
fun readVersionString_withCorrectVersionString_returnsIt() {
val propertiesFile =
@@ -405,9 +556,25 @@ class DependencyUtilsTest {
)
}
val versionString = readVersionAndGroupStrings(propertiesFile).versionString
val hermesVersionFile =
tempFolder.newFile("version.properties").apply {
writeText(
"""
HERMES_V1_VERSION_NAME=1000.0.0
ANOTHER_PROPERTY=true
"""
.trimIndent()
)
}
val strings = readVersionAndGroupStrings(propertiesFile, hermesVersionFile)
val versionString = strings.versionString
val hermesVersionString = strings.hermesVersionString
val hermesV1VersionString = strings.hermesV1VersionString
assertThat(versionString).isEqualTo("1000.0.0")
assertThat(hermesVersionString).isEqualTo("1000.0.0")
assertThat(hermesV1VersionString).isEqualTo("1000.0.0")
}
@Test
@@ -417,15 +584,33 @@ class DependencyUtilsTest {
writeText(
"""
VERSION_NAME=0.0.0-20221101-2019-cfe811ab1
HERMES_VERSION_NAME=0.12.0-commitly-20221101-2019-cfe811ab1
HERMES_V1_VERSION_NAME=250829098.0.0-stable
ANOTHER_PROPERTY=true
"""
.trimIndent()
)
}
val versionString = readVersionAndGroupStrings(propertiesFile).versionString
val hermesVersionFile =
tempFolder.newFile("version.properties").apply {
writeText(
"""
HERMES_V1_VERSION_NAME=250829098.0.0-stable
ANOTHER_PROPERTY=true
"""
.trimIndent()
)
}
val strings = readVersionAndGroupStrings(propertiesFile, hermesVersionFile)
val versionString = strings.versionString
val hermesVersionString = strings.hermesVersionString
val hermesV1VersionString = strings.hermesV1VersionString
assertThat(versionString).isEqualTo("0.0.0-20221101-2019-cfe811ab1-SNAPSHOT")
assertThat(hermesVersionString).isEqualTo("0.0.0-20221101-2019-cfe811ab1-SNAPSHOT")
assertThat(hermesV1VersionString).isEqualTo("250829098.0.0-stable")
}
@Test
@@ -440,8 +625,23 @@ class DependencyUtilsTest {
)
}
val versionString = readVersionAndGroupStrings(propertiesFile).versionString
val hermesVersionFile =
tempFolder.newFile("version.properties").apply {
writeText(
"""
ANOTHER_PROPERTY=true
"""
.trimIndent()
)
}
val strings = readVersionAndGroupStrings(propertiesFile, hermesVersionFile)
val versionString = strings.versionString
val hermesVersionString = strings.hermesVersionString
val hermesV1VersionString = strings.hermesV1VersionString
assertThat(versionString).isEqualTo("")
assertThat(hermesVersionString).isEqualTo("")
assertThat(hermesV1VersionString).isEqualTo("")
}
@Test
@@ -457,8 +657,24 @@ class DependencyUtilsTest {
)
}
val versionString = readVersionAndGroupStrings(propertiesFile).versionString
val hermesVersionFile =
tempFolder.newFile("version.properties").apply {
writeText(
"""
HERMES_V1_VERSION_NAME=
ANOTHER_PROPERTY=true
"""
.trimIndent()
)
}
val strings = readVersionAndGroupStrings(propertiesFile, hermesVersionFile)
val versionString = strings.versionString
val hermesVersionString = strings.hermesVersionString
val hermesV1VersionString = strings.hermesV1VersionString
assertThat(versionString).isEqualTo("")
assertThat(hermesVersionString).isEqualTo("")
assertThat(hermesV1VersionString).isEqualTo("")
}
@Test
@@ -468,15 +684,30 @@ class DependencyUtilsTest {
writeText(
"""
react.internal.publishingGroup=io.github.test
react.internal.hermesPublishingGroup=io.github.test
ANOTHER_PROPERTY=true
"""
.trimIndent()
)
}
val groupString = readVersionAndGroupStrings(propertiesFile).reactGroupString
val hermesVersionFile =
tempFolder.newFile("version.properties").apply {
writeText(
"""
HERMES_V1_VERSION_NAME=
ANOTHER_PROPERTY=true
"""
.trimIndent()
)
}
assertThat(groupString).isEqualTo("io.github.test")
val strings = readVersionAndGroupStrings(propertiesFile, hermesVersionFile)
val reactGroupString = strings.reactGroupString
val hermesGroupString = strings.hermesGroupString
assertThat(reactGroupString).isEqualTo("io.github.test")
assertThat(hermesGroupString).isEqualTo("io.github.test")
}
@Test
@@ -491,9 +722,23 @@ class DependencyUtilsTest {
)
}
val groupString = readVersionAndGroupStrings(propertiesFile).reactGroupString
val hermesVersionFile =
tempFolder.newFile("version.properties").apply {
writeText(
"""
HERMES_V1_VERSION_NAME=
ANOTHER_PROPERTY=true
"""
.trimIndent()
)
}
assertThat(groupString).isEqualTo("com.facebook.react")
val strings = readVersionAndGroupStrings(propertiesFile, hermesVersionFile)
val reactGroupString = strings.reactGroupString
val hermesGroupString = strings.hermesGroupString
assertThat(reactGroupString).isEqualTo("com.facebook.react")
assertThat(hermesGroupString).isEqualTo("com.facebook.hermes")
}
@Test
@@ -166,7 +166,7 @@ class PathUtilsTest {
@WithOs(OS.MAC)
fun detectOSAwareHermesCommand_withHermesV1Enabled() {
tempFolder.newFolder("node_modules/hermes-compiler/osx-bin/")
val expected = tempFolder.newFile("node_modules/hermes-compiler/osx-bin//hermesc")
val expected = tempFolder.newFile("node_modules/hermes-compiler/osx-bin/hermesc")
assertThat(detectOSAwareHermesCommand(tempFolder.root, "", hermesV1Enabled = true))
.isEqualTo(expected.toString())
@@ -264,7 +264,7 @@ const Switch: component(
disabled,
onTintColor: trackColorForTrue,
style: StyleSheet.compose(
{height: 31, width: 51},
{alignSelf: 'flex-start' as const},
StyleSheet.compose(
style,
ios_backgroundColor == null
@@ -29,6 +29,16 @@ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
supportedCommands: ['hotspotUpdate', 'setPressed'],
});
/**
* `ViewNativeComponent` is an internal React Native host component, and is
* exported to provide lower-level access for libraries.
*
* @warning `<unstable_NativeView>` provides no semver guarantees and is not
* intended to be used in app code. Please use
* [`<View>`](https://reactnative.dev/docs/view) instead.
*/
// Additional note: Our long term plan is to reduce the overhead of the <Text>
// and <View> wrappers so that we no longer have any reason to export these APIs.
export default ViewNativeComponent;
export type ViewNativeComponentType = HostComponent<Props>;
@@ -30,7 +30,8 @@ UIImage *RCTBlurredImageWithRadius(UIImage *inputImage, CGFloat radius)
}].CGImage;
}
vImage_Buffer buffer1, buffer2;
vImage_Buffer buffer1;
vImage_Buffer buffer2;
buffer1.width = buffer2.width = CGImageGetWidth(imageRef);
buffer1.height = buffer2.height = CGImageGetHeight(imageRef);
buffer1.rowBytes = buffer2.rowBytes = CGImageGetBytesPerRow(imageRef);
@@ -212,7 +212,8 @@ BOOL RCTUpscalingRequired(
sourceSize.height *= scale;
// Calculate aspect ratios if needed (don't bother if resizeMode == stretch)
CGFloat aspect = 0.0, targetAspect = 0.0;
CGFloat aspect = 0.0;
CGFloat targetAspect = 0.0;
if (resizeMode != RCTResizeModeStretch) {
aspect = sourceSize.width / sourceSize.height;
targetAspect = destSize.width / destSize.height;
+5 -1
View File
@@ -35,7 +35,11 @@ export interface SectionBase<ItemT, SectionT = DefaultSectionT> {
renderItem?: SectionListRenderItem<ItemT, SectionT> | undefined;
ItemSeparatorComponent?: React.ComponentType<any> | null | undefined;
ItemSeparatorComponent?:
| React.ComponentType<any>
| React.ReactElement
| null
| undefined;
keyExtractor?: ((item: ItemT, index: number) => string) | undefined;
}
@@ -90,9 +90,15 @@ uint32_t RCTInterpolateColorInRange(CGFloat value, NSArray<NSNumber *> *inputRan
CGFloat inputMin = inputRange[rangeIndex].doubleValue;
CGFloat inputMax = inputRange[rangeIndex + 1].doubleValue;
CGFloat redMin, greenMin, blueMin, alphaMin;
CGFloat redMin;
CGFloat greenMin;
CGFloat blueMin;
CGFloat alphaMin;
[outputRange[rangeIndex] getRed:&redMin green:&greenMin blue:&blueMin alpha:&alphaMin];
CGFloat redMax, greenMax, blueMax, alphaMax;
CGFloat redMax;
CGFloat greenMax;
CGFloat blueMax;
CGFloat alphaMax;
[outputRange[rangeIndex + 1] getRed:&redMax green:&greenMax blue:&blueMax alpha:&alphaMax];
return RCTColorFromComponents(
@@ -50,6 +50,7 @@ Pod::Spec.new do |s|
add_dependency(s, "React-jsinspectorcdp", :framework_name => 'jsinspector_moderncdp')
add_dependency(s, "React-jsinspectornetwork", :framework_name => 'jsinspector_modernnetwork')
add_dependency(s, "React-NativeModulesApple", :additional_framework_paths => ["build/generated/ios"])
add_dependency(s, "React-networking", :framework_name => 'React_networking')
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
@@ -65,6 +65,16 @@ const virtualTextViewConfig = {
uiViewClassName: 'RCTVirtualText',
};
/**
* `NativeText` is an internal React Native host component, and is exported to
* provide lower-level access for libraries.
*
* @warning `<unstable_NativeText>` provides no semver guarantees and is not
* intended to be used in app code. Please use
* [`<Text>`](https://reactnative.dev/docs/text) instead.
*/
// Additional note: Our long term plan is to reduce the overhead of the <Text>
// and <View> wrappers so that we no longer have any reason to export these APIs.
export const NativeText: HostComponent<NativeTextProps> =
(createReactNativeComponentClass('RCTText', () =>
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
+17 -1
View File
@@ -68,6 +68,17 @@ let reactOSCompat = RNTarget(
path: "ReactCommon/oscompat"
)
let rctSwiftUI = RNTarget(
name: .rctSwiftUI,
path: "ReactApple/RCTSwiftUI"
)
let rctSwiftUIWrapper = RNTarget(
name: .rctSwiftUIWrapper,
path: "ReactApple/RCTSwiftUIWrapper",
dependencies: [.rctSwiftUI]
)
// React-rendererconsistency.podspec
let reactRendererConsistency = RNTarget(
name: .reactRendererConsistency,
@@ -439,7 +450,7 @@ let reactFabric = RNTarget(
let reactRCTFabric = RNTarget(
name: .reactRCTFabric,
path: "React/Fabric",
dependencies: [.reactNativeDependencies, .reactCore, .reactRCTImage, .yoga, .reactRCTText, .jsi, .reactFabricComponents, .reactGraphics, .reactImageManager, .reactDebug, .reactUtils, .reactPerformanceTimeline, .reactRendererDebug, .reactRendererConsistency, .reactRuntimeScheduler, .reactRCTAnimation, .reactJsInspector, .reactJsInspectorNetwork, .reactJsInspectorTracing, .reactFabric, .reactFabricImage]
dependencies: [.reactNativeDependencies, .reactCore, .reactRCTImage, .yoga, .reactRCTText, .jsi, .reactFabricComponents, .reactGraphics, .reactImageManager, .reactDebug, .reactUtils, .reactPerformanceTimeline, .reactRendererDebug, .reactRendererConsistency, .reactRuntimeScheduler, .reactRCTAnimation, .reactJsInspector, .reactJsInspectorNetwork, .reactJsInspectorTracing, .reactFabric, .reactFabricImage, .rctSwiftUIWrapper]
)
/// React-FabricComponents.podspec
@@ -579,6 +590,8 @@ let targets = [
reactCore,
reactCoreRCTWebsocket,
reactFabric,
rctSwiftUI,
rctSwiftUIWrapper,
reactRCTFabric,
reactFabricComponents,
reactFabricImage,
@@ -728,6 +741,9 @@ extension String {
static let logger = "React-logger"
static let mapbuffer = "React-Mapbuffer"
static let rctSwiftUI = "RCTSwiftUI"
static let rctSwiftUIWrapper = "RCTSwiftUIWrapper"
static let rctDeprecation = "RCT-Deprecation"
static let yoga = "Yoga"
static let reactUtils = "React-utils"
@@ -69,6 +69,7 @@ RCT_EXTERN_C_END
* will be used as the JS module name. If omitted, the JS module name will
* match the Objective-C class name.
*/
#ifndef RCT_FIT_RM_OLD_RUNTIME
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+(NSString *)moduleName \
@@ -80,6 +81,17 @@ RCT_EXTERN_C_END
RCTRegisterModule(self); \
}
#else
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+(NSString *)moduleName \
{ \
return @ #js_name; \
}
#endif // RCT_FIT_RM_OLD_RUNTIME
/**
* Same as RCT_EXPORT_MODULE, but uses __attribute__((constructor)) for module
* registration. Useful for registering swift classes that forbids use of load
@@ -81,7 +81,8 @@
return;
}
CGFloat top = 0, left = 0;
CGFloat top = 0;
CGFloat left = 0;
if (contentSize.width < boundsSize.width) {
left = (boundsSize.width - contentSize.width) * 0.5f;
}
@@ -12,6 +12,7 @@
#import <objc/runtime.h>
#import <ranges>
#import <RCTSwiftUIWrapper/RCTSwiftUIContainerViewWrapper.h>
#import <React/RCTAssert.h>
#import <React/RCTBorderDrawing.h>
#import <React/RCTBoxShadow.h>
@@ -50,6 +51,7 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
UIView *_containerView;
BOOL _useCustomContainerView;
NSMutableSet<NSString *> *_accessibilityOrderNativeIDs;
RCTSwiftUIContainerViewWrapper *_swiftUIWrapper;
}
#ifdef RCT_DYNAMIC_FRAMEWORKS
@@ -576,6 +578,10 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
auto newTransform = _props->resolveTransform(layoutMetrics);
self.layer.transform = RCTCATransform3DFromTransformMatrix(newTransform);
}
if (_swiftUIWrapper != nullptr) {
[_swiftUIWrapper updateLayoutWithBounds:self.bounds];
}
}
- (BOOL)isJSResponder
@@ -793,43 +799,95 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
((!_props->boxShadow.empty() || (clipToPaddingBox && nonZeroBorderWidth)) || _props->outlineWidth != 0);
}
// The view that is used as the receiver for all styling (borders, background,
// etc.). Most of the time, this is just `self`. When a view has a filter like
// `blur` applied, we need to wrap it in a SwiftUI view to render the effect.
// In this case, `effectiveContentView` will be the content view inside the
// SwiftUI wrapper.
- (UIView *)effectiveContentView
{
if (!ReactNativeFeatureFlags::enableSwiftUIBasedFilters()) {
return self;
}
UIView *effectiveContentView = self;
if (self.styleNeedsSwiftUIContainer) {
if (_swiftUIWrapper == nullptr) {
_swiftUIWrapper = [RCTSwiftUIContainerViewWrapper new];
UIView *swiftUIContentView = [[UIView alloc] init];
for (UIView *subview = nullptr in self.subviews) {
[swiftUIContentView addSubview:subview];
}
swiftUIContentView.clipsToBounds = self.clipsToBounds;
self.clipsToBounds = NO;
swiftUIContentView.layer.mask = self.layer.mask;
self.layer.mask = nil;
[_swiftUIWrapper updateContentView:swiftUIContentView];
[_swiftUIWrapper updateLayoutWithBounds:self.bounds];
[self addSubview:_swiftUIWrapper.hostingView];
[self transferVisualPropertiesFromView:self toView:swiftUIContentView];
}
effectiveContentView = _swiftUIWrapper.contentView;
} else {
if (_swiftUIWrapper != nullptr) {
UIView *swiftUIContentView = _swiftUIWrapper.contentView;
for (UIView *subview = nullptr in swiftUIContentView.subviews) {
[self addSubview:subview];
}
self.clipsToBounds = swiftUIContentView.clipsToBounds;
self.layer.mask = swiftUIContentView.layer.mask;
[self transferVisualPropertiesFromView:swiftUIContentView toView:self];
[_swiftUIWrapper.hostingView removeFromSuperview];
_swiftUIWrapper = nil;
}
}
return effectiveContentView;
}
// This UIView is the UIView that holds all subviews. It is sometimes not self
// because we want to render "overflow ink" that extends beyond the bounds of
// the view and is not affected by clipping.
- (UIView *)currentContainerView
{
UIView *effectiveContentView = self.effectiveContentView;
if (_useCustomContainerView) {
if (!_containerView) {
_containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
for (UIView *subview in self.subviews) {
for (UIView *subview = nullptr in effectiveContentView.subviews) {
[_containerView addSubview:subview];
}
_containerView.clipsToBounds = self.clipsToBounds;
self.clipsToBounds = NO;
_containerView.layer.mask = self.layer.mask;
self.layer.mask = nil;
[self addSubview:_containerView];
_containerView.clipsToBounds = effectiveContentView.clipsToBounds;
effectiveContentView.clipsToBounds = NO;
_containerView.layer.mask = effectiveContentView.layer.mask;
effectiveContentView.layer.mask = nil;
[effectiveContentView addSubview:_containerView];
}
return _containerView;
effectiveContentView = _containerView;
} else {
if (_containerView) {
for (UIView *subview in _containerView.subviews) {
[self addSubview:subview];
[effectiveContentView addSubview:subview];
}
self.clipsToBounds = _containerView.clipsToBounds;
self.layer.mask = _containerView.layer.mask;
effectiveContentView.clipsToBounds = _containerView.clipsToBounds;
effectiveContentView.layer.mask = _containerView.layer.mask;
[_containerView removeFromSuperview];
_containerView = nil;
}
return self;
}
return effectiveContentView;
}
- (void)invalidateLayer
{
CALayer *layer = self.layer;
CALayer *layer = self.effectiveContentView.layer;
if (CGSizeEqualToSize(layer.bounds.size, CGSizeZero)) {
return;
@@ -910,7 +968,7 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
if (!_backgroundColorLayer) {
_backgroundColorLayer = [CALayer layer];
_backgroundColorLayer.zPosition = BACKGROUND_COLOR_ZPOSITION;
[self.layer addSublayer:_backgroundColorLayer];
[layer addSublayer:_backgroundColorLayer];
}
[self shapeLayerToMatchView:_backgroundColorLayer borderMetrics:borderMetrics];
_backgroundColorLayer.backgroundColor = backgroundColor.CGColor;
@@ -986,31 +1044,43 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
// filter
[_filterLayer removeFromSuperlayer];
_filterLayer = nil;
if (_swiftUIWrapper != nullptr) {
[_swiftUIWrapper updateBlurRadius:@(0)];
}
self.layer.opacity = (float)_props->opacity;
if (!_props->filter.empty()) {
float multiplicativeBrightness = 1;
bool hasBrightnessFilter = false;
for (const auto &primitive : _props->filter) {
if (std::holds_alternative<Float>(primitive.parameters)) {
if (primitive.type == FilterType::Brightness) {
multiplicativeBrightness *= std::get<Float>(primitive.parameters);
hasBrightnessFilter = true;
} else if (primitive.type == FilterType::Opacity) {
self.layer.opacity *= std::get<Float>(primitive.parameters);
} else if (primitive.type == FilterType::Blur) {
if (_swiftUIWrapper != nullptr) {
Float blurRadius = std::get<Float>(primitive.parameters);
[_swiftUIWrapper updateBlurRadius:@(blurRadius)];
}
}
}
}
_filterLayer = [CALayer layer];
[self shapeLayerToMatchView:_filterLayer borderMetrics:borderMetrics];
_filterLayer.compositingFilter = @"multiplyBlendMode";
_filterLayer.backgroundColor = [UIColor colorWithRed:multiplicativeBrightness
green:multiplicativeBrightness
blue:multiplicativeBrightness
alpha:self.layer.opacity]
.CGColor;
// So that this layer is always above any potential sublayers this view may
// add
_filterLayer.zPosition = CGFLOAT_MAX;
[self.layer addSublayer:_filterLayer];
if (hasBrightnessFilter) {
_filterLayer = [CALayer layer];
[self shapeLayerToMatchView:_filterLayer borderMetrics:borderMetrics];
_filterLayer.compositingFilter = @"multiplyBlendMode";
_filterLayer.backgroundColor = [UIColor colorWithRed:multiplicativeBrightness
green:multiplicativeBrightness
blue:multiplicativeBrightness
alpha:self.layer.opacity]
.CGColor;
// So that this layer is always above any potential sublayers this view may
// add
_filterLayer.zPosition = CGFLOAT_MAX;
[layer addSublayer:_filterLayer];
}
}
// background image
@@ -1025,7 +1095,7 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
[self shapeLayerToMatchView:backgroundImageLayer borderMetrics:borderMetrics];
backgroundImageLayer.masksToBounds = YES;
backgroundImageLayer.zPosition = BACKGROUND_COLOR_ZPOSITION;
[self.layer addSublayer:backgroundImageLayer];
[layer addSublayer:backgroundImageLayer];
[_backgroundImageLayers addObject:backgroundImageLayer];
} else if (std::holds_alternative<RadialGradient>(backgroundImage)) {
const auto &radialGradient = std::get<RadialGradient>(backgroundImage);
@@ -1034,7 +1104,7 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
[self shapeLayerToMatchView:backgroundImageLayer borderMetrics:borderMetrics];
backgroundImageLayer.masksToBounds = YES;
backgroundImageLayer.zPosition = BACKGROUND_COLOR_ZPOSITION;
[self.layer addSublayer:backgroundImageLayer];
[layer addSublayer:backgroundImageLayer];
[_backgroundImageLayers addObject:backgroundImageLayer];
}
}
@@ -1056,7 +1126,7 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
self.layer.bounds.size);
shadowLayer.zPosition = _borderLayer.zPosition;
[self.layer addSublayer:shadowLayer];
[layer addSublayer:shadowLayer];
[_boxShadowLayers addObject:shadowLayer];
}
}
@@ -1410,6 +1480,66 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
return RCTNSStringFromString([[self class] componentDescriptorProvider].name);
}
- (BOOL)styleNeedsSwiftUIContainer
{
if (!_props->filter.empty()) {
for (const auto &primitive : _props->filter) {
if (primitive.type == FilterType::Blur) {
return YES;
}
}
}
return NO;
}
- (void)transferVisualPropertiesFromView:(UIView *)sourceView toView:(UIView *)destinationView
{
// shadow
destinationView.layer.shadowColor = sourceView.layer.shadowColor;
sourceView.layer.shadowColor = nil;
destinationView.layer.shadowOffset = sourceView.layer.shadowOffset;
sourceView.layer.shadowOffset = CGSizeZero;
destinationView.layer.shadowOpacity = sourceView.layer.shadowOpacity;
sourceView.layer.shadowOpacity = 0;
destinationView.layer.shadowRadius = sourceView.layer.shadowRadius;
sourceView.layer.shadowRadius = 0;
// background
destinationView.layer.backgroundColor = sourceView.layer.backgroundColor;
sourceView.layer.backgroundColor = nil;
if (_backgroundColorLayer != nullptr) {
[destinationView.layer addSublayer:_backgroundColorLayer];
}
// border
destinationView.layer.borderColor = sourceView.layer.borderColor;
sourceView.layer.borderColor = nil;
destinationView.layer.borderWidth = sourceView.layer.borderWidth;
sourceView.layer.borderWidth = 0;
// corner
destinationView.layer.cornerRadius = sourceView.layer.cornerRadius;
sourceView.layer.cornerRadius = 0;
destinationView.layer.cornerCurve = sourceView.layer.cornerCurve;
// custom layers
if (_borderLayer != nullptr) {
[destinationView.layer addSublayer:_borderLayer];
}
if (_outlineLayer != nullptr) {
[destinationView.layer addSublayer:_outlineLayer];
}
if (_filterLayer != nullptr) {
[destinationView.layer addSublayer:_filterLayer];
}
for (CALayer *layer = nullptr in _backgroundImageLayers) {
[destinationView.layer addSublayer:layer];
}
for (CALayer *layer = nullptr in _boxShadowLayers) {
[destinationView.layer addSublayer:layer];
}
}
@end
#ifdef __cplusplus
@@ -63,6 +63,7 @@ Pod::Spec.new do |s|
s.dependency "Yoga"
s.dependency "React-RCTText"
s.dependency "React-jsi"
s.dependency "RCTSwiftUIWrapper"
add_dependency(s, "React-FabricImage")
add_dependency(s, "React-Fabric", :additional_framework_paths => [
@@ -80,7 +80,9 @@ static void RCTPathAddEllipticArc(
CGFloat endAngle,
BOOL clockwise)
{
CGFloat xScale = 1, yScale = 1, radius = 0;
CGFloat xScale = 1;
CGFloat yScale = 1;
CGFloat radius = 0;
if (size.width != 0) {
xScale = 1;
yScale = size.height / size.width;
@@ -0,0 +1,16 @@
/*
* 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.
*/
#import <UIKit/UIKit.h>
#import <React/RCTDefines.h>
NS_ASSUME_NONNULL_BEGIN
RCT_EXTERN UIFont *__nullable RCTGetLegacyDefaultFont(CGFloat fontSize, UIFontWeight fontWeight);
NS_ASSUME_NONNULL_END
+3 -1
View File
@@ -17,8 +17,10 @@ typedef CGFloat RCTFontWeight;
* provide a different base font, use this override. The font weight supplied to your
* handler will be one of "ultralight", "thin", "light", "regular", "medium",
* "semibold", "extrabold", "bold", "heavy", or "black".
*
* @deprecated Use RCTSetDefaultFontResolver
*/
RCT_EXTERN void RCTSetDefaultFontHandler(RCTFontHandler handler);
RCT_EXTERN void RCTSetDefaultFontHandler(RCTFontHandler handler) __attribute__((deprecated));
RCT_EXTERN BOOL RCTHasFontHandlerSet(void);
RCT_EXTERN RCTFontWeight RCTGetFontWeight(UIFont *font);
+13 -2
View File
@@ -6,6 +6,8 @@
*/
#import "RCTFont.h"
#import "RCTFont+Private.h"
#import "RCTAssert.h"
#import "RCTLog.h"
@@ -122,6 +124,15 @@ static NSString *FontWeightDescriptionFromUIFontWeight(UIFontWeight fontWeight)
return @"regular";
}
UIFont *RCTGetLegacyDefaultFont(CGFloat size, UIFontWeight fontWeight)
{
if (defaultFontHandler != nil) {
return defaultFontHandler(size, FontWeightDescriptionFromUIFontWeight(fontWeight));
} else {
return nil;
}
}
static UIFont *cachedSystemFont(CGFloat size, RCTFontWeight weight)
{
static NSCache<NSValue *, UIFont *> *fontCache = [NSCache new];
@@ -135,8 +146,8 @@ static UIFont *cachedSystemFont(CGFloat size, RCTFontWeight weight)
NSValue *cacheKey = [[NSValue alloc] initWithBytes:&key objCType:@encode(CacheKey)];
UIFont *font = [fontCache objectForKey:cacheKey];
if (!font) {
if (defaultFontHandler) {
if (font == nil) {
if (defaultFontHandler != nil) {
NSString *fontWeightDescription = FontWeightDescriptionFromUIFontWeight(weight);
font = defaultFontHandler(size, fontWeightDescription);
} else {
@@ -679,7 +679,8 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidScrollToTop, onScrollToTop)
return;
}
CGFloat top = 0, left = 0;
CGFloat top = 0;
CGFloat left = 0;
if (contentSize.width < boundsSize.width) {
left = (boundsSize.width - contentSize.width) * 0.5f;
}
@@ -5246,6 +5246,7 @@ public class com/facebook/react/viewmanagers/VirtualViewExperimentalManagerDeleg
public abstract interface class com/facebook/react/viewmanagers/VirtualViewExperimentalManagerInterface : com/facebook/react/uimanager/ViewManagerWithGeneratedInterface {
public abstract fun setInitialHidden (Landroid/view/View;Z)V
public abstract fun setRemoveClippedSubviews (Landroid/view/View;Z)V
public abstract fun setRenderState (Landroid/view/View;I)V
}
@@ -6725,9 +6726,10 @@ public final class com/facebook/react/views/virtual/viewexperimental/ReactVirtua
public fun onLayoutChange (Landroid/view/View;IIIIIIII)V
public fun onModeChange (Lcom/facebook/react/views/virtual/VirtualViewMode;Landroid/graphics/Rect;)V
public synthetic fun recycleView$xplat_js_react_native_github_packages_react_native_ReactAndroid_src_main_java_com_facebook_react_views_view_viewAndroid ()V
public fun updateClippingRect (Ljava/util/Set;)V
}
public final class com/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimentalManager : com/facebook/react/uimanager/ViewGroupManager, com/facebook/react/viewmanagers/VirtualViewExperimentalManagerInterface {
public final class com/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimentalManager : com/facebook/react/views/view/ReactClippingViewManager, com/facebook/react/viewmanagers/VirtualViewExperimentalManagerInterface {
public static final field Companion Lcom/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimentalManager$Companion;
public static final field REACT_CLASS Ljava/lang/String;
public fun <init> ()V
@@ -6739,6 +6741,7 @@ public final class com/facebook/react/views/virtual/viewexperimental/ReactVirtua
public fun setInitialHidden (Lcom/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimental;Z)V
public synthetic fun setNativeId (Landroid/view/View;Ljava/lang/String;)V
public fun setNativeId (Lcom/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimental;Ljava/lang/String;)V
public synthetic fun setRemoveClippedSubviews (Landroid/view/View;Z)V
public synthetic fun setRenderState (Landroid/view/View;I)V
public fun setRenderState (Lcom/facebook/react/views/virtual/viewexperimental/ReactVirtualViewExperimental;I)V
}
@@ -1,5 +1,4 @@
VERSION_NAME=1000.0.0
HERMES_VERSION_NAME=1000.0.0
react.internal.publishingGroup=com.facebook.react
react.internal.hermesPublishingGroup=com.facebook.hermes
@@ -196,11 +196,9 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
private val nodesManagerRef = AtomicReference<NativeAnimatedNodesManager?>()
private var batchingControlledByJS = false // TODO T71377544: delete
@Volatile private var currentFrameNumber: Long = 0
@Volatile private var currentFrameNumber: Long = 0 // TODO T71377544: delete
@Volatile private var currentBatchNumber: Long = 0
@Volatile private var currentBatchNumber: Long = 0 // frame number at last operations dispatch
private var initializedForFabric = false
private var initializedForNonFabric = false
@@ -282,22 +280,19 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
var batchNumber = currentBatchNumber - 1
// TODO T71377544: delete this when the JS method is confirmed safe
if (!batchingControlledByJS) {
// The problem we're trying to solve here: we could be in the middle of queueing
// a batch of related animation operations when Fabric flushes a batch of MountItems.
// It's visually bad if we execute half of the animation ops and then wait another frame
// (or more) to execute the rest.
// See mFrameNumber. If the dispatchedFrameNumber drifts too far - that
// is, if no MountItems are scheduled for a while, which can happen if a tree
// is committed but there are no changes - bring these counts back in sync and
// execute any queued operations. This number is arbitrary, but we want it low
// enough that the user shouldn't be able to see this delay in most cases.
currentFrameNumber++
if ((currentFrameNumber - currentBatchNumber) > 2) {
currentBatchNumber = currentFrameNumber
batchNumber = currentBatchNumber
}
// The problem we're trying to solve here: we could be in the middle of queueing
// a batch of related animation operations when Fabric flushes a batch of MountItems.
// It's visually bad if we execute half of the animation ops and then wait another frame
// (or more) to execute the rest.
// See mFrameNumber. If the dispatchedFrameNumber drifts too far - that
// is, if no MountItems are scheduled for a while, which can happen if a tree
// is committed but there are no changes - bring these counts back in sync and
// execute any queued operations. This number is arbitrary, but we want it low
// enough that the user shouldn't be able to see this delay in most cases.
currentFrameNumber++
if ((currentFrameNumber - currentBatchNumber) > 2) {
currentBatchNumber = currentFrameNumber
batchNumber = currentBatchNumber
}
preOperations.executeBatch(batchNumber, nodesManager)
@@ -484,14 +479,14 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
}
}
@Suppress("DEPRECATION")
override fun startOperationBatch() {
batchingControlledByJS = true
currentBatchNumber++
// no-op
}
@Suppress("DEPRECATION")
override fun finishOperationBatch() {
batchingControlledByJS = false
currentBatchNumber++
// no-op
}
override fun createAnimatedNode(tagDouble: Double, config: ReadableMap) {
@@ -774,12 +774,8 @@ public class NativeAnimatedNodesManager(
("Looks like animated nodes graph has ${reason}, there are $activeNodesCount but toposort visited only $updatedNodesCount")
)
if (eventListenerInitializedForFabric && cyclesDetected == 0) {
// TODO T71377544: investigate these SoftExceptions and see if we can remove entirely
// or fix the root cause
ReactSoftExceptionLogger.logSoftException(TAG, ReactNoCrashSoftException(ex))
} else if (eventListenerInitializedForFabric) {
// TODO T71377544: investigate these SoftExceptions and see if we can remove entirely
// or fix the root cause
ReactSoftExceptionLogger.logSoftException(TAG, ReactNoCrashSoftException(ex))
} else {
throw ex
@@ -96,7 +96,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
private final TraceListener mTraceListener;
private final JavaScriptModuleRegistry mJSModuleRegistry;
private final JSBundleLoader mJSBundleLoader;
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<PendingJSCall>();
private final ArrayList<PendingJSCall> mJSCallsPendingInit = new ArrayList<>();
private final Object mJSCallsPendingInitLock = new Object();
private final NativeModuleRegistry mNativeModuleRegistry;
@@ -25,7 +25,7 @@ public abstract class ReactContextBaseJavaModule : BaseJavaModule {
* this method whenever you actually need the Activity and make sure to check for `null`.
*/
@Deprecated(
"Deprecated in 0.80.0. Use getReactApplicationContext.getCurrentActivity() instead.",
"Deprecated in 0.80.0. Use getReactApplicationContext().getCurrentActivity() instead.",
ReplaceWith("reactApplicationContext.currentActivity"),
)
protected fun getCurrentActivity(): Activity? {
@@ -387,10 +387,14 @@ public abstract class DevSupportManagerBase(
TracingState.ENABLEDINBACKGROUNDMODE ->
DevOptionHandler {
UiThreadUtil.runOnUiThread {
if (reactInstanceDevHelper is PerfMonitorDevHelper)
reactInstanceDevHelper.inspectorTarget?.pauseAndAnalyzeBackgroundTrace()
if (reactInstanceDevHelper is PerfMonitorDevHelper) {
reactInstanceDevHelper.inspectorTarget?.let {
if (it.pauseAndAnalyzeBackgroundTrace()) {
openDebugger(DebuggerFrontendPanelName.PERFORMANCE.toString())
}
}
}
}
openDebugger(DebuggerFrontendPanelName.PERFORMANCE.toString())
}
TracingState.DISABLED ->
DevOptionHandler {
@@ -17,8 +17,11 @@ internal interface PerfMonitorInspectorTargetBinding {
/** Get the current CDP or background performance tracing state. */
public fun getTracingState(): TracingState
/** Attempt to pause the current background performance trace, and open in DevTools. */
public fun pauseAndAnalyzeBackgroundTrace()
/**
* Attempt to pause the current background performance trace, and open in DevTools. Returns true
* if there is an active session that can display the trace, false otherwise.
*/
public fun pauseAndAnalyzeBackgroundTrace(): Boolean
/** Attempt to start a new background performance trace. */
public fun resumeBackgroundTrace()
@@ -43,29 +43,23 @@ internal class PerfMonitorOverlayManager(
fun enable() {
enabled = true
init()
UiThreadUtil.runOnUiThread { view?.show() }
}
/** Disable the Perf Monitor overlay. Will remain hidden when updates are received. */
fun disable() {
UiThreadUtil.runOnUiThread { view?.hide() }
view = null
enabled = false
}
/** Reset the Perf Monitor overlay, e.g. after a reload. */
fun reset() {
UiThreadUtil.runOnUiThread { view?.resetState() }
// Update with current recording state
onRecordingStateChanged(
devHelper.inspectorTarget?.getTracingState() ?: TracingState.ENABLEDINCDPMODE
)
}
override fun onNewFocusedEvent(data: PerfMonitorUpdateListener.LongTaskEventData) {
view?.updateFocusedEvent(data)
}
override fun onRecordingStateChanged(state: TracingState) {
tracingState = state
view?.updateRecordingState(state)
@@ -74,8 +68,11 @@ internal class PerfMonitorOverlayManager(
private fun handleRecordingButtonPress() {
when (tracingState) {
TracingState.ENABLEDINBACKGROUNDMODE -> {
devHelper.inspectorTarget?.pauseAndAnalyzeBackgroundTrace()
onRequestOpenDevTools()
devHelper.inspectorTarget?.let {
if (!it.pauseAndAnalyzeBackgroundTrace()) {
onRequestOpenDevTools()
}
}
}
TracingState.DISABLED -> {
devHelper.inspectorTarget?.resumeBackgroundTrace()
@@ -13,8 +13,6 @@ import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.Window
import android.view.WindowManager
@@ -26,194 +24,93 @@ import com.facebook.react.R
import com.facebook.react.devsupport.interfaces.TracingState
import com.facebook.react.uimanager.DisplayMetricsHolder
import com.facebook.react.uimanager.PixelUtil
import java.util.Locale
internal class PerfMonitorOverlayView(
private val context: Context,
private val onButtonPress: () -> Unit,
) {
private var hidden: Boolean = true
private var hasEventData: Boolean = false
private val metricsDialog: Dialog
private val toolbarDialog: Dialog
private val tooltipDialog: Dialog
private lateinit var buttonLabel: TextView
private lateinit var recordingStateLabel: TextView
private lateinit var durationLabel: TextView
private val dialog: Dialog
private lateinit var statusLabel: TextView
private lateinit var tooltipLabel: TextView
private var ttl: Int = 0
private var hideAfterTimeoutHandler: Handler? = null
private lateinit var statusIndicator: TextView
init {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)
tooltipDialog = createTooltipDialog()
metricsDialog = createMetricsDialog()
toolbarDialog = createToolbarDialog()
dialog = createToolbarDialog()
}
fun show() {
toolbarDialog.show()
tooltipDialog.show()
if (hasEventData) {
toolbarDialog.window?.decorView?.post { updateMetricsDialogPosition() }
metricsDialog.show()
}
hidden = false
dialog.show()
}
fun hide() {
metricsDialog.hide()
toolbarDialog.hide()
tooltipDialog.hide()
hidden = true
}
fun resetState() {
hasEventData = false
metricsDialog.hide()
}
fun updateFocusedEvent(data: PerfMonitorUpdateListener.LongTaskEventData) {
durationLabel.text = String.format(Locale.US, "%d ms", data.durationMs)
durationLabel.setTextColor(getDurationHighlightColor(data.responsivenessScore))
hasEventData = true
ttl = data.ttl
hideAfterTimeoutHandler?.removeCallbacksAndMessages(null)
if (!hidden) {
metricsDialog.show()
// Schedule hiding metrics overlay after ttl milliseconds
if (ttl > 0) {
if (hideAfterTimeoutHandler == null) {
hideAfterTimeoutHandler = Handler(Looper.getMainLooper())
}
hideAfterTimeoutHandler?.postDelayed({ metricsDialog.hide() }, ttl.toLong())
}
}
dialog.hide()
}
fun updateRecordingState(state: TracingState) {
recordingStateLabel.text =
when (state) {
TracingState.ENABLEDINBACKGROUNDMODE -> "Profiling: ON"
TracingState.DISABLED -> "Profiling: OFF"
TracingState.ENABLEDINCDPMODE -> "Profiling: DISABLED"
}
buttonLabel.text =
when (state) {
TracingState.ENABLEDINBACKGROUNDMODE -> "Open ↗️"
TracingState.DISABLED -> "Start"
TracingState.ENABLEDINCDPMODE -> ""
}
tooltipLabel.text =
when (state) {
TracingState.ENABLEDINBACKGROUNDMODE -> "Dev Menu > Finish performance trace"
TracingState.DISABLED -> "Dev Menu > Start performance trace"
TracingState.ENABLEDINCDPMODE -> ""
}
if (state == TracingState.ENABLEDINCDPMODE) {
tooltipDialog.hide()
dialog.hide()
return
}
if (state == TracingState.ENABLEDINBACKGROUNDMODE) {
(statusIndicator.background as GradientDrawable).setColor(Color.RED)
statusLabel.text = "Background Profiling Active"
tooltipLabel.text = "Press ☰ to open"
} else {
tooltipDialog.show()
(statusIndicator.background as GradientDrawable).setColor(Color.GRAY)
statusLabel.text = "Background Profiling Stopped"
tooltipLabel.text = "Press ☰ to restart"
}
toolbarDialog.window?.decorView?.post { updateMetricsDialogPosition() }
}
private fun createMetricsDialog(): Dialog {
val containerLayout = createInnerLayout()
val longTaskLabel =
TextView(context).apply {
textSize = TEXT_SIZE_PRIMARY
text = "Long Task"
setTextColor(Color.WHITE)
typeface = TYPEFACE_BOLD
}
durationLabel =
TextView(context).apply {
textSize = TEXT_SIZE_PRIMARY
setTextColor(COLOR_TEXT_GREEN)
typeface = TYPEFACE_BOLD
}
containerLayout.addView(longTaskLabel)
containerLayout.addView(durationLabel)
val dialog =
createAnchoredDialog(getMetricsDialogOffsetX(), dpToPx(16f)).apply {
setContentView(containerLayout)
}
dialog.window?.apply {
attributes =
attributes?.apply {
flags =
flags or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
}
}
return dialog
dialog.show()
}
private fun createToolbarDialog(): Dialog {
val buttonInner = createInnerLayout()
recordingStateLabel =
statusIndicator =
TextView(context).apply {
width = dpToPx(12f).toInt()
height = dpToPx(12f).toInt()
background =
GradientDrawable().apply {
shape = GradientDrawable.OVAL
setColor(Color.RED)
}
}
val textContainer =
LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
layoutParams =
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT,
)
}
statusLabel =
TextView(context).apply {
textSize = TEXT_SIZE_PRIMARY
setTextColor(Color.WHITE)
typeface = TYPEFACE_BOLD
}
buttonInner.addView(recordingStateLabel)
buttonLabel =
TextView(context).apply {
textSize = TEXT_SIZE_PRIMARY
setTextColor(COLOR_TEXT_BLUE)
typeface = TYPEFACE_BOLD
}
buttonInner.addView(buttonLabel)
val buttonView =
LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(
dpToPx(8f).toInt(),
dpToPx(16f).toInt(),
dpToPx(16f).toInt(),
dpToPx(8f).toInt(),
)
addView(buttonInner)
setOnClickListener { onButtonPress() }
}
val dialog = createAnchoredDialog(dpToPx(0f), dpToPx(0f)).apply { setContentView(buttonView) }
dialog.window?.apply {
attributes =
attributes?.apply { flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE }
}
return dialog
}
private fun createTooltipDialog(): Dialog {
val containerLayout = createInnerLayout()
tooltipLabel =
TextView(context).apply {
textSize = TEXT_SIZE_ACCESSORY
setTextColor(Color.WHITE)
typeface = TYPEFACE_BOLD
}
containerLayout.addView(tooltipLabel)
textContainer.addView(statusLabel)
textContainer.addView(tooltipLabel)
val containerLayout = createInnerLayout()
containerLayout.setOnClickListener { onButtonPress() }
containerLayout.addView(statusIndicator)
containerLayout.addView(textContainer)
val dialog =
createAnchoredDialog(dpToPx(16f), dpToPx(52f)).apply { setContentView(containerLayout) }
createAnchoredDialog(dpToPx(12f), dpToPx(12f)).apply { setContentView(containerLayout) }
dialog.window?.apply {
attributes =
attributes?.apply {
flags =
flags or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
}
attributes?.apply { flags = flags or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE }
}
return dialog
@@ -278,35 +175,12 @@ internal class PerfMonitorOverlayView(
}
}
private fun getMetricsDialogOffsetX(): Float {
val toolbarWidth = toolbarDialog?.window?.decorView?.width ?: 0
return toolbarWidth.toFloat()
}
private fun updateMetricsDialogPosition() {
metricsDialog?.window?.apply {
attributes = attributes?.apply { x = getMetricsDialogOffsetX().toInt() }
}
}
private fun getDurationHighlightColor(responsivenessScore: Int): Int {
return when (responsivenessScore) {
2 -> COLOR_TEXT_RED
1 -> COLOR_TEXT_YELLOW
else -> COLOR_TEXT_GREEN
}
}
private fun dpToPx(dp: Float): Float = PixelUtil.toPixelFromDIP(dp)
companion object {
private val COLOR_TEXT_GREEN = Color.parseColor("#4AEB2F")
private val COLOR_TEXT_YELLOW = Color.parseColor("#FFAA00")
private val COLOR_TEXT_RED = Color.parseColor("#FF0000")
private val COLOR_TEXT_BLUE = Color.parseColor("#00B0FF")
private val COLOR_OVERLAY_BORDER = Color.parseColor("#6C6C6C")
private val TEXT_SIZE_PRIMARY = 13f
private val TEXT_SIZE_ACCESSORY = 9f
private val TEXT_SIZE_PRIMARY = 12f
private val TEXT_SIZE_ACCESSORY = 10f
private val TYPEFACE_BOLD = Typeface.create("sans-serif", Typeface.BOLD)
}
}
@@ -10,15 +10,6 @@ import com.facebook.react.devsupport.interfaces.TracingState
/** [Experimental] An interface for subscribing to updates for the V2 Perf Monitor. */
internal interface PerfMonitorUpdateListener {
data class LongTaskEventData(
val durationMs: Int,
val responsivenessScore: Int,
val ttl: Int,
)
/** Called when a new active performance event should be displayed. */
fun onNewFocusedEvent(data: LongTaskEventData)
/** Called when the recording state of the background performance trace has changed. */
fun onRecordingStateChanged(state: TracingState)
}
@@ -99,7 +99,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -738,11 +737,17 @@ public class FabricUIManager
* vertical insets.
*/
private long getEncodedScreenSizeWithoutVerticalInsets(int surfaceId) {
SurfaceMountingManager surfaceMountingManager = mMountingManager.getSurfaceManager(surfaceId);
Objects.requireNonNull(surfaceMountingManager);
ThemedReactContext context = Objects.requireNonNull(surfaceMountingManager.getContext());
return DisplayMetricsHolder.getEncodedScreenSizeWithoutVerticalInsets(
context.getCurrentActivity());
ThemedReactContext context =
mMountingManager
.getSurfaceManagerEnforced(surfaceId, "getEncodedScreenSizeWithoutVerticalInsets")
.getContext();
if (context == null) {
FLog.w(TAG, "Couldn't get context from SurfaceMountingManager for surfaceId %d", surfaceId);
return 0;
} else {
return DisplayMetricsHolder.getEncodedScreenSizeWithoutVerticalInsets(
context.getCurrentActivity());
}
}
@Override
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8300eb10fb4906468cd5f04afd8e16cd>>
* @generated SignedSource<<f089964c958dfcac00f83c3371ccbefc>>
*/
/**
@@ -90,6 +90,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean = accessor.enableAccumulatedUpdatesInRawPropsAndroid()
/**
* Enables linear text rendering on Android wherever subpixel text rendering is enabled
*/
@JvmStatic
public fun enableAndroidLinearText(): Boolean = accessor.enableAndroidLinearText()
/**
* Enables various optimizations throughout the path of measuring text on Android.
*/
@@ -246,6 +252,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableResourceTimingAPI(): Boolean = accessor.enableResourceTimingAPI()
/**
* When enabled, it will use SwiftUI for filter effects like blur on iOS.
*/
@JvmStatic
public fun enableSwiftUIBasedFilters(): Boolean = accessor.enableSwiftUIBasedFilters()
/**
* Enables View Culling: as soon as a view goes off screen, it can be reused anywhere in the UI and pieced together with other items to create new UI elements.
*/
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<ea5133af611c4d1e7e70155a3a74492d>>
* @generated SignedSource<<912dec895495052328e7e52b94a6738a>>
*/
/**
@@ -30,6 +30,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
private var enableAccessibilityOrderCache: Boolean? = null
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
private var enableAndroidLinearTextCache: Boolean? = null
private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null
private var enableBridgelessArchitectureCache: Boolean? = null
private var enableCppPropsIteratorSetterCache: Boolean? = null
@@ -56,6 +57,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enablePreparedTextLayoutCache: Boolean? = null
private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null
private var enableResourceTimingAPICache: Boolean? = null
private var enableSwiftUIBasedFiltersCache: Boolean? = null
private var enableViewCullingCache: Boolean? = null
private var enableViewRecyclingCache: Boolean? = null
private var enableViewRecyclingForImageCache: Boolean? = null
@@ -182,6 +184,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableAndroidLinearText(): Boolean {
var cached = enableAndroidLinearTextCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableAndroidLinearText()
enableAndroidLinearTextCache = cached
}
return cached
}
override fun enableAndroidTextMeasurementOptimizations(): Boolean {
var cached = enableAndroidTextMeasurementOptimizationsCache
if (cached == null) {
@@ -416,6 +427,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableSwiftUIBasedFilters(): Boolean {
var cached = enableSwiftUIBasedFiltersCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableSwiftUIBasedFilters()
enableSwiftUIBasedFiltersCache = cached
}
return cached
}
override fun enableViewCulling(): Boolean {
var cached = enableViewCullingCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<cf5b9f099846d4f954304905f77ebe17>>
* @generated SignedSource<<ba1e53d93b9fdaf298a034543bc44a57>>
*/
/**
@@ -48,6 +48,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun enableAndroidLinearText(): Boolean
@DoNotStrip @JvmStatic public external fun enableAndroidTextMeasurementOptimizations(): Boolean
@DoNotStrip @JvmStatic public external fun enableBridgelessArchitecture(): Boolean
@@ -100,6 +102,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableResourceTimingAPI(): Boolean
@DoNotStrip @JvmStatic public external fun enableSwiftUIBasedFilters(): Boolean
@DoNotStrip @JvmStatic public external fun enableViewCulling(): Boolean
@DoNotStrip @JvmStatic public external fun enableViewRecycling(): Boolean
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<305496047314dc97352a6e0dad0bba54>>
* @generated SignedSource<<7601cbcde75ff19ef0fe67f6faef2549>>
*/
/**
@@ -43,6 +43,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean = false
override fun enableAndroidLinearText(): Boolean = false
override fun enableAndroidTextMeasurementOptimizations(): Boolean = false
override fun enableBridgelessArchitecture(): Boolean = false
@@ -95,6 +97,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableResourceTimingAPI(): Boolean = false
override fun enableSwiftUIBasedFilters(): Boolean = false
override fun enableViewCulling(): Boolean = false
override fun enableViewRecycling(): Boolean = false
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<f3798eada96337cf98715fa580823b30>>
* @generated SignedSource<<3a13f8e35423b634ed4ef46fbac3c1e9>>
*/
/**
@@ -34,6 +34,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var disableTextLayoutManagerCacheAndroidCache: Boolean? = null
private var enableAccessibilityOrderCache: Boolean? = null
private var enableAccumulatedUpdatesInRawPropsAndroidCache: Boolean? = null
private var enableAndroidLinearTextCache: Boolean? = null
private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null
private var enableBridgelessArchitectureCache: Boolean? = null
private var enableCppPropsIteratorSetterCache: Boolean? = null
@@ -60,6 +61,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enablePreparedTextLayoutCache: Boolean? = null
private var enablePropsUpdateReconciliationAndroidCache: Boolean? = null
private var enableResourceTimingAPICache: Boolean? = null
private var enableSwiftUIBasedFiltersCache: Boolean? = null
private var enableViewCullingCache: Boolean? = null
private var enableViewRecyclingCache: Boolean? = null
private var enableViewRecyclingForImageCache: Boolean? = null
@@ -196,6 +198,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableAndroidLinearText(): Boolean {
var cached = enableAndroidLinearTextCache
if (cached == null) {
cached = currentProvider.enableAndroidLinearText()
accessedFeatureFlags.add("enableAndroidLinearText")
enableAndroidLinearTextCache = cached
}
return cached
}
override fun enableAndroidTextMeasurementOptimizations(): Boolean {
var cached = enableAndroidTextMeasurementOptimizationsCache
if (cached == null) {
@@ -456,6 +468,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableSwiftUIBasedFilters(): Boolean {
var cached = enableSwiftUIBasedFiltersCache
if (cached == null) {
cached = currentProvider.enableSwiftUIBasedFilters()
accessedFeatureFlags.add("enableSwiftUIBasedFilters")
enableSwiftUIBasedFiltersCache = cached
}
return cached
}
override fun enableViewCulling(): Boolean {
var cached = enableViewCullingCache
if (cached == null) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<f82b5905d822f8009c054a9443f86e39>>
* @generated SignedSource<<4464982256b8ce543d9235fccbf67054>>
*/
/**
@@ -27,6 +27,8 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Canary_Android : ReactN
override fun enableFabricRenderer(): Boolean = true
override fun enableWebPerformanceAPIsByDefault(): Boolean = true
override fun useNativeViewConfigsInBridgelessMode(): Boolean = true
override fun useTurboModuleInterop(): Boolean = true
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<c36e7ac588eb68660f000e7792ec2384>>
* @generated SignedSource<<143b568248e68033efaefc3f178ff6db>>
*/
/**
@@ -43,6 +43,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableAccumulatedUpdatesInRawPropsAndroid(): Boolean
@DoNotStrip public fun enableAndroidLinearText(): Boolean
@DoNotStrip public fun enableAndroidTextMeasurementOptimizations(): Boolean
@DoNotStrip public fun enableBridgelessArchitecture(): Boolean
@@ -95,6 +97,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableResourceTimingAPI(): Boolean
@DoNotStrip public fun enableSwiftUIBasedFilters(): Boolean
@DoNotStrip public fun enableViewCulling(): Boolean
@DoNotStrip public fun enableViewRecycling(): Boolean
@@ -55,7 +55,7 @@ public class AndroidInfoModule(reactContext: ReactApplicationContext) :
constants["ServerHost"] =
AndroidInfoHelpers.getServerHost(reactApplicationContext.applicationContext)
}
constants["isTesting"] = "true" == System.getProperty(IS_TESTING) || isRunningScreenshotTest()
constants["isTesting"] = "true" == System.getProperty(IS_TESTING)
val isDisableAnimations = System.getProperty(IS_DISABLE_ANIMATIONS)
if (isDisableAnimations != null) {
constants["isDisableAnimations"] = "true" == isDisableAnimations
@@ -71,15 +71,6 @@ public class AndroidInfoModule(reactContext: ReactApplicationContext) :
override fun invalidate() {}
private fun isRunningScreenshotTest(): Boolean {
return try {
Class.forName("com.facebook.testing.react.screenshots.ReactAppScreenshotTestActivity")
true
} catch (ignored: ClassNotFoundException) {
false
}
}
public companion object {
public const val NAME: String = NativePlatformConstantsAndroidSpec.NAME
private const val IS_TESTING = "IS_TESTING"
@@ -37,7 +37,7 @@ internal class ReactHostInspectorTarget(reactHostImpl: ReactHostImpl) :
external fun startBackgroundTrace(): Boolean
external fun stopAndStashBackgroundTrace()
external fun stopAndMaybeEmitBackgroundTrace(): Boolean
external fun stopAndDiscardBackgroundTrace()
@@ -51,11 +51,13 @@ internal class ReactHostInspectorTarget(reactHostImpl: ReactHostImpl) :
perfMonitorListeners.add(listener)
}
override fun pauseAndAnalyzeBackgroundTrace() {
stopAndStashBackgroundTrace()
override fun pauseAndAnalyzeBackgroundTrace(): Boolean {
val emitted = stopAndMaybeEmitBackgroundTrace()
perfMonitorListeners.forEach { listener ->
listener.onRecordingStateChanged(TracingState.DISABLED)
}
return emitted
}
override fun resumeBackgroundTrace() {
@@ -65,18 +67,6 @@ internal class ReactHostInspectorTarget(reactHostImpl: ReactHostImpl) :
}
}
fun handleNativePerfMonitorMetricUpdate(
longTaskDurationMs: Int,
responsivenessScore: Int,
ttl: Int,
) {
perfMonitorListeners.forEach { listener ->
listener.onNewFocusedEvent(
PerfMonitorUpdateListener.LongTaskEventData(longTaskDurationMs, responsivenessScore, ttl)
)
}
}
override fun close() {
mHybridData.resetNative()
}
@@ -245,7 +245,15 @@ public object ReactScrollViewHelper {
@RequiresApi(Build.VERSION_CODES.N)
@JvmStatic
public fun removeScrollListener(listener: ScrollListener) {
scrollListeners.removeIf { it.get() == null || it.get() == listener }
// Avoid using removeIf, only available in API 26+
val toRemove = ArrayList<WeakReference<ScrollListener>>()
for (ref in scrollListeners) {
val target = ref.get()
if (target == null || target == listener) {
toRemove.add(ref)
}
}
scrollListeners.removeAll(toRemove)
}
@JvmStatic
@@ -256,7 +264,15 @@ public object ReactScrollViewHelper {
@RequiresApi(Build.VERSION_CODES.N)
@JvmStatic
public fun removeLayoutChangeListener(listener: LayoutChangeListener) {
layoutChangeListeners.removeIf { it.get() == null || it.get() == listener }
// Avoid using removeIf, only available in API 26+
val toRemove = ArrayList<WeakReference<LayoutChangeListener>>()
for (ref in layoutChangeListeners) {
val target = ref.get()
if (target == null || target == listener) {
toRemove.add(ref)
}
}
layoutChangeListeners.removeAll(toRemove)
}
/**
@@ -50,11 +50,13 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
internal class VirtualViewContainerState {
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
private val hysteresisRatio: Double = ReactNativeFeatureFlags.virtualViewHysteresisRatio()
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
private val emptyRect: Rect = Rect()
private val visibleRect: Rect = Rect()
private val prerenderRect: Rect = Rect()
private val hysteresisRect: Rect = Rect()
private val onWindowFocusChangeListener =
if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) {
ViewTreeObserver.OnWindowFocusChangeListener {
@@ -119,11 +121,12 @@ internal class VirtualViewContainerState {
(-prerenderRect.height() * prerenderRatio).toInt(),
)
val virtualViewsIt = if (virtualView != null) listOf(virtualView) else virtualViews
val virtualViewsIt =
if (virtualView != null) listOf(virtualView) else virtualViews.toMutableSet()
virtualViewsIt.forEach { vv ->
val rect = vv.containerRelativeRect
var mode = VirtualViewMode.Hidden
var mode: VirtualViewMode? = VirtualViewMode.Hidden
var thresholdRect = emptyRect
when {
rectsOverlap(rect, visibleRect) -> {
@@ -142,14 +145,29 @@ internal class VirtualViewContainerState {
mode = VirtualViewMode.Prerender
thresholdRect = prerenderRect
}
else -> {}
else -> {
if (hysteresisRatio > 0.0) {
hysteresisRect.set(prerenderRect)
hysteresisRect.inset(
(-visibleRect.width() * hysteresisRatio).toInt(),
(-visibleRect.height() * hysteresisRatio).toInt(),
)
if (rectsOverlap(rect, hysteresisRect)) {
mode = null
}
}
}
}
debugLog(
"updateModes",
{ "virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect" },
)
vv.onModeChange(mode, thresholdRect)
if (mode != null) {
vv.onModeChange(mode, thresholdRect)
debugLog(
"updateModes",
{
"virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect"
},
)
}
}
}
}
@@ -14,6 +14,7 @@ import android.text.TextPaint
import android.text.style.MetricAffectingSpan
import com.facebook.react.common.ReactConstants
import com.facebook.react.common.assets.ReactFontManager
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.views.text.ReactTypefaceUtils
/**
@@ -73,6 +74,9 @@ internal class CustomStyleSpan(
fontFeatureSettings = fontFeatureSettingsParam
setTypeface(typeface)
isSubpixelText = true
if (ReactNativeFeatureFlags.enableAndroidLinearText()) {
isLinearText = true
}
}
}
}
@@ -611,17 +611,26 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
// Match behavior of CustomStyleSpan and enable SUBPIXEL_TEXT_FLAG when setting anything
// nonstandard
val enableSubpixelText =
fontStyle != ReactConstants.UNSET ||
fontWeight != ReactConstants.UNSET ||
fontFamily != null ||
fontFeatureSettings != null
paintFlags =
if (
fontStyle != ReactConstants.UNSET ||
fontWeight != ReactConstants.UNSET ||
fontFamily != null ||
fontFeatureSettings != null
) {
if (enableSubpixelText) {
paintFlags or Paint.SUBPIXEL_TEXT_FLAG
} else {
paintFlags and (Paint.SUBPIXEL_TEXT_FLAG.inv())
paintFlags and Paint.SUBPIXEL_TEXT_FLAG.inv()
}
if (ReactNativeFeatureFlags.enableAndroidLinearText()) {
paintFlags =
if (enableSubpixelText) {
paintFlags or Paint.LINEAR_TEXT_FLAG
} else {
paintFlags and Paint.LINEAR_TEXT_FLAG.inv()
}
}
}
public fun requestFocusFromJS() {
@@ -16,9 +16,11 @@ import com.facebook.common.logging.FLog
import com.facebook.react.R
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.uimanager.ReactClippingViewGroup
import com.facebook.react.uimanager.ReactRoot
import com.facebook.react.views.scroll.VirtualView
import com.facebook.react.views.scroll.VirtualViewContainer
import com.facebook.react.views.scroll.debugLog
import com.facebook.react.views.view.ReactViewGroup
import com.facebook.react.views.virtual.VirtualViewMode
import com.facebook.react.views.virtual.VirtualViewModeChangeEmitter
@@ -34,6 +36,7 @@ public class ReactVirtualViewExperimental(context: Context) :
private var scrollView: VirtualViewContainer? = null
private val lastContainerRelativeRect: Rect = Rect()
private val lastClippingRect: Rect = Rect()
override val containerRelativeRect: Rect = Rect()
private var offsetX: Int = 0
private var offsetY: Int = 0
@@ -120,6 +123,7 @@ public class ReactVirtualViewExperimental(context: Context) :
modeChangeEmitter = null
hadLayout = false
lastContainerRelativeRect.setEmpty()
lastClippingRect.setEmpty()
containerRelativeRect.setEmpty()
}
@@ -132,7 +136,12 @@ public class ReactVirtualViewExperimental(context: Context) :
modeChangeEmitter ?: return
scrollView ?: return
if (newMode == VirtualViewMode.Visible) {
updateClippingRect(null)
}
if (newMode == mode) {
debugLog("onModeChange") { "no change $newMode" }
return
}
@@ -141,6 +150,10 @@ public class ReactVirtualViewExperimental(context: Context) :
debugLog("onModeChange") { "$oldMode->$newMode" }
if (oldMode == VirtualViewMode.Visible) {
updateClippingRect(null)
}
when (newMode) {
VirtualViewMode.Visible -> {
if (renderState == VirtualViewRenderState.Unknown) {
@@ -187,6 +200,37 @@ public class ReactVirtualViewExperimental(context: Context) :
}
}
// Note: We co-opt subview clipping on ReactVirtualView by returning the
// clipping rect of the ScrollView. This means we clip the children of ReactVirtualView
// when they are out of the viewport, but not ReactVirtualView itself.
override fun updateClippingRect(excludedViews: Set<Int>?) {
if (!_removeClippedSubviews) {
return
}
// If no ScrollView, or ScrollView has disabled removeClippedSubviews, use default behavior
if (
scrollView == null ||
!((scrollView as ReactClippingViewGroup)?.removeClippedSubviews ?: false)
) {
super.updateClippingRect(excludedViews)
return
}
val clippingRect = checkNotNull(clippingRect)
(scrollView as ReactClippingViewGroup).getClippingRect(clippingRect)
clippingRect.intersect(containerRelativeRect)
clippingRect.offset(-containerRelativeRect.left, -containerRelativeRect.top)
if (lastClippingRect == clippingRect) {
return
}
updateClippingToRect(clippingRect, excludedViews)
lastClippingRect.set(clippingRect)
}
private fun updateParentOffset() {
val virtualViewScrollView = scrollView ?: return
offsetX = 0
@@ -212,8 +256,11 @@ public class ReactVirtualViewExperimental(context: Context) :
debugLog("reportRectChangeToContainer") { "no rect change $containerRelativeRect" }
return
}
scrollView?.virtualViewContainerState?.onChange(this)
lastContainerRelativeRect.set(containerRelativeRect)
if (scrollView != null) {
scrollView?.virtualViewContainerState?.onChange(this)
lastContainerRelativeRect.set(containerRelativeRect)
}
}
private fun getScrollView(): VirtualViewContainer? = traverseParentStack(true)
@@ -13,12 +13,12 @@ import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.uimanager.events.EventDispatcher
import com.facebook.react.viewmanagers.VirtualViewExperimentalManagerDelegate
import com.facebook.react.viewmanagers.VirtualViewExperimentalManagerInterface
import com.facebook.react.views.view.ReactClippingViewManager
import com.facebook.react.views.virtual.VirtualViewMode
import com.facebook.react.views.virtual.VirtualViewModeChangeEmitter
import com.facebook.react.views.virtual.VirtualViewModeChangeEvent
@@ -26,7 +26,7 @@ import com.facebook.react.views.virtual.VirtualViewRenderState
@ReactModule(name = ReactVirtualViewExperimentalManager.REACT_CLASS)
public class ReactVirtualViewExperimentalManager :
ViewGroupManager<ReactVirtualViewExperimental>(),
ReactClippingViewManager<ReactVirtualViewExperimental>(),
VirtualViewExperimentalManagerInterface<ReactVirtualViewExperimental> {
private val _delegate = VirtualViewExperimentalManagerDelegate(this)
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<be39ff0cb3b7c5881510b1528c17465a>>
* @generated SignedSource<<92c9aa29df580c6d12faa14e60b00e4f>>
*/
/**
@@ -99,6 +99,12 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool enableAndroidLinearText() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableAndroidLinearText");
return method(javaProvider_);
}
bool enableAndroidTextMeasurementOptimizations() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableAndroidTextMeasurementOptimizations");
@@ -255,6 +261,12 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool enableSwiftUIBasedFilters() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableSwiftUIBasedFilters");
return method(javaProvider_);
}
bool enableViewCulling() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableViewCulling");
@@ -519,6 +531,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableAccumulatedUpdatesInRawPropsAndro
return ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::enableAndroidLinearText(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableAndroidLinearText();
}
bool JReactNativeFeatureFlagsCxxInterop::enableAndroidTextMeasurementOptimizations(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations();
@@ -649,6 +666,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableResourceTimingAPI(
return ReactNativeFeatureFlags::enableResourceTimingAPI();
}
bool JReactNativeFeatureFlagsCxxInterop::enableSwiftUIBasedFilters(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableSwiftUIBasedFilters();
}
bool JReactNativeFeatureFlagsCxxInterop::enableViewCulling(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableViewCulling();
@@ -885,6 +907,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableAccumulatedUpdatesInRawPropsAndroid",
JReactNativeFeatureFlagsCxxInterop::enableAccumulatedUpdatesInRawPropsAndroid),
makeNativeMethod(
"enableAndroidLinearText",
JReactNativeFeatureFlagsCxxInterop::enableAndroidLinearText),
makeNativeMethod(
"enableAndroidTextMeasurementOptimizations",
JReactNativeFeatureFlagsCxxInterop::enableAndroidTextMeasurementOptimizations),
@@ -963,6 +988,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableResourceTimingAPI",
JReactNativeFeatureFlagsCxxInterop::enableResourceTimingAPI),
makeNativeMethod(
"enableSwiftUIBasedFilters",
JReactNativeFeatureFlagsCxxInterop::enableSwiftUIBasedFilters),
makeNativeMethod(
"enableViewCulling",
JReactNativeFeatureFlagsCxxInterop::enableViewCulling),
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<8cd0afd7badb6739b4e8f9b0e630677c>>
* @generated SignedSource<<124d1378990fdd09a4d97f9103f81a6a>>
*/
/**
@@ -60,6 +60,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool enableAccumulatedUpdatesInRawPropsAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableAndroidLinearText(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableAndroidTextMeasurementOptimizations(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -138,6 +141,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool enableResourceTimingAPI(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableSwiftUIBasedFilters(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableViewCulling(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -120,17 +120,6 @@ void JReactHostInspectorTarget::onSetPausedInDebuggerMessage(
}
}
void JReactHostInspectorTarget::unstable_onPerfMonitorUpdate(
const PerfMonitorUpdateRequest& request) {
static auto method = javaClassStatic()->getMethod<void(jint, jint, jint)>(
"handleNativePerfMonitorMetricUpdate");
method(
jobj_,
request.activeInteraction.duration,
static_cast<jint>(request.activeInteraction.responsivenessScore),
request.activeInteraction.ttl);
}
void JReactHostInspectorTarget::loadNetworkResource(
const jsinspector_modern::LoadNetworkResourceRequest& params,
jsinspector_modern::ScopedExecutor<
@@ -168,9 +157,16 @@ tracing::TraceRecordingState JReactHostInspectorTarget::stopTracing() {
}
}
void JReactHostInspectorTarget::stopAndStashBackgroundTrace() {
jboolean JReactHostInspectorTarget::stopAndMaybeEmitBackgroundTrace() {
auto capturedTrace = inspectorTarget_->stopTracing();
if (inspectorTarget_->hasActiveSessionWithFuseboxClient()) {
inspectorTarget_->emitTraceRecordingForFirstFuseboxClient(
std::move(capturedTrace));
return jboolean(true);
}
stashTraceRecordingState(std::move(capturedTrace));
return jboolean(false);
}
void JReactHostInspectorTarget::stopAndDiscardBackgroundTrace() {
@@ -199,8 +195,8 @@ void JReactHostInspectorTarget::registerNatives() {
"startBackgroundTrace",
JReactHostInspectorTarget::startBackgroundTrace),
makeNativeMethod(
"stopAndStashBackgroundTrace",
JReactHostInspectorTarget::stopAndStashBackgroundTrace),
"stopAndMaybeEmitBackgroundTrace",
JReactHostInspectorTarget::stopAndMaybeEmitBackgroundTrace),
makeNativeMethod(
"stopAndDiscardBackgroundTrace",
JReactHostInspectorTarget::stopAndDiscardBackgroundTrace),
@@ -82,17 +82,6 @@ class JReactHostInspectorTarget
static void registerNatives();
void sendDebuggerResumeCommand();
/**
* Starts a background trace recording for this HostTarget.
*
* \return false if already tracing, true otherwise.
*/
bool startBackgroundTrace();
/**
* Stops previously started trace recording and stashes the captured trace,
* which will be emitted the next time CDP session is created.
*/
void stopAndStashBackgroundTrace();
/**
* Get the state of the background trace: running, stopped, or disabled
* Background tracing will be disabled if there is no metro connection or if
@@ -101,6 +90,20 @@ class JReactHostInspectorTarget
* \return the background trace state
*/
jint tracingState();
/**
* Starts a background trace recording for this HostTarget.
*
* \return false if already tracing, true otherwise.
*/
bool startBackgroundTrace();
/**
* Stops previously started trace recording and:
* - If there is an active CDP session with Fusebox client enabled, emits the
* trace and returns true.
* - Otherwise, stashes the captured trace, that will be emitted when the CDP
* session is initialized. Returns false.
*/
jboolean stopAndMaybeEmitBackgroundTrace();
/**
* Stops previously started trace recording and discards the captured trace.
*/
@@ -113,8 +116,6 @@ class JReactHostInspectorTarget
void onReload(const PageReloadRequest& request) override;
void onSetPausedInDebuggerMessage(
const OverlaySetPausedInDebuggerMessageRequest& request) override;
void unstable_onPerfMonitorUpdate(
const jsinspector_modern::PerfMonitorUpdateRequest& request) override;
void loadNetworkResource(
const jsinspector_modern::LoadNetworkResourceRequest& params,
jsinspector_modern::ScopedExecutor<
@@ -0,0 +1,39 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which we're presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "RCTSwiftUI"
s.version = version
s.summary = "Swift utilities for React Native."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = "*.{h,m,swift}"
s.public_header_files = "*.h"
s.module_name = "RCTSwiftUI"
s.header_dir = "RCTSwiftUI"
# Swift-specific configuration
s.pod_target_xcconfig = {
"SWIFT_VERSION" => "5.0",
"DEFINES_MODULE" => "YES",
}
end
@@ -0,0 +1,76 @@
/*
* 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.
*/
import SwiftUI
import UIKit
@MainActor @objc public class RCTSwiftUIContainerView: NSObject {
private var containerViewModel = ContainerViewModel()
private var hostingController: UIHostingController<SwiftUIContainerView>?
@objc public override init() {
super.init()
hostingController = UIHostingController(rootView: SwiftUIContainerView(viewModel: containerViewModel))
guard let view = hostingController?.view else {
return
}
view.backgroundColor = .clear
}
@objc public func updateContentView(_ view: UIView) {
containerViewModel.contentView = view
}
@objc public func hostingView() -> UIView? {
return hostingController?.view
}
@objc public func contentView() -> UIView? {
return containerViewModel.contentView
}
@objc public func updateBlurRadius(_ radius: NSNumber) {
let blurRadius = CGFloat(radius.floatValue)
containerViewModel.blurRadius = blurRadius
}
@objc public func updateLayout(withBounds bounds: CGRect) {
hostingController?.view.frame = bounds
containerViewModel.contentView?.frame = bounds
}
@objc public func resetStyles() {
containerViewModel.blurRadius = 0
}
}
class ContainerViewModel: ObservableObject {
@Published var blurRadius: CGFloat = 0
@Published var contentView: UIView?
}
struct SwiftUIContainerView: View {
@ObservedObject var viewModel: ContainerViewModel
var body: some View {
if let contentView = viewModel.contentView {
UIViewWrapper(view: contentView)
.blur(radius: viewModel.blurRadius)
}
}
}
struct UIViewWrapper: UIViewRepresentable {
let view: UIView
func makeUIView(context: Context) -> UIView {
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
@@ -0,0 +1,24 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTSwiftUIContainerViewWrapper : NSObject
- (UIView *_Nullable)contentView;
- (void)updateBlurRadius:(NSNumber *)radius;
- (void)updateContentView:(UIView *)view;
- (UIView *_Nullable)hostingView;
- (void)resetStyles;
- (void)updateLayoutWithBounds:(CGRect)bounds;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,56 @@
/*
* 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.
*/
#import "RCTSwiftUIContainerViewWrapper.h"
@import RCTSwiftUI;
@interface RCTSwiftUIContainerViewWrapper ()
@property (nonatomic, strong) RCTSwiftUIContainerView *swiftContainerView;
@end
@implementation RCTSwiftUIContainerViewWrapper
- (instancetype)init
{
if (self = [super init]) {
_swiftContainerView = [RCTSwiftUIContainerView new];
}
return self;
}
- (UIView *_Nullable)contentView
{
return [self.swiftContainerView contentView];
}
- (UIView *_Nullable)hostingView
{
return [self.swiftContainerView hostingView];
}
- (void)resetStyles
{
[self.swiftContainerView resetStyles];
}
- (void)updateContentView:(UIView *)view
{
return [self.swiftContainerView updateContentView:view];
}
- (void)updateBlurRadius:(NSNumber *)radius
{
[self.swiftContainerView updateBlurRadius:radius];
}
- (void)updateLayoutWithBounds:(CGRect)bounds
{
[self.swiftContainerView updateLayoutWithBounds:bounds];
}
@end
@@ -0,0 +1,38 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which we're presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
Pod::Spec.new do |s|
s.name = "RCTSwiftUIWrapper"
s.version = version
s.summary = "Swift utilities for React Native."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.source = source
s.source_files = "*.{h,m}"
s.public_header_files = "*.h"
s.module_name = "RCTSwiftUIWrapper"
s.header_dir = "RCTSwiftUIWrapper"
s.dependency "RCTSwiftUI"
s.pod_target_xcconfig = {
"SWIFT_VERSION" => "5.0",
}
end
@@ -15,16 +15,16 @@ namespace facebook::react::jsinspector_modern {
namespace {
// Vendored from Folly
// https://github.com/facebook/folly/blob/v2024.07.08.00/folly/detail/base64_detail/Base64Scalar.h
constexpr std::string_view kBase64Charset =
constexpr char kBase64Charset[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
constexpr std::string_view kBase64URLCharset =
constexpr char kBase64URLCharset[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
template <bool isURL>
struct Base64ScalarImpl {
static constexpr const char* kCharset =
isURL ? kBase64URLCharset.data() : kBase64Charset.data();
isURL ? kBase64URLCharset : kBase64Charset;
// 0, 1 or 2 bytes
static constexpr char* encodeTail(const char* f, const char* l, char* o) {
@@ -41,18 +41,14 @@ class HostAgent::Impl final {
HostTargetController& targetController,
HostTargetMetadata hostMetadata,
SessionState& sessionState,
VoidExecutor executor,
std::optional<tracing::TraceRecordingState> traceRecordingToEmit)
VoidExecutor executor)
: frontendChannel_(frontendChannel),
targetController_(targetController),
hostMetadata_(std::move(hostMetadata)),
sessionState_(sessionState),
networkIOAgent_(NetworkIOAgent(frontendChannel, std::move(executor))),
tracingAgent_(TracingAgent(
frontendChannel,
sessionState,
targetController,
std::move(traceRecordingToEmit))) {}
tracingAgent_(
TracingAgent(frontendChannel, sessionState, targetController)) {}
~Impl() {
if (isPausedInDebuggerOverlayVisible_) {
@@ -201,6 +197,14 @@ class HostAgent::Impl final {
"ReactNativeApplication.metadataUpdated",
createHostMetadataPayload(hostMetadata_)));
auto stashedTraceRecording =
targetController_.getDelegate()
.unstable_getTraceRecordingThatWillBeEmittedOnInitialization();
if (stashedTraceRecording.has_value()) {
tracingAgent_.emitExternalTraceRecording(
std::move(stashedTraceRecording.value()));
}
return {
.isFinishedHandlingRequest = true,
.shouldSendOKResponse = true,
@@ -336,12 +340,24 @@ class HostAgent::Impl final {
}
}
bool hasFuseboxClientConnected() const {
return fuseboxClientType_ == FuseboxClientType::Fusebox;
}
void emitExternalTraceRecording(
tracing::TraceRecordingState traceRecording) const {
assert(
hasFuseboxClientConnected() &&
"Attempted to emit a trace recording to a non-Fusebox client");
tracingAgent_.emitExternalTraceRecording(std::move(traceRecording));
}
private:
enum class FuseboxClientType { Unknown, Fusebox, NonFusebox };
/**
* Send a simple Log.entryAdded notification with the given
* \param text. You must ensure that the frontend has enabled Log
* \param text You must ensure that the frontend has enabled Log
* notifications (using Log.enable) prior to calling this function. In Chrome
* DevTools, the message will appear in the Console tab along with regular
* console messages. The difference between Log.entryAdded and
@@ -432,11 +448,15 @@ class HostAgent::Impl final {
HostTargetController& targetController,
HostTargetMetadata hostMetadata,
SessionState& sessionState,
VoidExecutor executor,
std::optional<tracing::TraceRecordingState> traceRecordingToEmit) {}
VoidExecutor executor) {}
void handleRequest(const cdp::PreparsedRequest& req) {}
void setCurrentInstanceAgent(std::shared_ptr<InstanceAgent> agent) {}
bool hasFuseboxClientConnected() const {
return false;
}
void emitExternalTraceRecording(tracing::TraceRecordingState traceRecording) {
}
};
#endif // REACT_NATIVE_DEBUGGER_ENABLED
@@ -446,16 +466,14 @@ HostAgent::HostAgent(
HostTargetController& targetController,
HostTargetMetadata hostMetadata,
SessionState& sessionState,
VoidExecutor executor,
std::optional<tracing::TraceRecordingState> traceRecordingToEmit)
VoidExecutor executor)
: impl_(std::make_unique<Impl>(
*this,
frontendChannel,
targetController,
std::move(hostMetadata),
sessionState,
std::move(executor),
std::move(traceRecordingToEmit))) {}
std::move(executor))) {}
HostAgent::~HostAgent() = default;
@@ -468,6 +486,15 @@ void HostAgent::setCurrentInstanceAgent(
impl_->setCurrentInstanceAgent(std::move(instanceAgent));
}
bool HostAgent::hasFuseboxClientConnected() const {
return impl_->hasFuseboxClientConnected();
}
void HostAgent::emitExternalTraceRecording(
tracing::TraceRecordingState traceRecording) const {
impl_->emitExternalTraceRecording(std::move(traceRecording));
}
#pragma mark - Tracing
HostTracingAgent::HostTracingAgent(tracing::TraceRecordingState& state)
@@ -36,16 +36,13 @@ class HostAgent final {
* \param hostMetadata Metadata about the host that created this agent.
* \param sessionState The state of the session that created this agent.
* \param executor A void executor to be used by async-aware handlers.
* \param traceRecordingToEmit If set, this is the trace that Host has
* requested to display in the Frontend.
*/
HostAgent(
const FrontendChannel& frontendChannel,
HostTargetController& targetController,
HostTargetMetadata hostMetadata,
SessionState& sessionState,
VoidExecutor executor,
std::optional<tracing::TraceRecordingState> traceRecordingToEmit);
VoidExecutor executor);
HostAgent(const HostAgent&) = delete;
HostAgent(HostAgent&&) = delete;
@@ -69,6 +66,20 @@ class HostAgent final {
*/
void setCurrentInstanceAgent(std::shared_ptr<InstanceAgent> agent);
/**
* Returns whether this HostAgent is part of the session that has an active
* Fusebox client connecte, i.e. with Chrome DevTools Frontend fork for React
* Native.
*/
bool hasFuseboxClientConnected() const;
/**
* Emits the trace recording that was captured externally, not via the
* CDP-initiated request.
*/
void emitExternalTraceRecording(
tracing::TraceRecordingState traceRecording) const;
private:
// We use the private implementation idiom to ensure this class has the same
// layout regardless of whether REACT_NATIVE_DEBUGGER_ENABLED is defined. The
@@ -34,8 +34,7 @@ class HostTargetSession {
std::unique_ptr<IRemoteConnection> remote,
HostTargetController& targetController,
HostTargetMetadata hostMetadata,
VoidExecutor executor,
std::optional<tracing::TraceRecordingState> traceRecordingToEmit)
VoidExecutor executor)
: remote_(std::make_shared<RAIIRemoteConnection>(std::move(remote))),
frontendChannel_(
[remoteWeak = std::weak_ptr(remote_)](std::string_view message) {
@@ -48,8 +47,7 @@ class HostTargetSession {
targetController,
std::move(hostMetadata),
state_,
std::move(executor),
std::move(traceRecordingToEmit)) {}
std::move(executor)) {}
/**
* Called by CallbackLocalConnection to send a message to this Session's
@@ -103,6 +101,19 @@ class HostTargetSession {
}
}
/**
* Returns whether the ReactNativeApplication CDP domain is enabled.
*
* Chrome DevTools Frontend enables this domain as a client.
*/
bool hasFuseboxClient() const {
return hostAgent_.hasFuseboxClientConnected();
}
void emitTraceRecording(tracing::TraceRecordingState traceRecording) const {
hostAgent_.emitExternalTraceRecording(std::move(traceRecording));
}
private:
// Owned by this instance, but shared (weakly) with the frontend channel
std::shared_ptr<RAIIRemoteConnection> remote_;
@@ -192,9 +203,6 @@ std::shared_ptr<HostTarget> HostTarget::create(
VoidExecutor executor) {
std::shared_ptr<HostTarget> hostTarget{new HostTarget(delegate)};
hostTarget->setExecutor(std::move(executor));
if (InspectorFlags::getInstance().getPerfMonitorV2Enabled()) {
hostTarget->installPerfMetricsBinding();
}
return hostTarget;
}
@@ -208,8 +216,7 @@ std::unique_ptr<ILocalConnection> HostTarget::connect(
std::move(connectionToFrontend),
controller_,
delegate_.getMetadata(),
makeVoidExecutor(executorFromThis()),
delegate_.unstable_getTraceRecordingThatWillBeEmittedOnInitialization());
makeVoidExecutor(executorFromThis()));
session->setCurrentInstance(currentInstance_.get());
sessions_.insert(std::weak_ptr(session));
return std::make_unique<CallbackLocalConnection>(
@@ -279,17 +286,6 @@ void HostTarget::sendCommand(HostCommand command) {
});
}
void HostTarget::installPerfMetricsBinding() {
perfMonitorUpdateHandler_ =
std::make_unique<PerfMonitorUpdateHandler>(delegate_);
perfMetricsBinding_ = std::make_unique<HostRuntimeBinding>(
*this, // Used immediately
"__chromium_devtools_metrics_reporter",
[this](const std::string& message) {
perfMonitorUpdateHandler_->handlePerfMetricsUpdate(message);
});
}
HostTargetController::HostTargetController(HostTarget& target)
: target_(target) {}
@@ -361,4 +357,32 @@ folly::dynamic createHostMetadataPayload(const HostTargetMetadata& metadata) {
return result;
}
bool HostTarget::hasActiveSessionWithFuseboxClient() const {
bool hasActiveFuseboxSession = false;
sessions_.forEach([&](HostTargetSession& session) {
hasActiveFuseboxSession |= session.hasFuseboxClient();
});
return hasActiveFuseboxSession;
}
void HostTarget::emitTraceRecordingForFirstFuseboxClient(
tracing::TraceRecordingState traceRecording) const {
bool emitted = false;
sessions_.forEach([&](HostTargetSession& session) {
if (emitted) {
/**
* TraceRecordingState object is not copiable for performance reasons,
* because it could contain large Runtime sampling profile object.
*
* This approach would not work with multi-client debugger setup.
*/
return;
}
if (session.hasFuseboxClient()) {
session.emitTraceRecording(std::move(traceRecording));
emitted = true;
}
});
}
} // namespace facebook::react::jsinspector_modern
@@ -12,7 +12,6 @@
#include "InspectorInterfaces.h"
#include "InstanceTarget.h"
#include "NetworkIOAgent.h"
#include "PerfMonitorV2.h"
#include "ScopedExecutor.h"
#include "WeakList.h"
@@ -128,13 +127,6 @@ class HostTargetDelegate : public LoadNetworkResourceDelegate {
virtual void onSetPausedInDebuggerMessage(
const OverlaySetPausedInDebuggerMessageRequest& request) = 0;
/**
* [Experimental] Called when the runtime has new data for the V2 Perf
* Monitor overlay. This is called on the inspector thread.
*/
virtual void unstable_onPerfMonitorUpdate(
const PerfMonitorUpdateRequest& /*request*/) {}
/**
* Called by NetworkIOAgent on handling a `Network.loadNetworkResource` CDP
* request. Platform implementations should override this to perform a
@@ -305,6 +297,21 @@ class JSINSPECTOR_EXPORT HostTarget
*/
tracing::TracingState tracingState() const;
/**
* Returns whether there is an active session with the Fusebox client, i.e.
* with Chrome DevTools Frontend fork for React Native.
*/
bool hasActiveSessionWithFuseboxClient() const;
/**
* Emits the trace recording for the first active session with the Fusebox
* client.
*
* @see \c hasActiveFrontendSession
*/
void emitTraceRecordingForFirstFuseboxClient(
tracing::TraceRecordingState traceRecording) const;
private:
/**
* Constructs a new HostTarget.
@@ -324,7 +331,6 @@ class JSINSPECTOR_EXPORT HostTarget
std::shared_ptr<ExecutionContextManager> executionContextManager_;
std::shared_ptr<InstanceTarget> currentInstance_{nullptr};
std::unique_ptr<HostCommandSender> commandSender_;
std::unique_ptr<PerfMonitorUpdateHandler> perfMonitorUpdateHandler_;
std::unique_ptr<HostRuntimeBinding> perfMetricsBinding_;
/**
@@ -343,13 +349,6 @@ class JSINSPECTOR_EXPORT HostTarget
return currentInstance_ != nullptr;
}
/**
* Install a runtime binding subscribing to the Interaction to Next Paint
* (INP) live metric, which we broadcast to the V2 Perf Monitor overlay
* via \ref HostTargetDelegate::unstable_onPerfMonitorUpdate.
*/
void installPerfMetricsBinding();
// Necessary to allow HostAgent to access HostTarget's internals in a
// controlled way (i.e. only HostTargetController gets friend access, while
// HostAgent itself doesn't).
@@ -33,10 +33,6 @@ bool InspectorFlags::getNetworkInspectionEnabled() const {
return loadFlagsAndAssertUnchanged().networkInspectionEnabled;
}
bool InspectorFlags::getPerfMonitorV2Enabled() const {
return loadFlagsAndAssertUnchanged().perfMonitorV2Enabled;
}
void InspectorFlags::dangerouslyResetFlags() {
*this = InspectorFlags{};
}
@@ -63,9 +59,6 @@ const InspectorFlags::Values& InspectorFlags::loadFlagsAndAssertUnchanged()
.networkInspectionEnabled =
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
ReactNativeFeatureFlags::fuseboxNetworkInspectionEnabled(),
.perfMonitorV2Enabled =
ReactNativeFeatureFlags::enableBridgelessArchitecture() &&
ReactNativeFeatureFlags::perfMonitorV2Enabled(),
};
if (cachedValues_.has_value() && !inconsistentFlagsStateLogged_) {
@@ -35,11 +35,6 @@ class InspectorFlags {
*/
bool getNetworkInspectionEnabled() const;
/**
* Flag determining if the V2 in-app Performance Monitor is enabled.
*/
bool getPerfMonitorV2Enabled() const;
/**
* Forcibly disable the main `getFuseboxEnabled()` flag. This should ONLY be
* used by `ReactInstanceIntegrationTest`.
@@ -57,7 +52,6 @@ class InspectorFlags {
bool fuseboxEnabled;
bool isProfilingBuild;
bool networkInspectionEnabled;
bool perfMonitorV2Enabled;
bool operator==(const Values&) const = default;
};
@@ -1,92 +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.
*/
#include "PerfMonitorV2.h"
#include "HostTarget.h"
#include <folly/json.h>
#include <react/timing/primitives.h>
namespace facebook::react::jsinspector_modern {
namespace {
constexpr uint16_t MIN_DURATION = 200;
constexpr uint16_t DEFAULT_TTL = 4000;
constexpr uint16_t BAD_EVENT_TTL = 20000;
InteractionResponsivenessScore getInteractionScore(uint16_t duration) {
constexpr uint16_t GOOD_THRESHOLD = 200;
constexpr uint16_t NEEDS_IMPROVEMENT_THRESHOLD = 500;
if (duration < GOOD_THRESHOLD) {
return InteractionResponsivenessScore::Good;
} else if (duration < NEEDS_IMPROVEMENT_THRESHOLD) {
return InteractionResponsivenessScore::NeedsImprovement;
} else {
return InteractionResponsivenessScore::Poor;
}
}
} // namespace
void PerfMonitorUpdateHandler::handlePerfMetricsUpdate(
const std::string& message) {
auto payload = folly::parseJson(message);
if (payload.isObject()) {
if (payload["name"] != "__ReactNative__LongTask") {
return;
}
auto duration = static_cast<uint16_t>(payload["duration"].asInt());
if (duration < MIN_DURATION) {
return;
}
auto responsivenessScore = getInteractionScore(duration);
auto ttl =
responsivenessScore == InteractionResponsivenessScore::NeedsImprovement
? BAD_EVENT_TTL
: DEFAULT_TTL;
LongTaskPayload newEvent{
static_cast<uint16_t>(payload["startTime"].asInt()),
duration,
responsivenessScore,
ttl};
if (shouldOverrideLastEvent(newEvent)) {
lastEvent_ = newEvent;
delegate_.unstable_onPerfMonitorUpdate(
PerfMonitorUpdateRequest{newEvent});
}
}
}
bool PerfMonitorUpdateHandler::shouldOverrideLastEvent(
const LongTaskPayload& newEvent) {
if (!lastEvent_) {
return true;
}
// Override if last event has expired
if (HighResTimeStamp::now().toDOMHighResTimeStamp() >
lastEvent_->startTime + lastEvent_->ttl) {
return true;
}
// Override if same or greater responsiveness score
if (newEvent.responsivenessScore >= lastEvent_->responsivenessScore) {
return true;
}
return false;
}
} // namespace facebook::react::jsinspector_modern
@@ -1,59 +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.
*/
#pragma once
#include <cstdint>
#include <optional>
#include <string>
namespace facebook::react::jsinspector_modern {
class HostTargetDelegate;
/**
* See https://web.dev/articles/inp#good-score.
*/
enum class InteractionResponsivenessScore : int32_t {
Good = 0,
NeedsImprovement = 1,
Poor = 2
};
struct LongTaskPayload {
uint16_t startTime;
uint16_t duration;
InteractionResponsivenessScore responsivenessScore;
uint16_t ttl;
};
struct PerfMonitorUpdateRequest {
LongTaskPayload activeInteraction;
};
/**
* [Experimental] Utility to handle performance metrics updates received from
* the runtime and forward update events to the V2 Perf Monitor UI.
*/
class PerfMonitorUpdateHandler {
public:
explicit PerfMonitorUpdateHandler(HostTargetDelegate& delegate)
: delegate_(delegate) {}
/**
* Handle a new "__chromium_devtools_metrics_reporter" message.
*/
void handlePerfMetricsUpdate(const std::string& message);
private:
HostTargetDelegate& delegate_;
std::optional<LongTaskPayload> lastEvent_;
bool shouldOverrideLastEvent(const LongTaskPayload& newInteraction);
};
} // namespace facebook::react::jsinspector_modern
@@ -38,17 +38,10 @@ const uint16_t PROFILE_TRACE_EVENT_CHUNK_SIZE = 1;
TracingAgent::TracingAgent(
FrontendChannel frontendChannel,
SessionState& sessionState,
HostTargetController& hostTargetController,
std::optional<tracing::TraceRecordingState> traceRecordingToEmit)
HostTargetController& hostTargetController)
: frontendChannel_(std::move(frontendChannel)),
sessionState_(sessionState),
hostTargetController_(hostTargetController) {
if (traceRecordingToEmit.has_value()) {
frontendChannel_(
cdp::jsonNotification("ReactNativeApplication.traceRequested"));
emitTraceRecording(std::move(traceRecordingToEmit.value()));
}
}
hostTargetController_(hostTargetController) {}
TracingAgent::~TracingAgent() {
// Agents are owned by the session. If the agent is destroyed, it means that
@@ -100,15 +93,22 @@ bool TracingAgent::handleRequest(const cdp::PreparsedRequest& req) {
return false;
}
void TracingAgent::emitExternalTraceRecording(
tracing::TraceRecordingState traceRecording) const {
frontendChannel_(
cdp::jsonNotification("ReactNativeApplication.traceRequested"));
emitTraceRecording(std::move(traceRecording));
}
void TracingAgent::emitTraceRecording(
tracing::TraceRecordingState state) const {
tracing::TraceRecordingState traceRecording) const {
auto dataCollectedCallback = [this](folly::dynamic&& eventsChunk) {
frontendChannel_(cdp::jsonNotification(
"Tracing.dataCollected",
folly::dynamic::object("value", std::move(eventsChunk))));
};
tracing::TraceRecordingStateSerializer::emitAsDataCollectedChunks(
std::move(state),
std::move(traceRecording),
dataCollectedCallback,
TRACE_EVENT_CHUNK_SIZE,
PROFILE_TRACE_EVENT_CHUNK_SIZE);
@@ -28,14 +28,11 @@ class TracingAgent {
* \param hostTargetController An interface to the HostTarget that this agent
* is attached to. The caller is responsible for ensuring that the
* HostTargetDelegate and underlying HostTarget both outlive the agent.
* \param traceRecordingToEmit If set, this is the trace that Host has
* requested to display in the Frontend.
*/
TracingAgent(
FrontendChannel frontendChannel,
SessionState& sessionState,
HostTargetController& hostTargetController,
std::optional<tracing::TraceRecordingState> traceRecordingToEmit);
HostTargetController& hostTargetController);
~TracingAgent();
@@ -46,6 +43,12 @@ class TracingAgent {
*/
bool handleRequest(const cdp::PreparsedRequest& req);
/**
* Emits the Trace Recording that was stashed externally by the HostTarget.
*/
void emitExternalTraceRecording(
tracing::TraceRecordingState traceRecording) const;
private:
/**
* A channel used to send responses and events to the frontend.
@@ -60,7 +63,7 @@ class TracingAgent {
* Emits the captured Trace Recording state in a series of
* Tracing.dataCollected events, followed by a Tracing.tracingComplete event.
*/
void emitTraceRecording(tracing::TraceRecordingState state) const;
void emitTraceRecording(tracing::TraceRecordingState traceRecording) const;
};
} // namespace facebook::react::jsinspector_modern
@@ -49,7 +49,7 @@ std::string jsonResult(RequestId id, const folly::dynamic& result) {
}
std::string jsonNotification(
std::string_view method,
const std::string& method,
std::optional<folly::dynamic> params) {
auto dynamicNotification = folly::dynamic::object("method", method);
if (params) {
@@ -60,7 +60,7 @@ std::string jsonNotification(
std::string jsonRequest(
RequestId id,
std::string_view method,
const std::string& method,
std::optional<folly::dynamic> params) {
auto dynamicRequest = folly::dynamic::object("id", id)("method", method);
if (params) {
@@ -118,7 +118,7 @@ std::string jsonResult(
* \param params Optional payload object.
*/
std::string jsonNotification(
std::string_view method,
const std::string& method,
std::optional<folly::dynamic> params = std::nullopt);
/**
@@ -132,7 +132,7 @@ std::string jsonNotification(
*/
std::string jsonRequest(
RequestId id,
std::string_view method,
const std::string& method,
std::optional<folly::dynamic> params = std::nullopt);
} // namespace facebook::react::jsinspector_modern::cdp
@@ -56,7 +56,7 @@ bool NetworkHandler::disable() {
}
enabled_.store(false, std::memory_order_release);
requestBodyBuffer_.clear();
responseBodyBuffer_.clear();
return true;
}
@@ -113,7 +113,10 @@ void NetworkHandler::onResponseReceived(
}
auto resourceType = cdp::network::resourceTypeFromMimeType(response.mimeType);
resourceTypeMap_.emplace(requestId, resourceType);
{
std::lock_guard<std::mutex> lock(resourceTypeMapMutex_);
resourceTypeMap_.emplace(requestId, resourceType);
}
auto params = cdp::network::ResponseReceivedParams{
.requestId = requestId,
@@ -166,23 +169,26 @@ void NetworkHandler::onLoadingFinished(
void NetworkHandler::onLoadingFailed(
const std::string& requestId,
bool cancelled) const {
bool cancelled) {
if (!isEnabledNoSync()) {
return;
}
auto params = cdp::network::LoadingFailedParams{
.requestId = requestId,
.timestamp = getCurrentUnixTimestampSeconds(),
.type = resourceTypeMap_.find(requestId) != resourceTypeMap_.end()
? resourceTypeMap_.at(requestId)
: "Other",
.errorText = cancelled ? "net::ERR_ABORTED" : "net::ERR_FAILED",
.canceled = cancelled,
};
{
std::lock_guard<std::mutex> lock(resourceTypeMapMutex_);
auto params = cdp::network::LoadingFailedParams{
.requestId = requestId,
.timestamp = getCurrentUnixTimestampSeconds(),
.type = resourceTypeMap_.find(requestId) != resourceTypeMap_.end()
? resourceTypeMap_.at(requestId)
: "Other",
.errorText = cancelled ? "net::ERR_ABORTED" : "net::ERR_FAILED",
.canceled = cancelled,
};
frontendChannel_(
cdp::jsonNotification("Network.loadingFailed", params.toDynamic()));
frontendChannel_(
cdp::jsonNotification("Network.loadingFailed", params.toDynamic()));
}
}
void NetworkHandler::storeResponseBody(
@@ -190,13 +196,13 @@ void NetworkHandler::storeResponseBody(
std::string_view body,
bool base64Encoded) {
std::lock_guard<std::mutex> lock(requestBodyMutex_);
requestBodyBuffer_.put(requestId, body, base64Encoded);
responseBodyBuffer_.put(requestId, body, base64Encoded);
}
std::optional<std::tuple<std::string, bool>> NetworkHandler::getResponseBody(
const std::string& requestId) {
std::lock_guard<std::mutex> lock(requestBodyMutex_);
auto responseBody = requestBodyBuffer_.get(requestId);
auto responseBody = responseBodyBuffer_.get(requestId);
if (responseBody == nullptr) {
return std::nullopt;
@@ -100,7 +100,7 @@ class NetworkHandler {
/**
* @cdp Network.loadingFailed
*/
void onLoadingFailed(const std::string& requestId, bool cancelled) const;
void onLoadingFailed(const std::string& requestId, bool cancelled);
/**
* Store the fetched response body for a text or image network response.
@@ -139,8 +139,9 @@ class NetworkHandler {
FrontendChannel frontendChannel_;
std::map<std::string, std::string> resourceTypeMap_{};
std::mutex resourceTypeMapMutex_{};
BoundedRequestBuffer requestBodyBuffer_{};
BoundedRequestBuffer responseBodyBuffer_{};
std::mutex requestBodyMutex_;
};
@@ -124,7 +124,7 @@ std::optional<std::vector<TraceEvent>> PerformanceTracer::stopTracing() {
}
void PerformanceTracer::reportMark(
const std::string_view& name,
const std::string& name,
HighResTimeStamp start,
folly::dynamic&& detail) {
if (!tracingAtomic_) {
@@ -137,7 +137,7 @@ void PerformanceTracer::reportMark(
}
enqueueEvent(PerformanceTracerEventMark{
.name = std::string(name),
.name = name,
.start = start,
.detail = std::move(detail),
.threadId = getCurrentThreadId(),
@@ -145,7 +145,7 @@ void PerformanceTracer::reportMark(
}
void PerformanceTracer::reportMeasure(
const std::string_view& name,
const std::string& name,
HighResTimeStamp start,
HighResDuration duration,
folly::dynamic&& detail) {
@@ -159,7 +159,7 @@ void PerformanceTracer::reportMeasure(
}
enqueueEvent(PerformanceTracerEventMeasure{
.name = std::string(name),
.name = name,
.start = start,
.duration = duration,
.detail = std::move(detail),
@@ -168,7 +168,7 @@ void PerformanceTracer::reportMeasure(
}
void PerformanceTracer::reportTimeStamp(
std::string name,
const std::string& name,
std::optional<ConsoleTimeStampEntry> start,
std::optional<ConsoleTimeStampEntry> end,
std::optional<std::string> trackName,
@@ -184,7 +184,7 @@ void PerformanceTracer::reportTimeStamp(
}
enqueueEvent(PerformanceTracerEventTimeStamp{
.name = std::move(name),
.name = name,
.start = std::move(start),
.end = std::move(end),
.trackName = std::move(trackName),
@@ -232,6 +232,50 @@ void PerformanceTracer::reportEventLoopMicrotasks(
});
}
void PerformanceTracer::reportResourceTiming(
const std::string& requestId,
const std::string& url,
HighResTimeStamp fetchStart,
HighResTimeStamp responseStart,
HighResTimeStamp responseEnd,
int statusCode,
const std::string& requestMethod,
const std::string& resourceType) {
if (!tracingAtomic_) {
return;
}
std::lock_guard<std::mutex> lock(mutex_);
if (!tracingAtomic_) {
return;
}
enqueueEvent(PerformanceTracerResourceWillSendRequest{
.requestId = requestId,
.start = fetchStart,
.threadId = getCurrentThreadId(),
});
enqueueEvent(PerformanceTracerResourceSendRequest{
.requestId = requestId,
.url = url,
.start = fetchStart,
.requestMethod = requestMethod,
.resourceType = resourceType,
.threadId = getCurrentThreadId(),
});
enqueueEvent(PerformanceTracerResourceReceiveResponse{
.requestId = requestId,
.start = responseStart,
.statusCode = statusCode,
.threadId = getCurrentThreadId(),
});
enqueueEvent(PerformanceTracerResourceFinish{
.requestId = requestId,
.start = responseEnd,
.threadId = getCurrentThreadId(),
});
}
/* static */ TraceEvent PerformanceTracer::constructRuntimeProfileTraceEvent(
RuntimeProfileId profileId,
ProcessId processId,
@@ -505,6 +549,73 @@ void PerformanceTracer::enqueueTraceEventsFromPerformanceTracerEvent(
.args = folly::dynamic::object("data", std::move(data)),
});
},
[&](PerformanceTracerResourceWillSendRequest&& event) {
folly::dynamic data =
folly::dynamic::object("requestId", std::move(event.requestId));
events.emplace_back(TraceEvent{
.name = "ResourceWillSendRequest",
.cat = "devtools.timeline",
.ph = 'I',
.ts = event.start,
.pid = processId_,
.s = 't',
.tid = event.threadId,
.args = folly::dynamic::object("data", std::move(data)),
});
},
[&](PerformanceTracerResourceSendRequest&& event) {
folly::dynamic data =
folly::dynamic::object("initiator", folly::dynamic::object())(
"renderBlocking", "non_blocking")(
"requestId", std::move(event.requestId))(
"requestMethod", std::move(event.requestMethod))(
"resourceType", std::move(event.resourceType))(
"url", std::move(event.url));
events.emplace_back(TraceEvent{
.name = "ResourceSendRequest",
.cat = "devtools.timeline",
.ph = 'I',
.ts = event.start,
.pid = processId_,
.s = 't',
.tid = event.threadId,
.args = folly::dynamic::object("data", std::move(data)),
});
},
[&](PerformanceTracerResourceReceiveResponse&& event) {
folly::dynamic data = folly::dynamic::object("protocol", "h2")(
"requestId", std::move(event.requestId))(
"statusCode", event.statusCode)(
"timing", folly::dynamic::object());
events.emplace_back(TraceEvent{
.name = "ResourceReceiveResponse",
.cat = "devtools.timeline",
.ph = 'I',
.ts = event.start,
.pid = processId_,
.s = 't',
.tid = event.threadId,
.args = folly::dynamic::object("data", std::move(data)),
});
},
[&](PerformanceTracerResourceFinish&& event) {
folly::dynamic data = folly::dynamic::object("didFail", false)(
"requestId", std::move(event.requestId));
events.emplace_back(TraceEvent{
.name = "ResourceFinish",
.cat = "devtools.timeline",
.ph = 'I',
.ts = event.start,
.pid = processId_,
.s = 't',
.tid = event.threadId,
.args = folly::dynamic::object("data", std::move(data)),
});
},
},
std::move(event));
}
@@ -65,7 +65,7 @@ class PerformanceTracer {
* See https://w3c.github.io/user-timing/#mark-method.
*/
void reportMark(
const std::string_view& name,
const std::string& name,
HighResTimeStamp start,
folly::dynamic&& detail = nullptr);
@@ -76,7 +76,7 @@ class PerformanceTracer {
* See https://w3c.github.io/user-timing/#measure-method.
*/
void reportMeasure(
const std::string_view& name,
const std::string& name,
HighResTimeStamp start,
HighResDuration duration,
folly::dynamic&& detail = nullptr);
@@ -89,7 +89,7 @@ class PerformanceTracer {
https://developer.chrome.com/docs/devtools/performance/extension#inject_your_data_with_consoletimestamp
*/
void reportTimeStamp(
std::string name,
const std::string& name,
std::optional<ConsoleTimeStampEntry> start = std::nullopt,
std::optional<ConsoleTimeStampEntry> end = std::nullopt,
std::optional<std::string> trackName = std::nullopt,
@@ -108,6 +108,21 @@ class PerformanceTracer {
*/
void reportEventLoopMicrotasks(HighResTimeStamp start, HighResTimeStamp end);
/**
* Record a "ResourceSendRequest"/"ResourceFinish" event pair - a labelled
* duration in the Performance timeline Network track. If not currently
* tracing, this is a no-op.
*/
void reportResourceTiming(
const std::string& requestId,
const std::string& url,
HighResTimeStamp fetchStart,
HighResTimeStamp responseStart,
HighResTimeStamp responseEnd,
int statusCode,
const std::string& requestMethod,
const std::string& resourceType);
/**
* Creates "Profile" Trace Event.
*
@@ -181,12 +196,48 @@ class PerformanceTracer {
HighResTimeStamp createdAt = HighResTimeStamp::now();
};
struct PerformanceTracerResourceWillSendRequest {
std::string requestId;
HighResTimeStamp start;
ThreadId threadId;
HighResTimeStamp createdAt = HighResTimeStamp::now();
};
struct PerformanceTracerResourceSendRequest {
std::string requestId;
std::string url;
HighResTimeStamp start;
std::string requestMethod;
std::string resourceType;
ThreadId threadId;
HighResTimeStamp createdAt = HighResTimeStamp::now();
};
struct PerformanceTracerResourceFinish {
std::string requestId;
HighResTimeStamp start;
ThreadId threadId;
HighResTimeStamp createdAt = HighResTimeStamp::now();
};
struct PerformanceTracerResourceReceiveResponse {
std::string requestId;
HighResTimeStamp start;
int statusCode;
ThreadId threadId;
HighResTimeStamp createdAt = HighResTimeStamp::now();
};
using PerformanceTracerEvent = std::variant<
PerformanceTracerEventTimeStamp,
PerformanceTracerEventEventLoopTask,
PerformanceTracerEventEventLoopMicrotask,
PerformanceTracerEventMark,
PerformanceTracerEventMeasure>;
PerformanceTracerEventMeasure,
PerformanceTracerResourceWillSendRequest,
PerformanceTracerResourceSendRequest,
PerformanceTracerResourceReceiveResponse,
PerformanceTracerResourceFinish>;
#pragma mark - Private fields and methods
@@ -49,8 +49,14 @@ struct RuntimeSamplingProfile {
/// id of the corresponding script in the VM.
uint32_t scriptId;
/// name of the function that represents call frame.
/// Storing a std::string_view should be considered safe here, beacause
/// the lifetime of the string contents are guaranteed as long as the raw
// Sampling Profiler object from Hermes is allocated.
std::string_view functionName;
/// source url of the corresponding script in the VM.
/// Storing a std::string_view should be considered safe here, beacause
/// the lifetime of the string contents are guaranteed as long as the raw
// Sampling Profiler object from Hermes is allocated.
std::optional<std::string_view> scriptURL = std::nullopt;
/// 0-based line number of the corresponding call frame.
std::optional<uint32_t> lineNumber = std::nullopt;
@@ -55,6 +55,12 @@ struct TraceEvent {
/** The ID for the process that output this event. */
ProcessId pid;
/**
* The scope of the event, either global (g), process (p), or thread (t).
* Only applicable to instant events ("ph": "i").
*/
std::optional<char> s;
/** The ID for the thread that output this event. */
ThreadId tid;
@@ -26,6 +26,9 @@ namespace facebook::react::jsinspector_modern::tracing {
result["ph"] = std::string(1, event.ph);
result["ts"] = highResTimeStampToTracingClockTimeStamp(event.ts);
result["pid"] = event.pid;
if (event.s.has_value()) {
result["s"] = std::string(1, event.s.value());
}
result["tid"] = event.tid;
result["args"] = std::move(event.args);
if (event.dur.has_value()) {
@@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<a4fb74cf8a68181f509794e387bf666d>>
* @generated SignedSource<<42bea10b4b62a91dce8fde7674f76ceb>>
*/
/**
@@ -66,6 +66,10 @@ bool ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid() {
return getAccessor().enableAccumulatedUpdatesInRawPropsAndroid();
}
bool ReactNativeFeatureFlags::enableAndroidLinearText() {
return getAccessor().enableAndroidLinearText();
}
bool ReactNativeFeatureFlags::enableAndroidTextMeasurementOptimizations() {
return getAccessor().enableAndroidTextMeasurementOptimizations();
}
@@ -170,6 +174,10 @@ bool ReactNativeFeatureFlags::enableResourceTimingAPI() {
return getAccessor().enableResourceTimingAPI();
}
bool ReactNativeFeatureFlags::enableSwiftUIBasedFilters() {
return getAccessor().enableSwiftUIBasedFilters();
}
bool ReactNativeFeatureFlags::enableViewCulling() {
return getAccessor().enableViewCulling();
}

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