Files
react-native/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js
T
Rubén Norte 332355e80f Implement UI consistency mechanism for JS thread (#43581)
Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/43581

Changelog: [internal]

This implements a mechanism to ensure that JavaScript tasks have a consistent view of the state of the UI during their execution.

## Context

Fabric allows committing new revisions of the ShadowTree from any thread, but we don't make use of this capability and instead always commit them from the JS thread (e.g.: when we schedule Fabric state updates to update the offset of a list on scroll). This was done to make sure that JS work didn't see changes in the state of the tree at random points during its execution. E.g.:

```
useEffect(() => {
  const rect = ref.current.getBoundingClientRect();
  // do something
  const newRect = ref.current.getBoundingClientRect();
  // `rect` and `newRect` should always be the same
}, []);
```

This isn't used by Reanimated at the moment, which means JS can inadvertently see the result of animations in non-specific times during execution.

You can find additional context about this in the [RFC for DOM Traversal & Layout APIs in RN](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0607-dom-traversal-and-layout-apis.md#consistency-and-updates).

This works correctly at the moment, but we introduce a limitation in the execution model to prevent updating the tree synchronously from the main thread. One of the main problems this introduces is that computing intersections (for `IntersectionObserver`) relies on the information in the shadow tree, but this is updated asynchronously on scroll.

There are 2 potential solutions for that problem:
1) Send the timestamp of the scroll even with the state update to backdate the timestamps of the intersections. This could work but introduces more complexity and possibly accuracy problems due to batching those state updates with other changes (e.g.: what happens if we update the state and commit another tree in the same task? should we use the backdated timestamp or wait for mount?).
2) (**Preferred**/ this diff) Allow committing new revisions from any thread, but lock the JS thread into seeing a specific revision, which would only update/progress in specific moments when it's safe. Some of those moments would be:
    1) When we start a new JS task.
    2) When we commit a new tree from React (JS).

## Changes

This implements the solution outlined in 2), creating a few abstractions to handle what's the current tree that should be visible to JS and to lock/unlock it in specific moments.

More specifically:
* Creates `ShadowTreeRevisionProvider` as an abstract class for APIs consuming the visible revision of the ShadowTree (mainly DOM APIs and layout methods like `measure`, etc.).
* Creates `ShadowTreeRevisionConsistencyManager` as an abstract class to handle what trees are visible (with a `lockRevision` and `unlockRevision` to be called from `RuntimeScheduler` at the beginning and end of each JS task).
* Creates 2 different implementations of these abstractions:
  * One that preserves the current behavior (`LatestShadowTreeRevisionProvider`, which just returns the last committed revision at the time of the call).
  * One that locks revisions lazily (the first time they're accessed) (`LazyShadowTreeRevisionConsistencyManager`).

Reviewed By: sammy-SC

Differential Revision: D55024832

fbshipit-source-id: b59985bc83714ae7ec915baba72bf92b3d6fa140
2024-03-25 09:50:21 -07:00

133 lines
4.5 KiB
JavaScript

/**
* 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
*/
/* eslint sort-keys: 'error' */
import type {FeatureFlagDefinitions} from './types';
// These flags are only used in tests for the feature flags system
const testDefinitions: FeatureFlagDefinitions = {
common: {
commonTestFlag: {
defaultValue: false,
description: 'Common flag for testing. Do NOT modify.',
},
},
jsOnly: {
jsOnlyTestFlag: {
defaultValue: false,
description: 'JS-only flag for testing. Do NOT modify.',
},
},
};
const definitions: FeatureFlagDefinitions = {
common: {
...testDefinitions.common,
batchRenderingUpdatesInEventLoop: {
defaultValue: false,
description:
'When enabled, the RuntimeScheduler processing the event loop will batch all rendering updates and dispatch them together at the end of each iteration of the loop.',
},
enableBackgroundExecutor: {
defaultValue: false,
description:
'Enables the use of a background executor to compute layout and commit updates on Fabric (this system is deprecated and should not be used).',
},
enableCustomDrawOrderFabric: {
defaultValue: false,
description:
'When enabled, Fabric will use customDrawOrder in ReactViewGroup (similar to old architecture).',
},
enableFixForClippedSubviewsCrash: {
defaultValue: false,
description:
'Attempt at fixing a crash related to subview clipping on Android. This is a kill switch for the fix',
},
enableMicrotasks: {
defaultValue: false,
description:
'Enables the use of microtasks in Hermes (scheduling) and RuntimeScheduler (execution).',
},
enableMountHooksAndroid: {
defaultValue: false,
description:
'Enables the notification of mount operations to mount hooks on Android.',
},
enableSpannableBuildingUnification: {
defaultValue: false,
description:
'Uses new, deduplicated logic for constructing Android Spannables from text fragments',
},
enableUIConsistency: {
defaultValue: false,
description:
'Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution).',
},
inspectorEnableCxxInspectorPackagerConnection: {
defaultValue: false,
description:
'Flag determining if the C++ implementation of InspectorPackagerConnection should be used instead of the per-platform one. This flag is global and should not be changed across React Host lifetimes.',
},
inspectorEnableModernCDPRegistry: {
defaultValue: false,
description:
'Flag determining if the modern CDP backend should be enabled. This flag is global and should not be changed across React Host lifetimes.',
},
useModernRuntimeScheduler: {
defaultValue: false,
description:
'When enabled, it uses the modern fork of RuntimeScheduler that allows scheduling tasks with priorities from any thread.',
},
},
jsOnly: {
...testDefinitions.jsOnly,
animatedShouldDebounceQueueFlush: {
defaultValue: false,
description:
'Enables an experimental flush-queue debouncing in Animated.js.',
},
animatedShouldUseSingleOp: {
defaultValue: false,
description:
'Enables an experimental mega-operation for Animated.js that replaces many calls to native with a single call into native, to reduce JSI/JNI traffic.',
},
enableAccessToHostTreeInFabric: {
defaultValue: false,
description:
'Enables access to the host tree in Fabric using DOM-compatible APIs.',
},
isLayoutAnimationEnabled: {
defaultValue: true,
description:
'Function used to enable / disabled Layout Animations in React Native.',
},
shouldUseAnimatedObjectForTransform: {
defaultValue: false,
description:
'Enables use of AnimatedObject for animating transform values.',
},
shouldUseRemoveClippedSubviewsAsDefaultOnIOS: {
defaultValue: false,
description:
'removeClippedSubviews prop will be used as the default in FlatList on iOS to match Android',
},
shouldUseSetNativePropsInFabric: {
defaultValue: true,
description: 'Enables use of setNativeProps in JS driven animations.',
},
},
};
export default definitions;