Compare commits

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

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

## Changelog:

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


Test Plan:
Tested locally with RNTester by doing:

```
./gradlew installDebugOptimized
```

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

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

Rollback Plan:

Reviewed By: cipolleschi

Differential Revision: D78351347

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

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

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

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

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

Reviewed By: NickGerleman

Differential Revision: D78323174

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

Changelog: [internal]

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

Reviewed By: rshest

Differential Revision: D78550866

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

Changelog: [internal]

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

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

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

This improves performance slightly for `performance` methods:

* Before

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

* After

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

Reviewed By: hoxyq

Differential Revision: D78412931

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

Changelog: [internal]

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

Reviewed By: huntie

Differential Revision: D78412932

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

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

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

Reviewed By: cortinico

Differential Revision: D78494491

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

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

Changelog: [Internal]

Reviewed By: sbuggay

Differential Revision: D78534334

fbshipit-source-id: 291bdd01b41fa6efea00ed63a0dee8bdb14cbc3a
2025-07-17 20:42:02 -07:00
Ramanpreet NaraandFacebook GitHub Bot 862e8c7049 IntersectionObserver: Clean up legacy observe/unobserve methods
Summary:
In this diff, we migrated IntersectionObserver to tokens: D74262804.

So that we wouldn't have to store shadow nodes on the javascript side.

Storing shadow nodes lead to a memory leak in the past.

Changelog: [internal]

Reviewed By: rubennorte

Differential Revision: D78494075

fbshipit-source-id: 38923ca4b265de6ff81ea20e5649e0bd2e39afc9
2025-07-17 17:35:22 -07:00
Sam ZhouandFacebook GitHub Bot 23c8787fe2 Add annotations to fix future errors after fix for unsound array types (#52691)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52691

Unannotated array literals are unsound in Flow right now. This diff adds in annotations and makes a few things readonly, to reduce future errors.

Changelog: [Internal]

Reviewed By: marcoww6

Differential Revision: D78519638

fbshipit-source-id: d98a7668ecf97bcc87dcb3fad25ade736d885d9a
2025-07-17 17:30:43 -07:00
Tim YungandFacebook GitHub Bot d78c242e4d RN: Enable enableVirtualViewRenderState by Default (#52686)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52686

Enables the `enableVirtualViewRenderState` feature flag by default.

Also, fixed classification for this and the `enableVirtualViewWindowFocusDetection` feature flags. (They were incorrect.)

Changelog:
[Internal]

Reviewed By: lunaleaps

Differential Revision: D78500846

fbshipit-source-id: 099adaa4ed0026c8bb7438df869564d76a999007
2025-07-17 12:59:13 -07:00
Tim YungandFacebook GitHub Bot 5d4d1ce84b RN: Cleanup scheduleAnimatedCleanupInMicrotask Flag (#52685)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52685

Cleans up the `scheduleAnimatedCleanupInMicrotask ` feature flag and deletes code paths that are now unreachable.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D78498600

fbshipit-source-id: 307562030373742e64968ae4c92d6bfbb48ddc71
2025-07-17 12:59:13 -07:00
Tim YungandFacebook GitHub Bot 5c7fdb252e RN: Cleanup disableInteractionManager Flag (#52684)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52684

Cleans up the `disableInteractionManager` feature flag and deletes code paths that are now unreachable.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D78498348

fbshipit-source-id: a3128c1e5ca9c5879080df54b7683a3c916cec13
2025-07-17 12:59:13 -07:00
Tim YungandFacebook GitHub Bot 1814418f12 RN: Cleanup avoidStateUpdateInAnimatedPropsMemo Flag (#52683)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52683

Cleans up the `avoidStateUpdateInAnimatedPropsMemo` feature flag and deletes code paths that are now unreachable.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D78497862

fbshipit-source-id: 26406e713ca5b7b194159638a20dcfca877ba380
2025-07-17 12:59:13 -07:00
Tim YungandFacebook GitHub Bot 0a3808d07c RN: Cleanup alwaysFlattenAnimatedStyles Flag (#52682)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52682

Cleans up the `alwaysFlattenAnimatedStyles` feature flag.

This was already unused starting with {D77314904}.

Changelog:
[Internal]

Reviewed By: mdvacca

Differential Revision: D78497554

fbshipit-source-id: 8bfbe6485812a675cdf3e78612347457f45ada90
2025-07-17 12:59:13 -07:00
Tim YungandFacebook GitHub Bot 0508eddfe6 RN: Default Hermes Parser to reactRuntimeTarget: "19" (#52625)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52625

Changes `react-native/babel-preset` so that by default, `hermes-parser` is configured with `reactRuntimeTarget: "19"`. This changes the compiled output of Component Syntax to not use `forwardRef` when a `ref` prop is present.

Additionally, this adds a new preset option property, `hermesParserOptions`. This object allows users of `react-native/babel-preset` to supply overrides for any `hermes-parser` options.

Changelog:
[General][Changed] - Configures `react-native/babel-preset` to target React 19 by default, meaning Component Syntax will not compile to `forwardRef` calls when a `ref` prop is present.
[General][Added] - Added support to `react-native/babel-preset` for a `hermesParserOptions` option, that expects an object that enables overriding `hermes-parser` options.

Reviewed By: SamChou19815

Differential Revision: D78383269

fbshipit-source-id: 1e6b66b9bfbeaf8a06fdc39031cb6de7e921765f
2025-07-17 12:53:36 -07:00
Rubén NorteandFacebook GitHub Bot 25c61123fb Fix incorrect locking and attempts check in ShadowTree experiment (#52681)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52681

Changelog: [internal]

In the original change I made in D78418504 / https://github.com/facebook/react-native/pull/52645 I made 2 mistakes:
1. Used a lock that would try to re-lock on itself without it being recursive (which would cause a deadlock). I didn't see that because when testing I didn't hit the case where we'd exhaust the options.
2. The `attemps` variable wasn't incremented, so we never left the loop in case of exhaustion.

This propagates a flag to `tryCommit` to indicate we've already locked on the commitMutex_ so we don't need to lock again in that case and increases the counter, fixing the issue.

Reviewed By: cortinico

Differential Revision: D78497509

fbshipit-source-id: 546ccd0c84aed5416ce1aef47d79419b4fe06f66
2025-07-17 12:18:52 -07:00
Rubén NorteandFacebook GitHub Bot 9b30fe0c45 Back out "Add missing attempts++ in ShadowNode with MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING" (#52680)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52680

Changelog: [internal]

Reverting to make the fix easier to pick. Will land again on top.

Original commit changeset: 91d7f5d88a90

Original Phabricator Diff: D78487202

Reviewed By: cortinico

Differential Revision: D78497511

fbshipit-source-id: 58379787786cadcbcbe354feb97bc400a24f9d58
2025-07-17 12:18:52 -07:00
Rubén NorteandFacebook GitHub Bot fef6e0f584 Back out "Fix incorrect locking in ShadowTree experiment" (#52679)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52679

Changelog: [internal]

Reverting to make the fix easier to pick. Will land again on top.

Original commit changeset: c18e967488de

Original Phabricator Diff: D78480136

Reviewed By: cortinico

Differential Revision: D78497510

fbshipit-source-id: 8037276cb70d27ff695c76cd9575b525568c4489
2025-07-17 12:18:52 -07:00
Luna WeiandFacebook GitHub Bot 4c9591c0ea Use experimental VirtualView (#52592)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52592

Changelog: [Internal] - Expose VirtualViewExperimental in JS, use in a test surface

Reviewed By: yungsters

Differential Revision: D78027899

fbshipit-source-id: d3d9808b8ba8e36c5fdb0c831c0855ed318d0a30
2025-07-17 12:15:57 -07:00
Luna WeiandFacebook GitHub Bot 937eac64e8 Move off of ReactScrollViewHelper.ScrollListener (#52591)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52591

Changelog: [Internal] Stop using ReactScrollViewHelper.ScrollListener for layout, scroll events from ScrollView

Reviewed By: yungsters

Differential Revision: D78287689

fbshipit-source-id: 3fe944d90e0165d10953a603e5694ac7bbaeb9b8
2025-07-17 12:15:57 -07:00
Luna WeiandFacebook GitHub Bot 510bfd4101 Fix VirtualViewExperimental (#52590)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52590

Changelog: [Internal] - Fix a couple of bugs with VirtualViewExperimental

1. `also` side-effect bug where we were accessing `scrollView` before it was assigned
2. Handling empty `rect`. Now, we don't add the virtualView until it has a non-empty rect layout

Reviewed By: yungsters

Differential Revision: D78287690

fbshipit-source-id: 1806b748a0117e51c7a201191396d79db3a8d205
2025-07-17 12:15:57 -07:00
Nicola CortiandFacebook GitHub Bot 847721dc9b Add missing attempts++ in ShadowNode with MAX_COMMIT_ATTEMPTS_BEFORE_LOCKING (#52673)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52673

We should be incrementing the `attempts` variable as otherwise this loop with never stop.

Created from CodeHub with https://fburl.com/edit-in-codehub

Changelog:
[Internal] -

Reviewed By: rubennorte

Differential Revision: D78487202

fbshipit-source-id: 91d7f5d88a90bdfa806195fd35897f2f51c1deb9
2025-07-17 09:02:19 -07:00
Rubén NorteandFacebook GitHub Bot 2a01eadcb8 Remove unnecessary $FlowFixMe after correct typing for console.timeStamp (#52633)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52633

Changelog: [internal]

Now that we have the correct type definition for `console.timeStamp` we can remove the unnecessary `$FlowFixMe` annotations we had to add to use the new arguments.

Reviewed By: hoxyq

Differential Revision: D78405312

fbshipit-source-id: 29378ee6fa5986e22d0dfdfb85b7e25375361dc9
2025-07-17 08:35:36 -07:00
Rubén NorteandFacebook GitHub Bot c247554342 Add correct typing for console (including console.timeStamp) (#52632)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52632

Changelog: [internal]

This improves the type definitions for methods in the global `console` object, most importantly:
- Removes use of `any`.
- Defines correct typing for `console.timeStamp`, including new parameters for the Chrome Extensibility API.

Reviewed By: hoxyq

Differential Revision: D78405314

fbshipit-source-id: 6f31cc3005ff708ef6aa155bcd150d53a845e66c
2025-07-17 08:35:36 -07:00
Rubén NorteandFacebook GitHub Bot 10ddec7aeb Add stubs for missing console methods (#52643)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52643

Changelog: [internal]

Defines stubs for the following `console` methods:
* `time`
* `timeEnd`
* `count`
* `countReset`

Reviewed By: huntie

Differential Revision: D78418212

fbshipit-source-id: 8063f240f1e3fcfb3e36a2b43e61ca0e8cdf94db
2025-07-17 08:35:36 -07:00
Rubén NorteandFacebook GitHub Bot b9e1e37c87 Define typing for performance extensibility API (#52631)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52631

Changelog: [internal]

Just a small improvement in the type definitions to serve as documentation for the expected arguments for custom tracks in RNDT.

Reviewed By: hoxyq

Differential Revision: D78405313

fbshipit-source-id: 75d250ea27f2e55e172aaf6e72399b85cecd43f4
2025-07-17 08:35:36 -07:00
Krystof WoldrichandFacebook GitHub Bot c4082c9ce2 surface uncaught promise rejections with ExceptionsManager.handleException instead of swallowing them (#51594)
Summary:
This PR fixes logging of rejections, which stopped working with the transition from using `LogBox` for warnings to directing users to use React Native Debugger, where warnings can be viewed in the `Console` tab.

Rejected promises before this PR used `LogBox.addLog` with the `level: warn` directly without `console.warn`, which notified users to open debugger to view a warning (after rejected promise) without actually printing any warnings to the console.

This PR uses `ExceptionsManager.handleException(error, false /* isFatal */);` for promise rejections that correctly raising an error to the console if needed.

### Rejected Promise in Chrome
Displaying the error as long as it's uncaught in the form:
`Uncaught (in promise) Error`
and if there's a message in that error:
`Uncaught (in promise) Error: ${message}`
once the error is handled, this error disappears:
{F1978806409}
This is done with `Runtime.exceptionThrown`, and  `Runtime.exceptionRevoked`:
{F1979815326}

## Changelog:
[General][Breaking] unhandled promises are now handled by ExceptionsManager.handleException, instead of being swallowed as Logbox Warnings.
----
This is a breaking change because we changed the wording for these errors and because some systems (like Sentry) might start monitoring a new set of errors when this code is adopted.

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

Test Plan:
Use `Promise.reject` and observe that the rejection is now shown symbolicated in LogBox and also printed in React Native Debugger Console.

```
<Button
  title="Uncaught Promise Rejection"
  onPress={() => {
    const err = new Error('test error');
    err.cause = new Error('cause');
    const promise = Promise.resolve().then(() => {
      throw err;
      // also test with throwing a string / object:
      // throw 'aa';
      // throw {test: 'object'};
      // return Promise.reject();
    });

    setTimeout(() => {
      promise.catch(e => {
        console.log('promise error caught', e);
      });
    }, 4000);
  }}
/>
```

### Before
There's no indication whatsoever there's an uncaught promise.

### After
An error is logged:
Error-
{F1979749366}
Text-
{F1979749350}
Object-
{F1979749351}
Promise.reject() or undefined-
 {F1979749826}

Reviewed By: hoxyq

Differential Revision: D75442651

Pulled By: vzaidman

fbshipit-source-id: bf6c56e643f03997f5a604b85c543aad62648a29
2025-07-17 07:55:50 -07:00
Christian FalchandFacebook GitHub Bot 4ee2b60a1e resolve xcframework paths from conf switch script (#52664)
Summary:
When switching between debug/release we run a small script to make sure to copy the correct version of the RNDeps xcframework.

This script was missing a resolve function that fixed up some path issues that we do when installing in the podspec.

## Changelog:

[IOS] [FIXED] - Fixed issue with RNDeps release/debug switch failing

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

Test Plan:
- Create new RN App
- Install pod with prebuilt deps
- Build (success)
- Switch to release
- Build (success)

Reviewed By: cortinico

Differential Revision: D78481590

Pulled By: cipolleschi

fbshipit-source-id: 2d02b0bc55e8aef6f3fafb4f7aa193c4cf00414e
2025-07-17 06:46:00 -07:00
Rubén NorteandFacebook GitHub Bot 2d4c43c267 Fix incorrect locking in ShadowTree experiment (#52662)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52662

Changelog: [internal]

In the original change I made in D78418504 / https://github.com/facebook/react-native/pull/52645 I made a mistake and used a lock that would try to re-lock on itself without it being recursive (which would cause a deadlock). I didn't see that because when testing I didn't hit the case where we'd exhaust the options.

This propagates a flag to `tryCommit` to indicate we've already locked on the `commitMutex_` so we don't need to lock again in that case, fixing the issue.

Reviewed By: sammy-SC

Differential Revision: D78480136

fbshipit-source-id: c18e967488de14e73e6abf6f6e82c16bc42a12c6
2025-07-17 05:37:07 -07:00
Christian FalchandFacebook GitHub Bot 2e55241a90 added missing script in package.json (#52663)
Summary:
When switching between release/debug we're running a script to copy the correct xcframework. This script for the React-Core prebuilts was not part of the package.json file.

This caused the build to fail after trying to switch from debug -> release.

## Changelog:
[IOS] [FIXED] - Fixed missing script for resolving prebuilt xcframework when switching between release/debug

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

Test Plan:
- Create new RN App
- Install pod with prebuilt deps and core
- Build (success)
- Switch to release
- Build (success)

Reviewed By: cortinico

Differential Revision: D78481302

Pulled By: cipolleschi

fbshipit-source-id: 1c7181e63219098ae140d77ff1cb2c0c9b9642e5
2025-07-17 05:26:07 -07:00
Riccardo CipolleschiandFacebook GitHub Bot 2c752af535 Fix Windows CI (#52666)
Summary:
As per [this issue](https://github.com/actions/runner-images/issues/12416), Windows machine doesn't have access to D: drive anymore

## Changelog:
[Internal] -

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

Test Plan: GHA

Reviewed By: huntie

Differential Revision: D78484060

Pulled By: cipolleschi

fbshipit-source-id: 36d844f9d7d69f1d74a154b019307cc1e269ad66
2025-07-17 05:15:04 -07:00
Pieter De BaetsandFacebook GitHub Bot b6f4142bb2 Reduce legacy arch warning for OnLayoutEvent (#52665)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52665

This API is being used by react-native-svg (https://github.com/software-mansion/react-native-svg/blob/main/android/src/main/java/com/horcrux/svg/VirtualView.java#L606) which is causing issues when enabling legacy arch minification.

Changelog: [Internal]

Reviewed By: cortinico

Differential Revision: D78478751

fbshipit-source-id: 7f48368c11ec855bbeb5e904628b082a5bc741ff
2025-07-17 04:39:41 -07:00
Nicola CortiandFacebook GitHub Bot 2f553ec7f6 Unbreak build_android due to missing dependency on rrc_view (#52661)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52661

The CI is currently red on main due to a missing dependency on `rrc_view`
from the recent TransformHelper.cpp addition.
This fixes it.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi

Differential Revision: D78479785

fbshipit-source-id: 3d2b698d9ce46cf22c15ad43005f621526590145
2025-07-17 04:09:25 -07:00
Nicola CortiandFacebook GitHub Bot 7ef57163cb Make accessors inside HeadlessJsTaskService open again (#52660)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52660

The documentation for those methods mention that users should override them to
provide their own implementation.

However those vals are not `open` so users cannot really override them.
This fixes it.

See more context on https://github.com/facebook/react-native/pull/48800#issuecomment-3082665024

So this was practically a breaking change, that I'm attempting to mitigate.

Changelog:
[Android] [Fixed] - Make accessors inside HeadlessJsTaskService open again

Reviewed By: cipolleschi

Differential Revision: D78479162

fbshipit-source-id: eefc7332e2004198cd6bd64b60a66215f137ad4a
2025-07-17 04:05:47 -07:00
Moti ZilbermanandFacebook GitHub Bot bbda71f2c8 Fix argv handling for packaged mode (#52641)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52641

Changelog: [Internal]

TSIA, missed this in D77591742

bypass-github-export-checks

Reviewed By: huntie

Differential Revision: D78413090

fbshipit-source-id: a02d604def172b531a89021496208171373ddd8c
2025-07-17 03:13:26 -07:00
Moti ZilbermanandFacebook GitHub Bot 60efc757d9 Implement --version flag
Summary:
Changelog: [Internal]

Adds a basic `--version` command line flag to the RNDT shell. This will be used in code paths (including tests) that need to verify that the shell is executable, but do not need to actually display a window or hand over control to the main shell instance.

bypass-github-export-checks

Reviewed By: huntie

Differential Revision: D78351936

fbshipit-source-id: e8982cfea6435da0ac9ea5f50d57c7642a8e2edb
2025-07-17 03:13:26 -07:00
Moti ZilbermanandFacebook GitHub Bot 4a1884e5e5 Exclude unimportant files from Electron build
Summary:
TSIA

Changelog: [Internal]

bypass-github-export-checks

Reviewed By: huntie

Differential Revision: D78351935

fbshipit-source-id: 7f71f3df3ecf8ce92891109b8d9f8e0a74361f21
2025-07-17 03:13:26 -07:00
David VaccaandFacebook GitHub Bot 20baf2a992 Delete ReactNativeNewArchitectureFeatureFlags.isNewArchitectureStrictModeEnabled() (#52650)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52650

ReactNativeNewArchitectureFeatureFlags.isNewArchitectureStrictModeEnabled() is unused, let's delete it

changelog: [internal] internal

Reviewed By: arushikesarwani94

Differential Revision: D78438409

fbshipit-source-id: 8208ae0d3f59e6a3197643e4a7610db3116eb1c6
2025-07-16 18:06:29 -07:00
Rubén NorteandFacebook GitHub Bot 21cd09d4c0 Implement mechanism to prevent ShadowTree commit exhaustion (#52645)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52645

Changelog: [internal]

This add a new feature flag to test a fix for https://github.com/facebook/react-native/issues/51870

Reviewed By: cortinico, sammy-SC

Differential Revision: D78418504

fbshipit-source-id: 2792026b6936393d196fd1e3162f8b2c61a38ed6
2025-07-16 12:02:07 -07:00
Joe VilchesandFacebook GitHub Bot d00de31aef Use setter for accessibilityElements (#52644)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52644

There have been some hard to find crashes with iOS outlined in https://fb.workplace.com/groups/3615245781855602/permalink/23898365966450285/. Talking with lenaic this may be due to us not using the setter to set this property. Let's try that

Changelog: [Internal]

Reviewed By: lenaic

Differential Revision: D78413954

fbshipit-source-id: a68b90a2276805283082ceaf1020e13bd8ef8eb6
2025-07-16 10:56:28 -07:00
generatedunixname89002005287564andFacebook GitHub Bot de67d6ec67 Fix CQS signal modernize-use-using in xplat/js/react-native-github/packages (#52629)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52629

Reviewed By: javache

Differential Revision: D78404030

fbshipit-source-id: 553a9f26327c4b3439b773981c25b5e1d469d8f1
2025-07-16 10:23:13 -07:00
Nicola CortiandFacebook GitHub Bot 88bafeddab Add TransformHelper.cpp to reactnativejni_common (#52640)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52640

Not having `TransformHelper.cpp` included in CMake is causing the C++ code to fail compiling.
This diff fixes it.

Changelog:
[Internal] [Changed] -

Reviewed By: cipolleschi, javache

Differential Revision: D78414015

fbshipit-source-id: 4900427a86eb38bfec10e5e385296d89c73e9051
2025-07-16 09:52:04 -07:00
Mathieu ActhernoeneandFacebook GitHub Bot 86994a6e22 Fix Dimensions window values on Android < 15 (#52481)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52481

This PR (initially created for edge-to-edge opt-in support, rebased multiple times) fixes the `Dimensions` API `window` values on Android < 15, when edge-to-edge is enabled.

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

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

Using `WindowMetricsCalculator` from AndroidX:

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

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

## Changelog:

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

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

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

Rollback Plan:

Reviewed By: cortinico

Differential Revision: D77906644

Pulled By: alanleedev

fbshipit-source-id: 121cd6bc4133973f06b28eb9e79c9387ac7070a1
2025-07-16 09:15:43 -07:00
Fabrizio CucciandFacebook GitHub Bot b795d61592 Add skipActivityIdentityAssertionOnHostPause feature flag (#52639)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52639

Add feature flag in the attempt to address the pile of tasks reporting the following error in panelapps:

> Error: android_crash:java.lang.AssertionError:com.facebook.react.runtime.ReactHostImpl.onHostPause

Changelog: [Internal]

Reviewed By: javache

Differential Revision: D78339196

fbshipit-source-id: 9ef748e7ea85f179d8f8c418a978bd5c99f70601
2025-07-16 08:42:41 -07:00
Samuel SuslaandFacebook GitHub Bot 0fd060f558 disable view culling when accessibility API is detected (#52612)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52612

changelog: [internal]

View Culling is purely performance optimisation but it must work correctly with accessibility features like VoiceOver and Switch Control.
In this diff, View Culling is lazily disabled whenever use of an accessibility feature is detected to make sure all views are present in the view hierarchy.

Reviewed By: NickGerleman, philIip

Differential Revision: D78336010

fbshipit-source-id: 7a201afc8e2ffd8b586d75ed4de2c03d7966750c
2025-07-16 08:16:49 -07:00
Samuel SuslaandFacebook GitHub Bot 9967908e89 introduce UpdateMode::unstable_Immediate (#52604)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52604

changelog: [internal]

For Fabric View Culling to work correctly, content offset changes must be applied synchronously to avoid UI flicker.

The flicker happens because content offset update happens asynchronously and the OS paints scroll position update before React Native has a chance to adjust view hierarchy for the new scroll position.

Reviewed By: lenaic

Differential Revision: D78334322

fbshipit-source-id: dc1f1b3f9db9f9547e5a588dc184fcf21cca2727
2025-07-16 08:16:49 -07:00
Pieter De BaetsandFacebook GitHub Bot a9ece0c302 Use native helpers to accelerate transform processing (#52603)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52603

Processing transforms is expensive in Java, as it requires bridging the entire ReadableNativeArray/Map. Instead, we can use the existing parser logic `resolveTransform` logic to perform this operation in C++.

Ideally, we actually re-use the existing parsed transform from Props, that could be something we revisit after Props 2.0.

As a follow-up, we should consider also moving the matrix decomposition logic from MatrixMathHelper here, and make that the only information we send back to Java.

Changelog: [Internal]

Reviewed By: NickGerleman

Differential Revision: D78298588

fbshipit-source-id: a698ac8587ccfb2be04665747082398ccdde9294
2025-07-16 06:29:10 -07:00
Pieter De BaetsandFacebook GitHub Bot 5d3d23b32e Use native implementation of equals in ReadableNativeArray (#52611)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52611

We compare the current transform (represented as a ReadableArray) with the incoming one to know whether to invalidate. This can be expensive as it requires to materialize the entire transform data structure over JNI. Instead, we can delegate this comparison to native code, which can compare the underlying folly::dynamic directly.

Changelog: [Internal]

Reviewed By: NickGerleman

Differential Revision: D78340288

fbshipit-source-id: f44a054e234694c316fb080fe2dbc2017780123a
2025-07-16 06:29:10 -07:00
Rubén NorteandFacebook GitHub Bot bd198324d8 Add support for details field and custom tracks in performance.mark and performance.measure for DevTools (#52613)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52613

Changelog: [internal]

This adds first-class support for the `detail` field in `performance.mark` and `performance.measure`.

Now that we have access the JS entry in native, we can access the `detail` field to propagate it to DevTools (and use it to extract track names for Perfetto).

In order to avoid the performance overhead of always having to extract the `detail` field from the entry, this is done lazily only if we're actively profiling with DevTools or Perfetto.

Reviewed By: sbuggay

Differential Revision: D78340911

fbshipit-source-id: 383dd1cb6fcc8a04be9e65038503986f196e23c9
2025-07-16 05:55:03 -07:00
Rubén NorteandFacebook GitHub Bot 2f67285545 Remove support for specifying track names for Perfetto and RNDT using the "Track:" prefix (#52614)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52614

Changelog: [internal]

We're adding first-class support for custom tracks, so we can remove the legacy mechanism to specify tracks with the "Tracks:" prefix in the event names.

Reviewed By: sbuggay

Differential Revision: D78340910

fbshipit-source-id: cbbadd519baf7bb50072cb97d8cd1ccc87a8a35c
2025-07-16 05:55:03 -07:00
Rubén NorteandFacebook GitHub Bot 4edc33e4e1 Refactor implementation of performance.mark and performance.measure (#52586)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52586

Changelog: [internal]

This fixes a "bug" (or spec-compliance issue) in the `performance.measure` method where `end` and `duration` couldn't be used together (only `start` and `duration`, and `start` and `end` could be used).

This also refactors the API to be "JS-first", preparing the native module methods to support passing instances of entries to fix other issues.

Verified performance impact: `mark` is slightly regressed (~7% slower) due to an additional JSI call to get the default start time and `measure` is significantly optimized (25/30% faster) due to simplified parameter handling in JSI calls.

* Before

| (index) | Task name                                                 | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples |
| ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- |
| 0       | 'mark (default)'                                          | '1621.03 ± 1.17%'    | '1562.00'           | '636986 ± 0.01%'           | '640205'                  | 616890  |
| 1       | 'mark (with custom startTime)'                            | '1756.88 ± 1.15%'    | '1693.00'           | '586826 ± 0.01%'           | '590667'                  | 569190  |
| 2       | 'measure (default)'                                       | '2424.66 ± 1.35%'    | '2333.00'           | '426122 ± 0.02%'           | '428633'                  | 412429  |
| 3       | 'measure (with start and end timestamps)'                 | '2679.96 ± 1.23%'    | '2574.00'           | '385266 ± 0.02%'           | '388500'                  | 373140  |
| 4       | 'measure (with mark names)'                               | '2713.49 ± 0.50%'    | '2644.00'           | '375383 ± 0.02%'           | '378215'                  | 368530  |
| 5       | 'clearMarks'                                              | '691.13 ± 0.07%'     | '681.00'            | '1467016 ± 0.01%'          | '1468429'                 | 1446900 |
| 6       | 'clearMeasures'                                           | '706.00 ± 0.05%'     | '691.00'            | '1431489 ± 0.01%'          | '1447178'                 | 1416435 |
| 7       | 'mark + clearMarks'                                       | '2083.21 ± 1.14%'    | '2003.00'           | '497974 ± 0.01%'           | '499251'                  | 480028  |
| 8       | 'measure + clearMeasures (with start and end timestamps)' | '3085.14 ± 0.88%'    | '2974.00'           | '334337 ± 0.02%'           | '336247'                  | 324135  |
| 9       | 'measure + clearMeasures (with mark names)'               | '2949.45 ± 0.62%'    | '2884.00'           | '345335 ± 0.02%'           | '346741'                  | 339046  |

* After

| (index) | Task name                                                 | Latency average (ns) | Latency median (ns) | Throughput average (ops/s) | Throughput median (ops/s) | Samples |
| ------- | --------------------------------------------------------- | -------------------- | ------------------- | -------------------------- | ------------------------- | ------- |
| 0       | 'mark (default)'                                          | '1740.06 ± 1.01%'    | '1692.00'           | '587400 ± 0.01%'           | '591017'                  | 574695  |
| 1       | 'mark (with custom startTime)'                            | '1661.64 ± 1.16%'    | '1612.00'           | '617453 ± 0.01%'           | '620347'                  | 601815  |
| 2       | 'measure (default)'                                       | '1808.71 ± 1.28%'    | '1753.00'           | '566516 ± 0.01%'           | '570451'                  | 552882  |
| 3       | 'measure (with start and end timestamps)'                 | '1869.21 ± 1.00%'    | '1823.00'           | '546571 ± 0.01%'           | '548546'                  | 534987  |
| 4       | 'measure (with mark names)'                               | '2016.40 ± 0.74%'    | '1983.00'           | '502987 ± 0.01%'           | '504286'                  | 496075  |
| 5       | 'clearMarks'                                              | '682.18 ± 0.03%'     | '671.00'            | '1476364 ± 0.01%'          | '1490313'                 | 1465899 |
| 6       | 'clearMeasures'                                           | '686.78 ± 0.03%'     | '681.00'            | '1467264 ± 0.01%'          | '1468429'                 | 1456081 |
| 7       | 'mark + clearMarks'                                       | '2148.90 ± 1.28%'    | '2073.00'           | '480925 ± 0.01%'           | '482393'                  | 465356  |
| 8       | 'measure + clearMeasures (with start and end timestamps)' | '2277.26 ± 1.10%'    | '2204.00'           | '451016 ± 0.01%'           | '453721'                  | 439125  |
| 9       | 'measure + clearMeasures (with mark names)'               | '2277.82 ± 0.51%'    | '2243.00'           | '443853 ± 0.01%'           | '445831'                  | 439016  |

Reviewed By: hoxyq

Differential Revision: D78193072

fbshipit-source-id: 03b52927a1999f19a2baf0d02335a959525d7add
2025-07-16 02:48:43 -07:00
Rubén NorteandFacebook GitHub Bot 1b7c9ea523 Add 2 tests demonstrating broken behavior in performance.measure (#52588)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52588

Changelog: [internal]

This adds tests to show how `performance.measure` isn't spec compliant when `end` and `duration` are used. This will be fixed in the following diff.

Reviewed By: javache

Differential Revision: D78193069

fbshipit-source-id: 30ba4874c4d2b4adb20608fc8d5ed61bfd6d92d8
2025-07-16 02:48:43 -07:00
Rubén NorteandFacebook GitHub Bot cb59eb2b57 Reorganize tests for PerformanceObserver and User Timing API (#52587)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/52587

Changelog: [internal]

This just re-organizes the tests for the `Performance` API and `PerformanceObserver` as the previous organization didn't make much sense. Now it's a test for `PerformanceObserver` and another for the User Timing API.

Reviewed By: huntie

Differential Revision: D78193070

fbshipit-source-id: f15524bf07d2dc9edc155214279ce3af705cde67
2025-07-16 02:48:43 -07:00
207 changed files with 2315 additions and 2558 deletions
@@ -14,23 +14,23 @@ runs:
uses: actions/download-artifact@v4
with:
name: hermes-workspace
path: 'D:\tmp\hermes'
path: 'C:\tmp\hermes'
- name: Set up workspace
shell: powershell
run: |
mkdir -p D:\tmp\hermes\osx-bin
mkdir -p C:\tmp\hermes\osx-bin
mkdir -p .\packages\react-native\sdks\hermes
cp -r -Force D:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
cp -r -Force C:\tmp\hermes\hermes\* .\packages\react-native\sdks\hermes\.
cp -r -Force .\packages\react-native\sdks\hermes-engine\utils\* .\packages\react-native\sdks\hermes\.
- name: Windows cache
uses: actions/cache@v4
with:
key: v3-hermes-${{ github.job }}-windows-${{ inputs.hermes-version }}-${{ inputs.react-native-version }}
path: |
D:\tmp\hermes\win64-bin\
D:\tmp\hermes\hermes\icu\
D:\tmp\hermes\hermes\deps\
D:\tmp\hermes\hermes\build_release\
C:\tmp\hermes\win64-bin\
C:\tmp\hermes\hermes\icu\
C:\tmp\hermes\hermes\deps\
C:\tmp\hermes\hermes\build_release\
- name: setup-msbuild
uses: microsoft/setup-msbuild@v1.3.2
- name: Set up workspace
@@ -83,4 +83,4 @@ runs:
uses: actions/upload-artifact@v4.3.4
with:
name: hermes-win64-bin
path: D:\tmp\hermes\win64-bin\
path: C:\tmp\hermes\win64-bin\
+3 -3
View File
@@ -128,9 +128,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
+3 -3
View File
@@ -125,9 +125,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
+4 -4
View File
@@ -382,9 +382,9 @@ jobs:
runs-on: windows-2025
needs: prepare_hermes_workspace
env:
HERMES_WS_DIR: 'D:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'D:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'D:\tmp\hermes\osx-bin'
HERMES_WS_DIR: 'C:\tmp\hermes'
HERMES_TARBALL_ARTIFACTS_DIR: 'C:\tmp\hermes\hermes-runtime-darwin'
HERMES_OSXBIN_ARTIFACTS_DIR: 'C:\tmp\hermes\osx-bin'
ICU_URL: "https://github.com/unicode-org/icu/releases/download/release-64-2/icu4c-64_2-Win64-MSVC2017.zip"
MSBUILD_DIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin'
CMAKE_DIR: 'C:\Program Files\CMake\bin'
@@ -583,7 +583,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: ["24", "22"]
node-version: ["24", "22", "20"]
steps:
- name: Checkout
uses: actions/checkout@v4
+1 -1
View File
@@ -17,7 +17,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"files": [
"path-support.js",
+1 -1
View File
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"files": [
"index.js"
+1 -1
View File
@@ -43,6 +43,6 @@
}
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"files": [
"dist"
+1 -1
View File
@@ -20,6 +20,6 @@
"BUILD_INFO"
],
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -26,7 +26,7 @@
},
"license": "MIT",
"engines": {
"node": ">= 22.14.0",
"node": ">= 20.19.4",
"electron": ">=36.3.0"
},
"dependencies": {
@@ -10,9 +10,25 @@
// $FlowFixMe[unclear-type] We have no Flow types for the Electron API.
const {app} = require('electron') as any;
const util = require('util');
// Handle global command line arguments which don't require a window
// or the single instance lock to be held.
const {
values: {version = false},
} = util.parseArgs({
options: {version: {type: 'boolean'}},
args: process.argv.slice(app.isPackaged ? 1 : 2),
strict: false,
});
if (version) {
console.log(`${app.getName()} v${app.getVersion()}`);
// Not app.quit() - we want to exit immediately without initialising the graphical subsystem.
app.exit(0);
}
const gotTheLock = app.requestSingleInstanceLock({
argv: process.argv.slice(2),
argv: process.argv.slice(app.isPackaged ? 1 : 2),
});
if (!gotTheLock) {
+1 -1
View File
@@ -35,7 +35,7 @@
"ws": "^6.2.3"
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"devDependencies": {
"selfsigned": "^2.4.1",
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"main": "index.js",
"dependencies": {
@@ -22,6 +22,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -36,6 +36,6 @@
"hermes-eslint": "0.29.1"
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"scripts": {
"build": "./gradlew build",
@@ -18,6 +18,7 @@ import com.facebook.react.tasks.GenerateEntryPointTask
import com.facebook.react.tasks.GeneratePackageListTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildTypesForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevServerLocation
import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
@@ -84,6 +85,7 @@ class ReactPlugin : Plugin<Project> {
configureAutolinking(project, extension)
configureCodegen(project, extension, rootExtension, isLibrary = false)
configureResources(project, extension)
configureBuildTypesForApp(project)
}
// Library Only Configuration
@@ -19,6 +19,7 @@ import java.net.Inet4Address
import java.net.NetworkInterface
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.plus
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
@@ -27,6 +28,26 @@ import org.w3c.dom.Element
@Suppress("UnstableApiUsage")
internal object AgpConfiguratorUtils {
fun configureBuildTypesForApp(project: Project) {
val action =
Action<AppliedPlugin> {
project.extensions
.getByType(ApplicationAndroidComponentsExtension::class.java)
.finalizeDsl { ext ->
ext.buildTypes {
val debug =
getByName("debug").apply {
manifestPlaceholders["usesCleartextTraffic"] = "true"
}
getByName("release").apply {
manifestPlaceholders["usesCleartextTraffic"] = "false"
}
}
}
}
project.pluginManager.withPlugin("com.android.application", action)
}
fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
val action =
Action<AppliedPlugin> {
+1 -1
View File
@@ -16,7 +16,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"exports": {
".": "./src/index.js",
+1 -1
View File
@@ -30,6 +30,6 @@
}
},
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
}
}
@@ -16,6 +16,7 @@ describe('console.timeStamp()', () => {
});
it("doesn't throw when label is not specified", () => {
// $FlowExpectedError[incompatible-call] not passing label intentionally
expect(() => console.timeStamp()).not.toThrow();
});
@@ -25,7 +26,6 @@ describe('console.timeStamp()', () => {
it("doesn't throw when additional arguments are specified", () => {
expect(() =>
// $FlowExpectedError[extra-arg]
console.timeStamp('label', 100, 500, 'Track', 'Group', 'error'),
).not.toThrow();
});
@@ -34,7 +34,7 @@ describe('console.timeStamp()', () => {
// $FlowExpectedError[incompatible-call]
expect(() => console.timeStamp({})).not.toThrow();
expect(() =>
// $FlowExpectedError[extra-arg]
// $FlowExpectedError[incompatible-call]
console.timeStamp('label', true, null, {}, [], () => {}),
).not.toThrow();
});
+10 -6
View File
@@ -569,10 +569,7 @@ function consoleAssertPolyfill(expression, label) {
}
}
// https://developer.mozilla.org/en-US/docs/Web/API/console/timeStamp_static.
// Non-standard API for recording markers on a timeline of the Performance instrumentation.
// The actual logging is not provided by definition.
function consoleTimeStampPolyfill() {}
function stub() {}
if (global.nativeLoggingHook) {
const originalConsole = global.console;
@@ -585,7 +582,11 @@ if (global.nativeLoggingHook) {
}
global.console = {
timeStamp: consoleTimeStampPolyfill,
time: stub,
timeEnd: stub,
timeStamp: stub,
count: stub,
countReset: stub,
...(originalConsole ?? {}),
error: getNativeLogFunction(LOG_LEVELS.error),
info: getNativeLogFunction(LOG_LEVELS.info),
@@ -676,7 +677,6 @@ if (global.nativeLoggingHook) {
});
}
} else if (!global.console) {
function stub() {}
const log = global.print || stub;
global.console = {
@@ -692,6 +692,8 @@ if (global.nativeLoggingHook) {
}
},
clear: stub,
count: stub,
countReset: stub,
dir: stub,
dirxml: stub,
group: stub,
@@ -700,6 +702,8 @@ if (global.nativeLoggingHook) {
profile: stub,
profileEnd: stub,
table: stub,
time: stub,
timeEnd: stub,
timeStamp: stub,
};
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"files": [
"console.js",
@@ -13,7 +13,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"main": "src/index.js",
"files": [
+22 -14
View File
@@ -38,19 +38,6 @@ function isFirstParty(fileName) {
// use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
const loose = true;
const defaultPlugins = [
[require('babel-plugin-syntax-hermes-parser'), {parseLangTypes: 'flow'}],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-transform-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[require('@babel/plugin-transform-private-property-in-object'), {loose}],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
];
// For Static Hermes testing (experimental), the hermes-canary transformProfile
// is used to enable regenerator (and some related lowering passes) because SH
// requires more Babel lowering than Hermes temporarily.
@@ -234,7 +221,28 @@ const getPreset = (src, options) => {
plugins: [require('@babel/plugin-transform-flow-strip-types')],
},
{
plugins: defaultPlugins,
plugins: [
[
require('babel-plugin-syntax-hermes-parser'),
{
parseLangTypes: 'flow',
reactRuntimeTarget: '19',
...options.hermesParserOptions,
},
],
[require('babel-plugin-transform-flow-enums')],
[require('@babel/plugin-transform-block-scoping')],
[require('@babel/plugin-transform-class-properties'), {loose}],
[require('@babel/plugin-transform-private-methods'), {loose}],
[
require('@babel/plugin-transform-private-property-in-object'),
{loose},
],
[require('@babel/plugin-syntax-dynamic-import')],
[require('@babel/plugin-syntax-export-default-from')],
...passthroughSyntaxPlugins,
[require('@babel/plugin-transform-unicode-regex')],
],
},
{
test: isTypeScriptSource,
@@ -14,7 +14,7 @@
],
"license": "MIT",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"main": "src/index.js",
"files": [
+1 -1
View File
@@ -18,7 +18,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"scripts": {
"build": "yarn clean && node scripts/build.js --verbose",
@@ -19,7 +19,7 @@
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">= 22.14.0"
"node": ">= 20.19.4"
},
"exports": {
".": "./src/index.js",
@@ -31,12 +31,8 @@ function mockQueueMicrotask() {
}
describe('Animated', () => {
let ReactNativeFeatureFlags;
beforeEach(() => {
jest.resetModules();
ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
});
mockQueueMicrotask();
@@ -113,50 +109,7 @@ describe('Animated', () => {
expect(callback.mock.calls.length).toBe(1);
});
it('does not detach on updates', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => false,
});
const opacity = new Animated.Value(0);
jest.spyOn(opacity, '__detach');
const root = await create(<Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
await update(root, <Animated.View style={{opacity}} />);
expect(opacity.__detach).not.toBeCalled();
await unmount(root);
expect(opacity.__detach).toBeCalled();
});
it('stops animation when detached', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => false,
});
const opacity = new Animated.Value(0);
const callback = jest.fn();
const root = await create(<Animated.View style={{opacity}} />);
Animated.timing(opacity, {
toValue: 10,
duration: 1000,
useNativeDriver: false,
}).start(callback);
await unmount(root);
expect(callback).toBeCalledWith({finished: false});
});
it('detaches only on unmount (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const opacity = new Animated.Value(0);
jest.spyOn(opacity, '__detach');
@@ -175,10 +128,6 @@ describe('Animated', () => {
});
it('restores default values only on update (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const __restoreDefaultValues = jest.spyOn(
AnimatedProps.prototype,
'__restoreDefaultValues',
@@ -213,10 +162,6 @@ describe('Animated', () => {
});
it('stops animation when detached (in a microtask)', async () => {
ReactNativeFeatureFlags.override({
scheduleAnimatedCleanupInMicrotask: () => true,
});
const opacity = new Animated.Value(0);
const callback = jest.fn();
@@ -49,6 +49,7 @@ function _defineCheckVersionTests() {
// $FlowFixMe[cannot-write]
console.error = jest.fn();
// $FlowFixMe[cannot-write]
// $FlowFixMe[unsafe-addition]
global.console = {error: jest.fn(error => (consoleOutput += error))};
spyOnConsoleError = jest.spyOn(global.console, 'error');
});
+2 -1
View File
@@ -8,12 +8,13 @@
* @format
*/
import Performance from '../../src/private/webapis/performance/Performance';
import NativePerformance from '../../src/private/webapis/performance/specs/NativePerformance';
// In case if the native implementation of the Performance API is available, use it,
// otherwise fall back to the legacy/default one, which only defines 'Performance.now()'
if (NativePerformance) {
const Performance =
require('../../src/private/webapis/performance/Performance').default;
// $FlowExpectedError[cannot-write]
global.performance = new Performance();
} else {
@@ -4,180 +4,32 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @flow strict
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
import type {Task} from './TaskQueue';
import * as ReactNativeFeatureFlags from '../../src/private/featureflags/ReactNativeFeatureFlags';
import EventEmitter from '../vendor/emitter/EventEmitter';
const BatchedBridge = require('../BatchedBridge/BatchedBridge').default;
const TaskQueue = require('./TaskQueue').default;
const invariant = require('invariant');
export type {Task, SimpleTask, PromiseTask} from './TaskQueue';
export type SimpleTask = {
name: string,
run: () => void,
};
export type PromiseTask = {
name: string,
gen: () => Promise<void>,
};
export type Task = SimpleTask | PromiseTask | (() => void);
export type Handle = number;
const _emitter = new EventEmitter<{
interactionComplete: [],
interactionStart: [],
}>();
const DEBUG_DELAY: 0 = 0;
const DEBUG: false = false;
const InteractionManagerImpl = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
const tasks: Array<Task> = [];
const promise = new Promise((resolve: () => void) => {
_scheduleUpdate();
if (task) {
tasks.push(task);
}
tasks.push({
run: resolve,
name: 'resolve ' + ((task && task.name) || '?'),
});
_taskQueue.enqueueTasks(tasks);
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel: function () {
_taskQueue.cancelTasks(tasks);
},
};
},
/**
* Notify manager that an interaction has started.
*/
createInteractionHandle(): Handle {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('InteractionManager: create interaction handle');
_scheduleUpdate();
const handle = ++_inc;
_addInteractionSet.add(handle);
return handle;
},
/**
* Notify manager that an interaction has completed.
*/
clearInteractionHandle(handle: Handle) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('InteractionManager: clear interaction handle');
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
_scheduleUpdate();
_addInteractionSet.delete(handle);
_deleteInteractionSet.add(handle);
},
// $FlowFixMe[unclear-type] unclear type of _emitter
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
addListener: _emitter.addListener.bind(_emitter) as (
eventType: string,
// $FlowFixMe[unclear-type] unclear type of arguments
listener: (...args: any) => mixed,
context: mixed,
) => EventSubscription,
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*/
setDeadline(deadline: number) {
_deadline = deadline;
},
};
const _interactionSet = new Set<number | Handle>();
const _addInteractionSet = new Set<number | Handle>();
const _deleteInteractionSet = new Set<Handle>();
const _taskQueue = new TaskQueue({onMoreTasks: _scheduleUpdate});
let _nextUpdateHandle: $FlowFixMe | TimeoutID = 0;
let _inc = 0;
let _deadline = -1;
/**
* Schedule an asynchronous update to the interaction state.
*/
function _scheduleUpdate() {
if (!_nextUpdateHandle) {
if (_deadline > 0) {
_nextUpdateHandle = setTimeout(_processUpdate, 0 + DEBUG_DELAY);
} else {
_nextUpdateHandle = setImmediate(_processUpdate);
}
}
}
/**
* Notify listeners, process queue, etc
*/
function _processUpdate() {
_nextUpdateHandle = 0;
const interactionCount = _interactionSet.size;
_addInteractionSet.forEach(handle => _interactionSet.add(handle));
_deleteInteractionSet.forEach(handle => _interactionSet.delete(handle));
const nextInteractionCount = _interactionSet.size;
if (interactionCount !== 0 && nextInteractionCount === 0) {
// transition from 1+ --> 0 interactions
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
_emitter.emit(InteractionManager.Events.interactionComplete);
} else if (interactionCount === 0 && nextInteractionCount !== 0) {
// transition from 0 --> 1+ interactions
/* $FlowFixMe[prop-missing] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
/* $FlowFixMe[invalid-computed-prop] Natural Inference rollout. See
* https://fburl.com/workplace/6291gfvu */
_emitter.emit(InteractionManager.Events.interactionStart);
}
// process the queue regardless of a transition
if (nextInteractionCount === 0) {
while (_taskQueue.hasTasksToProcess()) {
_taskQueue.processNext();
if (
_deadline > 0 &&
BatchedBridge.getEventLoopRunningTime() >= _deadline
) {
// Hit deadline before processing all tasks, so process more later.
_scheduleUpdate();
break;
}
}
}
_addInteractionSet.clear();
_deleteInteractionSet.clear();
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
}
/**
@@ -231,11 +83,106 @@ function _processUpdate() {
*
* @deprecated
*/
const InteractionManager = (
ReactNativeFeatureFlags.disableInteractionManager()
? // $FlowFixMe[incompatible-variance]
require('./InteractionManagerStub').default
: InteractionManagerImpl
) as typeof InteractionManagerImpl;
const InteractionManagerStub = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
export default InteractionManager;
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*
* @deprecated
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
task.gen().then(resolve, reject);
} else if (typeof task.run === 'function') {
try {
task.run();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
}
} else if (typeof task === 'function') {
try {
task();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError('Invalid task of type: ' + typeof task));
}
});
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel() {
clearImmediate(immediateID);
},
};
},
/**
* Notify manager that an interaction has started.
*
* @deprecated
*/
createInteractionHandle(): Handle {
return -1;
},
/**
* Notify manager that an interaction has completed.
*
* @deprecated
*/
clearInteractionHandle(handle: Handle) {
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
},
/**
* @deprecated
*/
addListener(
eventType: string,
// $FlowIgnore[unclear-type]
listener: (...args: any) => mixed,
context: mixed,
): EventSubscription {
return {
remove() {},
};
},
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*
* @deprecated
*/
setDeadline(deadline: number) {
// Do nothing.
},
};
export default InteractionManagerStub;
@@ -1,184 +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.
*
* @flow strict
* @format
*/
import type {EventSubscription} from '../vendor/emitter/EventEmitter';
const invariant = require('invariant');
export type Handle = number;
type Task =
| {
name: string,
run: () => void,
}
| {
name: string,
gen: () => Promise<void>,
}
| (() => void);
// NOTE: The original implementation of `InteractionManager` never rejected
// the returned promise. This preserves that behavior in the stub.
function reject(error: Error): void {
setTimeout(() => {
throw error;
}, 0);
}
/**
* InteractionManager allows long-running work to be scheduled after any
* interactions/animations have completed. In particular, this allows JavaScript
* animations to run smoothly.
*
* Applications can schedule tasks to run after interactions with the following:
*
* ```
* InteractionManager.runAfterInteractions(() => {
* // ...long-running synchronous task...
* });
* ```
*
* Compare this to other scheduling alternatives:
*
* - requestAnimationFrame(): for code that animates a view over time.
* - setImmediate/setTimeout(): run code later, note this may delay animations.
* - runAfterInteractions(): run code later, without delaying active animations.
*
* The touch handling system considers one or more active touches to be an
* 'interaction' and will delay `runAfterInteractions()` callbacks until all
* touches have ended or been cancelled.
*
* InteractionManager also allows applications to register animations by
* creating an interaction 'handle' on animation start, and clearing it upon
* completion:
*
* ```
* var handle = InteractionManager.createInteractionHandle();
* // run animation... (`runAfterInteractions` tasks are queued)
* // later, on animation completion:
* InteractionManager.clearInteractionHandle(handle);
* // queued tasks run if all handles were cleared
* ```
*
* `runAfterInteractions` takes either a plain callback function, or a
* `PromiseTask` object with a `gen` method that returns a `Promise`. If a
* `PromiseTask` is supplied, then it is fully resolved (including asynchronous
* dependencies that also schedule more tasks via `runAfterInteractions`) before
* starting on the next task that might have been queued up synchronously
* earlier.
*
* By default, queued tasks are executed together in a loop in one
* `setImmediate` batch. If `setDeadline` is called with a positive number, then
* tasks will only be executed until the deadline (in terms of js event loop run
* time) approaches, at which point execution will yield via setTimeout,
* allowing events such as touches to start interactions and block queued tasks
* from executing, making apps more responsive.
*
* @deprecated
*/
const InteractionManagerStub = {
Events: {
interactionStart: 'interactionStart',
interactionComplete: 'interactionComplete',
},
/**
* Schedule a function to run after all interactions have completed. Returns a cancellable
* "promise".
*
* @deprecated
*/
runAfterInteractions(task: ?Task): {
then: <U>(
onFulfill?: ?(void) => ?(Promise<U> | U),
onReject?: ?(error: mixed) => ?(Promise<U> | U),
) => Promise<U>,
cancel: () => void,
...
} {
let immediateID: ?$FlowIssue;
const promise = new Promise(resolve => {
immediateID = setImmediate(() => {
if (typeof task === 'object' && task !== null) {
if (typeof task.gen === 'function') {
task.gen().then(resolve, reject);
} else if (typeof task.run === 'function') {
try {
task.run();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError(`Task "${task.name}" missing gen or run.`));
}
} else if (typeof task === 'function') {
try {
task();
resolve();
} catch (error) {
reject(error);
}
} else {
reject(new TypeError('Invalid task of type: ' + typeof task));
}
});
});
return {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
then: promise.then.bind(promise),
cancel() {
clearImmediate(immediateID);
},
};
},
/**
* Notify manager that an interaction has started.
*
* @deprecated
*/
createInteractionHandle(): Handle {
return -1;
},
/**
* Notify manager that an interaction has completed.
*
* @deprecated
*/
clearInteractionHandle(handle: Handle) {
invariant(!!handle, 'InteractionManager: Must provide a handle to clear.');
},
/**
* @deprecated
*/
addListener(): EventSubscription {
return {
remove() {},
};
},
/**
* A positive number will use setTimeout to schedule any tasks after the
* eventLoopRunningTime hits the deadline value, otherwise all tasks will be
* executed in one setImmediate batch (default).
*
* @deprecated
*/
setDeadline(deadline: number) {
// Do nothing.
},
};
export default InteractionManagerStub;
-197
View File
@@ -1,197 +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.
*
* @flow strict
* @format
*/
'use strict';
const invariant = require('invariant');
export type SimpleTask = {
name: string,
run: () => void,
};
export type PromiseTask = {
name: string,
gen: () => Promise<void>,
};
export type Task = SimpleTask | PromiseTask | (() => void);
const DEBUG: false = false;
/**
* TaskQueue - A system for queueing and executing a mix of simple callbacks and
* trees of dependent tasks based on Promises. No tasks are executed unless
* `processNext` is called.
*
* `enqueue` takes a Task object with either a simple `run` callback, or a
* `gen` function that returns a `Promise` and puts it in the queue. If a gen
* function is supplied, then the promise it returns will block execution of
* tasks already in the queue until it resolves. This can be used to make sure
* the first task is fully resolved (including asynchronous dependencies that
* also schedule more tasks via `enqueue`) before starting on the next task.
* The `onMoreTasks` constructor argument is used to inform the owner that an
* async task has resolved and that the queue should be processed again.
*
* Note: Tasks are only actually executed with explicit calls to `processNext`.
*/
class TaskQueue {
/**
* TaskQueue instances are self contained and independent, so multiple tasks
* of varying semantics and priority can operate together.
*
* `onMoreTasks` is invoked when `PromiseTask`s resolve if there are more
* tasks to process.
*/
constructor({onMoreTasks}: {onMoreTasks: () => void, ...}) {
this._onMoreTasks = onMoreTasks;
this._queueStack = [{tasks: [], popable: false}];
}
/**
* Add a task to the queue. It is recommended to name your tasks for easier
* async debugging. Tasks will not be executed until `processNext` is called
* explicitly.
*/
enqueue(task: Task): void {
this._getCurrentQueue().push(task);
}
enqueueTasks(tasks: Array<Task>): void {
tasks.forEach(task => this.enqueue(task));
}
cancelTasks(tasksToCancel: Array<Task>): void {
// search through all tasks and remove them.
this._queueStack = this._queueStack
.map(queue => ({
...queue,
tasks: queue.tasks.filter(task => tasksToCancel.indexOf(task) === -1),
}))
.filter((queue, idx) => queue.tasks.length > 0 || idx === 0);
}
/**
* Check to see if `processNext` should be called.
*
* @returns {boolean} Returns true if there are tasks that are ready to be
* processed with `processNext`, or returns false if there are no more tasks
* to be processed right now, although there may be tasks in the queue that
* are blocked by earlier `PromiseTask`s that haven't resolved yet.
* `onMoreTasks` will be called after each `PromiseTask` resolves if there are
* tasks ready to run at that point.
*/
hasTasksToProcess(): boolean {
return this._getCurrentQueue().length > 0;
}
/**
* Executes the next task in the queue.
*/
processNext(): void {
const queue = this._getCurrentQueue();
if (queue.length) {
const task = queue.shift();
try {
if (typeof task === 'object' && task.gen) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: genPromise for task ' + task.name);
this._genPromise(task);
} else if (typeof task === 'object' && task.run) {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: run task ' + task.name);
task.run();
} else {
invariant(
typeof task === 'function',
'Expected Function, SimpleTask, or PromiseTask, but got:\n' +
JSON.stringify(task, null, 2),
);
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: run anonymous task');
task();
}
} catch (e) {
e.message =
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
'TaskQueue: Error with task ' + (task.name || '') + ': ' + e.message;
throw e;
}
}
}
_queueStack: Array<{
tasks: Array<Task>,
popable: boolean,
...
}>;
_onMoreTasks: () => void;
_getCurrentQueue(): Array<Task> {
const stackIdx = this._queueStack.length - 1;
const queue = this._queueStack[stackIdx];
if (
queue.popable &&
queue.tasks.length === 0 &&
this._queueStack.length > 1
) {
this._queueStack.pop();
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG &&
console.log('TaskQueue: popped queue: ', {
stackIdx,
queueStackSize: this._queueStack.length,
});
return this._getCurrentQueue();
} else {
return queue.tasks;
}
}
_genPromise(task: PromiseTask) {
// Each async task pushes it's own queue onto the queue stack. This
// effectively defers execution of previously queued tasks until the promise
// resolves, at which point we allow the new queue to be popped, which
// happens once it is fully processed.
this._queueStack.push({tasks: [], popable: false});
const stackIdx = this._queueStack.length - 1;
const stackItem = this._queueStack[stackIdx];
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: push new queue: ', {stackIdx});
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG && console.log('TaskQueue: exec gen task ' + task.name);
task
.gen()
.then(() => {
/* $FlowFixMe[constant-condition] Error discovered during Constant
* Condition roll out. See https://fburl.com/workplace/1v97vimq. */
DEBUG &&
console.log('TaskQueue: onThen for gen task ' + task.name, {
stackIdx,
queueStackSize: this._queueStack.length,
});
stackItem.popable = true;
this.hasTasksToProcess() && this._onMoreTasks();
})
.catch(ex => {
setTimeout(() => {
ex.message = `TaskQueue: Error resolving Promise in task ${task.name}: ${ex.message}`;
throw ex;
}, 0);
});
}
}
export default TaskQueue;
@@ -1,342 +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.
*
* @noflow
* @format
*/
'use strict';
import type {ReactNativeFeatureFlagsJsOnlyOverrides} from '../../../src/private/featureflags/ReactNativeFeatureFlags';
function importModules(overrides: ReactNativeFeatureFlagsJsOnlyOverrides) {
const ReactNativeFeatureFlags = require('../../../src/private/featureflags/ReactNativeFeatureFlags');
// Make sure to setup overrides before importing any modules.
ReactNativeFeatureFlags.override(overrides);
const BatchedBridge = require('../../BatchedBridge/BatchedBridge').default;
const InteractionManager = require('../InteractionManager').default;
jest.mock('../../vendor/core/ErrorUtils');
jest.mock('../../BatchedBridge/BatchedBridge');
return {
BatchedBridge,
InteractionManager,
};
}
const isWindows = process.platform === 'win32';
const itif = (condition: boolean) => (condition ? it : it.skip);
function expectToBeCalledOnce(
fn: JestMockFn<$ReadOnlyArray<mixed>, mixed>,
): void {
expect(fn.mock.calls.length).toBe(1);
}
describe('InteractionManager', () => {
let InteractionManager;
let interactionStart;
let interactionComplete;
beforeEach(() => {
jest.resetModules();
({InteractionManager} = importModules({
disableInteractionManager: () => false,
}));
interactionStart = jest.fn();
interactionComplete = jest.fn();
InteractionManager.addListener(
InteractionManager.Events.interactionStart,
interactionStart,
);
InteractionManager.addListener(
InteractionManager.Events.interactionComplete,
interactionComplete,
);
});
it('throws when clearing an undefined handle', () => {
// $FlowExpectedError[incompatible-call]
expect(() => InteractionManager.clearInteractionHandle()).toThrow();
});
it('notifies asynchronously when interaction starts', () => {
InteractionManager.createInteractionHandle();
expect(interactionStart).not.toBeCalled();
jest.runAllTimers();
expect(interactionStart).toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('notifies asynchronously when interaction stops', () => {
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
interactionStart.mockClear();
InteractionManager.clearInteractionHandle(handle);
expect(interactionComplete).not.toBeCalled();
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).toBeCalled();
});
it('does not notify when started & stopped in same event loop', () => {
const handle = InteractionManager.createInteractionHandle();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('does not notify when going from two -> one active interactions', () => {
InteractionManager.createInteractionHandle();
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
interactionStart.mockClear();
interactionComplete.mockClear();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expect(interactionStart).not.toBeCalled();
expect(interactionComplete).not.toBeCalled();
});
it('runs tasks asynchronously when there are interactions', () => {
const task = jest.fn();
InteractionManager.runAfterInteractions(task);
expect(task).not.toBeCalled();
jest.runAllTimers();
expect(task).toBeCalled();
});
it('runs tasks when interactions complete', () => {
const task = jest.fn();
const handle = InteractionManager.createInteractionHandle();
InteractionManager.runAfterInteractions(task);
jest.runAllTimers();
InteractionManager.clearInteractionHandle(handle);
expect(task).not.toBeCalled();
jest.runAllTimers();
expect(task).toBeCalled();
});
it('does not run tasks twice', () => {
const task1 = jest.fn();
const task2 = jest.fn();
InteractionManager.runAfterInteractions(task1);
jest.runAllTimers();
InteractionManager.runAfterInteractions(task2);
jest.runAllTimers();
expectToBeCalledOnce(task1);
});
it('runs tasks added while processing previous tasks', () => {
const task1 = jest.fn(() => {
InteractionManager.runAfterInteractions(task2);
});
const task2 = jest.fn();
InteractionManager.runAfterInteractions(task1);
expect(task2).not.toBeCalled();
jest.runAllTimers();
expect(task1).toBeCalled();
expect(task2).toBeCalled();
});
it('allows tasks to be cancelled', () => {
const task1 = jest.fn();
const task2 = jest.fn();
const promise1 = InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
expect(task1).not.toBeCalled();
expect(task2).not.toBeCalled();
promise1.cancel();
jest.runAllTimers();
expect(task1).not.toBeCalled();
expect(task2).toBeCalled();
});
});
describe('promise tasks', () => {
let BatchedBridge;
let InteractionManager;
let sequenceId;
function createSequenceTask(expectedSequenceId: number) {
return jest.fn(() => {
expect(++sequenceId).toBe(expectedSequenceId);
});
}
beforeEach(() => {
jest.resetModules();
({BatchedBridge, InteractionManager} = importModules({
disableInteractionManager: () => false,
}));
sequenceId = 0;
});
it('should run a basic promise task', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
return new Promise(resolve => resolve());
});
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
jest.runAllTimers();
expectToBeCalledOnce(task1);
});
it('should handle nested promises', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
return new Promise(resolve => {
InteractionManager.runAfterInteractions({
gen: task2,
name: 'gen2',
}).then(resolve);
});
});
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => resolve());
});
InteractionManager.runAfterInteractions({gen: task1, name: 'gen1'});
jest.runAllTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
});
it('should pause promise tasks during interactions then resume', () => {
const task1 = createSequenceTask(1);
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => {
setTimeout(() => {
InteractionManager.runAfterInteractions(task3).then(resolve);
}, 1);
});
});
const task3 = createSequenceTask(3);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
const handle = InteractionManager.createInteractionHandle();
jest.runAllTimers();
jest.runAllTimers(); // Just to be sure...
expect(task3).not.toBeCalled();
InteractionManager.clearInteractionHandle(handle);
jest.runAllTimers();
expectToBeCalledOnce(task3);
});
it('should execute tasks in loop within deadline', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(10);
const task1 = createSequenceTask(1);
const task2 = createSequenceTask(2);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
});
it('should execute tasks one at a time if deadline exceeded', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
const task1 = createSequenceTask(1);
const task2 = createSequenceTask(2);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions(task2);
jest.runOnlyPendingTimers();
expectToBeCalledOnce(task1);
expect(task2).not.toBeCalled();
jest.runOnlyPendingTimers(); // resolve1
jest.runOnlyPendingTimers(); // task2
expectToBeCalledOnce(task2);
});
const bigAsyncTest = resolveTest => {
jest.useRealTimers();
const task1 = createSequenceTask(1);
const task2 = jest.fn(() => {
expect(++sequenceId).toBe(2);
return new Promise(resolve => {
InteractionManager.runAfterInteractions(task3);
setTimeout(() => {
InteractionManager.runAfterInteractions({
gen: task4,
name: 'gen4',
}).then(resolve);
}, 1);
});
});
const task3 = createSequenceTask(3);
const task4 = jest.fn(() => {
expect(++sequenceId).toBe(4);
return new Promise(resolve => {
InteractionManager.runAfterInteractions(task5).then(resolve);
});
});
const task5 = createSequenceTask(5);
const task6 = createSequenceTask(6);
InteractionManager.runAfterInteractions(task1);
InteractionManager.runAfterInteractions({gen: task2, name: 'gen2'});
InteractionManager.runAfterInteractions(task6);
setTimeout(() => {
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
expectToBeCalledOnce(task4);
expectToBeCalledOnce(task5);
expectToBeCalledOnce(task6);
resolveTest();
}, 100);
};
itif(!isWindows)(
'resolves async tasks recursively before other queued tasks',
() => {
return new Promise(bigAsyncTest);
},
);
itif(!isWindows)('should also work with a deadline', () => {
InteractionManager.setDeadline(100);
BatchedBridge.getEventLoopRunningTime.mockReturnValue(200);
return new Promise(bigAsyncTest);
});
});
@@ -1,169 +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.
*
* @noflow
* @format
*/
'use strict';
const Promise = require('promise');
function expectToBeCalledOnce(fn) {
expect(fn.mock.calls.length).toBe(1);
}
function clearTaskQueue(taskQueue) {
do {
jest.runAllTimers();
taskQueue.processNext();
jest.runAllTimers();
} while (taskQueue.hasTasksToProcess());
}
describe('TaskQueue', () => {
let taskQueue;
let onMoreTasks;
let sequenceId;
function createSequenceTask(expectedSequenceId) {
return jest.fn(() => {
expect(++sequenceId).toBe(expectedSequenceId);
});
}
beforeEach(() => {
jest.resetModules();
onMoreTasks = jest.fn();
const TaskQueue = require('../TaskQueue').default;
taskQueue = new TaskQueue({onMoreTasks});
sequenceId = 0;
});
it('should run a basic task', () => {
const task1 = createSequenceTask(1);
taskQueue.enqueue({run: task1, name: 'run1'});
expect(taskQueue.hasTasksToProcess()).toBe(true);
taskQueue.processNext();
expectToBeCalledOnce(task1);
});
it('should handle blocking promise task', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(1);
resolve();
}, 1);
});
});
const task2 = createSequenceTask(2);
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.enqueue({run: task2, name: 'run2'});
taskQueue.processNext();
expectToBeCalledOnce(task1);
expect(task2).not.toBeCalled();
expect(onMoreTasks).not.toBeCalled();
expect(taskQueue.hasTasksToProcess()).toBe(false);
clearTaskQueue(taskQueue);
expectToBeCalledOnce(onMoreTasks);
expectToBeCalledOnce(task2);
});
it('should handle nested simple tasks', () => {
const task1 = jest.fn(() => {
expect(++sequenceId).toBe(1);
taskQueue.enqueue({run: task3, name: 'run3'});
});
const task2 = createSequenceTask(2);
const task3 = createSequenceTask(3);
taskQueue.enqueue({run: task1, name: 'run1'});
taskQueue.enqueue({run: task2, name: 'run2'}); // not blocked by task 1
clearTaskQueue(taskQueue);
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
});
it('should handle nested promises', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(1);
taskQueue.enqueue({gen: task2, name: 'gen2'});
taskQueue.enqueue({run: resolve, name: 'resolve1'});
}, 1);
});
});
const task2 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
expect(++sequenceId).toBe(2);
taskQueue.enqueue({run: task3, name: 'run3'});
taskQueue.enqueue({run: resolve, name: 'resolve2'});
}, 1);
});
});
const task3 = createSequenceTask(3);
const task4 = createSequenceTask(4);
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.enqueue({run: task4, name: 'run4'}); // blocked by task 1 promise
clearTaskQueue(taskQueue);
expectToBeCalledOnce(task1);
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task3);
expectToBeCalledOnce(task4);
});
it('should be able to cancel tasks', () => {
const task1 = jest.fn();
const task2 = createSequenceTask(1);
const task3 = jest.fn();
const task4 = createSequenceTask(2);
taskQueue.enqueue(task1);
taskQueue.enqueue(task2);
taskQueue.enqueue(task3);
taskQueue.enqueue(task4);
taskQueue.cancelTasks([task1, task3]);
clearTaskQueue(taskQueue);
expect(task1).not.toBeCalled();
expect(task3).not.toBeCalled();
expectToBeCalledOnce(task2);
expectToBeCalledOnce(task4);
expect(taskQueue.hasTasksToProcess()).toBe(false);
});
it('should not crash when last task is cancelled', () => {
const task1 = jest.fn();
taskQueue.enqueue(task1);
taskQueue.cancelTasks([task1]);
clearTaskQueue(taskQueue);
expect(task1).not.toBeCalled();
expect(taskQueue.hasTasksToProcess()).toBe(false);
});
it('should not crash when task is cancelled between being started and resolved', () => {
const task1 = jest.fn(() => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 1);
});
});
taskQueue.enqueue({gen: task1, name: 'gen1'});
taskQueue.processNext();
taskQueue.cancelTasks([task1]);
jest.runAllTimers();
});
});
@@ -400,7 +400,6 @@ class XMLHttpRequest extends EventTarget {
if (XMLHttpRequest._profiling) {
console.timeStamp(
'Incremental Data: ' + this._getMeasureURL(),
// $FlowFixMe[extra-arg] Add correct typing for `console.timeStamp`
start,
undefined,
PERFORMANCE_TRACK_NAME,
@@ -455,7 +454,6 @@ class XMLHttpRequest extends EventTarget {
const start = this._startTime;
console.timeStamp(
this._getMeasureURL(),
// $FlowFixMe[extra-arg] Add correct typing for `console.timeStamp`
start,
undefined,
PERFORMANCE_TRACK_NAME,
@@ -10,21 +10,21 @@
import typeof {enable} from 'promise/setimmediate/rejection-tracking';
import LogBox from './LogBox/LogBox';
import ExceptionsManager from './Core/ExceptionsManager';
let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
allRejections: true,
onUnhandled: (id, rejection = {}) => {
onUnhandled: (id, rejection) => {
let message: string;
let stack: ?string;
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
const stringValue = Object.prototype.toString.call(rejection);
if (stringValue === '[object Error]') {
if (rejection === undefined) {
message = '';
} else if (
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
Object.prototype.toString.call(rejection) === '[object Error]'
) {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
message = Error.prototype.toString.call(rejection);
const error: Error = (rejection: $FlowFixMe);
stack = error.stack;
} else {
try {
message = require('pretty-format').format(rejection);
@@ -34,37 +34,23 @@ let rejectionTrackingOptions: $NonMaybeType<Parameters<enable>[0]> = {
? rejection
: JSON.stringify((rejection: $FlowFixMe));
}
// It could although this object is not a standard error, it still has stack information to unwind
// $FlowFixMe ignore types just check if stack is there
if (rejection?.stack && typeof rejection.stack === 'string') {
stack = rejection.stack;
}
}
const warning = `Possible unhandled promise rejection (id: ${id}):\n${
message ?? ''
}`;
if (__DEV__) {
LogBox.addLog({
level: 'warn',
message: {
content: warning,
substitutions: [],
ExceptionsManager.handleException(
new Error(
`Uncaught (in promise, id: ${id})${message ? `: "${message}"` : ''}`,
{
cause: rejection,
},
componentStack: [],
componentStackType: null,
stack,
category: 'possible_unhandled_promise_rejection',
});
} else {
console.warn(warning);
}
),
false /* isFatal */,
);
},
onHandled: id => {
const warning =
`Promise rejection handled (id: ${id})\n` +
'This means you can ignore any previous messages of the form ' +
`"Possible unhandled promise rejection (id: ${id}):"`;
`"Uncaught (in promise, id: ${id})"`;
console.warn(warning);
},
};
@@ -66,7 +66,7 @@
static NSString *const RCTJSThreadName = @"com.facebook.react.JavaScript";
using RCTPendingCall = void (^)();
typedef void (^RCTPendingCall)();
using namespace facebook::jsi;
using namespace facebook::react;
@@ -11,11 +11,11 @@
#import <jsinspector-modern/ReactCdp.h>
using RCTInspectorNetworkListener = facebook::react::jsinspector_modern::NetworkRequestListener;
typedef facebook::react::jsinspector_modern::NetworkRequestListener RCTInspectorNetworkListener;
using RCTInspectorNetworkExecutor = facebook::react::jsinspector_modern::ScopedExecutor<RCTInspectorNetworkListener>;
typedef facebook::react::jsinspector_modern::ScopedExecutor<RCTInspectorNetworkListener> RCTInspectorNetworkExecutor;
using RCTInspectorLoadNetworkResourceRequest = facebook::react::jsinspector_modern::LoadNetworkResourceRequest;
typedef facebook::react::jsinspector_modern::LoadNetworkResourceRequest RCTInspectorLoadNetworkResourceRequest;
/**
* A helper class that wraps around NSURLSession to make network requests.
@@ -8,7 +8,7 @@
#import "RCTInspectorNetworkHelper.h"
#import <React/RCTLog.h>
using ListenerBlock = void (^)(RCTInspectorNetworkListener *);
typedef void (^ListenerBlock)(RCTInspectorNetworkListener *);
@interface RCTInspectorNetworkHelper () <NSURLSessionDataDelegate>
@property (nonatomic, strong) NSURLSession *session;
@@ -112,6 +112,11 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
__weak UIView *_firstVisibleView;
CGFloat _endDraggingSensitivityMultiplier;
// A flag indicating that accessibility API is used.
// It is not restored to the default value in prepareForRecycle.
// Once an accessibility API is used, view culling will be disabled for the entire session.
BOOL _isAccessibilityAPIUsed;
}
+ (RCTScrollViewComponentView *_Nullable)findScrollViewComponentViewForView:(UIView *)view
@@ -134,6 +139,7 @@ RCTSendScrollEventForNativeAnimations_DEPRECATED(UIScrollView *scrollView, NSInt
_isUserTriggeredScrolling = NO;
_shouldUpdateContentInsetAdjustmentBehavior = YES;
_automaticallyAdjustKeyboardInsets = NO;
_isAccessibilityAPIUsed = NO;
[self addSubview:_scrollView];
_containerView = [[UIView alloc] initWithFrame:CGRectZero];
@@ -593,6 +599,38 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
return metrics;
}
/**
* When use of accessibility APIs is detected, view culling is disabled to make accessibility work correctly.
*/
- (void)_disableViewCullingIfNecessary
{
if (!_isAccessibilityAPIUsed) {
_isAccessibilityAPIUsed = YES;
[self _updateStateWithContentOffset];
}
}
- (NSInteger)accessibilityElementCount
{
// From empirical testing, method `accessibilityElementCount` is called lazily only
// when accessibility is used.
// Why we don't use UIAccessibilitySwitchControlStatusDidChangeNotification and
// UIAccessibilityVoiceOverStatusDidChangeNotification? The notifications are not called when using Accessibility
// Inspector. We anticipate developers will want to debug accessbility with Accessibility Inspector and don't want to
// break that developer workflow with view culling. Therefore, we are using API use detection to disable view culling
// instead of the notifications.
[self _disableViewCullingIfNecessary];
return [super accessibilityElementCount];
}
- (NSArray<id<UIFocusItem>> *)focusItemsInRect:(CGRect)rect
{
// From empirical testing, method `focusItemsInRect:` is called lazily only
// when keyboard navigation is used.
[self _disableViewCullingIfNecessary];
return [super focusItemsInRect:rect];
}
- (void)_updateStateWithContentOffset
{
if (!_state) {
@@ -600,17 +638,23 @@ static inline UIViewAnimationOptions animationOptionsWithCurve(UIViewAnimationCu
}
auto contentOffset = RCTPointFromCGPoint(_scrollView.contentOffset);
BOOL isAccessibilityAPIUsed = _isAccessibilityAPIUsed;
_state->updateState(
[contentOffset](
[contentOffset, isAccessibilityAPIUsed](
const ScrollViewShadowNode::ConcreteState::Data &oldData) -> ScrollViewShadowNode::ConcreteState::SharedData {
if (oldData.contentOffset == contentOffset) {
// avoid doing a state update if content offset didn't change.
if (oldData.contentOffset == contentOffset && oldData.disableViewCulling == isAccessibilityAPIUsed) {
// avoid doing a state update if content offset and use of accessibility didn't change.
return nullptr;
}
auto newData = oldData;
newData.contentOffset = contentOffset;
newData.disableViewCulling =
UIAccessibilityIsVoiceOverRunning() || UIAccessibilityIsSwitchControlRunning() || isAccessibilityAPIUsed;
return std::make_shared<const ScrollViewShadowNode::ConcreteState::Data>(newData);
});
},
ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges()
? EventQueue::UpdateMode::unstable_Immediate
: EventQueue::UpdateMode::Asynchronous);
}
- (void)prepareForRecycle
@@ -9,7 +9,6 @@
#import "RCTParagraphComponentAccessibilityProvider.h"
#import <MobileCoreServices/UTCoreTypes.h>
#import <React/RCTViewAccessibilityElement.h>
#import <react/renderer/components/text/ParagraphComponentDescriptor.h>
#import <react/renderer/components/text/ParagraphProps.h>
#import <react/renderer/components/text/ParagraphState.h>
@@ -227,10 +226,6 @@ using namespace facebook::react;
for (NSObject *element in elements) {
if ([element isKindOfClass:[UIView class]] && [cooptingCandidates containsObject:((UIView *)element)]) {
return YES;
} else if (
[element isKindOfClass:[RCTViewAccessibilityElement class]] &&
[cooptingCandidates containsObject:((RCTViewAccessibilityElement *)element).view]) {
return YES;
}
}
}
@@ -1,28 +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.
*/
#import "RCTViewComponentView.h"
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/*
* A UIAcccessibilityElement representing a RCTViewComponentView from an
* accessibility standpoint. This enables RCTViewComponentView's to reference
* themselves in `accessibilityElements` without actually being an accessibility
* element. If it were, then iOS would not call into `accessibilityElements`.
*/
@interface RCTViewAccessibilityElement : UIAccessibilityElement
@property (readonly) RCTViewComponentView *view;
- (instancetype)initWithView:(RCTViewComponentView *)view;
@end
NS_ASSUME_NONNULL_END
@@ -1,83 +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.
*/
#import "RCTViewAccessibilityElement.h"
@implementation RCTViewAccessibilityElement
- (instancetype)initWithView:(RCTViewComponentView *)view
{
if (self = [super initWithAccessibilityContainer:view]) {
_view = view;
}
return self;
}
- (CGRect)accessibilityFrame
{
return UIAccessibilityConvertFrameToScreenCoordinates(_view.bounds, _view);
}
#pragma mark - Forwarding to _view
- (NSString *)accessibilityLabel
{
return _view.accessibilityLabel;
}
- (NSString *)accessibilityValue
{
return _view.accessibilityValue;
}
- (UIAccessibilityTraits)accessibilityTraits
{
return _view.accessibilityTraits;
}
- (NSString *)accessibilityHint
{
return _view.accessibilityHint;
}
- (BOOL)accessibilityIgnoresInvertColors
{
return _view.accessibilityIgnoresInvertColors;
}
- (BOOL)shouldGroupAccessibilityChildren
{
return _view.shouldGroupAccessibilityChildren;
}
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
{
return _view.accessibilityCustomActions;
}
- (NSString *)accessibilityLanguage
{
return _view.accessibilityLanguage;
}
- (BOOL)accessibilityViewIsModal
{
return _view.accessibilityViewIsModal;
}
- (BOOL)accessibilityElementsHidden
{
return _view.accessibilityElementsHidden;
}
- (BOOL)accessibilityRespondsToUserInteraction
{
return _view.accessibilityRespondsToUserInteraction;
}
@end
@@ -6,7 +6,6 @@
*/
#import "RCTViewComponentView.h"
#import "RCTViewAccessibilityElement.h"
#import <CoreGraphics/CoreGraphics.h>
#import <QuartzCore/QuartzCore.h>
@@ -51,8 +50,6 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
UIView *_containerView;
BOOL _useCustomContainerView;
NSMutableSet<NSString *> *_accessibilityOrderNativeIDs;
NSMutableArray<NSObject *> *_accessibilityElements;
RCTViewAccessibilityElement *_axElementDescribingSelf;
}
#ifdef RCT_DYNAMIC_FRAMEWORKS
@@ -405,7 +402,11 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
[_accessibilityOrderNativeIDs addObject:RCTNSStringFromString(childId)];
}
_accessibilityElements = [NSMutableArray new];
// If we are prop updating and have children we can go ahead and assign this prop.
// Otherwise, we might not have children attached yet and need to wait before then.
if (self.currentContainerView.subviews.count > 0) {
[self updateAccessibilityElements];
}
}
// `accessibilityTraits`
@@ -617,7 +618,6 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
_isJSResponder = NO;
_removeClippedSubviews = NO;
_reactSubviews = [NSMutableArray new];
_accessibilityElements = [NSMutableArray new];
}
- (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(NSSet<NSString *> *_Nullable)props
@@ -1149,43 +1149,37 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
return self;
}
- (NSArray<NSObject *> *)accessibilityElements
- (void)didMoveToSuperview
{
if ([_accessibilityOrderNativeIDs count] <= 0) {
return super.accessibilityElements;
// At this point we are guaranteed to have subviews, if we are going to have them
if (ReactNativeFeatureFlags::enableAccessibilityOrder()) {
[self updateAccessibilityElements];
}
}
// TODO: Currently this ignores changes to descendant nativeID's. While that should rarely, if ever happen, it's an
// edge case we should address. Currently this fixes some app deaths so landing this without addressing that edge case
// for now.
if ([_accessibilityElements count] > 0) {
return _accessibilityElements;
- (void)updateAccessibilityElements
{
if ([_accessibilityOrderNativeIDs count] == 0) {
self.accessibilityElements = nil;
return;
}
NSMutableDictionary<NSString *, UIView *> *nativeIdToView = [NSMutableDictionary new];
[RCTViewComponentView collectAccessibilityElements:self
intoDictionary:nativeIdToView
nativeIds:_accessibilityOrderNativeIDs];
for (auto childId : _props->accessibilityOrder) {
NSMutableArray *accessibilityElements = [NSMutableArray new];
for (const auto &childId : _props->accessibilityOrder) {
NSString *nsStringChildId = RCTNSStringFromString(childId);
// Special case to allow for self-referencing with accessibilityOrder
if ([nsStringChildId isEqualToString:self.nativeId]) {
if (!_axElementDescribingSelf) {
_axElementDescribingSelf = [[RCTViewAccessibilityElement alloc] initWithView:self];
}
_axElementDescribingSelf.isAccessibilityElement = [super isAccessibilityElement];
[_accessibilityElements addObject:_axElementDescribingSelf];
} else {
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:nsStringChildId];
if (viewWithMatchingNativeId) {
[_accessibilityElements addObject:viewWithMatchingNativeId];
}
UIView *viewWithMatchingNativeId = [nativeIdToView objectForKey:nsStringChildId];
if (viewWithMatchingNativeId != nil) {
[accessibilityElements addObject:viewWithMatchingNativeId];
}
}
return _accessibilityElements;
self.accessibilityElements = accessibilityElements;
}
+ (void)collectAccessibilityElements:(UIView *)view
@@ -1252,13 +1246,6 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
return self.contentView.isAccessibilityElement;
}
// If we reference ourselves in accessibilityOrder then we will make a
// UIAccessibilityElement object to represent ourselves since returning YES
// here would mean iOS would not call into accessibilityElements
if ([_accessibilityOrderNativeIDs containsObject:self.nativeId]) {
return NO;
}
return [super isAccessibilityElement];
}
@@ -9,7 +9,7 @@
#import <React/RCTViewManager.h>
using RCTUIManagerMountingBlock = dispatch_block_t;
typedef dispatch_block_t RCTUIManagerMountingBlock;
/**
* Allows hooking into UIManager internals. This can be used to execute code at
+2 -2
View File
@@ -205,7 +205,7 @@ RCT_ENUM_CONVERTER(
UIFontWeightRegular,
doubleValue)
using RCTFontStyle = BOOL;
typedef BOOL RCTFontStyle;
RCT_ENUM_CONVERTER(
RCTFontStyle,
(@{
@@ -216,7 +216,7 @@ RCT_ENUM_CONVERTER(
NO,
boolValue)
using RCTFontVariantDescriptor = NSDictionary;
typedef NSDictionary RCTFontVariantDescriptor;
+ (RCTFontVariantDescriptor *)RCTFontVariantDescriptor:(id)json
{
static NSDictionary *mapping;
@@ -31,8 +31,8 @@ public abstract class com/facebook/react/HeadlessJsTaskService : android/app/Ser
public fun <init> ()V
public static final fun acquireWakeLockNow (Landroid/content/Context;)V
protected final fun getReactContext ()Lcom/facebook/react/bridge/ReactContext;
protected final fun getReactHost ()Lcom/facebook/react/ReactHost;
protected final fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
protected fun getReactHost ()Lcom/facebook/react/ReactHost;
protected fun getReactNativeHost ()Lcom/facebook/react/ReactNativeHost;
protected fun getTaskConfig (Landroid/content/Intent;)Lcom/facebook/react/jstasks/HeadlessJsTaskConfig;
public fun onBind (Landroid/content/Intent;)Landroid/os/IBinder;
public fun onDestroy ()V
@@ -621,6 +621,7 @@ dependencies {
api(libs.androidx.autofill)
api(libs.androidx.swiperefreshlayout)
api(libs.androidx.tracing)
api(libs.androidx.window)
api(libs.fbjni)
api(libs.fresco)
@@ -113,7 +113,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
* somewhere.
*/
@Suppress("DEPRECATION")
protected val reactNativeHost: ReactNativeHost
protected open val reactNativeHost: ReactNativeHost
get() = (application as ReactApplication).reactNativeHost
/**
@@ -121,7 +121,7 @@ public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventList
* [ReactApplication] and calls [ReactApplication.reactHost]. This method assumes it is called in
* new architecture and returns null if not.
*/
protected val reactHost: ReactHost?
protected open val reactHost: ReactHost?
get() = (application as ReactApplication).reactHost
protected val reactContext: ReactContext?
@@ -135,6 +135,10 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private void init() {
setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag());
setClipChildren(false);
if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) {
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
}
}
@Override
@@ -865,7 +869,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
private int mDeviceRotation = 0;
/* package */ CustomGlobalLayoutListener() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext());
mVisibleViewArea = new Rect();
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
}
@@ -988,7 +992,7 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot {
return;
}
mDeviceRotation = rotation;
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
DisplayMetricsHolder.initDisplayMetrics(getContext());
emitOrientationChanged(rotation);
}
@@ -8,6 +8,7 @@
package com.facebook.react.bridge
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import java.util.ArrayList
import java.util.Arrays
import kotlin.jvm.JvmStatic
@@ -65,9 +66,16 @@ public open class ReadableNativeArray protected constructor() : NativeArray(), R
if (other !is ReadableNativeArray) {
return false
}
return localArray.contentDeepEquals(other.localArray)
return if (ReactNativeFeatureFlags.useNativeEqualsInNativeReadableArrayAndroid()) {
nativeEquals(other)
} else {
localArray.contentDeepEquals(other.localArray)
}
}
private external fun nativeEquals(other: ReadableNativeArray): Boolean
override fun toArrayList(): ArrayList<Any?> {
val arrayList = ArrayList<Any?>()
repeat(size()) { i ->
@@ -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<<2d4b6e579aaa2b8392fa65d7f86d0351>>
* @generated SignedSource<<a90efac589511beb130c499e51150de8>>
*/
/**
@@ -162,6 +162,12 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun enableIOSViewClipToPaddingBox(): Boolean = accessor.enableIOSViewClipToPaddingBox()
/**
* Dispatches state updates for content offset changes synchronously on the main thread.
*/
@JvmStatic
public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = accessor.enableImmediateUpdateModeForContentOffsetChanges()
/**
* This is to fix the issue with interop view manager where component descriptor lookup is causing ViewManager to preload.
*/
@@ -300,12 +306,24 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun preparedTextCacheSize(): Double = accessor.preparedTextCacheSize()
/**
* Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again.
*/
@JvmStatic
public fun preventShadowTreeCommitExhaustionWithLocking(): Boolean = accessor.preventShadowTreeCommitExhaustionWithLocking()
/**
* Releases the cached image data when it is consumed by the observers.
*/
@JvmStatic
public fun releaseImageDataWhenConsumed(): Boolean = accessor.releaseImageDataWhenConsumed()
/**
* Skip activity identity assertion in ReactHostImpl::onHostPause()
*/
@JvmStatic
public fun skipActivityIdentityAssertionOnHostPause(): Boolean = accessor.skipActivityIdentityAssertionOnHostPause()
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
@@ -330,6 +348,18 @@ public object ReactNativeFeatureFlags {
@JvmStatic
public fun useFabricInterop(): Boolean = accessor.useFabricInterop()
/**
* Use a native implementation of equals in NativeReadableArray.
*/
@JvmStatic
public fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = accessor.useNativeEqualsInNativeReadableArrayAndroid()
/**
* Use a native implementation of TransformHelper
*/
@JvmStatic
public fun useNativeTransformHelperAndroid(): Boolean = accessor.useNativeTransformHelperAndroid()
/**
* When enabled, the native view configs are used in bridgeless mode.
*/
@@ -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<<7e07bb5556c0935dd1dd7be433a32065>>
* @generated SignedSource<<b32f66fb09971e786dd1380bbf417720>>
*/
/**
@@ -42,6 +42,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null
private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
@@ -65,11 +66,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
private var fuseboxNetworkInspectionEnabledCache: Boolean? = null
private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null
private var preparedTextCacheSizeCache: Double? = null
private var preventShadowTreeCommitExhaustionWithLockingCache: Boolean? = null
private var releaseImageDataWhenConsumedCache: Boolean? = null
private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
private var useFabricInteropCache: Boolean? = null
private var useNativeEqualsInNativeReadableArrayAndroidCache: Boolean? = null
private var useNativeTransformHelperAndroidCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
private var useRawPropsJsiValueCache: Boolean? = null
@@ -276,6 +281,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean {
var cached = enableImmediateUpdateModeForContentOffsetChangesCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.enableImmediateUpdateModeForContentOffsetChanges()
enableImmediateUpdateModeForContentOffsetChangesCache = cached
}
return cached
}
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean {
var cached = enableInteropViewManagerClassLookUpOptimizationIOSCache
if (cached == null) {
@@ -483,6 +497,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean {
var cached = preventShadowTreeCommitExhaustionWithLockingCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.preventShadowTreeCommitExhaustionWithLocking()
preventShadowTreeCommitExhaustionWithLockingCache = cached
}
return cached
}
override fun releaseImageDataWhenConsumed(): Boolean {
var cached = releaseImageDataWhenConsumedCache
if (cached == null) {
@@ -492,6 +515,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun skipActivityIdentityAssertionOnHostPause(): Boolean {
var cached = skipActivityIdentityAssertionOnHostPauseCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.skipActivityIdentityAssertionOnHostPause()
skipActivityIdentityAssertionOnHostPauseCache = cached
}
return cached
}
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean {
var cached = traceTurboModulePromiseRejectionsOnAndroidCache
if (cached == null) {
@@ -528,6 +560,24 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
return cached
}
override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean {
var cached = useNativeEqualsInNativeReadableArrayAndroidCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.useNativeEqualsInNativeReadableArrayAndroid()
useNativeEqualsInNativeReadableArrayAndroidCache = cached
}
return cached
}
override fun useNativeTransformHelperAndroid(): Boolean {
var cached = useNativeTransformHelperAndroidCache
if (cached == null) {
cached = ReactNativeFeatureFlagsCxxInterop.useNativeTransformHelperAndroid()
useNativeTransformHelperAndroidCache = cached
}
return cached
}
override fun useNativeViewConfigsInBridgelessMode(): Boolean {
var cached = useNativeViewConfigsInBridgelessModeCache
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<<1cbcbe3a0563e6254d4e377be394f1ab>>
* @generated SignedSource<<d1da48f826bc6a1793d1630cb89cb5c1>>
*/
/**
@@ -72,6 +72,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun enableIOSViewClipToPaddingBox(): Boolean
@DoNotStrip @JvmStatic public external fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean
@DoNotStrip @JvmStatic public external fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnAndroid(): Boolean
@@ -118,8 +120,12 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun preparedTextCacheSize(): Double
@DoNotStrip @JvmStatic public external fun preventShadowTreeCommitExhaustionWithLocking(): Boolean
@DoNotStrip @JvmStatic public external fun releaseImageDataWhenConsumed(): Boolean
@DoNotStrip @JvmStatic public external fun skipActivityIdentityAssertionOnHostPause(): Boolean
@DoNotStrip @JvmStatic public external fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean
@@ -128,6 +134,10 @@ public object ReactNativeFeatureFlagsCxxInterop {
@DoNotStrip @JvmStatic public external fun useFabricInterop(): Boolean
@DoNotStrip @JvmStatic public external fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun useNativeTransformHelperAndroid(): Boolean
@DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): Boolean
@DoNotStrip @JvmStatic public external fun useOptimizedEventBatchingOnAndroid(): 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<<070a222658505c3ddc87911441499518>>
* @generated SignedSource<<c5ed8a904dcda7ade87527233b879b92>>
*/
/**
@@ -67,6 +67,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableIOSViewClipToPaddingBox(): Boolean = false
override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean = false
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean = false
override fun enableLayoutAnimationsOnAndroid(): Boolean = false
@@ -99,7 +101,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun enableVirtualViewDebugFeatures(): Boolean = false
override fun enableVirtualViewRenderState(): Boolean = false
override fun enableVirtualViewRenderState(): Boolean = true
override fun enableVirtualViewWindowFocusDetection(): Boolean = false
@@ -113,8 +115,12 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun preparedTextCacheSize(): Double = 200.0
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean = false
override fun releaseImageDataWhenConsumed(): Boolean = false
override fun skipActivityIdentityAssertionOnHostPause(): Boolean = false
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean = false
override fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean = false
@@ -123,6 +129,10 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
override fun useFabricInterop(): Boolean = true
override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean = false
override fun useNativeTransformHelperAndroid(): Boolean = false
override fun useNativeViewConfigsInBridgelessMode(): Boolean = false
override fun useOptimizedEventBatchingOnAndroid(): 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<<872fdabc2969e8c8e77993d73a152a4b>>
* @generated SignedSource<<23605f090bfbebe911caa9d3d834d3e8>>
*/
/**
@@ -46,6 +46,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
private var enableIOSTextBaselineOffsetPerLineCache: Boolean? = null
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
private var enableImmediateUpdateModeForContentOffsetChangesCache: Boolean? = null
private var enableInteropViewManagerClassLookUpOptimizationIOSCache: Boolean? = null
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
@@ -69,11 +70,15 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
private var fuseboxNetworkInspectionEnabledCache: Boolean? = null
private var hideOffscreenVirtualViewsOnIOSCache: Boolean? = null
private var preparedTextCacheSizeCache: Double? = null
private var preventShadowTreeCommitExhaustionWithLockingCache: Boolean? = null
private var releaseImageDataWhenConsumedCache: Boolean? = null
private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null
private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null
private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null
private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null
private var useFabricInteropCache: Boolean? = null
private var useNativeEqualsInNativeReadableArrayAndroidCache: Boolean? = null
private var useNativeTransformHelperAndroidCache: Boolean? = null
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
private var useOptimizedEventBatchingOnAndroidCache: Boolean? = null
private var useRawPropsJsiValueCache: Boolean? = null
@@ -302,6 +307,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean {
var cached = enableImmediateUpdateModeForContentOffsetChangesCache
if (cached == null) {
cached = currentProvider.enableImmediateUpdateModeForContentOffsetChanges()
accessedFeatureFlags.add("enableImmediateUpdateModeForContentOffsetChanges")
enableImmediateUpdateModeForContentOffsetChangesCache = cached
}
return cached
}
override fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean {
var cached = enableInteropViewManagerClassLookUpOptimizationIOSCache
if (cached == null) {
@@ -532,6 +547,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun preventShadowTreeCommitExhaustionWithLocking(): Boolean {
var cached = preventShadowTreeCommitExhaustionWithLockingCache
if (cached == null) {
cached = currentProvider.preventShadowTreeCommitExhaustionWithLocking()
accessedFeatureFlags.add("preventShadowTreeCommitExhaustionWithLocking")
preventShadowTreeCommitExhaustionWithLockingCache = cached
}
return cached
}
override fun releaseImageDataWhenConsumed(): Boolean {
var cached = releaseImageDataWhenConsumedCache
if (cached == null) {
@@ -542,6 +567,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun skipActivityIdentityAssertionOnHostPause(): Boolean {
var cached = skipActivityIdentityAssertionOnHostPauseCache
if (cached == null) {
cached = currentProvider.skipActivityIdentityAssertionOnHostPause()
accessedFeatureFlags.add("skipActivityIdentityAssertionOnHostPause")
skipActivityIdentityAssertionOnHostPauseCache = cached
}
return cached
}
override fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean {
var cached = traceTurboModulePromiseRejectionsOnAndroidCache
if (cached == null) {
@@ -582,6 +617,26 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
return cached
}
override fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean {
var cached = useNativeEqualsInNativeReadableArrayAndroidCache
if (cached == null) {
cached = currentProvider.useNativeEqualsInNativeReadableArrayAndroid()
accessedFeatureFlags.add("useNativeEqualsInNativeReadableArrayAndroid")
useNativeEqualsInNativeReadableArrayAndroidCache = cached
}
return cached
}
override fun useNativeTransformHelperAndroid(): Boolean {
var cached = useNativeTransformHelperAndroidCache
if (cached == null) {
cached = currentProvider.useNativeTransformHelperAndroid()
accessedFeatureFlags.add("useNativeTransformHelperAndroid")
useNativeTransformHelperAndroidCache = cached
}
return cached
}
override fun useNativeViewConfigsInBridgelessMode(): Boolean {
var cached = useNativeViewConfigsInBridgelessModeCache
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<<63bdcd9aeb09a55028595eecb42cdcae>>
* @generated SignedSource<<780793412b76f101be1569d7a866c435>>
*/
/**
@@ -67,6 +67,8 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun enableIOSViewClipToPaddingBox(): Boolean
@DoNotStrip public fun enableImmediateUpdateModeForContentOffsetChanges(): Boolean
@DoNotStrip public fun enableInteropViewManagerClassLookUpOptimizationIOS(): Boolean
@DoNotStrip public fun enableLayoutAnimationsOnAndroid(): Boolean
@@ -113,8 +115,12 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun preparedTextCacheSize(): Double
@DoNotStrip public fun preventShadowTreeCommitExhaustionWithLocking(): Boolean
@DoNotStrip public fun releaseImageDataWhenConsumed(): Boolean
@DoNotStrip public fun skipActivityIdentityAssertionOnHostPause(): Boolean
@DoNotStrip public fun traceTurboModulePromiseRejectionsOnAndroid(): Boolean
@DoNotStrip public fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean
@@ -123,6 +129,10 @@ public interface ReactNativeFeatureFlagsProvider {
@DoNotStrip public fun useFabricInterop(): Boolean
@DoNotStrip public fun useNativeEqualsInNativeReadableArrayAndroid(): Boolean
@DoNotStrip public fun useNativeTransformHelperAndroid(): Boolean
@DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): Boolean
@DoNotStrip public fun useOptimizedEventBatchingOnAndroid(): Boolean
@@ -23,10 +23,6 @@ import com.facebook.react.common.build.ReactBuildConfig
@SuppressLint("UseReactNativeNewArchitectureFeatureFlagDetector")
public object ReactNativeNewArchitectureFeatureFlags {
@JvmStatic
public fun isNewArchitectureStrictModeEnabled(): Boolean =
ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE
@JvmStatic
public fun enableBridgelessArchitecture(): Boolean {
if (ReactBuildConfig.UNSTABLE_ENABLE_MINIFY_LEGACY_ARCHITECTURE) {
@@ -253,11 +253,18 @@ public class ReactHostImpl(
val currentActivity = this.currentActivity
if (currentActivity != null) {
val currentActivityClass = currentActivity.javaClass.simpleName
val activityClass = if (activity == null) "null" else activity.javaClass.simpleName
Assertions.assertCondition(
activity === currentActivity,
"Pausing an activity that is not the current activity, this is incorrect! Current activity: $currentActivityClass Paused activity: $activityClass")
val isSameActivity = activity === currentActivity
if (!isSameActivity) {
val currentActivityClass = currentActivity.javaClass.simpleName
val activityClass = if (activity == null) "null" else activity.javaClass.simpleName
val isNotSameActivityMessage =
"Pausing an activity that is not the current activity, this is incorrect! Current activity: $currentActivityClass Paused activity: $activityClass"
if (ReactNativeFeatureFlags.skipActivityIdentityAssertionOnHostPause()) {
log(method, isNotSameActivityMessage)
} else {
Assertions.assertCondition(isSameActivity, isNotSameActivityMessage)
}
}
}
maybeEnableDevSupport(false)
@@ -13,8 +13,10 @@ import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.window.layout.WindowMetricsCalculator
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
/**
* Holds an instance of the current DisplayMetrics so we don't have to thread it through all the
@@ -62,9 +64,19 @@ public object DisplayMetricsHolder {
@JvmStatic
public fun initDisplayMetrics(context: Context) {
val displayMetrics = context.resources.displayMetrics
windowDisplayMetrics = displayMetrics
val windowDisplayMetrics = DisplayMetrics()
val screenDisplayMetrics = DisplayMetrics()
windowDisplayMetrics.setTo(displayMetrics)
screenDisplayMetrics.setTo(displayMetrics)
if (isEdgeToEdgeFeatureFlagOn) {
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context).let {
windowDisplayMetrics.widthPixels = it.bounds.width()
windowDisplayMetrics.heightPixels = it.bounds.height()
}
}
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
// Get the real display metrics if we are using API level 17 or higher.
// The real metrics include system decor elements (e.g. soft menu bar).
@@ -72,6 +84,8 @@ public object DisplayMetricsHolder {
// See:
// http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
@Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(screenDisplayMetrics)
DisplayMetricsHolder.windowDisplayMetrics = windowDisplayMetrics
DisplayMetricsHolder.screenDisplayMetrics = screenDisplayMetrics
}
@@ -19,7 +19,7 @@ import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel
import com.facebook.react.uimanager.events.Event
/** Event used to notify JS component about changes of its position or dimensions. */
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.ERROR)
@LegacyArchitecture(logLevel = LegacyArchitectureLogLevel.WARNING)
public class OnLayoutEvent private constructor() : Event<OnLayoutEvent>() {
@VisibleForTesting internal var x: Int = 0
@VisibleForTesting internal var y: Int = 0
@@ -60,7 +60,7 @@ public class OnLayoutEvent private constructor() : Event<OnLayoutEvent>() {
public companion object {
init {
LegacyArchitectureLogger.assertLegacyArchitecture(
"OnLayoutEvent", LegacyArchitectureLogLevel.ERROR)
"OnLayoutEvent", LegacyArchitectureLogLevel.WARNING)
}
private val EVENTS_POOL: SynchronizedPool<OnLayoutEvent> = SynchronizedPool<OnLayoutEvent>(20)
@@ -8,10 +8,12 @@
package com.facebook.react.uimanager
import com.facebook.common.logging.FLog
import com.facebook.react.bridge.NativeArray
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.facebook.react.common.ReactConstants
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
public object TransformHelper {
@@ -69,6 +71,14 @@ public object TransformHelper {
transformOrigin: ReadableArray?,
allowPercentageResolution: Boolean
) {
if (allowPercentageResolution &&
ReactNativeFeatureFlags.useNativeTransformHelperAndroid() &&
transforms is NativeArray &&
transformOrigin is NativeArray?) {
nativeProcessTransform(transforms, result, viewWidth, viewHeight, transformOrigin)
return
}
val helperMatrix = helperMatrix.get()!!
MatrixMathHelper.resetIdentityMatrix(result)
val offsets =
@@ -220,4 +230,13 @@ public object TransformHelper {
return doubleArrayOf(newTranslateX, newTranslateY, newTranslateZ)
}
@JvmStatic
private external fun nativeProcessTransform(
transforms: NativeArray,
result: DoubleArray,
viewWidth: Float,
viewHeight: Float,
transformOrigin: NativeArray?
)
}
@@ -377,6 +377,9 @@ public class ReactScrollView extends ScrollView
}
ReactScrollViewHelper.emitLayoutEvent(this);
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
@Override
@@ -486,6 +489,9 @@ public class ReactScrollView extends ScrollView
this,
mOnScrollDispatchHelper.getXFlingVelocity(),
mOnScrollDispatchHelper.getYFlingVelocity());
if (mVirtualViewContainerState != null) {
mVirtualViewContainerState.updateState();
}
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT);
@@ -11,6 +11,7 @@ import android.graphics.Rect
import android.view.ViewGroup
import com.facebook.common.logging.FLog
import com.facebook.react.common.build.ReactBuildConfig
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
import com.facebook.react.views.virtual.VirtualViewMode
import java.util.*
@@ -45,22 +46,19 @@ private fun rectsOverlap(rect1: Rect, rect2: Rect): Boolean {
return true
}
internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
ReactScrollViewHelper.ScrollListener {
internal class VirtualViewContainerState(private val scrollView: ViewGroup) {
private val prerenderRatio: Int = 1
private val prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio()
private val virtualViews: MutableSet<VirtualView> = mutableSetOf()
private val emptyRect: Rect = Rect()
private val visibleRect: Rect = Rect()
private val prerenderRect: Rect = Rect()
init {
ReactScrollViewHelper.addScrollListener(this)
}
public fun add(virtualView: VirtualView) {
assert(virtualViews.add(virtualView)) {
"Attempting to add duplicate VirtualView: ${virtualView.virtualViewID}"
public fun onChange(virtualView: VirtualView) {
if (virtualViews.add(virtualView)) {
debugLog("add", { "virtualViewID=${virtualView.virtualViewID}" })
} else {
debugLog("update", { "virtualViewID=${virtualView.virtualViewID}" })
}
updateModes(virtualView)
}
@@ -69,35 +67,13 @@ internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
assert(virtualViews.remove(virtualView)) {
"Attempting to remove non-existent VirtualView: ${virtualView.virtualViewID}"
}
debugLog("remove", { "virtualViewID=${virtualView.virtualViewID}" })
}
// ReactScrollViewHelper.ScrollListener.onLayout
// Emitted from ScrollView's onLayout
override fun onLayout(scrollView: ViewGroup?) {
// ReactScrollViewHelper is global
if (this.scrollView == scrollView) {
debugLog("ReactScrollViewHelper.onLayout")
updateModes()
}
}
// ReactScrollViewHelper.ScrollListener.onScroll
// Emitted from ScrollView's onLayout
override fun onScroll(
scrollView: ViewGroup?,
scrollEventType: ScrollEventType?,
xVelocity: Float,
yVelocity: Float
) {
// ReactScrollViewHelper is global
if (this.scrollView == scrollView) {
debugLog("ReactScrollViewHelper.onScroll")
updateModes()
}
}
public fun update(virtualView: VirtualView) {
updateModes(virtualView)
// Called on ScrollView onLayout or onScroll
public fun updateState() {
debugLog("VirtualViewContainer.updateState")
updateModes()
}
private fun updateModes(virtualView: VirtualView? = null) {
@@ -110,17 +86,26 @@ internal class VirtualViewContainerState(private val scrollView: ViewGroup) :
val virtualViewsIt = if (virtualView != null) listOf(virtualView) else virtualViews
virtualViewsIt.forEach { vv ->
val rect = vv.containerRelativeRect
var mode = VirtualViewMode.Hidden
var thresholdRect = emptyRect
when {
rect.isEmpty -> {}
rectsOverlap(rect, visibleRect) -> {
vv.onModeChange(VirtualViewMode.Visible, visibleRect)
mode = VirtualViewMode.Visible
thresholdRect = visibleRect
}
rectsOverlap(rect, prerenderRect) -> {
vv.onModeChange(VirtualViewMode.Prerender, prerenderRect)
}
else -> {
vv.onModeChange(VirtualViewMode.Hidden, emptyRect)
mode = VirtualViewMode.Prerender
thresholdRect = prerenderRect
}
else -> {}
}
debugLog(
"updateModes",
{ "virtualView=${vv.virtualViewID} mode=$mode rect=$rect thresholdRect=$thresholdRect" })
vv.onModeChange(mode, thresholdRect)
}
}
}
@@ -34,6 +34,7 @@ public class ReactVirtualViewExperimental(context: Context) :
override val containerRelativeRect: Rect = Rect()
private var offsetX: Int = 0
private var offsetY: Int = 0
private var hadLayout: Boolean = false
internal val nativeId: String?
get() = getTag(R.id.view_tag_native_id) as? String
@@ -45,14 +46,20 @@ public class ReactVirtualViewExperimental(context: Context) :
@VisibleForTesting
internal fun doAttachedToWindow() {
// Assuming that layout has been called before this
scrollView = getScrollView()?.also { scrollView?.virtualViewContainerState?.add(this) }
scrollView = getScrollView()
// onAttachedToWindow is usually called before layout but there are cases where it's called
// after. If called after, we need to report the updated layout to the VirtualViewContainer
if (hadLayout) {
updateParentOffset()
reportChangeToContainer()
}
}
/** From [View#onLayout] */
// This is when the view itself has layout changes
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
hadLayout = true
if (changed) {
containerRelativeRect.set(
left + offsetX,
@@ -60,7 +67,7 @@ public class ReactVirtualViewExperimental(context: Context) :
right + offsetX,
bottom + offsetY,
)
updateContainer()
reportChangeToContainer()
}
}
@@ -77,24 +84,8 @@ public class ReactVirtualViewExperimental(context: Context) :
oldBottom: Int
) {
if (oldLeft != left || oldTop != top) {
val virtualViewScrollView = scrollView ?: return
offsetX = 0
offsetY = 0
var parent: ViewParent? = parent
while (parent != null && parent != virtualViewScrollView) {
if (parent is View) {
offsetX += parent.left
offsetY += parent.top
}
parent = parent.parent
}
containerRelativeRect.set(
left + offsetX,
top + offsetY,
right + offsetX,
bottom + offsetY,
)
updateContainer()
updateParentOffset()
reportChangeToContainer()
}
}
@@ -109,6 +100,8 @@ public class ReactVirtualViewExperimental(context: Context) :
scrollView = null
mode = null
modeChangeEmitter = null
hadLayout = false
containerRelativeRect.setEmpty()
}
override val virtualViewID: String
@@ -156,8 +149,28 @@ public class ReactVirtualViewExperimental(context: Context) :
}
}
private fun updateContainer() {
scrollView?.virtualViewContainerState?.update(this)
private fun updateParentOffset() {
val virtualViewScrollView = scrollView ?: return
offsetX = 0
offsetY = 0
var parent: ViewParent? = parent
while (parent != null && parent != virtualViewScrollView) {
if (parent is View) {
offsetX += parent.left
offsetY += parent.top
}
parent = parent.parent
}
containerRelativeRect.set(
left + offsetX,
top + offsetY,
right + offsetX,
bottom + offsetY,
)
}
private fun reportChangeToContainer() {
scrollView?.virtualViewContainerState?.onChange(this)
}
private fun getScrollView(): VirtualViewContainer? = traverseParentStack(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<<116217d3620e8ba52a6e0427a0d46fde>>
* @generated SignedSource<<f310a5dc27fd655eab4445cb25d4c85c>>
*/
/**
@@ -171,6 +171,12 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool enableImmediateUpdateModeForContentOffsetChanges() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableImmediateUpdateModeForContentOffsetChanges");
return method(javaProvider_);
}
bool enableInteropViewManagerClassLookUpOptimizationIOS() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableInteropViewManagerClassLookUpOptimizationIOS");
@@ -309,12 +315,24 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("preventShadowTreeCommitExhaustionWithLocking");
return method(javaProvider_);
}
bool releaseImageDataWhenConsumed() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("releaseImageDataWhenConsumed");
return method(javaProvider_);
}
bool skipActivityIdentityAssertionOnHostPause() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("skipActivityIdentityAssertionOnHostPause");
return method(javaProvider_);
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("traceTurboModulePromiseRejectionsOnAndroid");
@@ -339,6 +357,18 @@ class ReactNativeFeatureFlagsJavaProvider
return method(javaProvider_);
}
bool useNativeEqualsInNativeReadableArrayAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useNativeEqualsInNativeReadableArrayAndroid");
return method(javaProvider_);
}
bool useNativeTransformHelperAndroid() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useNativeTransformHelperAndroid");
return method(javaProvider_);
}
bool useNativeViewConfigsInBridgelessMode() override {
static const auto method =
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("useNativeViewConfigsInBridgelessMode");
@@ -495,6 +525,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox(
return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
}
bool JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges();
}
bool JReactNativeFeatureFlagsCxxInterop::enableInteropViewManagerClassLookUpOptimizationIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS();
@@ -610,11 +645,21 @@ double JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize(
return ReactNativeFeatureFlags::preparedTextCacheSize();
}
bool JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking();
}
bool JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::releaseImageDataWhenConsumed();
}
bool JReactNativeFeatureFlagsCxxInterop::skipActivityIdentityAssertionOnHostPause(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause();
}
bool JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid();
@@ -635,6 +680,16 @@ bool JReactNativeFeatureFlagsCxxInterop::useFabricInterop(
return ReactNativeFeatureFlags::useFabricInterop();
}
bool JReactNativeFeatureFlagsCxxInterop::useNativeEqualsInNativeReadableArrayAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::useNativeTransformHelperAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useNativeTransformHelperAndroid();
}
bool JReactNativeFeatureFlagsCxxInterop::useNativeViewConfigsInBridgelessMode(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
return ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode();
@@ -767,6 +822,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"enableIOSViewClipToPaddingBox",
JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox),
makeNativeMethod(
"enableImmediateUpdateModeForContentOffsetChanges",
JReactNativeFeatureFlagsCxxInterop::enableImmediateUpdateModeForContentOffsetChanges),
makeNativeMethod(
"enableInteropViewManagerClassLookUpOptimizationIOS",
JReactNativeFeatureFlagsCxxInterop::enableInteropViewManagerClassLookUpOptimizationIOS),
@@ -836,9 +894,15 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"preparedTextCacheSize",
JReactNativeFeatureFlagsCxxInterop::preparedTextCacheSize),
makeNativeMethod(
"preventShadowTreeCommitExhaustionWithLocking",
JReactNativeFeatureFlagsCxxInterop::preventShadowTreeCommitExhaustionWithLocking),
makeNativeMethod(
"releaseImageDataWhenConsumed",
JReactNativeFeatureFlagsCxxInterop::releaseImageDataWhenConsumed),
makeNativeMethod(
"skipActivityIdentityAssertionOnHostPause",
JReactNativeFeatureFlagsCxxInterop::skipActivityIdentityAssertionOnHostPause),
makeNativeMethod(
"traceTurboModulePromiseRejectionsOnAndroid",
JReactNativeFeatureFlagsCxxInterop::traceTurboModulePromiseRejectionsOnAndroid),
@@ -851,6 +915,12 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
makeNativeMethod(
"useFabricInterop",
JReactNativeFeatureFlagsCxxInterop::useFabricInterop),
makeNativeMethod(
"useNativeEqualsInNativeReadableArrayAndroid",
JReactNativeFeatureFlagsCxxInterop::useNativeEqualsInNativeReadableArrayAndroid),
makeNativeMethod(
"useNativeTransformHelperAndroid",
JReactNativeFeatureFlagsCxxInterop::useNativeTransformHelperAndroid),
makeNativeMethod(
"useNativeViewConfigsInBridgelessMode",
JReactNativeFeatureFlagsCxxInterop::useNativeViewConfigsInBridgelessMode),
@@ -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<<96750d4cfdb356392256a175a3c740ef>>
* @generated SignedSource<<8c1da07c0b7d2053f7fdaac4326c3ac1>>
*/
/**
@@ -96,6 +96,9 @@ class JReactNativeFeatureFlagsCxxInterop
static bool enableIOSViewClipToPaddingBox(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableImmediateUpdateModeForContentOffsetChanges(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool enableInteropViewManagerClassLookUpOptimizationIOS(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -165,9 +168,15 @@ class JReactNativeFeatureFlagsCxxInterop
static double preparedTextCacheSize(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool preventShadowTreeCommitExhaustionWithLocking(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool releaseImageDataWhenConsumed(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool skipActivityIdentityAssertionOnHostPause(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool traceTurboModulePromiseRejectionsOnAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -180,6 +189,12 @@ class JReactNativeFeatureFlagsCxxInterop
static bool useFabricInterop(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool useNativeEqualsInNativeReadableArrayAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool useNativeTransformHelperAndroid(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
static bool useNativeViewConfigsInBridgelessMode(
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
@@ -31,13 +31,14 @@ add_library(
OnLoad-common.cpp
ReadableNativeArray.cpp
ReadableNativeMap.cpp
TransformHelper.cpp
WritableNativeArray.cpp
WritableNativeMap.cpp
)
target_merge_so(reactnativejni_common)
target_include_directories(reactnativejni_common PUBLIC ../../)
target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact)
target_link_libraries(reactnativejni_common fbjni folly_runtime react_cxxreact rrc_view)
target_compile_reactnative_options(reactnativejni_common PRIVATE)
target_compile_options(reactnativejni_common PRIVATE -Wno-unused-lambda-capture)
@@ -21,6 +21,10 @@ class NativeArray : public jni::HybridClass<NativeArray> {
jni::local_ref<jstring> toString();
const folly::dynamic& getArray() const {
return array_;
}
RN_EXPORT folly::dynamic consume();
// Whether this array has been added to another array or map and no longer
@@ -11,6 +11,7 @@
#include "JReactMarker.h"
#include "NativeArray.h"
#include "NativeMap.h"
#include "TransformHelper.h"
#include "WritableNativeArray.h"
#include "WritableNativeMap.h"
@@ -27,6 +28,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
ReadableNativeMap::registerNatives();
WritableNativeArray::registerNatives();
WritableNativeMap::registerNatives();
TransformHelper::registerNatives();
});
}
@@ -40,10 +40,16 @@ local_ref<JArrayClass<jobject>> ReadableNativeArray::importTypeArray() {
return jarray;
}
bool ReadableNativeArray::equals(
jni::alias_ref<ReadableNativeArray::javaobject> other) {
return array_ == other->cthis()->array_;
}
void ReadableNativeArray::registerNatives() {
registerHybrid({
makeNativeMethod("importArray", ReadableNativeArray::importArray),
makeNativeMethod("importTypeArray", ReadableNativeArray::importTypeArray),
makeNativeMethod("nativeEquals", ReadableNativeArray::equals),
});
}
@@ -35,6 +35,7 @@ class ReadableNativeArray
static void mapException(std::exception_ptr ex);
static void registerNatives();
bool equals(jni::alias_ref<ReadableNativeArray::javaobject> other);
jni::local_ref<jni::JArrayClass<jobject>> importArray();
jni::local_ref<jni::JArrayClass<jobject>> importTypeArray();
};
@@ -0,0 +1,60 @@
/*
* 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 "TransformHelper.h"
#include <react/renderer/components/view/BaseViewProps.h>
#include <react/renderer/components/view/conversions.h>
#include "NativeArray.h"
using namespace facebook::jni;
namespace facebook::react {
namespace {
void processTransform(
jni::alias_ref<jclass> /*unused*/,
NativeArray* jTransforms,
jni::alias_ref<jni::JArrayDouble> jResult,
float viewWidth,
float viewHeight,
NativeArray* jTransformOrigin) {
// Assuming parsing transforms doesn't require a real PropsParserContext
static ContextContainer contextContainer;
static PropsParserContext context(0, contextContainer);
RawValue transformValue(jTransforms->getArray());
Transform transform;
fromRawValue(context, transformValue, transform);
TransformOrigin transformOrigin;
if (jTransformOrigin != nullptr) {
RawValue transformOriginValue(jTransformOrigin->getArray());
fromRawValue(context, transformOriginValue, transformOrigin);
}
auto result = BaseViewProps::resolveTransform(
Size(viewWidth, viewHeight), transform, transformOrigin);
// Convert from matrix of floats to double matrix
constexpr size_t MatrixSize = std::tuple_size_v<decltype(result.matrix)>;
std::array<double, MatrixSize> doubleTransform{};
std::copy(
result.matrix.begin(), result.matrix.end(), doubleTransform.begin());
jResult->setRegion(0, MatrixSize, doubleTransform.data());
}
} // namespace
void TransformHelper::registerNatives() {
javaClassLocal()->registerNatives({
makeNativeMethod("nativeProcessTransform", processTransform),
});
}
} // namespace facebook::react
@@ -0,0 +1,22 @@
/*
* 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 <fbjni/fbjni.h>
namespace facebook::react {
class TransformHelper : public jni::JavaClass<TransformHelper> {
public:
static auto constexpr* kJavaDescriptor =
"Lcom/facebook/react/uimanager/TransformHelper;";
static void registerNatives();
};
} // namespace facebook::react
@@ -53,9 +53,9 @@ class CxxModule {
class SyncTagType {};
public:
using Provider = std::function<std::unique_ptr<CxxModule>()>;
typedef std::function<std::unique_ptr<CxxModule>()> Provider;
using Callback = std::function<void(std::vector<folly::dynamic>)>;
typedef std::function<void(std::vector<folly::dynamic>)> Callback;
constexpr static AsyncTagType AsyncTag = AsyncTagType();
constexpr static SyncTagType SyncTag = SyncTagType();
@@ -68,8 +68,8 @@ namespace detail {
// only for types compatible with folly::dynamic.
template <typename T>
struct is_dynamic {
using type = typename std::
enable_if<std::is_assignable<folly::dynamic, T>::value, T>::type;
typedef typename std::
enable_if<std::is_assignable<folly::dynamic, T>::value, T>::type type;
};
} // end namespace detail
@@ -1,21 +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 <string>
namespace facebook::react::jsinspector_modern {
/**
* https://developer.chrome.com/docs/devtools/performance/extension?utm_source=devtools#devtools_object
*/
struct DevToolsTrackEntryPayload {
std::string track;
};
} // namespace facebook::react::jsinspector_modern
@@ -113,7 +113,8 @@ void PerformanceTracer::collectEvents(
void PerformanceTracer::reportMark(
const std::string_view& name,
HighResTimeStamp start) {
HighResTimeStamp start,
folly::dynamic&& detail) {
if (!tracingAtomic_) {
return;
}
@@ -123,6 +124,13 @@ void PerformanceTracer::reportMark(
return;
}
folly::dynamic eventArgs = folly::dynamic::object();
if (detail != nullptr) {
eventArgs = folly::dynamic::object(
"data",
folly::dynamic::object("detail", folly::toJson(std::move(detail))));
}
buffer_.emplace_back(TraceEvent{
.name = std::string(name),
.cat = "blink.user_timing",
@@ -130,6 +138,7 @@ void PerformanceTracer::reportMark(
.ts = start,
.pid = processId_,
.tid = oscompat::getCurrentThreadId(),
.args = eventArgs,
});
}
@@ -137,18 +146,15 @@ void PerformanceTracer::reportMeasure(
const std::string_view& name,
HighResTimeStamp start,
HighResDuration duration,
const std::optional<DevToolsTrackEntryPayload>& trackMetadata) {
folly::dynamic&& detail) {
if (!tracingAtomic_) {
return;
}
folly::dynamic beginEventArgs = folly::dynamic::object();
if (trackMetadata.has_value()) {
folly::dynamic devtoolsObject = folly::dynamic::object(
"devtools",
folly::dynamic::object("track", trackMetadata.value().track));
if (detail != nullptr) {
beginEventArgs =
folly::dynamic::object("detail", folly::toJson(devtoolsObject));
folly::dynamic::object("detail", folly::toJson(std::move(detail)));
}
auto currentThreadId = oscompat::getCurrentThreadId();
@@ -7,7 +7,6 @@
#pragma once
#include "CdpTracing.h"
#include "ConsoleTimeStamp.h"
#include "TraceEvent.h"
#include "TraceEventProfile.h"
@@ -60,13 +59,17 @@ class PerformanceTracer {
const std::function<void(const folly::dynamic& eventsChunk)>&
resultCallback,
uint16_t chunkSize);
/**
* Record a `Performance.mark()` event - a labelled timestamp. If not
* currently tracing, this is a no-op.
*
* See https://w3c.github.io/user-timing/#mark-method.
*/
void reportMark(const std::string_view& name, HighResTimeStamp start);
void reportMark(
const std::string_view& name,
HighResTimeStamp start,
folly::dynamic&& detail = nullptr);
/**
* Record a `Performance.measure()` event - a labelled duration. If not
@@ -78,7 +81,7 @@ class PerformanceTracer {
const std::string_view& name,
HighResTimeStamp start,
HighResDuration duration,
const std::optional<DevToolsTrackEntryPayload>& trackMetadata);
folly::dynamic&& detail = nullptr);
/**
* Record a "TimeStamp" Trace Event - a labelled entry on Performance
@@ -14,7 +14,7 @@ enum ReactNativeLogLevel {
ReactNativeLogLevelFatal = 4
};
using reactnativelogfunctype = void (*)(ReactNativeLogLevel, const char*);
typedef void (*reactnativelogfunctype)(ReactNativeLogLevel, const char*);
#ifdef __cplusplus
extern "C" {
@@ -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<<8e952ab168159599f6c97fbc68dd57bb>>
* @generated SignedSource<<355265aa8f13e3f307d6e30db6b80d41>>
*/
/**
@@ -114,6 +114,10 @@ bool ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox() {
return getAccessor().enableIOSViewClipToPaddingBox();
}
bool ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges() {
return getAccessor().enableImmediateUpdateModeForContentOffsetChanges();
}
bool ReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS() {
return getAccessor().enableInteropViewManagerClassLookUpOptimizationIOS();
}
@@ -206,10 +210,18 @@ double ReactNativeFeatureFlags::preparedTextCacheSize() {
return getAccessor().preparedTextCacheSize();
}
bool ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking() {
return getAccessor().preventShadowTreeCommitExhaustionWithLocking();
}
bool ReactNativeFeatureFlags::releaseImageDataWhenConsumed() {
return getAccessor().releaseImageDataWhenConsumed();
}
bool ReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause() {
return getAccessor().skipActivityIdentityAssertionOnHostPause();
}
bool ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid() {
return getAccessor().traceTurboModulePromiseRejectionsOnAndroid();
}
@@ -226,6 +238,14 @@ bool ReactNativeFeatureFlags::useFabricInterop() {
return getAccessor().useFabricInterop();
}
bool ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid() {
return getAccessor().useNativeEqualsInNativeReadableArrayAndroid();
}
bool ReactNativeFeatureFlags::useNativeTransformHelperAndroid() {
return getAccessor().useNativeTransformHelperAndroid();
}
bool ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode() {
return getAccessor().useNativeViewConfigsInBridgelessMode();
}
@@ -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<<b6df25d8c333bf7656ade934b404020d>>
* @generated SignedSource<<c584290ce61ee78bdf843c19dd40c40d>>
*/
/**
@@ -149,6 +149,11 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static bool enableIOSViewClipToPaddingBox();
/**
* Dispatches state updates for content offset changes synchronously on the main thread.
*/
RN_EXPORT static bool enableImmediateUpdateModeForContentOffsetChanges();
/**
* This is to fix the issue with interop view manager where component descriptor lookup is causing ViewManager to preload.
*/
@@ -264,11 +269,21 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static double preparedTextCacheSize();
/**
* Enables a new mechanism in ShadowTree to prevent problems caused by multiple threads trying to commit concurrently. If a thread tries to commit a few times unsuccessfully, it will acquire a lock and try again.
*/
RN_EXPORT static bool preventShadowTreeCommitExhaustionWithLocking();
/**
* Releases the cached image data when it is consumed by the observers.
*/
RN_EXPORT static bool releaseImageDataWhenConsumed();
/**
* Skip activity identity assertion in ReactHostImpl::onHostPause()
*/
RN_EXPORT static bool skipActivityIdentityAssertionOnHostPause();
/**
* Enables storing js caller stack when creating promise in native module. This is useful in case of Promise rejection and tracing the cause.
*/
@@ -289,6 +304,16 @@ class ReactNativeFeatureFlags {
*/
RN_EXPORT static bool useFabricInterop();
/**
* Use a native implementation of equals in NativeReadableArray.
*/
RN_EXPORT static bool useNativeEqualsInNativeReadableArrayAndroid();
/**
* Use a native implementation of TransformHelper
*/
RN_EXPORT static bool useNativeTransformHelperAndroid();
/**
* When enabled, the native view configs are used in bridgeless mode.
*/
@@ -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<<074c50ff00ac8dcc6ff0d766ce44bd53>>
* @generated SignedSource<<dc5c6b3273ef312426c4ac366cc13772>>
*/
/**
@@ -425,6 +425,24 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSViewClipToPaddingBox() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetChanges() {
auto flagValue = enableImmediateUpdateModeForContentOffsetChanges_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(22, "enableImmediateUpdateModeForContentOffsetChanges");
flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges();
enableImmediateUpdateModeForContentOffsetChanges_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::enableInteropViewManagerClassLookUpOptimizationIOS() {
auto flagValue = enableInteropViewManagerClassLookUpOptimizationIOS_.load();
@@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableInteropViewManagerClassLookUpOptimiz
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(22, "enableInteropViewManagerClassLookUpOptimizationIOS");
markFlagAsAccessed(23, "enableInteropViewManagerClassLookUpOptimizationIOS");
flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS();
enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue;
@@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(23, "enableLayoutAnimationsOnAndroid");
markFlagAsAccessed(24, "enableLayoutAnimationsOnAndroid");
flagValue = currentProvider_->enableLayoutAnimationsOnAndroid();
enableLayoutAnimationsOnAndroid_ = flagValue;
@@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(24, "enableLayoutAnimationsOnIOS");
markFlagAsAccessed(25, "enableLayoutAnimationsOnIOS");
flagValue = currentProvider_->enableLayoutAnimationsOnIOS();
enableLayoutAnimationsOnIOS_ = flagValue;
@@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueCoordinatorOnIOS() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(25, "enableMainQueueCoordinatorOnIOS");
markFlagAsAccessed(26, "enableMainQueueCoordinatorOnIOS");
flagValue = currentProvider_->enableMainQueueCoordinatorOnIOS();
enableMainQueueCoordinatorOnIOS_ = flagValue;
@@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableModuleArgumentNSNullConversionIOS()
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(26, "enableModuleArgumentNSNullConversionIOS");
markFlagAsAccessed(27, "enableModuleArgumentNSNullConversionIOS");
flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS();
enableModuleArgumentNSNullConversionIOS_ = flagValue;
@@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(27, "enableNativeCSSParsing");
markFlagAsAccessed(28, "enableNativeCSSParsing");
flagValue = currentProvider_->enableNativeCSSParsing();
enableNativeCSSParsing_ = flagValue;
@@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(28, "enableNetworkEventReporting");
markFlagAsAccessed(29, "enableNetworkEventReporting");
flagValue = currentProvider_->enableNetworkEventReporting();
enableNetworkEventReporting_ = flagValue;
@@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNewBackgroundAndBorderDrawables() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(29, "enableNewBackgroundAndBorderDrawables");
markFlagAsAccessed(30, "enableNewBackgroundAndBorderDrawables");
flagValue = currentProvider_->enableNewBackgroundAndBorderDrawables();
enableNewBackgroundAndBorderDrawables_ = flagValue;
@@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(30, "enablePreparedTextLayout");
markFlagAsAccessed(31, "enablePreparedTextLayout");
flagValue = currentProvider_->enablePreparedTextLayout();
enablePreparedTextLayout_ = flagValue;
@@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(31, "enablePropsUpdateReconciliationAndroid");
markFlagAsAccessed(32, "enablePropsUpdateReconciliationAndroid");
flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid();
enablePropsUpdateReconciliationAndroid_ = flagValue;
@@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(32, "enableResourceTimingAPI");
markFlagAsAccessed(33, "enableResourceTimingAPI");
flagValue = currentProvider_->enableResourceTimingAPI();
enableResourceTimingAPI_ = flagValue;
@@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(33, "enableViewCulling");
markFlagAsAccessed(34, "enableViewCulling");
flagValue = currentProvider_->enableViewCulling();
enableViewCulling_ = flagValue;
@@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(34, "enableViewRecycling");
markFlagAsAccessed(35, "enableViewRecycling");
flagValue = currentProvider_->enableViewRecycling();
enableViewRecycling_ = flagValue;
@@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(35, "enableViewRecyclingForText");
markFlagAsAccessed(36, "enableViewRecyclingForText");
flagValue = currentProvider_->enableViewRecyclingForText();
enableViewRecyclingForText_ = flagValue;
@@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(36, "enableViewRecyclingForView");
markFlagAsAccessed(37, "enableViewRecyclingForView");
flagValue = currentProvider_->enableViewRecyclingForView();
enableViewRecyclingForView_ = flagValue;
@@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(37, "enableVirtualViewDebugFeatures");
markFlagAsAccessed(38, "enableVirtualViewDebugFeatures");
flagValue = currentProvider_->enableVirtualViewDebugFeatures();
enableVirtualViewDebugFeatures_ = flagValue;
@@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewRenderState() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(38, "enableVirtualViewRenderState");
markFlagAsAccessed(39, "enableVirtualViewRenderState");
flagValue = currentProvider_->enableVirtualViewRenderState();
enableVirtualViewRenderState_ = flagValue;
@@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewWindowFocusDetection() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(39, "enableVirtualViewWindowFocusDetection");
markFlagAsAccessed(40, "enableVirtualViewWindowFocusDetection");
flagValue = currentProvider_->enableVirtualViewWindowFocusDetection();
enableVirtualViewWindowFocusDetection_ = flagValue;
@@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(40, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
markFlagAsAccessed(41, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact();
fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue;
@@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(41, "fuseboxEnabledRelease");
markFlagAsAccessed(42, "fuseboxEnabledRelease");
flagValue = currentProvider_->fuseboxEnabledRelease();
fuseboxEnabledRelease_ = flagValue;
@@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(42, "fuseboxNetworkInspectionEnabled");
markFlagAsAccessed(43, "fuseboxNetworkInspectionEnabled");
flagValue = currentProvider_->fuseboxNetworkInspectionEnabled();
fuseboxNetworkInspectionEnabled_ = flagValue;
@@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(43, "hideOffscreenVirtualViewsOnIOS");
markFlagAsAccessed(44, "hideOffscreenVirtualViewsOnIOS");
flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS();
hideOffscreenVirtualViewsOnIOS_ = flagValue;
@@ -830,7 +848,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(44, "preparedTextCacheSize");
markFlagAsAccessed(45, "preparedTextCacheSize");
flagValue = currentProvider_->preparedTextCacheSize();
preparedTextCacheSize_ = flagValue;
@@ -839,6 +857,24 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustionWithLocking() {
auto flagValue = preventShadowTreeCommitExhaustionWithLocking_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(46, "preventShadowTreeCommitExhaustionWithLocking");
flagValue = currentProvider_->preventShadowTreeCommitExhaustionWithLocking();
preventShadowTreeCommitExhaustionWithLocking_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() {
auto flagValue = releaseImageDataWhenConsumed_.load();
@@ -848,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(45, "releaseImageDataWhenConsumed");
markFlagAsAccessed(47, "releaseImageDataWhenConsumed");
flagValue = currentProvider_->releaseImageDataWhenConsumed();
releaseImageDataWhenConsumed_ = flagValue;
@@ -857,6 +893,24 @@ bool ReactNativeFeatureFlagsAccessor::releaseImageDataWhenConsumed() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() {
auto flagValue = skipActivityIdentityAssertionOnHostPause_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(48, "skipActivityIdentityAssertionOnHostPause");
flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause();
skipActivityIdentityAssertionOnHostPause_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid() {
auto flagValue = traceTurboModulePromiseRejectionsOnAndroid_.load();
@@ -866,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(46, "traceTurboModulePromiseRejectionsOnAndroid");
markFlagAsAccessed(49, "traceTurboModulePromiseRejectionsOnAndroid");
flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid();
traceTurboModulePromiseRejectionsOnAndroid_ = flagValue;
@@ -884,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit(
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(47, "updateRuntimeShadowNodeReferencesOnCommit");
markFlagAsAccessed(50, "updateRuntimeShadowNodeReferencesOnCommit");
flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit();
updateRuntimeShadowNodeReferencesOnCommit_ = flagValue;
@@ -902,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(48, "useAlwaysAvailableJSErrorHandling");
markFlagAsAccessed(51, "useAlwaysAvailableJSErrorHandling");
flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling();
useAlwaysAvailableJSErrorHandling_ = flagValue;
@@ -920,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(49, "useFabricInterop");
markFlagAsAccessed(52, "useFabricInterop");
flagValue = currentProvider_->useFabricInterop();
useFabricInterop_ = flagValue;
@@ -929,6 +983,42 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() {
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::useNativeEqualsInNativeReadableArrayAndroid() {
auto flagValue = useNativeEqualsInNativeReadableArrayAndroid_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(53, "useNativeEqualsInNativeReadableArrayAndroid");
flagValue = currentProvider_->useNativeEqualsInNativeReadableArrayAndroid();
useNativeEqualsInNativeReadableArrayAndroid_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::useNativeTransformHelperAndroid() {
auto flagValue = useNativeTransformHelperAndroid_.load();
if (!flagValue.has_value()) {
// This block is not exclusive but it is not necessary.
// If multiple threads try to initialize the feature flag, we would only
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(54, "useNativeTransformHelperAndroid");
flagValue = currentProvider_->useNativeTransformHelperAndroid();
useNativeTransformHelperAndroid_ = flagValue;
}
return flagValue.value();
}
bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() {
auto flagValue = useNativeViewConfigsInBridgelessMode_.load();
@@ -938,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(50, "useNativeViewConfigsInBridgelessMode");
markFlagAsAccessed(55, "useNativeViewConfigsInBridgelessMode");
flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode();
useNativeViewConfigsInBridgelessMode_ = flagValue;
@@ -956,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(51, "useOptimizedEventBatchingOnAndroid");
markFlagAsAccessed(56, "useOptimizedEventBatchingOnAndroid");
flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid();
useOptimizedEventBatchingOnAndroid_ = flagValue;
@@ -974,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(52, "useRawPropsJsiValue");
markFlagAsAccessed(57, "useRawPropsJsiValue");
flagValue = currentProvider_->useRawPropsJsiValue();
useRawPropsJsiValue_ = flagValue;
@@ -992,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(53, "useShadowNodeStateOnClone");
markFlagAsAccessed(58, "useShadowNodeStateOnClone");
flagValue = currentProvider_->useShadowNodeStateOnClone();
useShadowNodeStateOnClone_ = flagValue;
@@ -1010,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(54, "useTurboModuleInterop");
markFlagAsAccessed(59, "useTurboModuleInterop");
flagValue = currentProvider_->useTurboModuleInterop();
useTurboModuleInterop_ = flagValue;
@@ -1028,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(55, "useTurboModules");
markFlagAsAccessed(60, "useTurboModules");
flagValue = currentProvider_->useTurboModules();
useTurboModules_ = flagValue;
@@ -1046,7 +1136,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() {
// be accessing the provider multiple times but the end state of this
// instance and the returned flag value would be the same.
markFlagAsAccessed(56, "virtualViewPrerenderRatio");
markFlagAsAccessed(61, "virtualViewPrerenderRatio");
flagValue = currentProvider_->virtualViewPrerenderRatio();
virtualViewPrerenderRatio_ = flagValue;
@@ -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<<7dd91dc5181b06ffb8909245da18c347>>
* @generated SignedSource<<edfd880ca5034a683053dc95e177a7c2>>
*/
/**
@@ -54,6 +54,7 @@ class ReactNativeFeatureFlagsAccessor {
bool enableFontScaleChangesUpdatingLayout();
bool enableIOSTextBaselineOffsetPerLine();
bool enableIOSViewClipToPaddingBox();
bool enableImmediateUpdateModeForContentOffsetChanges();
bool enableInteropViewManagerClassLookUpOptimizationIOS();
bool enableLayoutAnimationsOnAndroid();
bool enableLayoutAnimationsOnIOS();
@@ -77,11 +78,15 @@ class ReactNativeFeatureFlagsAccessor {
bool fuseboxNetworkInspectionEnabled();
bool hideOffscreenVirtualViewsOnIOS();
double preparedTextCacheSize();
bool preventShadowTreeCommitExhaustionWithLocking();
bool releaseImageDataWhenConsumed();
bool skipActivityIdentityAssertionOnHostPause();
bool traceTurboModulePromiseRejectionsOnAndroid();
bool updateRuntimeShadowNodeReferencesOnCommit();
bool useAlwaysAvailableJSErrorHandling();
bool useFabricInterop();
bool useNativeEqualsInNativeReadableArrayAndroid();
bool useNativeTransformHelperAndroid();
bool useNativeViewConfigsInBridgelessMode();
bool useOptimizedEventBatchingOnAndroid();
bool useRawPropsJsiValue();
@@ -100,7 +105,7 @@ class ReactNativeFeatureFlagsAccessor {
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
bool wasOverridden_;
std::array<std::atomic<const char*>, 57> accessedFeatureFlags_;
std::array<std::atomic<const char*>, 62> accessedFeatureFlags_;
std::atomic<std::optional<bool>> commonTestFlag_;
std::atomic<std::optional<bool>> cxxNativeAnimatedEnabled_;
@@ -124,6 +129,7 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic<std::optional<bool>> enableFontScaleChangesUpdatingLayout_;
std::atomic<std::optional<bool>> enableIOSTextBaselineOffsetPerLine_;
std::atomic<std::optional<bool>> enableIOSViewClipToPaddingBox_;
std::atomic<std::optional<bool>> enableImmediateUpdateModeForContentOffsetChanges_;
std::atomic<std::optional<bool>> enableInteropViewManagerClassLookUpOptimizationIOS_;
std::atomic<std::optional<bool>> enableLayoutAnimationsOnAndroid_;
std::atomic<std::optional<bool>> enableLayoutAnimationsOnIOS_;
@@ -147,11 +153,15 @@ class ReactNativeFeatureFlagsAccessor {
std::atomic<std::optional<bool>> fuseboxNetworkInspectionEnabled_;
std::atomic<std::optional<bool>> hideOffscreenVirtualViewsOnIOS_;
std::atomic<std::optional<double>> preparedTextCacheSize_;
std::atomic<std::optional<bool>> preventShadowTreeCommitExhaustionWithLocking_;
std::atomic<std::optional<bool>> releaseImageDataWhenConsumed_;
std::atomic<std::optional<bool>> skipActivityIdentityAssertionOnHostPause_;
std::atomic<std::optional<bool>> traceTurboModulePromiseRejectionsOnAndroid_;
std::atomic<std::optional<bool>> updateRuntimeShadowNodeReferencesOnCommit_;
std::atomic<std::optional<bool>> useAlwaysAvailableJSErrorHandling_;
std::atomic<std::optional<bool>> useFabricInterop_;
std::atomic<std::optional<bool>> useNativeEqualsInNativeReadableArrayAndroid_;
std::atomic<std::optional<bool>> useNativeTransformHelperAndroid_;
std::atomic<std::optional<bool>> useNativeViewConfigsInBridgelessMode_;
std::atomic<std::optional<bool>> useOptimizedEventBatchingOnAndroid_;
std::atomic<std::optional<bool>> useRawPropsJsiValue_;
@@ -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<<64cac29f0902d801e8a7e1d2ab17ddd5>>
* @generated SignedSource<<614fe0894f6235d879a0b29e7dab7759>>
*/
/**
@@ -115,6 +115,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return false;
}
bool enableImmediateUpdateModeForContentOffsetChanges() override {
return false;
}
bool enableInteropViewManagerClassLookUpOptimizationIOS() override {
return false;
}
@@ -180,7 +184,7 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
}
bool enableVirtualViewRenderState() override {
return false;
return true;
}
bool enableVirtualViewWindowFocusDetection() override {
@@ -207,10 +211,18 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return 200.0;
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
return false;
}
bool releaseImageDataWhenConsumed() override {
return false;
}
bool skipActivityIdentityAssertionOnHostPause() override {
return false;
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
return false;
}
@@ -227,6 +239,14 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
return true;
}
bool useNativeEqualsInNativeReadableArrayAndroid() override {
return false;
}
bool useNativeTransformHelperAndroid() override {
return false;
}
bool useNativeViewConfigsInBridgelessMode() override {
return 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<<d8c2161445fd7dead9c4d3444b3c9065>>
* @generated SignedSource<<6502c090532e1d7d2ac67cf6e711d9f4>>
*/
/**
@@ -243,6 +243,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::enableIOSViewClipToPaddingBox();
}
bool enableImmediateUpdateModeForContentOffsetChanges() override {
auto value = values_["enableImmediateUpdateModeForContentOffsetChanges"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::enableImmediateUpdateModeForContentOffsetChanges();
}
bool enableInteropViewManagerClassLookUpOptimizationIOS() override {
auto value = values_["enableInteropViewManagerClassLookUpOptimizationIOS"];
if (!value.isNull()) {
@@ -450,6 +459,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::preparedTextCacheSize();
}
bool preventShadowTreeCommitExhaustionWithLocking() override {
auto value = values_["preventShadowTreeCommitExhaustionWithLocking"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::preventShadowTreeCommitExhaustionWithLocking();
}
bool releaseImageDataWhenConsumed() override {
auto value = values_["releaseImageDataWhenConsumed"];
if (!value.isNull()) {
@@ -459,6 +477,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::releaseImageDataWhenConsumed();
}
bool skipActivityIdentityAssertionOnHostPause() override {
auto value = values_["skipActivityIdentityAssertionOnHostPause"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::skipActivityIdentityAssertionOnHostPause();
}
bool traceTurboModulePromiseRejectionsOnAndroid() override {
auto value = values_["traceTurboModulePromiseRejectionsOnAndroid"];
if (!value.isNull()) {
@@ -495,6 +522,24 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
return ReactNativeFeatureFlagsDefaults::useFabricInterop();
}
bool useNativeEqualsInNativeReadableArrayAndroid() override {
auto value = values_["useNativeEqualsInNativeReadableArrayAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useNativeEqualsInNativeReadableArrayAndroid();
}
bool useNativeTransformHelperAndroid() override {
auto value = values_["useNativeTransformHelperAndroid"];
if (!value.isNull()) {
return value.getBool();
}
return ReactNativeFeatureFlagsDefaults::useNativeTransformHelperAndroid();
}
bool useNativeViewConfigsInBridgelessMode() override {
auto value = values_["useNativeViewConfigsInBridgelessMode"];
if (!value.isNull()) {
@@ -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<<d759f36e6b262bbcbd7bbdabece76042>>
* @generated SignedSource<<a26af781141939d35cff83bacf522bb0>>
*/
/**
@@ -47,6 +47,7 @@ class ReactNativeFeatureFlagsProvider {
virtual bool enableFontScaleChangesUpdatingLayout() = 0;
virtual bool enableIOSTextBaselineOffsetPerLine() = 0;
virtual bool enableIOSViewClipToPaddingBox() = 0;
virtual bool enableImmediateUpdateModeForContentOffsetChanges() = 0;
virtual bool enableInteropViewManagerClassLookUpOptimizationIOS() = 0;
virtual bool enableLayoutAnimationsOnAndroid() = 0;
virtual bool enableLayoutAnimationsOnIOS() = 0;
@@ -70,11 +71,15 @@ class ReactNativeFeatureFlagsProvider {
virtual bool fuseboxNetworkInspectionEnabled() = 0;
virtual bool hideOffscreenVirtualViewsOnIOS() = 0;
virtual double preparedTextCacheSize() = 0;
virtual bool preventShadowTreeCommitExhaustionWithLocking() = 0;
virtual bool releaseImageDataWhenConsumed() = 0;
virtual bool skipActivityIdentityAssertionOnHostPause() = 0;
virtual bool traceTurboModulePromiseRejectionsOnAndroid() = 0;
virtual bool updateRuntimeShadowNodeReferencesOnCommit() = 0;
virtual bool useAlwaysAvailableJSErrorHandling() = 0;
virtual bool useFabricInterop() = 0;
virtual bool useNativeEqualsInNativeReadableArrayAndroid() = 0;
virtual bool useNativeTransformHelperAndroid() = 0;
virtual bool useNativeViewConfigsInBridgelessMode() = 0;
virtual bool useOptimizedEventBatchingOnAndroid() = 0;
virtual bool useRawPropsJsiValue() = 0;
@@ -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<<3bb595d448d15f55ded8e5ddc4549bcb>>
* @generated SignedSource<<cadb1f2c1c10a03593dc7c9a9a94747b>>
*/
/**
@@ -154,6 +154,11 @@ bool NativeReactNativeFeatureFlags::enableIOSViewClipToPaddingBox(
return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
}
bool NativeReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableImmediateUpdateModeForContentOffsetChanges();
}
bool NativeReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::enableInteropViewManagerClassLookUpOptimizationIOS();
@@ -269,11 +274,21 @@ double NativeReactNativeFeatureFlags::preparedTextCacheSize(
return ReactNativeFeatureFlags::preparedTextCacheSize();
}
bool NativeReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::preventShadowTreeCommitExhaustionWithLocking();
}
bool NativeReactNativeFeatureFlags::releaseImageDataWhenConsumed(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::releaseImageDataWhenConsumed();
}
bool NativeReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::skipActivityIdentityAssertionOnHostPause();
}
bool NativeReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::traceTurboModulePromiseRejectionsOnAndroid();
@@ -294,6 +309,16 @@ bool NativeReactNativeFeatureFlags::useFabricInterop(
return ReactNativeFeatureFlags::useFabricInterop();
}
bool NativeReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useNativeEqualsInNativeReadableArrayAndroid();
}
bool NativeReactNativeFeatureFlags::useNativeTransformHelperAndroid(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useNativeTransformHelperAndroid();
}
bool NativeReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode(
jsi::Runtime& /*runtime*/) {
return ReactNativeFeatureFlags::useNativeViewConfigsInBridgelessMode();
@@ -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<<130929d2cc38d298a27dd6afc5c746ca>>
* @generated SignedSource<<de04d72490f2c09abdcea51e6f02796c>>
*/
/**
@@ -80,6 +80,8 @@ class NativeReactNativeFeatureFlags
bool enableIOSViewClipToPaddingBox(jsi::Runtime& runtime);
bool enableImmediateUpdateModeForContentOffsetChanges(jsi::Runtime& runtime);
bool enableInteropViewManagerClassLookUpOptimizationIOS(jsi::Runtime& runtime);
bool enableLayoutAnimationsOnAndroid(jsi::Runtime& runtime);
@@ -126,8 +128,12 @@ class NativeReactNativeFeatureFlags
double preparedTextCacheSize(jsi::Runtime& runtime);
bool preventShadowTreeCommitExhaustionWithLocking(jsi::Runtime& runtime);
bool releaseImageDataWhenConsumed(jsi::Runtime& runtime);
bool skipActivityIdentityAssertionOnHostPause(jsi::Runtime& runtime);
bool traceTurboModulePromiseRejectionsOnAndroid(jsi::Runtime& runtime);
bool updateRuntimeShadowNodeReferencesOnCommit(jsi::Runtime& runtime);
@@ -136,6 +142,10 @@ class NativeReactNativeFeatureFlags
bool useFabricInterop(jsi::Runtime& runtime);
bool useNativeEqualsInNativeReadableArrayAndroid(jsi::Runtime& runtime);
bool useNativeTransformHelperAndroid(jsi::Runtime& runtime);
bool useNativeViewConfigsInBridgelessMode(jsi::Runtime& runtime);
bool useOptimizedEventBatchingOnAndroid(jsi::Runtime& runtime);
@@ -49,21 +49,6 @@ NativeIntersectionObserver::NativeIntersectionObserver(
std::shared_ptr<CallInvoker> jsInvoker)
: NativeIntersectionObserverCxxSpec(std::move(jsInvoker)) {}
void NativeIntersectionObserver::observe(
jsi::Runtime& runtime,
NativeIntersectionObserverObserveOptions options) {
observeV2(runtime, std::move(options));
}
void NativeIntersectionObserver::unobserve(
jsi::Runtime& runtime,
IntersectionObserverObserverId intersectionObserverId,
std::shared_ptr<const ShadowNode> targetShadowNode) {
auto token =
tokenFromShadowNodeFamily(runtime, targetShadowNode->getFamilyShared());
unobserveV2(runtime, intersectionObserverId, std::move(token));
}
jsi::Object NativeIntersectionObserver::observeV2(
jsi::Runtime& runtime,
NativeIntersectionObserverObserveOptions options) {
@@ -64,19 +64,6 @@ class NativeIntersectionObserver
public:
NativeIntersectionObserver(std::shared_ptr<CallInvoker> jsInvoker);
// TODO(T223605846): Remove legacy observe method
[[deprecated("Please use observeV2")]]
void observe(
jsi::Runtime& runtime,
NativeIntersectionObserverObserveOptions options);
// TODO(T223605846): Remove legacy unobserve method
[[deprecated("Please use unobserveV2")]]
void unobserve(
jsi::Runtime& runtime,
IntersectionObserverObserverId intersectionObserverId,
std::shared_ptr<const ShadowNode> targetShadowNode);
jsi::Object observeV2(
jsi::Runtime& runtime,
NativeIntersectionObserverObserveOptions options);
@@ -13,6 +13,7 @@
#include <cxxreact/JSExecutor.h>
#include <cxxreact/ReactMarker.h>
#include <jsi/JSIDynamic.h>
#include <jsi/instrumentation.h>
#include <react/performance/timeline/PerformanceEntryReporter.h>
#include <react/performance/timeline/PerformanceObserver.h>
@@ -111,6 +112,19 @@ std::shared_ptr<PerformanceObserver> tryGetObserver(
return observerWrapper ? observerWrapper->observer : nullptr;
}
PerformanceEntryReporter::UserTimingDetailProvider getDetailProviderFromEntry(
jsi::Runtime& rt,
jsi::Value& entry) {
return [&rt, &entry]() -> folly::dynamic {
try {
auto detail = entry.asObject(rt).getProperty(rt, "detail");
return jsi::dynamicFromValue(rt, detail);
} catch (jsi::JSIException& ex) {
return nullptr;
}
};
}
} // namespace
NativePerformance::NativePerformance(std::shared_ptr<CallInvoker> jsInvoker)
@@ -120,108 +134,31 @@ HighResTimeStamp NativePerformance::now(jsi::Runtime& /*rt*/) {
return forcedCurrentTimeStamp_.value_or(HighResTimeStamp::now());
}
HighResTimeStamp NativePerformance::markWithResult(
void NativePerformance::reportMark(
jsi::Runtime& rt,
std::string name,
std::optional<HighResTimeStamp> startTime) {
auto entry = PerformanceEntryReporter::getInstance()->reportMark(
name, startTime.value_or(now(rt)));
return entry.startTime;
HighResTimeStamp startTime,
jsi::Value entry) {
PerformanceEntryReporter::getInstance()->reportMark(
name, startTime, getDetailProviderFromEntry(rt, entry));
}
std::tuple<HighResTimeStamp, HighResDuration> NativePerformance::measure(
jsi::Runtime& runtime,
std::string name,
std::optional<HighResTimeStamp> startTime,
std::optional<HighResTimeStamp> endTime,
std::optional<HighResDuration> duration,
std::optional<std::string> startMark,
std::optional<std::string> endMark) {
auto reporter = PerformanceEntryReporter::getInstance();
HighResTimeStamp startTimeValue;
// If the start time mark name is specified, it takes precedence over the
// startTime parameter, which can be set to 0 by default from JavaScript.
if (startTime) {
startTimeValue = *startTime;
} else if (startMark) {
if (auto startMarkBufferedTime = reporter->getMarkTime(*startMark)) {
startTimeValue = *startMarkBufferedTime;
} else {
throw jsi::JSError(
runtime, "The mark '" + *startMark + "' does not exist.");
}
} else {
startTimeValue = HighResTimeStamp::fromDOMHighResTimeStamp(0);
}
HighResTimeStamp endTimeValue;
if (endTime) {
endTimeValue = *endTime;
} else if (duration) {
endTimeValue = startTimeValue + *duration;
} else if (endMark) {
if (auto endMarkBufferedTime = reporter->getMarkTime(*endMark)) {
endTimeValue = *endMarkBufferedTime;
} else {
throw jsi::JSError(
runtime, "The mark '" + *endMark + "' does not exist.");
}
} else {
// The end time is not specified, take the current time, according to the
// standard
endTimeValue = now(runtime);
}
auto entry = reporter->reportMeasure(name, startTimeValue, endTimeValue);
return std::tuple{entry.startTime, entry.duration};
}
std::tuple<HighResTimeStamp, HighResDuration>
NativePerformance::measureWithResult(
jsi::Runtime& runtime,
void NativePerformance::reportMeasure(
jsi::Runtime& rt,
std::string name,
HighResTimeStamp startTime,
HighResTimeStamp endTime,
std::optional<HighResDuration> duration,
std::optional<std::string> startMark,
std::optional<std::string> endMark) {
auto reporter = PerformanceEntryReporter::getInstance();
HighResDuration duration,
jsi::Value entry) {
PerformanceEntryReporter::getInstance()->reportMeasure(
name, startTime, duration, getDetailProviderFromEntry(rt, entry));
}
HighResTimeStamp startTimeValue = startTime;
// If the start time mark name is specified, it takes precedence over the
// startTime parameter, which can be set to 0 by default from JavaScript.
if (startMark) {
if (auto startMarkBufferedTime = reporter->getMarkTime(*startMark)) {
startTimeValue = *startMarkBufferedTime;
} else {
throw jsi::JSError(
runtime, "The mark '" + *startMark + "' does not exist.");
}
}
HighResTimeStamp endTimeValue = endTime;
// If the end time mark name is specified, it takes precedence over the
// startTime parameter, which can be set to 0 by default from JavaScript.
if (endMark) {
if (auto endMarkBufferedTime = reporter->getMarkTime(*endMark)) {
endTimeValue = *endMarkBufferedTime;
} else {
throw jsi::JSError(
runtime, "The mark '" + *endMark + "' does not exist.");
}
} else if (duration) {
endTimeValue = startTimeValue + *duration;
} else if (endTimeValue < startTimeValue) {
// The end time is not specified, take the current time, according to the
// standard
endTimeValue = now(runtime);
}
auto entry = reporter->reportMeasure(name, startTime, endTime);
return std::tuple{entry.startTime, entry.duration};
std::optional<double> NativePerformance::getMarkTime(
jsi::Runtime& rt,
std::string name) {
auto markTime = PerformanceEntryReporter::getInstance()->getMarkTime(name);
return markTime ? std::optional{(*markTime).toDOMHighResTimeStamp()}
: std::nullopt;
}
void NativePerformance::clearMarks(
@@ -90,32 +90,20 @@ class NativePerformance : public NativePerformanceCxxSpec<NativePerformance> {
#pragma mark - User Timing Level 3 functions (https://w3c.github.io/user-timing/)
// https://w3c.github.io/user-timing/#mark-method
HighResTimeStamp markWithResult(
void reportMark(
jsi::Runtime& rt,
std::string name,
std::optional<HighResTimeStamp> startTime);
HighResTimeStamp time,
jsi::Value entry);
// https://w3c.github.io/user-timing/#measure-method
std::tuple<HighResTimeStamp, HighResDuration> measure(
jsi::Runtime& rt,
std::string name,
std::optional<HighResTimeStamp> startTime,
std::optional<HighResTimeStamp> endTime,
std::optional<HighResDuration> duration,
std::optional<std::string> startMark,
std::optional<std::string> endMark);
// https://w3c.github.io/user-timing/#measure-method
[[deprecated("This method is deprecated. Use the measure method instead.")]]
std::tuple<HighResTimeStamp, HighResDuration> measureWithResult(
void reportMeasure(
jsi::Runtime& rt,
std::string name,
HighResTimeStamp startTime,
HighResTimeStamp endTime,
std::optional<HighResDuration> duration,
std::optional<std::string> startMark,
std::optional<std::string> endMark);
HighResDuration duration,
jsi::Value entry);
std::optional<double> getMarkTime(jsi::Runtime& rt, std::string name);
// https://w3c.github.io/user-timing/#clearmarks-method
void clearMarks(
@@ -19,4 +19,5 @@ target_link_libraries(react_performance_timeline
jsinspector_tracing
reactperflogger
react_featureflags
react_timing)
react_timing
folly_runtime)
@@ -37,31 +37,40 @@ std::vector<PerformanceEntryType> getSupportedEntryTypesInternal() {
return supportedEntryTypes;
}
#if defined(__clang__)
#define NO_DESTROY [[clang::no_destroy]]
#else
#define NO_DESTROY
#endif
NO_DESTROY const std::string TRACK_PREFIX = "Track:";
std::tuple<std::optional<std::string>, std::string_view> parseTrackName(
const std::string& name) {
// Until there's a standard way to pass through track information, parse it
// manually, e.g., "Track:Foo:Event name"
// https://github.com/w3c/user-timing/issues/109
std::optional<std::string> trackName;
std::string_view eventName(name);
if (name.starts_with(TRACK_PREFIX)) {
const auto trackNameDelimiter = name.find(':', TRACK_PREFIX.length());
if (trackNameDelimiter != std::string::npos) {
trackName = name.substr(
TRACK_PREFIX.length(), trackNameDelimiter - TRACK_PREFIX.length());
eventName = std::string_view(name).substr(trackNameDelimiter + 1);
}
std::optional<std::string> getTrackFromDetail(folly::dynamic& detail) {
if (!detail.isObject()) {
return std::nullopt;
}
return std::make_tuple(trackName, eventName);
auto maybeDevtools = detail["devtools"];
if (!maybeDevtools.isObject()) {
return std::nullopt;
}
auto maybeTrack = maybeDevtools["track"];
if (!maybeTrack.isString()) {
return std::nullopt;
}
return maybeTrack.asString();
}
std::optional<std::string> getTrackGroupFromDetail(folly::dynamic& detail) {
if (!detail.isObject()) {
return std::nullopt;
}
auto maybeDevtools = detail["devtools"];
if (!maybeDevtools.isObject()) {
return std::nullopt;
}
auto maybeTrackGroup = maybeDevtools["trackGroup"];
if (!maybeTrackGroup.isString()) {
return std::nullopt;
}
return maybeTrackGroup.asString();
}
} // namespace
@@ -167,14 +176,13 @@ void PerformanceEntryReporter::clearEntries(
getBufferRef(entryType).clear(entryName);
}
PerformanceMark PerformanceEntryReporter::reportMark(
void PerformanceEntryReporter::reportMark(
const std::string& name,
const std::optional<HighResTimeStamp>& startTime) {
// Resolve timings
auto startTimeVal = startTime ? *startTime : getCurrentTimeStamp();
const auto entry = PerformanceMark{{.name = name, .startTime = startTimeVal}};
const HighResTimeStamp startTime,
UserTimingDetailProvider&& detailProvider) {
const auto entry = PerformanceMark{{.name = name, .startTime = startTime}};
traceMark(entry);
traceMark(entry, std::move(detailProvider));
// Add to buffers & notify observers
{
@@ -183,24 +191,19 @@ PerformanceMark PerformanceEntryReporter::reportMark(
}
observerRegistry_->queuePerformanceEntry(entry);
return entry;
}
PerformanceMeasure PerformanceEntryReporter::reportMeasure(
void PerformanceEntryReporter::reportMeasure(
const std::string& name,
HighResTimeStamp startTime,
HighResTimeStamp endTime,
const std::optional<jsinspector_modern::DevToolsTrackEntryPayload>&
trackMetadata) {
HighResDuration duration = endTime - startTime;
HighResDuration duration,
UserTimingDetailProvider&& detailProvider) {
const auto entry = PerformanceMeasure{
{.name = std::string(name),
.startTime = startTime,
.duration = duration}};
traceMeasure(entry);
traceMeasure(entry, std::move(detailProvider));
// Add to buffers & notify observers
{
@@ -209,8 +212,6 @@ PerformanceMeasure PerformanceEntryReporter::reportMeasure(
}
observerRegistry_->queuePerformanceEntry(entry);
return entry;
}
void PerformanceEntryReporter::clearEventCounts() {
@@ -275,7 +276,7 @@ void PerformanceEntryReporter::reportLongTask(
observerRegistry_->queuePerformanceEntry(entry);
}
PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming(
void PerformanceEntryReporter::reportResourceTiming(
const std::string& url,
HighResTimeStamp fetchStart,
HighResTimeStamp requestStart,
@@ -302,50 +303,47 @@ PerformanceResourceTiming PerformanceEntryReporter::reportResourceTiming(
}
observerRegistry_->queuePerformanceEntry(entry);
return entry;
}
void PerformanceEntryReporter::traceMark(const PerformanceMark& entry) const {
void PerformanceEntryReporter::traceMark(
const PerformanceMark& entry,
UserTimingDetailProvider&& detailProvider) const {
auto& performanceTracer =
jsinspector_modern::tracing::PerformanceTracer::getInstance();
if (ReactPerfettoLogger::isTracing() || performanceTracer.isTracing()) {
auto [trackName, eventName] = parseTrackName(entry.name);
if (performanceTracer.isTracing()) {
performanceTracer.reportMark(entry.name, entry.startTime);
if (ReactPerfettoLogger::isTracing()) {
ReactPerfettoLogger::mark(entry.name, entry.startTime);
}
if (ReactPerfettoLogger::isTracing()) {
ReactPerfettoLogger::mark(eventName, entry.startTime, trackName);
if (performanceTracer.isTracing()) {
performanceTracer.reportMark(
entry.name,
entry.startTime,
detailProvider != nullptr ? detailProvider() : nullptr);
}
}
}
void PerformanceEntryReporter::traceMeasure(
const PerformanceMeasure& entry) const {
const PerformanceMeasure& entry,
UserTimingDetailProvider&& detailProvider) const {
auto& performanceTracer =
jsinspector_modern::tracing::PerformanceTracer::getInstance();
if (performanceTracer.isTracing() || ReactPerfettoLogger::isTracing()) {
auto [trackName, eventName] = parseTrackName(entry.name);
if (performanceTracer.isTracing()) {
std::optional<jsinspector_modern::DevToolsTrackEntryPayload>
trackMetadata;
if (trackName.has_value()) {
trackMetadata = {.track = trackName.value()};
}
performanceTracer.reportMeasure(
eventName, entry.startTime, entry.duration, trackMetadata);
}
auto detail = detailProvider != nullptr ? detailProvider() : nullptr;
if (ReactPerfettoLogger::isTracing()) {
ReactPerfettoLogger::measure(
eventName,
entry.name,
entry.startTime,
entry.startTime + entry.duration,
trackName);
detail != nullptr ? getTrackFromDetail(detail) : std::nullopt,
detail != nullptr ? getTrackGroupFromDetail(detail) : std::nullopt);
}
if (performanceTracer.isTracing()) {
performanceTracer.reportMeasure(
entry.name, entry.startTime, entry.duration, std::move(detail));
}
}
}
@@ -11,7 +11,7 @@
#include "PerformanceEntryKeyedBuffer.h"
#include "PerformanceObserverRegistry.h"
#include <jsinspector-modern/tracing/CdpTracing.h>
#include <folly/dynamic.h>
#include <react/timing/primitives.h>
#include <memory>
@@ -86,16 +86,18 @@ class PerformanceEntryReporter {
std::optional<HighResTimeStamp> getMarkTime(
const std::string& markName) const;
PerformanceMark reportMark(
const std::string& name,
const std::optional<HighResTimeStamp>& startTime = std::nullopt);
using UserTimingDetailProvider = std::function<folly::dynamic()>;
PerformanceMeasure reportMeasure(
void reportMark(
const std::string& name,
HighResTimeStamp startTime,
HighResTimeStamp endTime,
const std::optional<jsinspector_modern::DevToolsTrackEntryPayload>&
trackMetadata = std::nullopt);
UserTimingDetailProvider&& detailProvider = nullptr);
void reportMeasure(
const std::string& name,
HighResTimeStamp startTime,
HighResDuration duration,
UserTimingDetailProvider&& detailProvider = nullptr);
void reportEvent(
std::string name,
@@ -107,7 +109,7 @@ class PerformanceEntryReporter {
void reportLongTask(HighResTimeStamp startTime, HighResDuration duration);
PerformanceResourceTiming reportResourceTiming(
void reportResourceTiming(
const std::string& url,
HighResTimeStamp fetchStart,
HighResTimeStamp requestStart,
@@ -169,8 +171,12 @@ class PerformanceEntryReporter {
throw std::logic_error("Unhandled PerformanceEntryType");
}
void traceMark(const PerformanceMark& entry) const;
void traceMeasure(const PerformanceMeasure& entry) const;
void traceMark(
const PerformanceMark& entry,
UserTimingDetailProvider&& detailProvider) const;
void traceMeasure(
const PerformanceMeasure& entry,
UserTimingDetailProvider&& detailProvider) const;
};
} // namespace facebook::react
@@ -129,20 +129,16 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) {
"mark2", timeOrigin + HighResDuration::fromMilliseconds(2));
reporter->reportMeasure(
"measure0",
timeOrigin,
timeOrigin + HighResDuration::fromMilliseconds(2));
"measure0", timeOrigin, HighResDuration::fromMilliseconds(2));
reporter->reportMeasure(
"measure1",
timeOrigin,
timeOrigin + HighResDuration::fromMilliseconds(3));
"measure1", timeOrigin, HighResDuration::fromMilliseconds(3));
reporter->reportMark(
"mark3", timeOrigin + HighResDuration::fromNanoseconds(2.5 * 1e6));
reporter->reportMeasure(
"measure2",
timeOrigin + HighResDuration::fromMilliseconds(2),
timeOrigin + HighResDuration::fromMilliseconds(2));
HighResDuration::fromMilliseconds(2));
reporter->reportMark(
"mark4", timeOrigin + HighResDuration::fromMilliseconds(3));
@@ -172,7 +168,7 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestReportMeasures) {
PerformanceMeasure{
{.name = "measure2",
.startTime = timeOrigin + HighResDuration::fromMilliseconds(2),
.duration = HighResDuration::zero()}},
.duration = HighResDuration::fromMilliseconds(2)}},
PerformanceMark{
{.name = "mark3",
.startTime =
@@ -203,21 +199,17 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) {
"mark2", timeOrigin + HighResDuration::fromMilliseconds(2));
reporter->reportMeasure(
"common_name",
timeOrigin,
timeOrigin + HighResDuration::fromMilliseconds(2));
"common_name", timeOrigin, HighResDuration::fromMilliseconds(2));
reporter->reportMeasure(
"measure1",
timeOrigin,
timeOrigin + HighResDuration::fromMilliseconds(3));
"measure1", timeOrigin, HighResDuration::fromMilliseconds(3));
reporter->reportMeasure(
"measure2",
timeOrigin + HighResDuration::fromMilliseconds(1),
timeOrigin + HighResDuration::fromMilliseconds(6));
HighResDuration::fromMilliseconds(6));
reporter->reportMeasure(
"measure3",
timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6),
timeOrigin + HighResDuration::fromMilliseconds(2));
HighResDuration::fromMilliseconds(2));
{
const auto allEntries = toSorted(reporter->getEntries());
@@ -241,12 +233,12 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) {
PerformanceMeasure{
{.name = "measure2",
.startTime = timeOrigin + HighResDuration::fromMilliseconds(1),
.duration = HighResDuration::fromMilliseconds(5)}},
.duration = HighResDuration::fromMilliseconds(6)}},
PerformanceMeasure{
{.name = "measure3",
.startTime =
timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6),
.duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}},
.duration = HighResDuration::fromMilliseconds(2)}},
PerformanceMark{
{.name = "mark2",
.startTime = timeOrigin + HighResDuration::fromMilliseconds(2),
@@ -288,12 +280,12 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestGetEntries) {
PerformanceMeasure{
{.name = "measure2",
.startTime = timeOrigin + HighResDuration::fromMilliseconds(1),
.duration = HighResDuration::fromMilliseconds(5)}},
.duration = HighResDuration::fromMilliseconds(6)}},
PerformanceMeasure{
{.name = "measure3",
.startTime =
timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6),
.duration = HighResDuration::fromNanoseconds(0.5 * 1e6)}}};
.duration = HighResDuration::fromMilliseconds(2)}}};
ASSERT_EQ(expected, measures);
}
@@ -330,21 +322,17 @@ TEST(PerformanceEntryReporter, PerformanceEntryReporterTestClearMarks) {
"mark2", timeOrigin + HighResDuration::fromMilliseconds(2));
reporter->reportMeasure(
"common_name",
timeOrigin,
timeOrigin + HighResDuration::fromMilliseconds(2));
"common_name", timeOrigin, HighResDuration::fromMilliseconds(2));
reporter->reportMeasure(
"measure1",
timeOrigin,
timeOrigin + HighResDuration::fromMilliseconds(3));
"measure1", timeOrigin, HighResDuration::fromMilliseconds(3));
reporter->reportMeasure(
"measure2",
timeOrigin + HighResDuration::fromMilliseconds(1),
timeOrigin + HighResDuration::fromMilliseconds(6));
HighResDuration::fromMilliseconds(6));
reporter->reportMeasure(
"measure3",
timeOrigin + HighResDuration::fromNanoseconds(1.5 * 1e6),
timeOrigin + HighResDuration::fromMilliseconds(2));
HighResDuration::fromMilliseconds(2));
reporter->clearEntries(PerformanceEntryType::MARK, "common_name");
@@ -190,7 +190,7 @@ TEST(PerformanceObserver, PerformanceObserverTestObserveTakeRecords) {
reporter->reportMeasure(
"off",
timeOrigin + HighResDuration::fromMilliseconds(10),
timeOrigin + HighResDuration::fromMilliseconds(20));
HighResDuration::fromMilliseconds(20));
reporter->reportMark(
"test2", timeOrigin + HighResDuration::fromMilliseconds(20));
reporter->reportMark(
@@ -371,9 +371,7 @@ TEST(PerformanceObserver, PerformanceObserverTestMultiple) {
{.durationThreshold = HighResDuration::fromMilliseconds(80)});
reporter->reportMeasure(
"measure",
timeOrigin,
timeOrigin + HighResDuration::fromMilliseconds(50));
"measure", timeOrigin, HighResDuration::fromMilliseconds(50));
reporter->reportEvent(
"event1",
timeOrigin,

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