diff --git a/.circleci/config.yml b/.circleci/config.yml index e96bd1e804..a906e8c23d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -558,6 +558,32 @@ jobs: - store_test_results: path: ./reports/junit + # ------------------------- + # JOBS: Test Android Template + # ------------------------- + test_android_template: + executor: reactnativeandroid + steps: + - checkout + - run_yarn + + - run: + name: Setup the Android Template + command: | + cd template + sed -i 's/1000\.0\.0/file\:\.\./g' package.json + npm install + # react-native-community/cli is needed as the Android template is referencing a .gradle file inside it. + npm i @react-native-community/cli + + - run: + name: Bundle the latest version of ReactAndroid + command: ./gradlew :ReactAndroid:publishReleasePublicationToNpmRepository + + - run: + name: Build the template application + command: cd template/android/ && ./gradlew assembleDebug + # ------------------------- # JOBS: Test Docker # ------------------------- @@ -715,6 +741,19 @@ jobs: git config --global user.name "npm Deployment Script" echo "machine github.com login react-native-bot password $GITHUB_TOKEN" > ~/.netrc - run: node ./scripts/publish-npm.js << parameters.publish_npm_args >> + - when: + condition: + equal: [ --dry-run, << parameters.publish_npm_args >> ] + steps: + - run: + name: Build release package as a job artifact + command: | + mkdir -p build + FILENAME=$(npm pack) + mv $FILENAME build/ + - store_artifacts: + path: ~/react-native/build/ + destination: build # ------------------------- # JOBS: Nightly @@ -746,6 +785,11 @@ workflows: branches: # TODO(macOS GH#774): disable this test which is redundant to Azure Devops test and it requires a CCI plan with resource-class:large. ignore: /.*/ + - test_android_template: + filters: + branches: + # TODO(macOS GH#774): disable this test which is redundant to Azure Devops test and it requires a CCI plan with resource-class:large. + ignore: /.*/ - test_ios: name: test_ios_unit_jsc run_unit_tests: true @@ -811,6 +855,16 @@ workflows: # Only act on version tags. tags: only: /v[0-9]+(\.[0-9]+)*(\-rc(\.[0-9]+)?)?/ + - publish_npm_package: + # Build a release package on every untagged commit, but do not publish to npm. + name: build_commit_package + publish_npm_args: --dry-run + filters: + branches: + only: + - main + tags: + ignore: /v[0-9]+(\.[0-9]+)*(\-rc(\.[0-9]+)?)?/ analysis: jobs: diff --git a/.flowconfig b/.flowconfig index c4759ad565..dc6f85d26b 100644 --- a/.flowconfig +++ b/.flowconfig @@ -79,4 +79,4 @@ untyped-import untyped-type-import [version] -^0.159.0 +^0.160.2 diff --git a/.flowconfig.android b/.flowconfig.android index 2d334fcbaa..62e88f8fc8 100644 --- a/.flowconfig.android +++ b/.flowconfig.android @@ -82,4 +82,4 @@ untyped-import untyped-type-import [version] -^0.159.0 +^0.160.2 diff --git a/BUCK b/BUCK index 9434d821f8..0b338cccef 100644 --- a/BUCK +++ b/BUCK @@ -1,6 +1,7 @@ load("//tools/build_defs:fb_native_wrapper.bzl", "fb_native") +load("//tools/build_defs/apple:config_utils_defs.bzl", "STATIC_LIBRARY_APPLETVOS_CONFIG", "fbobjc_configs") load("//tools/build_defs/apple:fb_apple_test.bzl", "fb_apple_test") -load("//tools/build_defs/apple:flag_defs.bzl", "get_objc_arc_preprocessor_flags", "get_preprocessor_flags_for_build_mode", "get_static_library_ios_flags") +load("//tools/build_defs/apple:flag_defs.bzl", "get_base_appletvos_flags", "get_objc_arc_preprocessor_flags", "get_preprocessor_flags_for_build_mode", "get_static_library_ios_flags") load("//tools/build_defs/apple/plugins:plugin_defs.bzl", "plugin") load("//tools/build_defs/oss:metro_defs.bzl", "rn_library") load( @@ -183,6 +184,8 @@ rn_xplat_cxx_library2( prefix = "React", ), apple_sdks = (IOS, APPLETVOS), + appletvos_configs = fbobjc_configs(STATIC_LIBRARY_APPLETVOS_CONFIG), + appletvos_inherited_buck_flags = get_base_appletvos_flags(), contacts = ["oncall+react_native@xmail.facebook.com"], fbobjc_enable_exceptions = True, frameworks = [ diff --git a/Libraries/ActionSheetIOS/ActionSheetIOS.js b/Libraries/ActionSheetIOS/ActionSheetIOS.js index 786839498f..889832df1b 100644 --- a/Libraries/ActionSheetIOS/ActionSheetIOS.js +++ b/Libraries/ActionSheetIOS/ActionSheetIOS.js @@ -47,6 +47,7 @@ const ActionSheetIOS = { +cancelButtonIndex?: ?number, +anchor?: ?number, +tintColor?: ColorValue | ProcessedColorValue, + +cancelButtonTintColor?: ColorValue | ProcessedColorValue, +userInterfaceStyle?: string, +disabledButtonIndices?: Array, |}, @@ -59,7 +60,12 @@ const ActionSheetIOS = { invariant(typeof callback === 'function', 'Must provide a valid callback'); invariant(RCTActionSheetManager, "ActionSheetManager doesn't exist"); - const {tintColor, destructiveButtonIndex, ...remainingOptions} = options; + const { + tintColor, + cancelButtonTintColor, + destructiveButtonIndex, + ...remainingOptions + } = options; let destructiveButtonIndices = null; if (Array.isArray(destructiveButtonIndex)) { @@ -69,14 +75,21 @@ const ActionSheetIOS = { } const processedTintColor = processColor(tintColor); + const processedCancelButtonTintColor = processColor(cancelButtonTintColor); invariant( processedTintColor == null || typeof processedTintColor === 'number', 'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions tintColor', ); + invariant( + processedCancelButtonTintColor == null || + typeof processedCancelButtonTintColor === 'number', + 'Unexpected color given for ActionSheetIOS.showActionSheetWithOptions cancelButtonTintColor', + ); RCTActionSheetManager.showActionSheetWithOptions( { ...remainingOptions, tintColor: processedTintColor, + cancelButtonTintColor: processedCancelButtonTintColor, destructiveButtonIndices, }, callback, diff --git a/Libraries/ActionSheetIOS/NativeActionSheetManager.js b/Libraries/ActionSheetIOS/NativeActionSheetManager.js index 7c48f8e2d4..56429c658a 100644 --- a/Libraries/ActionSheetIOS/NativeActionSheetManager.js +++ b/Libraries/ActionSheetIOS/NativeActionSheetManager.js @@ -22,6 +22,7 @@ export interface Spec extends TurboModule { +cancelButtonIndex?: ?number, +anchor?: ?number, +tintColor?: ?number, + +cancelButtonTintColor?: ?number, +userInterfaceStyle?: ?string, +disabledButtonIndices?: Array, |}, @@ -34,6 +35,7 @@ export interface Spec extends TurboModule { +subject?: ?string, +anchor?: ?number, +tintColor?: ?number, + +cancelButtonTintColor?: ?number, +excludedActivityTypes?: ?Array, +userInterfaceStyle?: ?string, |}, diff --git a/Libraries/Image/ImageBackground.js b/Libraries/Image/ImageBackground.js index 84bb98551f..5d0de1ca1a 100644 --- a/Libraries/Image/ImageBackground.js +++ b/Libraries/Image/ImageBackground.js @@ -10,10 +10,12 @@ 'use strict'; -const Image = require('./Image'); -const React = require('react'); -const StyleSheet = require('../StyleSheet/StyleSheet'); -const View = require('../Components/View/View'); +import Image from './Image'; +import * as React from 'react'; +import StyleSheet from '../StyleSheet/StyleSheet'; +import flattenStyle from '../StyleSheet/flattenStyle'; +import View from '../Components/View/View'; +import type {ImageBackgroundProps} from './ImageProps'; /** * Very simple drop-in replacement for which supports nesting views. @@ -39,7 +41,7 @@ const View = require('../Components/View/View'); * AppRegistry.registerComponent('DisplayAnImageBackground', () => DisplayAnImageBackground); * ``` */ -class ImageBackground extends React.Component<$FlowFixMeProps> { +class ImageBackground extends React.Component { setNativeProps(props: Object) { // Work-around flow const viewRef = this._viewRef; @@ -56,7 +58,7 @@ class ImageBackground extends React.Component<$FlowFixMeProps> { render(): React.Node { const {children, style, imageStyle, imageRef, ...props} = this.props; - + const flattenedStyle = flattenStyle(style); return ( { // So, we have to proxy/reapply these styles explicitly for actual component. // This workaround should be removed after implementing proper support of // intrinsic content size of the . - width: style?.width, - height: style?.height, + width: flattenedStyle?.width, + height: flattenedStyle?.height, }, imageStyle, ]} diff --git a/Libraries/Image/ImageProps.js b/Libraries/Image/ImageProps.js index 6e3c138cee..25d1bc0cb3 100644 --- a/Libraries/Image/ImageProps.js +++ b/Libraries/Image/ImageProps.js @@ -15,6 +15,8 @@ import type {EdgeInsetsProp} from '../StyleSheet/EdgeInsetsPropType'; import type {ImageSource} from './ImageSource'; import type {ViewStyleProp, ImageStyleProp} from '../StyleSheet/StyleSheet'; import type {ViewProps} from '../Components/View/ViewPropTypes'; +import type {Node, Ref} from 'react'; +import typeof Image from './Image'; export type ImageLoadEvent = SyntheticEvent< $ReadOnly<{| @@ -176,3 +178,29 @@ export type ImageProps = {| */ tooltip?: ?string, |}; + +export type ImageBackgroundProps = $ReadOnly<{| + ...ImageProps, + children?: Node, + + /** + * Style applied to the outer View component + * + * See https://reactnative.dev/docs/imagebackground#style + */ + style?: ?ViewStyleProp, + + /** + * Style applied to the inner Image component + * + * See https://reactnative.dev/docs/imagebackground#imagestyle + */ + imageStyle?: ?ImageStyleProp, + + /** + * Allows to set a reference to the inner Image component + * + * See https://reactnative.dev/docs/imagebackground#imageref + */ + imageRef?: Ref, +|}>; diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm b/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm index 5696ec7787..d7eddaea65 100644 --- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm +++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.mm @@ -142,7 +142,6 @@ static NSDictionary *RCTFormatLocalNotification(UILocalNotification *notificatio return formattedLocalNotification; } -API_AVAILABLE(ios(10.0)) static NSDictionary *RCTFormatUNNotification(UNNotification *notification) { NSMutableDictionary *formattedNotification = [NSMutableDictionary dictionary]; diff --git a/Libraries/Renderer/REVISION b/Libraries/Renderer/REVISION index c8c6628f9f..188feb01ce 100644 --- a/Libraries/Renderer/REVISION +++ b/Libraries/Renderer/REVISION @@ -1 +1 @@ -95d762e406bd37c07693e3a5ddbd0f75289e8c3f \ No newline at end of file +e8feb11b62e869804970258fa629922edbfc836b \ No newline at end of file diff --git a/Libraries/Renderer/implementations/ReactFabric-dev.fb.js b/Libraries/Renderer/implementations/ReactFabric-dev.fb.js index 94d93122c7..7de2d2c90a 100644 --- a/Libraries/Renderer/implementations/ReactFabric-dev.fb.js +++ b/Libraries/Renderer/implementations/ReactFabric-dev.fb.js @@ -7,7 +7,7 @@ * @noflow * @nolint * @preventMunge - * @generated SignedSource<<1a3cb2d94c0721509af63438d7639b77>> + * @generated SignedSource<<65d072c3d2937f89a7fadc55450af2f7>> */ 'use strict'; @@ -2853,6 +2853,7 @@ var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableLazyElements = false; var warnAboutStringRefs = false; +var warnOnSubscriptionInsideStartTransition = false; var enableNewReconciler = false; var enableLazyContextPropagation = false; @@ -2903,21 +2904,25 @@ var HydratingAndUpdate = var Visibility = /* */ 4096; -var LifecycleEffectMask = Passive | Update | Callback | Ref | Snapshot; // Union of all commit flags (flags with the lifetime of a particular commit) +var StoreConsistency = + /* */ + 8192; +var LifecycleEffectMask = + Passive | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit) var HostEffectMask = /* */ - 8191; // These are not really side effects, but we still reuse this field. + 16383; // These are not really side effects, but we still reuse this field. var Incomplete = /* */ - 8192; + 16384; var ShouldCapture = /* */ - 16384; + 32768; var ForceUpdateForLegacySuspense = /* */ - 32768; + 65536; // e.g. a fiber uses a passive effect (even if there are no updates on this particular render). // This enables us to defer more work in the unmount case, // since we can defer traversing the tree during layout to look for Passive effects, @@ -2925,22 +2930,22 @@ var ForceUpdateForLegacySuspense = var RefStatic = /* */ - 262144; + 524288; var LayoutStatic = /* */ - 524288; + 1048576; var PassiveStatic = /* */ - 1048576; // These flags allow us to traverse to fibers that have effects on mount + 2097152; // These flags allow us to traverse to fibers that have effects on mount // without traversing the entire tree after every commit for // double invoking var MountLayoutDev = /* */ - 2097152; + 4194304; var MountPassiveDev = /* */ - 4194304; // Groups of flags that are used in the commit phase to skip over trees that + 8388608; // Groups of flags that are used in the commit phase to skip over trees that // don't contain effects, by checking subtreeFlags. var BeforeMutationMask = // TODO: Remove Update flag from before mutation phase by re-landing Visibility @@ -4499,16 +4504,10 @@ function includesOnlyRetries(lanes) { function includesOnlyTransitions(lanes) { return (lanes & TransitionLanes) === lanes; } -function shouldTimeSlice(root, lanes) { - if ((lanes & root.expiredLanes) !== NoLanes) { - // At least one of these lanes expired. To prevent additional starvation, - // finish rendering without yielding execution. - return false; - } - +function includesBlockingLane(root, lanes) { if ((root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode) { // Concurrent updates by default always use time slicing. - return true; + return false; } var SyncDefaultLanes = @@ -4516,7 +4515,12 @@ function shouldTimeSlice(root, lanes) { InputContinuousLane | DefaultHydrationLane | DefaultLane; - return (lanes & SyncDefaultLanes) === NoLanes; + return (lanes & SyncDefaultLanes) !== NoLanes; +} +function includesExpiredLane(root, lanes) { + // This is a separate check from includesBlockingLane because a lane can + // expire after a render has already started. + return (lanes & root.expiredLanes) !== NoLanes; } function isTransitionLane(lane) { return (lane & TransitionLanes) !== 0; @@ -5835,6 +5839,18 @@ function findCurrentUnmaskedContext(fiber) { var LegacyRoot = 0; var ConcurrentRoot = 1; +/** + * inlined Object.is polyfill to avoid requiring consumers ship their own + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is + */ +function is(x, y) { + return ( + (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare + ); +} + +var objectIs = typeof Object.is === "function" ? Object.is : is; + var syncQueue = null; var includesLegacySyncCallbacks = false; var isFlushingSyncQueue = false; @@ -5904,7 +5920,7 @@ function flushSyncCallbacks() { return null; } -var ReactVersion = "18.0.0-95d762e40-20210908"; +var ReactVersion = "18.0.0-e8feb11b6-20210915"; var ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig; var NoTransition = 0; @@ -5912,18 +5928,6 @@ function requestCurrentTransition() { return ReactCurrentBatchConfig.transition; } -/** - * inlined Object.is polyfill to avoid requiring consumers ship their own - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is - */ -function is(x, y) { - return ( - (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare - ); -} - -var objectIs = typeof Object.is === "function" ? Object.is : is; - /** * Performs equality by iterating through keys on an object and returning false * when any key has values which are not strictly equal between the arguments. @@ -9858,19 +9862,22 @@ function findFirstSuspended(row) { } var NoFlags$1 = - /* */ + /* */ 0; // Represents whether effect should fire. var HasEffect = /* */ 1; // Represents the phase in which the effect (not the clean-up) fires. +var Insertion = + /* */ + 2; var Layout = /* */ - 2; + 4; var Passive$1 = /* */ - 4; + 8; var isHydrating = false; @@ -10433,7 +10440,8 @@ function updateWorkInProgressHook() { function createFunctionComponentUpdateQueue() { return { - lastEffect: null + lastEffect: null, + stores: null }; } @@ -10962,6 +10970,7 @@ function updateMutableSource(source, getSnapshot, subscribe) { } function mountSyncExternalStore(subscribe, getSnapshot) { + var fiber = currentlyRenderingFiber$1; var hook = mountWorkInProgressHook(); // Read the current snapshot from the store on every render. This breaks the // normal rules of React, and only works because store updates are // always synchronous. @@ -10985,11 +10994,43 @@ function mountSyncExternalStore(subscribe, getSnapshot) { value: nextSnapshot, getSnapshot: getSnapshot }; - hook.queue = inst; - return useSyncExternalStore(hook, inst, subscribe, getSnapshot, nextSnapshot); + hook.queue = inst; // Schedule an effect to subscribe to the store. + + mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); // Schedule an effect to update the mutable instance fields. We will update + // this whenever subscribe, getSnapshot, or value changes. Because there's no + // clean-up function, and we track the deps correctly, we can call pushEffect + // directly, without storing any additional state. For the same reason, we + // don't need to set a static flag, either. + // TODO: We can move this to the passive phase once we add a pre-commit + // consistency check. See the next comment. + + fiber.flags |= Passive; + pushEffect( + HasEffect | Passive$1, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + undefined, + null + ); // Unless we're rendering a blocking lane, schedule a consistency check. Right + // before committing, we will walk the tree and check if any of the stores + // were mutated. + + var root = getWorkInProgressRoot(); + + if (!(root !== null)) { + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + } + + if (!includesBlockingLane(root, renderLanes)) { + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + + return nextSnapshot; } function updateSyncExternalStore(subscribe, getSnapshot) { + var fiber = currentlyRenderingFiber$1; var hook = updateWorkInProgressHook(); // Read the current snapshot from the store on every render. This breaks the // normal rules of React, and only works because store updates are // always synchronous. @@ -11009,69 +11050,102 @@ function updateSyncExternalStore(subscribe, getSnapshot) { } var prevSnapshot = hook.memoizedState; + var snapshotChanged = !objectIs(prevSnapshot, nextSnapshot); - if (!objectIs(prevSnapshot, nextSnapshot)) { + if (snapshotChanged) { hook.memoizedState = nextSnapshot; markWorkInProgressReceivedUpdate(); } var inst = hook.queue; - return useSyncExternalStore(hook, inst, subscribe, getSnapshot, nextSnapshot); + updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [ + subscribe + ]); // Whenever getSnapshot or subscribe changes, we need to check in the + // commit phase if there was an interleaved mutation. In concurrent mode + // this can happen all the time, but even in synchronous mode, an earlier + // effect may have mutated the store. + + if ( + inst.getSnapshot !== getSnapshot || + snapshotChanged || // Check if the susbcribe function changed. We can save some memory by + // checking whether we scheduled a subscription effect above. + (workInProgressHook !== null && + workInProgressHook.memoizedState.tag & HasEffect) + ) { + fiber.flags |= Passive; + pushEffect( + HasEffect | Passive$1, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + undefined, + null + ); // Unless we're rendering a blocking lane, schedule a consistency check. + // Right before committing, we will walk the tree and check if any of the + // stores were mutated. + + var root = getWorkInProgressRoot(); + + if (!(root !== null)) { + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + } + + if (!includesBlockingLane(root, renderLanes)) { + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + } + + return nextSnapshot; } -function useSyncExternalStore( - hook, - inst, - subscribe, - getSnapshot, - nextSnapshot -) { - var fiber = currentlyRenderingFiber$1; - var dispatcher = ReactCurrentDispatcher$1.current; // Track the latest getSnapshot function with a ref. This needs to be updated - // in the layout phase so we can access it during the tearing check that - // happens on subscribe. - // TODO: Circumvent SSR warning +function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) { + fiber.flags |= StoreConsistency; + var check = { + getSnapshot: getSnapshot, + value: renderedSnapshot + }; + var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue; - dispatcher.useLayoutEffect( - function() { - inst.value = nextSnapshot; - inst.getSnapshot = getSnapshot; // Whenever getSnapshot or subscribe changes, we need to check in the - // commit phase if there was an interleaved mutation. In concurrent mode - // this can happen all the time, but even in synchronous mode, an earlier - // effect may have mutated the store. - // TODO: Move the tearing checks to an earlier, pre-commit phase so that the - // layout effects always observe a consistent tree. + if (componentUpdateQueue === null) { + componentUpdateQueue = createFunctionComponentUpdateQueue(); + currentlyRenderingFiber$1.updateQueue = componentUpdateQueue; + componentUpdateQueue.stores = [check]; + } else { + var stores = componentUpdateQueue.stores; - if (checkIfSnapshotChanged(inst)) { - // Force a re-render. - forceStoreRerender(fiber); - } - }, - [subscribe, nextSnapshot, getSnapshot] - ); - dispatcher.useEffect( - function() { - var handleStoreChange = function() { - // TODO: Because there is no cross-renderer API for batching updates, it's - // up to the consumer of this library to wrap their subscription event - // with unstable_batchedUpdates. Should we try to detect when this isn't - // the case and print a warning in development? - // The store changed. Check if the snapshot changed since the last time we - // read from the store. - if (checkIfSnapshotChanged(inst)) { - // Force a re-render. - forceStoreRerender(fiber); - } - }; // Check for changes right before subscribing. Subsequent changes will be - // detected in the subscription handler. + if (stores === null) { + componentUpdateQueue.stores = [check]; + } else { + stores.push(check); + } + } +} - handleStoreChange(); // Subscribe to the store and return a clean-up function. +function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) { + // These are updated in the passive phase + inst.value = nextSnapshot; + inst.getSnapshot = getSnapshot; // Something may have been mutated in between render and commit. This could + // have been in an event that fired before the passive effects, or it could + // have been in a layout effect. In that case, we would have used the old + // snapsho and getSnapshot values to bail out. We need to check one more time. - return subscribe(handleStoreChange); - }, - [subscribe] - ); - return nextSnapshot; + if (checkIfSnapshotChanged(inst)) { + // Force a re-render. + forceStoreRerender(fiber); + } +} + +function subscribeToStore(fiber, inst, subscribe) { + var handleStoreChange = function() { + // The store changed. Check if the snapshot changed since the last time we + // read from the store. + if (checkIfSnapshotChanged(inst)) { + // Force a re-render. + forceStoreRerender(fiber); + } + }; // Subscribe to the store and return a clean-up function. + + return subscribe(handleStoreChange); } function checkIfSnapshotChanged(inst) { @@ -11229,6 +11303,14 @@ function updateEffect(create, deps) { return updateEffectImpl(Passive, Passive$1, create, deps); } +function mountInsertionEffect(create, deps) { + return mountEffectImpl(Update, Insertion, create, deps); +} + +function updateInsertionEffect(create, deps) { + return updateEffectImpl(Update, Insertion, create, deps); +} + function mountLayoutEffect(create, deps) { var fiberFlags = Update; @@ -11464,6 +11546,26 @@ function startTransition(setPending, callback) { } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig$1.transition = prevTransition; + + { + if ( + prevTransition !== 1 && + warnOnSubscriptionInsideStartTransition && + ReactCurrentBatchConfig$1._updatedFibers + ) { + var updatedFibersCount = ReactCurrentBatchConfig$1._updatedFibers.size; + + if (updatedFibersCount > 10) { + warn( + "Detected a large number of updates inside startTransition. " + + "If this is due to a subscription please re-write it to use React provided hooks. " + + "Otherwise concurrent mode guarantees are off the table." + ); + } + + ReactCurrentBatchConfig$1._updatedFibers.clear(); + } + } } } @@ -11686,6 +11788,7 @@ var ContextOnlyDispatcher = { useContext: throwInvalidHookError, useEffect: throwInvalidHookError, useImperativeHandle: throwInvalidHookError, + useInsertionEffect: throwInvalidHookError, useLayoutEffect: throwInvalidHookError, useMemo: throwInvalidHookError, useReducer: throwInvalidHookError, @@ -11754,6 +11857,12 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; checkDepsAreArrayDev(deps); return mountImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + mountHookTypesDev(); + checkDepsAreArrayDev(deps); + return mountInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; mountHookTypesDev(); @@ -11859,6 +11968,11 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return mountImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + updateHookTypesDev(); + return mountInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; updateHookTypesDev(); @@ -11962,6 +12076,11 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return updateImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + updateHookTypesDev(); + return updateInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; updateHookTypesDev(); @@ -12065,6 +12184,11 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return updateImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + updateHookTypesDev(); + return updateInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; updateHookTypesDev(); @@ -12173,6 +12297,12 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; mountHookTypesDev(); return mountImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + warnInvalidHookAccess(); + mountHookTypesDev(); + return mountInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; warnInvalidHookAccess(); @@ -12292,6 +12422,12 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return updateImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; warnInvalidHookAccess(); @@ -12411,6 +12547,12 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return updateImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; warnInvalidHookAccess(); @@ -17387,6 +17529,8 @@ function safelyDetachRef(current, nearestMountedAncestor) { if (ref !== null) { if (typeof ref === "function") { + var retVal; + try { if ( enableProfilerTimer && @@ -17395,12 +17539,12 @@ function safelyDetachRef(current, nearestMountedAncestor) { ) { try { startLayoutEffectTimer(); - ref(null); + retVal = ref(null); } finally { recordLayoutEffectDuration(current); } } else { - ref(null); + retVal = ref(null); } } catch (error) { reportUncaughtErrorInDEV(error); @@ -17625,6 +17769,16 @@ function commitHookEffectListMount(tag, finishedWork) { var destroy = effect.destroy; if (destroy !== undefined && typeof destroy !== "function") { + var hookName = void 0; + + if ((effect.tag & Layout) !== NoFlags) { + hookName = "useLayoutEffect"; + } else if ((effect.tag & Insertion) !== NoFlags) { + hookName = "useInsertionEffect"; + } else { + hookName = "useEffect"; + } + var addendum = void 0; if (destroy === null) { @@ -17633,10 +17787,13 @@ function commitHookEffectListMount(tag, finishedWork) { "up, return undefined (or nothing)."; } else if (typeof destroy.then === "function") { addendum = - "\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. " + + "\n\nIt looks like you wrote " + + hookName + + "(async () => ...) or returned a Promise. " + "Instead, write the async function inside your effect " + "and call it immediately:\n\n" + - "useEffect(() => {\n" + + hookName + + "(() => {\n" + " async function fetchData() {\n" + " // You can await here\n" + " const response = await MyAPI.getData(someId);\n" + @@ -17650,8 +17807,9 @@ function commitHookEffectListMount(tag, finishedWork) { } error( - "An effect function must not return anything besides a function, " + + "%s must not return anything besides a function, " + "which is used for clean-up.%s", + hookName, addendum ); } @@ -18058,15 +18216,17 @@ function commitAttachRef(finishedWork) { } // Moved outside to ensure DCE works with this flag if (typeof ref === "function") { + var retVal; + if (finishedWork.mode & ProfileMode) { try { startLayoutEffectTimer(); - ref(instanceToUse); + retVal = ref(instanceToUse); } finally { recordLayoutEffectDuration(finishedWork); } } else { - ref(instanceToUse); + retVal = ref(instanceToUse); } } else { { @@ -18130,7 +18290,10 @@ function commitUnmount(finishedRoot, current, nearestMountedAncestor) { tag = _effect.tag; if (destroy !== undefined) { - if ((tag & Layout) !== NoFlags$1) { + if ( + (tag & Insertion) !== NoFlags$1 || + (tag & Layout) !== NoFlags$1 + ) { if (current.mode & ProfileMode) { startLayoutEffectTimer(); safelyCallDestroy(current, nearestMountedAncestor, destroy); @@ -18358,7 +18521,12 @@ function commitWork(current, finishedWork) { case ForwardRef: case MemoComponent: case SimpleMemoComponent: { - // Layout effects are destroyed during the mutation phase so that all + commitHookEffectListUnmount( + Insertion | HasEffect, + finishedWork, + finishedWork.return + ); + commitHookEffectListMount(Insertion | HasEffect, finishedWork); // Layout effects are destroyed during the mutation phase so that all // destroy functions for all fibers are called before any create functions. // This prevents sibling component effects from interfering with each other, // e.g. a destroy function in one component should never override a ref set @@ -18368,6 +18536,7 @@ function commitWork(current, finishedWork) { // layout hooks. (However, since we null out the `destroy` function // right before calling it, the behavior is already correct, so this // would mostly be for modeling purposes.) + if (finishedWork.mode & ProfileMode) { try { startLayoutEffectTimer(); @@ -19223,13 +19392,13 @@ function requestUpdateLane(fiber) { var isTransition = requestCurrentTransition() !== NoTransition; if (isTransition) { - // The algorithm for assigning an update to a lane should be stable for all // updates at the same priority within the same event. To do this, the // inputs to the algorithm must be the same. // // The trick we use is to cache the first of each of these inputs within an // event. Then reset the cached values once we can be sure the event is // over. Our heuristic for that is whenever we enter a concurrent work loop. + if (currentEventTransitionLane === NoLane) { // All transitions within the same event are assigned the same lane. currentEventTransitionLane = claimNextTransitionLane(); @@ -19559,38 +19728,26 @@ function performConcurrentWorkOnRoot(root, didTimeout) { // bug we're still investigating. Once the bug in Scheduler is fixed, // we can remove this, since we track expiration ourselves. - var exitStatus = - shouldTimeSlice(root, lanes) && !didTimeout - ? renderRootConcurrent(root, lanes) - : renderRootSync(root, lanes); + var shouldTimeSlice = + !includesBlockingLane(root, lanes) && + !includesExpiredLane(root, lanes) && + !didTimeout; + var exitStatus = shouldTimeSlice + ? renderRootConcurrent(root, lanes) + : renderRootSync(root, lanes); if (exitStatus !== RootIncomplete) { if (exitStatus === RootErrored) { - var prevExecutionContext = executionContext; - executionContext |= RetryAfterError; // If an error occurred during hydration, - // discard server response and fall back to client side render. - - if (root.hydrate) { - root.hydrate = false; - - { - errorHydratingContainer(root.containerInfo); - } - - clearContainer(root.containerInfo); - } // If something threw an error, try rendering one more time. We'll render - // synchronously to block concurrent data mutations, and we'll includes - // all pending updates are included. If it still fails after the second - // attempt, we'll give up and commit the resulting tree. - + // If something threw an error, try rendering one more time. We'll + // render synchronously to block concurrent data mutations, and we'll + // includes all pending updates are included. If it still fails after + // the second attempt, we'll give up and commit the resulting tree. var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root); if (errorRetryLanes !== NoLanes) { lanes = errorRetryLanes; - exitStatus = renderRootSync(root, errorRetryLanes); + exitStatus = recoverFromConcurrentError(root, errorRetryLanes); } - - executionContext = prevExecutionContext; } if (exitStatus === RootFatalErrored) { @@ -19599,10 +19756,43 @@ function performConcurrentWorkOnRoot(root, didTimeout) { markRootSuspended$1(root, lanes); ensureRootIsScheduled(root, now()); throw fatalError; + } // Check if this render may have yielded to a concurrent event, and if so, + // confirm that any newly rendered stores are consistent. + // TODO: It's possible that even a concurrent render may never have yielded + // to the main thread, if it was fast enough, or if it expired. We could + // skip the consistency check in that case, too. + + var renderWasConcurrent = !includesBlockingLane(root, lanes); + var finishedWork = root.current.alternate; + + if ( + renderWasConcurrent && + !isRenderConsistentWithExternalStores(finishedWork) + ) { + // A store was mutated in an interleaved event. Render again, + // synchronously, to block further mutations. + exitStatus = renderRootSync(root, lanes); // We need to check again if something threw + + if (exitStatus === RootErrored) { + var _errorRetryLanes = getLanesToRetrySynchronouslyOnError(root); + + if (_errorRetryLanes !== NoLanes) { + lanes = _errorRetryLanes; + exitStatus = recoverFromConcurrentError(root, _errorRetryLanes); // We assume the tree is now consistent because we didn't yield to any + // concurrent events. + } + } + + if (exitStatus === RootFatalErrored) { + var _fatalError = workInProgressRootFatalError; + prepareFreshStack(root, NoLanes); + markRootSuspended$1(root, lanes); + ensureRootIsScheduled(root, now()); + throw _fatalError; + } } // We now have a consistent tree. The next step is either to commit it, // or, if something suspended, wait to commit it after a timeout. - var finishedWork = root.current.alternate; root.finishedWork = finishedWork; root.finishedLanes = lanes; finishConcurrentRender(root, exitStatus, lanes); @@ -19619,6 +19809,26 @@ function performConcurrentWorkOnRoot(root, didTimeout) { return null; } +function recoverFromConcurrentError(root, errorRetryLanes) { + var prevExecutionContext = executionContext; + executionContext |= RetryAfterError; // If an error occurred during hydration, discard server response and fall + // back to client side render. + + if (root.hydrate) { + root.hydrate = false; + + { + errorHydratingContainer(root.containerInfo); + } + + clearContainer(root.containerInfo); + } + + var exitStatus = renderRootSync(root, errorRetryLanes); + executionContext = prevExecutionContext; + return exitStatus; +} + function finishConcurrentRender(root, exitStatus, lanes) { switch (exitStatus) { case RootIncomplete: @@ -19737,6 +19947,68 @@ function finishConcurrentRender(root, exitStatus, lanes) { } } +function isRenderConsistentWithExternalStores(finishedWork) { + // Search the rendered tree for external store reads, and check whether the + // stores were mutated in a concurrent event. Intentionally using a iterative + // loop instead of recursion so we can exit early. + var node = finishedWork; + + while (true) { + if (node.flags & StoreConsistency) { + var updateQueue = node.updateQueue; + + if (updateQueue !== null) { + var checks = updateQueue.stores; + + if (checks !== null) { + for (var i = 0; i < checks.length; i++) { + var check = checks[i]; + var getSnapshot = check.getSnapshot; + var renderedValue = check.value; + + try { + if (!objectIs(getSnapshot(), renderedValue)) { + // Found an inconsistent store. + return false; + } + } catch (error) { + // If `getSnapshot` throws, return `false`. This will schedule + // a re-render, and the error will be rethrown during render. + return false; + } + } + } + } + } + + var child = node.child; + + if (node.subtreeFlags & StoreConsistency && child !== null) { + child.return = node; + node = child; + continue; + } + + if (node === finishedWork) { + return true; + } + + while (node.sibling === null) { + if (node.return === null || node.return === finishedWork) { + return true; + } + + node = node.return; + } + + node.sibling.return = node.return; + node = node.sibling; + } // Flow doesn't know this is unreachable, but eslint does + // eslint-disable-next-line no-unreachable + + return true; +} + function markRootSuspended$1(root, suspendedLanes) { // When suspending, we should always exclude lanes that were pinged or (more // rarely, since we try to avoid it) updated during the render phase. @@ -20481,6 +20753,15 @@ function commitRootImpl(root, renderPriorityLevel) { } // Read this again, since an effect might have updated it remainingLanes = root.pendingLanes; // Check if there's remaining work on this root + // TODO: This is part of the `componentDidCatch` implementation. Its purpose + // is to detect whether something might have called setState inside + // `componentDidCatch`. The mechanism is known to be flawed because `setState` + // inside `componentDidCatch` is itself flawed — that's why we recommend + // `getDerivedStateFromError` instead. However, it could be improved by + // checking if remainingLanes includes Sync work, instead of whether there's + // any work remaining at all (which would also include stuff like Suspense + // retries or transitions). It's been like this for a while, though, so fixing + // it probably isn't that urgent. if (remainingLanes === NoLanes) { // If there's no remaining work, we can clear the set of already failed @@ -20494,22 +20775,6 @@ function commitRootImpl(root, renderPriorityLevel) { } } - if (includesSomeLane(remainingLanes, SyncLane)) { - { - markNestedUpdateScheduled(); - } // Count the number of times the root synchronously re-renders without - // finishing. If there are too many, it indicates an infinite update loop. - - if (root === rootWithNestedUpdates) { - nestedUpdateCount++; - } else { - nestedUpdateCount = 0; - rootWithNestedUpdates = root; - } - } else { - nestedUpdateCount = 0; - } - onCommitRoot(finishedWork.stateNode, renderPriorityLevel); { @@ -20540,6 +20805,24 @@ function commitRootImpl(root, renderPriorityLevel) { root.tag !== LegacyRoot ) { flushPassiveEffects(); + } // Read this again, since a passive effect might have updated it + + remainingLanes = root.pendingLanes; + + if (includesSomeLane(remainingLanes, SyncLane)) { + { + markNestedUpdateScheduled(); + } // Count the number of times the root synchronously re-renders without + // finishing. If there are too many, it indicates an infinite update loop. + + if (root === rootWithNestedUpdates) { + nestedUpdateCount++; + } else { + nestedUpdateCount = 0; + rootWithNestedUpdates = root; + } + } else { + nestedUpdateCount = 0; } // If layout work was scheduled, flush it now. flushSyncCallbacks(); diff --git a/Libraries/Renderer/implementations/ReactFabric-prod.fb.js b/Libraries/Renderer/implementations/ReactFabric-prod.fb.js index a0cfa98229..6a8a0754f0 100644 --- a/Libraries/Renderer/implementations/ReactFabric-prod.fb.js +++ b/Libraries/Renderer/implementations/ReactFabric-prod.fb.js @@ -7,7 +7,7 @@ * @noflow * @nolint * @preventMunge - * @generated SignedSource<<6fd8e2fc263f451cb6e073eae96b19f3>> + * @generated SignedSource<<4538b6a9895755454ebc3d0f7b8f568e>> */ "use strict"; @@ -931,7 +931,7 @@ eventPluginOrder = Array.prototype.slice.call([ "ReactNativeBridgeEventPlugin" ]); recomputePluginOrdering(); -var injectedNamesToPlugins$jscomp$inline_221 = { +var injectedNamesToPlugins$jscomp$inline_222 = { ResponderEventPlugin: ResponderEventPlugin, ReactNativeBridgeEventPlugin: { eventTypes: {}, @@ -966,34 +966,34 @@ var injectedNamesToPlugins$jscomp$inline_221 = { } } }, - isOrderingDirty$jscomp$inline_222 = !1, - pluginName$jscomp$inline_223; -for (pluginName$jscomp$inline_223 in injectedNamesToPlugins$jscomp$inline_221) + isOrderingDirty$jscomp$inline_223 = !1, + pluginName$jscomp$inline_224; +for (pluginName$jscomp$inline_224 in injectedNamesToPlugins$jscomp$inline_222) if ( - injectedNamesToPlugins$jscomp$inline_221.hasOwnProperty( - pluginName$jscomp$inline_223 + injectedNamesToPlugins$jscomp$inline_222.hasOwnProperty( + pluginName$jscomp$inline_224 ) ) { - var pluginModule$jscomp$inline_224 = - injectedNamesToPlugins$jscomp$inline_221[pluginName$jscomp$inline_223]; + var pluginModule$jscomp$inline_225 = + injectedNamesToPlugins$jscomp$inline_222[pluginName$jscomp$inline_224]; if ( - !namesToPlugins.hasOwnProperty(pluginName$jscomp$inline_223) || - namesToPlugins[pluginName$jscomp$inline_223] !== - pluginModule$jscomp$inline_224 + !namesToPlugins.hasOwnProperty(pluginName$jscomp$inline_224) || + namesToPlugins[pluginName$jscomp$inline_224] !== + pluginModule$jscomp$inline_225 ) { - if (namesToPlugins[pluginName$jscomp$inline_223]) + if (namesToPlugins[pluginName$jscomp$inline_224]) throw Error( "EventPluginRegistry: Cannot inject two different event plugins using the same name, `" + - pluginName$jscomp$inline_223 + + pluginName$jscomp$inline_224 + "`." ); namesToPlugins[ - pluginName$jscomp$inline_223 - ] = pluginModule$jscomp$inline_224; - isOrderingDirty$jscomp$inline_222 = !0; + pluginName$jscomp$inline_224 + ] = pluginModule$jscomp$inline_225; + isOrderingDirty$jscomp$inline_223 = !0; } } -isOrderingDirty$jscomp$inline_222 && recomputePluginOrdering(); +isOrderingDirty$jscomp$inline_223 && recomputePluginOrdering(); function getInstanceFromInstance(instanceHandle) { return instanceHandle; } @@ -1763,6 +1763,9 @@ function getLanesToRetrySynchronouslyOnError(root) { root = root.pendingLanes & -1073741825; return 0 !== root ? root : root & 1073741824 ? 1073741824 : 0; } +function includesBlockingLane(root, lanes) { + return 0 !== (root.current.mode & 32) ? !1 : 0 !== (lanes & 30); +} function createLaneMap(initial) { for (var laneMap = [], i = 0; 31 > i; i++) laneMap.push(initial); return laneMap; @@ -2085,7 +2088,11 @@ function invalidateContextProvider(workInProgress, type, didChange) { : pop(didPerformWorkStackCursor); push(didPerformWorkStackCursor, didChange); } -var syncQueue = null, +function is(x, y) { + return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); +} +var objectIs = "function" === typeof Object.is ? Object.is : is, + syncQueue = null, includesLegacySyncCallbacks = !1, isFlushingSyncQueue = !1; function flushSyncCallbacks() { @@ -2114,10 +2121,6 @@ function flushSyncCallbacks() { return null; } var ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig; -function is(x, y) { - return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); -} -var objectIs = "function" === typeof Object.is ? Object.is : is; function shallowEqual(objA, objB) { if (objectIs(objA, objB)) return !0; if ( @@ -2402,7 +2405,7 @@ function processUpdateQueue( newState = workInProgress; break a; case 3: - workInProgress.flags = (workInProgress.flags & -16385) | 128; + workInProgress.flags = (workInProgress.flags & -32769) | 128; case 0: workInProgress = update.payload; updateLane = @@ -3633,42 +3636,52 @@ function updateMutableSource(source, getSnapshot, subscribe) { return useMutableSource(hook, source, getSnapshot, subscribe); } function mountSyncExternalStore(subscribe, getSnapshot) { - var hook = mountWorkInProgressHook(), + var fiber = currentlyRenderingFiber$1, + hook = mountWorkInProgressHook(), nextSnapshot = getSnapshot(); hook.memoizedState = nextSnapshot; var inst = { value: nextSnapshot, getSnapshot: getSnapshot }; hook.queue = inst; - return useSyncExternalStore(hook, inst, subscribe, getSnapshot, nextSnapshot); -} -function useSyncExternalStore( - hook, - inst, - subscribe, - getSnapshot, - nextSnapshot -) { - var fiber = currentlyRenderingFiber$1; - hook = ReactCurrentDispatcher$1.current; - hook.useLayoutEffect( - function() { - inst.value = nextSnapshot; - inst.getSnapshot = getSnapshot; - checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); - }, - [subscribe, nextSnapshot, getSnapshot] - ); - hook.useEffect( - function() { - function handleStoreChange() { - checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); - } - handleStoreChange(); - return subscribe(handleStoreChange); - }, - [subscribe] + mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); + fiber.flags |= 1024; + pushEffect( + 9, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + void 0, + null ); + subscribe = workInProgressRoot; + if (null === subscribe) + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + includesBlockingLane(subscribe, renderLanes) || + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); return nextSnapshot; } +function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) { + fiber.flags |= 8192; + fiber = { getSnapshot: getSnapshot, value: renderedSnapshot }; + getSnapshot = currentlyRenderingFiber$1.updateQueue; + null === getSnapshot + ? ((getSnapshot = { lastEffect: null, stores: null }), + (currentlyRenderingFiber$1.updateQueue = getSnapshot), + (getSnapshot.stores = [fiber])) + : ((renderedSnapshot = getSnapshot.stores), + null === renderedSnapshot + ? (getSnapshot.stores = [fiber]) + : renderedSnapshot.push(fiber)); +} +function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) { + inst.value = nextSnapshot; + inst.getSnapshot = getSnapshot; + checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); +} +function subscribeToStore(fiber, inst, subscribe) { + return subscribe(function() { + checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); + }); +} function checkIfSnapshotChanged(inst) { var latestGetSnapshot = inst.getSnapshot; inst = inst.value; @@ -3703,7 +3716,7 @@ function pushEffect(tag, create, destroy, deps) { tag = { tag: tag, create: create, destroy: destroy, deps: deps, next: null }; create = currentlyRenderingFiber$1.updateQueue; null === create - ? ((create = { lastEffect: null }), + ? ((create = { lastEffect: null, stores: null }), (currentlyRenderingFiber$1.updateQueue = create), (create.lastEffect = tag.next = tag)) : ((destroy = create.lastEffect), @@ -3744,13 +3757,16 @@ function updateEffectImpl(fiberFlags, hookFlags, create, deps) { hook.memoizedState = pushEffect(1 | hookFlags, create, destroy, deps); } function mountEffect(create, deps) { - return mountEffectImpl(1049600, 4, create, deps); + return mountEffectImpl(2098176, 8, create, deps); } function updateEffect(create, deps) { - return updateEffectImpl(1024, 4, create, deps); + return updateEffectImpl(1024, 8, create, deps); +} +function updateInsertionEffect(create, deps) { + return updateEffectImpl(4, 2, create, deps); } function updateLayoutEffect(create, deps) { - return updateEffectImpl(4, 2, create, deps); + return updateEffectImpl(4, 4, create, deps); } function imperativeHandleEffect(create, ref) { if ("function" === typeof ref) @@ -3774,7 +3790,7 @@ function updateImperativeHandle(ref, create, deps) { deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null; return updateEffectImpl( 4, - 2, + 4, imperativeHandleEffect.bind(null, create, ref), deps ); @@ -3892,6 +3908,7 @@ var ContextOnlyDispatcher = { useContext: throwInvalidHookError, useEffect: throwInvalidHookError, useImperativeHandle: throwInvalidHookError, + useInsertionEffect: throwInvalidHookError, useLayoutEffect: throwInvalidHookError, useMemo: throwInvalidHookError, useReducer: throwInvalidHookError, @@ -3920,12 +3937,15 @@ var ContextOnlyDispatcher = { deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null; return mountEffectImpl( 4, - 2, + 4, imperativeHandleEffect.bind(null, create, ref), deps ); }, useLayoutEffect: function(create, deps) { + return mountEffectImpl(4, 4, create, deps); + }, + useInsertionEffect: function(create, deps) { return mountEffectImpl(4, 2, create, deps); }, useMemo: function(nextCreate, deps) { @@ -4008,6 +4028,7 @@ var ContextOnlyDispatcher = { useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, + useInsertionEffect: updateInsertionEffect, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: updateReducer, @@ -4041,17 +4062,44 @@ var ContextOnlyDispatcher = { }, useMutableSource: updateMutableSource, useSyncExternalStore: function(subscribe, getSnapshot) { - var hook = updateWorkInProgressHook(), - nextSnapshot = getSnapshot(); - objectIs(hook.memoizedState, nextSnapshot) || + var fiber = currentlyRenderingFiber$1, + hook = updateWorkInProgressHook(), + nextSnapshot = getSnapshot(), + snapshotChanged = !objectIs(hook.memoizedState, nextSnapshot); + snapshotChanged && ((hook.memoizedState = nextSnapshot), (didReceiveUpdate = !0)); - return useSyncExternalStore( - hook, - hook.queue, - subscribe, - getSnapshot, - nextSnapshot - ); + hook = hook.queue; + updateEffect(subscribeToStore.bind(null, fiber, hook, subscribe), [ + subscribe + ]); + if ( + hook.getSnapshot !== getSnapshot || + snapshotChanged || + (null !== workInProgressHook && + workInProgressHook.memoizedState.tag & 1) + ) { + fiber.flags |= 1024; + pushEffect( + 9, + updateStoreInstance.bind( + null, + fiber, + hook, + nextSnapshot, + getSnapshot + ), + void 0, + null + ); + subscribe = workInProgressRoot; + if (null === subscribe) + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + includesBlockingLane(subscribe, renderLanes) || + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + return nextSnapshot; }, useOpaqueIdentifier: function() { return updateReducer(basicStateReducer)[0]; @@ -4064,6 +4112,7 @@ var ContextOnlyDispatcher = { useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, + useInsertionEffect: updateInsertionEffect, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: rerenderReducer, @@ -4365,8 +4414,8 @@ function bubbleProperties(completedWork) { if (didBailout) for (var child$39 = completedWork.child; null !== child$39; ) (newChildLanes |= child$39.lanes | child$39.childLanes), - (subtreeFlags |= child$39.subtreeFlags & 1835008), - (subtreeFlags |= child$39.flags & 1835008), + (subtreeFlags |= child$39.subtreeFlags & 3670016), + (subtreeFlags |= child$39.flags & 3670016), (child$39.return = completedWork), (child$39 = child$39.sibling); else @@ -4584,7 +4633,7 @@ function completeWork(current, workInProgress, renderLanes) { for (newProps = workInProgress.child; null !== newProps; ) (renderLanes = newProps), (type = current), - (renderLanes.flags &= 1835010), + (renderLanes.flags &= 3670018), (renderedTail = renderLanes.alternate), null === renderedTail ? ((renderLanes.childLanes = 0), @@ -4810,7 +4859,7 @@ function updateSimpleMemoComponent( current.ref === workInProgress.ref ) if (((didReceiveUpdate = !1), 0 !== (current.lanes & renderLanes))) - 0 !== (current.flags & 32768) && (didReceiveUpdate = !0); + 0 !== (current.flags & 65536) && (didReceiveUpdate = !0); else return ( (workInProgress.lanes = current.lanes), @@ -5425,7 +5474,7 @@ function updateSuspenseFallbackChildren( primaryChildren ))), (current.subtreeFlags = - currentPrimaryChildFragment.subtreeFlags & 1835008)); + currentPrimaryChildFragment.subtreeFlags & 3670016)); null !== currentFallbackChildFragment ? (fallbackChildren = createWorkInProgress( currentFallbackChildFragment, @@ -5653,8 +5702,8 @@ function unwindWork(workInProgress) { case 1: isContextProvider(workInProgress.type) && popContext(); var flags = workInProgress.flags; - return flags & 16384 - ? ((workInProgress.flags = (flags & -16385) | 128), workInProgress) + return flags & 32768 + ? ((workInProgress.flags = (flags & -32769) | 128), workInProgress) : null; case 3: popHostContainer(); @@ -5666,7 +5715,7 @@ function unwindWork(workInProgress) { throw Error( "The root failed to unmount after an error. This is likely a bug in React. Please file an issue." ); - workInProgress.flags = (flags & -16385) | 128; + workInProgress.flags = (flags & -32769) | 128; return workInProgress; case 5: return popHostContext(workInProgress), null; @@ -5674,8 +5723,8 @@ function unwindWork(workInProgress) { return ( pop(suspenseStackCursor), (flags = workInProgress.flags), - flags & 16384 - ? ((workInProgress.flags = (flags & -16385) | 128), workInProgress) + flags & 32768 + ? ((workInProgress.flags = (flags & -32769) | 128), workInProgress) : null ); case 19: @@ -5831,6 +5880,8 @@ function commitWork(current, finishedWork) { case 14: case 15: commitHookEffectListUnmount(3, finishedWork, finishedWork.return); + commitHookEffectListMount(3, finishedWork); + commitHookEffectListUnmount(5, finishedWork, finishedWork.return); return; case 12: return; @@ -5905,7 +5956,10 @@ function commitMutationEffects(root, firstChild) { var _effect = effect, destroy = _effect.destroy, tag = _effect.tag; - if (void 0 !== destroy && 0 !== (tag & 2)) { + if ( + void 0 !== destroy && + (0 !== (tag & 2) || 0 !== (tag & 4)) + ) { _effect = current; var nearestMountedAncestor = root; try { @@ -6038,7 +6092,7 @@ function commitLayoutEffects(finishedWork) { case 0: case 11: case 15: - commitHookEffectListMount(3, firstChild); + commitHookEffectListMount(5, firstChild); break; case 1: var instance = firstChild.stateNode; @@ -6316,15 +6370,15 @@ function performConcurrentWorkOnRoot(root, didTimeout) { root === workInProgressRoot ? workInProgressRootRenderLanes : 0 ); if (0 === lanes) return null; - var JSCompiler_inline_result = - 0 !== (lanes & root.expiredLanes) - ? !1 - : 0 !== (root.current.mode & 32) - ? !0 - : 0 === (lanes & 30); - if (JSCompiler_inline_result && !didTimeout) { + if ( + includesBlockingLane(root, lanes) || + 0 !== (lanes & root.expiredLanes) || + didTimeout + ) + didTimeout = renderRootSync(root, lanes); + else { didTimeout = lanes; - JSCompiler_inline_result = executionContext; + var prevExecutionContext = executionContext; executionContext |= 2; var prevDispatcher = pushDispatcher(); if ( @@ -6343,30 +6397,44 @@ function performConcurrentWorkOnRoot(root, didTimeout) { while (1); resetContextDependencies(); ReactCurrentDispatcher$2.current = prevDispatcher; - executionContext = JSCompiler_inline_result; + executionContext = prevExecutionContext; null !== workInProgress ? (didTimeout = 0) : ((workInProgressRoot = null), (workInProgressRootRenderLanes = 0), (didTimeout = workInProgressRootExitStatus)); - } else didTimeout = renderRootSync(root, lanes); + } if (0 !== didTimeout) { 2 === didTimeout && - ((JSCompiler_inline_result = executionContext), - (executionContext |= 8), - root.hydrate && ((root.hydrate = !1), shim(root.containerInfo)), - (prevDispatcher = getLanesToRetrySynchronouslyOnError(root)), - 0 !== prevDispatcher && - ((lanes = prevDispatcher), - (didTimeout = renderRootSync(root, prevDispatcher))), - (executionContext = JSCompiler_inline_result)); + ((prevExecutionContext = getLanesToRetrySynchronouslyOnError(root)), + 0 !== prevExecutionContext && + ((lanes = prevExecutionContext), + (didTimeout = recoverFromConcurrentError(root, prevExecutionContext)))); if (1 === didTimeout) throw ((originalCallbackNode = workInProgressRootFatalError), prepareFreshStack(root, 0), markRootSuspended$1(root, lanes), ensureRootIsScheduled(root, now()), originalCallbackNode); - root.finishedWork = root.current.alternate; + prevDispatcher = !includesBlockingLane(root, lanes); + prevExecutionContext = root.current.alternate; + if ( + prevDispatcher && + !isRenderConsistentWithExternalStores(prevExecutionContext) && + ((didTimeout = renderRootSync(root, lanes)), + 2 === didTimeout && + ((prevDispatcher = getLanesToRetrySynchronouslyOnError(root)), + 0 !== prevDispatcher && + ((lanes = prevDispatcher), + (didTimeout = recoverFromConcurrentError(root, prevDispatcher)))), + 1 === didTimeout) + ) + throw ((originalCallbackNode = workInProgressRootFatalError), + prepareFreshStack(root, 0), + markRootSuspended$1(root, lanes), + ensureRootIsScheduled(root, now()), + originalCallbackNode); + root.finishedWork = prevExecutionContext; root.finishedLanes = lanes; switch (didTimeout) { case 0: @@ -6383,10 +6451,10 @@ function performConcurrentWorkOnRoot(root, didTimeout) { 10 < didTimeout) ) { if (0 !== getNextLanes(root, 0)) break; - JSCompiler_inline_result = root.suspendedLanes; - if ((JSCompiler_inline_result & lanes) !== lanes) { + prevExecutionContext = root.suspendedLanes; + if ((prevExecutionContext & lanes) !== lanes) { requestEventTime(); - root.pingedLanes |= root.suspendedLanes & JSCompiler_inline_result; + root.pingedLanes |= root.suspendedLanes & prevExecutionContext; break; } root.timeoutHandle = scheduleTimeout( @@ -6401,15 +6469,14 @@ function performConcurrentWorkOnRoot(root, didTimeout) { markRootSuspended$1(root, lanes); if ((lanes & 4194240) === lanes) break; didTimeout = root.eventTimes; - for (JSCompiler_inline_result = -1; 0 < lanes; ) { + for (prevExecutionContext = -1; 0 < lanes; ) { var index$4 = 31 - clz32(lanes); prevDispatcher = 1 << index$4; index$4 = didTimeout[index$4]; - index$4 > JSCompiler_inline_result && - (JSCompiler_inline_result = index$4); + index$4 > prevExecutionContext && (prevExecutionContext = index$4); lanes &= ~prevDispatcher; } - lanes = JSCompiler_inline_result; + lanes = prevExecutionContext; lanes = now() - lanes; lanes = (120 > lanes @@ -6446,6 +6513,48 @@ function performConcurrentWorkOnRoot(root, didTimeout) { ? performConcurrentWorkOnRoot.bind(null, root) : null; } +function recoverFromConcurrentError(root, errorRetryLanes) { + var prevExecutionContext = executionContext; + executionContext |= 8; + root.hydrate && ((root.hydrate = !1), shim(root.containerInfo)); + root = renderRootSync(root, errorRetryLanes); + executionContext = prevExecutionContext; + return root; +} +function isRenderConsistentWithExternalStores(finishedWork) { + for (var node = finishedWork; ; ) { + if (node.flags & 8192) { + var updateQueue = node.updateQueue; + if ( + null !== updateQueue && + ((updateQueue = updateQueue.stores), null !== updateQueue) + ) + for (var i = 0; i < updateQueue.length; i++) { + var check = updateQueue[i], + getSnapshot = check.getSnapshot; + check = check.value; + try { + if (!objectIs(getSnapshot(), check)) return !1; + } catch (error) { + return !1; + } + } + } + updateQueue = node.child; + if (node.subtreeFlags & 8192 && null !== updateQueue) + (updateQueue.return = node), (node = updateQueue); + else { + if (node === finishedWork) break; + for (; null === node.sibling; ) { + if (null === node.return || node.return === finishedWork) return !0; + node = node.return; + } + node.sibling.return = node.return; + node = node.sibling; + } + } + return !0; +} function markRootSuspended$1(root, suspendedLanes) { suspendedLanes &= ~workInProgressRootPingedLanes; suspendedLanes &= ~workInProgressRootUpdatedLanes; @@ -6593,7 +6702,7 @@ function handleError(root$jscomp$0, thrownValue) { sourceFiber = erroredWork, value = thrownValue; thrownValue = workInProgressRootRenderLanes; - sourceFiber.flags |= 8192; + sourceFiber.flags |= 16384; if ( null !== value && "object" === typeof value && @@ -6644,8 +6753,8 @@ function handleError(root$jscomp$0, thrownValue) { workInProgress$30 !== returnFiber ) { workInProgress$30.flags |= 128; - sourceFiber.flags |= 32768; - sourceFiber.flags &= -10053; + sourceFiber.flags |= 65536; + sourceFiber.flags &= -26437; if ( enablePersistentOffscreenHostContainer && null === workInProgress$30.alternate @@ -6690,7 +6799,7 @@ function handleError(root$jscomp$0, thrownValue) { ); wakeable.then(ping, ping); } - workInProgress$30.flags |= 16384; + workInProgress$30.flags |= 32768; workInProgress$30.lanes = thrownValue; break a; } @@ -6709,7 +6818,7 @@ function handleError(root$jscomp$0, thrownValue) { switch (workInProgress$30.tag) { case 3: root = value; - workInProgress$30.flags |= 16384; + workInProgress$30.flags |= 32768; thrownValue &= -thrownValue; workInProgress$30.lanes |= thrownValue; var update$31 = createRootErrorUpdate( @@ -6731,7 +6840,7 @@ function handleError(root$jscomp$0, thrownValue) { (null === legacyErrorBoundariesThatAlreadyFailed || !legacyErrorBoundariesThatAlreadyFailed.has(instance)))) ) { - workInProgress$30.flags |= 16384; + workInProgress$30.flags |= 32768; thrownValue &= -thrownValue; workInProgress$30.lanes |= thrownValue; var update$34 = createClassErrorUpdate( @@ -6805,7 +6914,7 @@ function completeUnitOfWork(unitOfWork) { do { var current = completedWork.alternate; unitOfWork = completedWork.return; - if (0 === (completedWork.flags & 8192)) { + if (0 === (completedWork.flags & 16384)) { if ( ((current = completeWork(current, completedWork, subtreeRenderLanes)), null !== current) @@ -6816,12 +6925,12 @@ function completeUnitOfWork(unitOfWork) { } else { current = unwindWork(completedWork); if (null !== current) { - current.flags &= 8191; + current.flags &= 16383; workInProgress = current; return; } null !== unitOfWork && - ((unitOfWork.flags |= 8192), + ((unitOfWork.flags |= 16384), (unitOfWork.subtreeFlags = 0), (unitOfWork.deletions = null)); } @@ -6900,11 +7009,6 @@ function commitRootImpl(root, renderPriorityLevel) { (pendingPassiveEffectsLanes = lanes)); remainingLanes = root.pendingLanes; 0 === remainingLanes && (legacyErrorBoundariesThatAlreadyFailed = null); - 0 !== (remainingLanes & 1) - ? root === rootWithNestedUpdates - ? nestedUpdateCount++ - : ((nestedUpdateCount = 0), (rootWithNestedUpdates = root)) - : (nestedUpdateCount = 0); onCommitRoot(finishedWork.stateNode, renderPriorityLevel); ensureRootIsScheduled(root, now()); if (hasUncaughtError) @@ -6915,6 +7019,12 @@ function commitRootImpl(root, renderPriorityLevel) { 0 !== (pendingPassiveEffectsLanes & 1) && 0 !== root.tag && flushPassiveEffects(); + remainingLanes = root.pendingLanes; + 0 !== (remainingLanes & 1) + ? root === rootWithNestedUpdates + ? nestedUpdateCount++ + : ((nestedUpdateCount = 0), (rootWithNestedUpdates = root)) + : (nestedUpdateCount = 0); flushSyncCallbacks(); return null; } @@ -6950,7 +7060,7 @@ function flushPassiveEffects() { case 0: case 11: case 15: - commitHookEffectListUnmount(4, fiber$jscomp$0, fiber); + commitHookEffectListUnmount(8, fiber$jscomp$0, fiber); } var child$jscomp$0 = fiber$jscomp$0.child; if (null !== child$jscomp$0) @@ -7000,7 +7110,7 @@ function flushPassiveEffects() { case 0: case 11: case 15: - commitHookEffectListUnmount(5, fiber, fiber.return); + commitHookEffectListUnmount(9, fiber, fiber.return); } var sibling$jscomp$0 = fiber.sibling; if (null !== sibling$jscomp$0) { @@ -7026,7 +7136,7 @@ function flushPassiveEffects() { case 0: case 11: case 15: - commitHookEffectListMount(5, deletions); + commitHookEffectListMount(9, deletions); } } catch (error) { captureCommitPhaseError(deletions, deletions.return, error); @@ -7172,7 +7282,7 @@ beginWork$1 = function(current, workInProgress, renderLanes) { renderLanes ) ); - didReceiveUpdate = 0 !== (current.flags & 32768) ? !0 : !1; + didReceiveUpdate = 0 !== (current.flags & 65536) ? !0 : !1; } else didReceiveUpdate = !1; workInProgress.lanes = 0; @@ -7638,7 +7748,7 @@ function createWorkInProgress(current, pendingProps) { (workInProgress.flags = 0), (workInProgress.subtreeFlags = 0), (workInProgress.deletions = null)); - workInProgress.flags = current.flags & 1835008; + workInProgress.flags = current.flags & 3670016; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; workInProgress.child = current.child; @@ -7995,10 +8105,10 @@ batchedUpdatesImpl = function(fn, a) { } }; var roots = new Map(), - devToolsConfig$jscomp$inline_953 = { + devToolsConfig$jscomp$inline_950 = { findFiberByHostInstance: getInstanceFromInstance, bundleType: 0, - version: "18.0.0-95d762e40-20210908", + version: "18.0.0-e8feb11b6-20210915", rendererPackageName: "react-native-renderer", rendererConfig: { getInspectorDataForViewTag: function() { @@ -8013,11 +8123,11 @@ var roots = new Map(), }.bind(null, findNodeHandle) } }; -var internals$jscomp$inline_1194 = { - bundleType: devToolsConfig$jscomp$inline_953.bundleType, - version: devToolsConfig$jscomp$inline_953.version, - rendererPackageName: devToolsConfig$jscomp$inline_953.rendererPackageName, - rendererConfig: devToolsConfig$jscomp$inline_953.rendererConfig, +var internals$jscomp$inline_1191 = { + bundleType: devToolsConfig$jscomp$inline_950.bundleType, + version: devToolsConfig$jscomp$inline_950.version, + rendererPackageName: devToolsConfig$jscomp$inline_950.rendererPackageName, + rendererConfig: devToolsConfig$jscomp$inline_950.rendererConfig, overrideHookState: null, overrideHookStateDeletePath: null, overrideHookStateRenamePath: null, @@ -8033,7 +8143,7 @@ var internals$jscomp$inline_1194 = { return null === fiber ? null : fiber.stateNode; }, findFiberByHostInstance: - devToolsConfig$jscomp$inline_953.findFiberByHostInstance || + devToolsConfig$jscomp$inline_950.findFiberByHostInstance || emptyFindFiberByHostInstance, findHostInstancesForRefresh: null, scheduleRefresh: null, @@ -8041,19 +8151,19 @@ var internals$jscomp$inline_1194 = { setRefreshHandler: null, getCurrentFiber: null, getIsStrictMode: null, - reconcilerVersion: "18.0.0-95d762e40-20210908" + reconcilerVersion: "18.0.0-e8feb11b6-20210915" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { - var hook$jscomp$inline_1195 = __REACT_DEVTOOLS_GLOBAL_HOOK__; + var hook$jscomp$inline_1192 = __REACT_DEVTOOLS_GLOBAL_HOOK__; if ( - !hook$jscomp$inline_1195.isDisabled && - hook$jscomp$inline_1195.supportsFiber + !hook$jscomp$inline_1192.isDisabled && + hook$jscomp$inline_1192.supportsFiber ) try { - (rendererID = hook$jscomp$inline_1195.inject( - internals$jscomp$inline_1194 + (rendererID = hook$jscomp$inline_1192.inject( + internals$jscomp$inline_1191 )), - (injectedHook = hook$jscomp$inline_1195); + (injectedHook = hook$jscomp$inline_1192); } catch (err) {} } exports.createPortal = function(children, containerTag) { diff --git a/Libraries/Renderer/implementations/ReactFabric-profiling.fb.js b/Libraries/Renderer/implementations/ReactFabric-profiling.fb.js index c46e03dc8c..1277c286d3 100644 --- a/Libraries/Renderer/implementations/ReactFabric-profiling.fb.js +++ b/Libraries/Renderer/implementations/ReactFabric-profiling.fb.js @@ -7,7 +7,7 @@ * @noflow * @nolint * @preventMunge - * @generated SignedSource<> + * @generated SignedSource<<9613963109b67072417dfb9843578a9d>> */ "use strict"; @@ -931,7 +931,7 @@ eventPluginOrder = Array.prototype.slice.call([ "ReactNativeBridgeEventPlugin" ]); recomputePluginOrdering(); -var injectedNamesToPlugins$jscomp$inline_229 = { +var injectedNamesToPlugins$jscomp$inline_230 = { ResponderEventPlugin: ResponderEventPlugin, ReactNativeBridgeEventPlugin: { eventTypes: {}, @@ -966,34 +966,34 @@ var injectedNamesToPlugins$jscomp$inline_229 = { } } }, - isOrderingDirty$jscomp$inline_230 = !1, - pluginName$jscomp$inline_231; -for (pluginName$jscomp$inline_231 in injectedNamesToPlugins$jscomp$inline_229) + isOrderingDirty$jscomp$inline_231 = !1, + pluginName$jscomp$inline_232; +for (pluginName$jscomp$inline_232 in injectedNamesToPlugins$jscomp$inline_230) if ( - injectedNamesToPlugins$jscomp$inline_229.hasOwnProperty( - pluginName$jscomp$inline_231 + injectedNamesToPlugins$jscomp$inline_230.hasOwnProperty( + pluginName$jscomp$inline_232 ) ) { - var pluginModule$jscomp$inline_232 = - injectedNamesToPlugins$jscomp$inline_229[pluginName$jscomp$inline_231]; + var pluginModule$jscomp$inline_233 = + injectedNamesToPlugins$jscomp$inline_230[pluginName$jscomp$inline_232]; if ( - !namesToPlugins.hasOwnProperty(pluginName$jscomp$inline_231) || - namesToPlugins[pluginName$jscomp$inline_231] !== - pluginModule$jscomp$inline_232 + !namesToPlugins.hasOwnProperty(pluginName$jscomp$inline_232) || + namesToPlugins[pluginName$jscomp$inline_232] !== + pluginModule$jscomp$inline_233 ) { - if (namesToPlugins[pluginName$jscomp$inline_231]) + if (namesToPlugins[pluginName$jscomp$inline_232]) throw Error( "EventPluginRegistry: Cannot inject two different event plugins using the same name, `" + - pluginName$jscomp$inline_231 + + pluginName$jscomp$inline_232 + "`." ); namesToPlugins[ - pluginName$jscomp$inline_231 - ] = pluginModule$jscomp$inline_232; - isOrderingDirty$jscomp$inline_230 = !0; + pluginName$jscomp$inline_232 + ] = pluginModule$jscomp$inline_233; + isOrderingDirty$jscomp$inline_231 = !0; } } -isOrderingDirty$jscomp$inline_230 && recomputePluginOrdering(); +isOrderingDirty$jscomp$inline_231 && recomputePluginOrdering(); function getInstanceFromInstance(instanceHandle) { return instanceHandle; } @@ -1781,6 +1781,9 @@ function getLanesToRetrySynchronouslyOnError(root) { root = root.pendingLanes & -1073741825; return 0 !== root ? root : root & 1073741824 ? 1073741824 : 0; } +function includesBlockingLane(root, lanes) { + return 0 !== (root.current.mode & 32) ? !1 : 0 !== (lanes & 30); +} function createLaneMap(initial) { for (var laneMap = [], i = 0; 31 > i; i++) laneMap.push(initial); return laneMap; @@ -2133,7 +2136,11 @@ function invalidateContextProvider(workInProgress, type, didChange) { : pop(didPerformWorkStackCursor); push(didPerformWorkStackCursor, didChange); } -var syncQueue = null, +function is(x, y) { + return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); +} +var objectIs = "function" === typeof Object.is ? Object.is : is, + syncQueue = null, includesLegacySyncCallbacks = !1, isFlushingSyncQueue = !1; function flushSyncCallbacks() { @@ -2162,10 +2169,6 @@ function flushSyncCallbacks() { return null; } var ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig; -function is(x, y) { - return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); -} -var objectIs = "function" === typeof Object.is ? Object.is : is; function shallowEqual(objA, objB) { if (objectIs(objA, objB)) return !0; if ( @@ -2450,7 +2453,7 @@ function processUpdateQueue( newState = workInProgress; break a; case 3: - workInProgress.flags = (workInProgress.flags & -16385) | 128; + workInProgress.flags = (workInProgress.flags & -32769) | 128; case 0: workInProgress = update.payload; updateLane = @@ -3681,42 +3684,52 @@ function updateMutableSource(source, getSnapshot, subscribe) { return useMutableSource(hook, source, getSnapshot, subscribe); } function mountSyncExternalStore(subscribe, getSnapshot) { - var hook = mountWorkInProgressHook(), + var fiber = currentlyRenderingFiber$1, + hook = mountWorkInProgressHook(), nextSnapshot = getSnapshot(); hook.memoizedState = nextSnapshot; var inst = { value: nextSnapshot, getSnapshot: getSnapshot }; hook.queue = inst; - return useSyncExternalStore(hook, inst, subscribe, getSnapshot, nextSnapshot); -} -function useSyncExternalStore( - hook, - inst, - subscribe, - getSnapshot, - nextSnapshot -) { - var fiber = currentlyRenderingFiber$1; - hook = ReactCurrentDispatcher$1.current; - hook.useLayoutEffect( - function() { - inst.value = nextSnapshot; - inst.getSnapshot = getSnapshot; - checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); - }, - [subscribe, nextSnapshot, getSnapshot] - ); - hook.useEffect( - function() { - function handleStoreChange() { - checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); - } - handleStoreChange(); - return subscribe(handleStoreChange); - }, - [subscribe] + mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); + fiber.flags |= 1024; + pushEffect( + 9, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + void 0, + null ); + subscribe = workInProgressRoot; + if (null === subscribe) + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + includesBlockingLane(subscribe, renderLanes) || + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); return nextSnapshot; } +function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) { + fiber.flags |= 8192; + fiber = { getSnapshot: getSnapshot, value: renderedSnapshot }; + getSnapshot = currentlyRenderingFiber$1.updateQueue; + null === getSnapshot + ? ((getSnapshot = { lastEffect: null, stores: null }), + (currentlyRenderingFiber$1.updateQueue = getSnapshot), + (getSnapshot.stores = [fiber])) + : ((renderedSnapshot = getSnapshot.stores), + null === renderedSnapshot + ? (getSnapshot.stores = [fiber]) + : renderedSnapshot.push(fiber)); +} +function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) { + inst.value = nextSnapshot; + inst.getSnapshot = getSnapshot; + checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); +} +function subscribeToStore(fiber, inst, subscribe) { + return subscribe(function() { + checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); + }); +} function checkIfSnapshotChanged(inst) { var latestGetSnapshot = inst.getSnapshot; inst = inst.value; @@ -3751,7 +3764,7 @@ function pushEffect(tag, create, destroy, deps) { tag = { tag: tag, create: create, destroy: destroy, deps: deps, next: null }; create = currentlyRenderingFiber$1.updateQueue; null === create - ? ((create = { lastEffect: null }), + ? ((create = { lastEffect: null, stores: null }), (currentlyRenderingFiber$1.updateQueue = create), (create.lastEffect = tag.next = tag)) : ((destroy = create.lastEffect), @@ -3792,13 +3805,16 @@ function updateEffectImpl(fiberFlags, hookFlags, create, deps) { hook.memoizedState = pushEffect(1 | hookFlags, create, destroy, deps); } function mountEffect(create, deps) { - return mountEffectImpl(1049600, 4, create, deps); + return mountEffectImpl(2098176, 8, create, deps); } function updateEffect(create, deps) { - return updateEffectImpl(1024, 4, create, deps); + return updateEffectImpl(1024, 8, create, deps); +} +function updateInsertionEffect(create, deps) { + return updateEffectImpl(4, 2, create, deps); } function updateLayoutEffect(create, deps) { - return updateEffectImpl(4, 2, create, deps); + return updateEffectImpl(4, 4, create, deps); } function imperativeHandleEffect(create, ref) { if ("function" === typeof ref) @@ -3822,7 +3838,7 @@ function updateImperativeHandle(ref, create, deps) { deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null; return updateEffectImpl( 4, - 2, + 4, imperativeHandleEffect.bind(null, create, ref), deps ); @@ -3940,6 +3956,7 @@ var ContextOnlyDispatcher = { useContext: throwInvalidHookError, useEffect: throwInvalidHookError, useImperativeHandle: throwInvalidHookError, + useInsertionEffect: throwInvalidHookError, useLayoutEffect: throwInvalidHookError, useMemo: throwInvalidHookError, useReducer: throwInvalidHookError, @@ -3968,12 +3985,15 @@ var ContextOnlyDispatcher = { deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null; return mountEffectImpl( 4, - 2, + 4, imperativeHandleEffect.bind(null, create, ref), deps ); }, useLayoutEffect: function(create, deps) { + return mountEffectImpl(4, 4, create, deps); + }, + useInsertionEffect: function(create, deps) { return mountEffectImpl(4, 2, create, deps); }, useMemo: function(nextCreate, deps) { @@ -4056,6 +4076,7 @@ var ContextOnlyDispatcher = { useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, + useInsertionEffect: updateInsertionEffect, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: updateReducer, @@ -4089,17 +4110,44 @@ var ContextOnlyDispatcher = { }, useMutableSource: updateMutableSource, useSyncExternalStore: function(subscribe, getSnapshot) { - var hook = updateWorkInProgressHook(), - nextSnapshot = getSnapshot(); - objectIs(hook.memoizedState, nextSnapshot) || + var fiber = currentlyRenderingFiber$1, + hook = updateWorkInProgressHook(), + nextSnapshot = getSnapshot(), + snapshotChanged = !objectIs(hook.memoizedState, nextSnapshot); + snapshotChanged && ((hook.memoizedState = nextSnapshot), (didReceiveUpdate = !0)); - return useSyncExternalStore( - hook, - hook.queue, - subscribe, - getSnapshot, - nextSnapshot - ); + hook = hook.queue; + updateEffect(subscribeToStore.bind(null, fiber, hook, subscribe), [ + subscribe + ]); + if ( + hook.getSnapshot !== getSnapshot || + snapshotChanged || + (null !== workInProgressHook && + workInProgressHook.memoizedState.tag & 1) + ) { + fiber.flags |= 1024; + pushEffect( + 9, + updateStoreInstance.bind( + null, + fiber, + hook, + nextSnapshot, + getSnapshot + ), + void 0, + null + ); + subscribe = workInProgressRoot; + if (null === subscribe) + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + includesBlockingLane(subscribe, renderLanes) || + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + return nextSnapshot; }, useOpaqueIdentifier: function() { return updateReducer(basicStateReducer)[0]; @@ -4112,6 +4160,7 @@ var ContextOnlyDispatcher = { useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, + useInsertionEffect: updateInsertionEffect, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: rerenderReducer, @@ -4477,8 +4526,8 @@ function bubbleProperties(completedWork) { ) (newChildLanes |= child$43.lanes | child$43.childLanes), - (subtreeFlags |= child$43.subtreeFlags & 1835008), - (subtreeFlags |= child$43.flags & 1835008), + (subtreeFlags |= child$43.subtreeFlags & 3670016), + (subtreeFlags |= child$43.flags & 3670016), (treeBaseDuration$42 += child$43.treeBaseDuration), (child$43 = child$43.sibling); completedWork.treeBaseDuration = treeBaseDuration$42; @@ -4490,8 +4539,8 @@ function bubbleProperties(completedWork) { ) (newChildLanes |= treeBaseDuration$42.lanes | treeBaseDuration$42.childLanes), - (subtreeFlags |= treeBaseDuration$42.subtreeFlags & 1835008), - (subtreeFlags |= treeBaseDuration$42.flags & 1835008), + (subtreeFlags |= treeBaseDuration$42.subtreeFlags & 3670016), + (subtreeFlags |= treeBaseDuration$42.flags & 3670016), (treeBaseDuration$42.return = completedWork), (treeBaseDuration$42 = treeBaseDuration$42.sibling); else if (0 !== (completedWork.mode & 2)) { @@ -4737,7 +4786,7 @@ function completeWork(current, workInProgress, renderLanes) { for (newProps = workInProgress.child; null !== newProps; ) (renderLanes = newProps), (renderedTail = current), - (renderLanes.flags &= 1835010), + (renderLanes.flags &= 3670018), (type = renderLanes.alternate), null === type ? ((renderLanes.childLanes = 0), @@ -4965,7 +5014,7 @@ function updateSimpleMemoComponent( current.ref === workInProgress.ref ) if (((didReceiveUpdate = !1), 0 !== (current.lanes & renderLanes))) - 0 !== (current.flags & 32768) && (didReceiveUpdate = !0); + 0 !== (current.flags & 65536) && (didReceiveUpdate = !0); else return ( (workInProgress.lanes = current.lanes), @@ -5596,7 +5645,7 @@ function updateSuspenseFallbackChildren( primaryChildren ))), (current.subtreeFlags = - currentPrimaryChildFragment.subtreeFlags & 1835008)); + currentPrimaryChildFragment.subtreeFlags & 3670016)); null !== currentFallbackChildFragment ? (fallbackChildren = createWorkInProgress( currentFallbackChildFragment, @@ -5832,8 +5881,8 @@ function unwindWork(workInProgress) { case 1: isContextProvider(workInProgress.type) && popContext(); var flags = workInProgress.flags; - return flags & 16384 - ? ((workInProgress.flags = (flags & -16385) | 128), + return flags & 32768 + ? ((workInProgress.flags = (flags & -32769) | 128), 0 !== (workInProgress.mode & 2) && transferActualDuration(workInProgress), workInProgress) @@ -5848,7 +5897,7 @@ function unwindWork(workInProgress) { throw Error( "The root failed to unmount after an error. This is likely a bug in React. Please file an issue." ); - workInProgress.flags = (flags & -16385) | 128; + workInProgress.flags = (flags & -32769) | 128; return workInProgress; case 5: return popHostContext(workInProgress), null; @@ -5856,8 +5905,8 @@ function unwindWork(workInProgress) { return ( pop(suspenseStackCursor), (flags = workInProgress.flags), - flags & 16384 - ? ((workInProgress.flags = (flags & -16385) | 128), + flags & 32768 + ? ((workInProgress.flags = (flags & -32769) | 128), 0 !== (workInProgress.mode & 2) && transferActualDuration(workInProgress), workInProgress) @@ -6023,14 +6072,16 @@ function commitWork(current, finishedWork) { case 11: case 14: case 15: + commitHookEffectListUnmount(3, finishedWork, finishedWork.return); + commitHookEffectListMount(3, finishedWork); if (finishedWork.mode & 2) try { startLayoutEffectTimer(), - commitHookEffectListUnmount(3, finishedWork, finishedWork.return); + commitHookEffectListUnmount(5, finishedWork, finishedWork.return); } finally { recordLayoutEffectDuration(finishedWork); } - else commitHookEffectListUnmount(3, finishedWork, finishedWork.return); + else commitHookEffectListUnmount(5, finishedWork, finishedWork.return); return; case 12: return; @@ -6120,8 +6171,8 @@ function commitMutationEffects(root, firstChild, committedLanes) { var _effect = effect, destroy = _effect.destroy, tag = _effect.tag; - void 0 !== destroy && - 0 !== (tag & 2) && + void 0 === destroy || + (0 === (tag & 2) && 0 === (tag & 4)) || (current.mode & 2 ? (startLayoutEffectTimer(), safelyCallDestroy(current, root, destroy), @@ -6271,11 +6322,11 @@ function commitLayoutEffects(finishedWork, root, committedLanes) { if (committedLanes.mode & 2) try { startLayoutEffectTimer(), - commitHookEffectListMount(3, committedLanes); + commitHookEffectListMount(5, committedLanes); } finally { recordLayoutEffectDuration(committedLanes); } - else commitHookEffectListMount(3, committedLanes); + else commitHookEffectListMount(5, committedLanes); break; case 1: var instance = committedLanes.stateNode; @@ -6629,15 +6680,15 @@ function performConcurrentWorkOnRoot(root, didTimeout) { root === workInProgressRoot ? workInProgressRootRenderLanes : 0 ); if (0 === lanes) return null; - var JSCompiler_inline_result = - 0 !== (lanes & root.expiredLanes) - ? !1 - : 0 !== (root.current.mode & 32) - ? !0 - : 0 === (lanes & 30); - if (JSCompiler_inline_result && !didTimeout) { + if ( + includesBlockingLane(root, lanes) || + 0 !== (lanes & root.expiredLanes) || + didTimeout + ) + didTimeout = renderRootSync(root, lanes); + else { didTimeout = lanes; - JSCompiler_inline_result = executionContext; + var prevExecutionContext = executionContext; executionContext |= 2; var prevDispatcher = pushDispatcher(); if ( @@ -6664,30 +6715,44 @@ function performConcurrentWorkOnRoot(root, didTimeout) { while (1); resetContextDependencies(); ReactCurrentDispatcher$2.current = prevDispatcher; - executionContext = JSCompiler_inline_result; + executionContext = prevExecutionContext; null !== workInProgress ? (didTimeout = 0) : ((workInProgressRoot = null), (workInProgressRootRenderLanes = 0), (didTimeout = workInProgressRootExitStatus)); - } else didTimeout = renderRootSync(root, lanes); + } if (0 !== didTimeout) { 2 === didTimeout && - ((JSCompiler_inline_result = executionContext), - (executionContext |= 8), - root.hydrate && ((root.hydrate = !1), shim(root.containerInfo)), - (prevDispatcher = getLanesToRetrySynchronouslyOnError(root)), - 0 !== prevDispatcher && - ((lanes = prevDispatcher), - (didTimeout = renderRootSync(root, prevDispatcher))), - (executionContext = JSCompiler_inline_result)); + ((prevExecutionContext = getLanesToRetrySynchronouslyOnError(root)), + 0 !== prevExecutionContext && + ((lanes = prevExecutionContext), + (didTimeout = recoverFromConcurrentError(root, prevExecutionContext)))); if (1 === didTimeout) throw ((originalCallbackNode = workInProgressRootFatalError), prepareFreshStack(root, 0), markRootSuspended$1(root, lanes), ensureRootIsScheduled(root, now()), originalCallbackNode); - root.finishedWork = root.current.alternate; + prevDispatcher = !includesBlockingLane(root, lanes); + prevExecutionContext = root.current.alternate; + if ( + prevDispatcher && + !isRenderConsistentWithExternalStores(prevExecutionContext) && + ((didTimeout = renderRootSync(root, lanes)), + 2 === didTimeout && + ((prevDispatcher = getLanesToRetrySynchronouslyOnError(root)), + 0 !== prevDispatcher && + ((lanes = prevDispatcher), + (didTimeout = recoverFromConcurrentError(root, prevDispatcher)))), + 1 === didTimeout) + ) + throw ((originalCallbackNode = workInProgressRootFatalError), + prepareFreshStack(root, 0), + markRootSuspended$1(root, lanes), + ensureRootIsScheduled(root, now()), + originalCallbackNode); + root.finishedWork = prevExecutionContext; root.finishedLanes = lanes; switch (didTimeout) { case 0: @@ -6704,10 +6769,10 @@ function performConcurrentWorkOnRoot(root, didTimeout) { 10 < didTimeout) ) { if (0 !== getNextLanes(root, 0)) break; - JSCompiler_inline_result = root.suspendedLanes; - if ((JSCompiler_inline_result & lanes) !== lanes) { + prevExecutionContext = root.suspendedLanes; + if ((prevExecutionContext & lanes) !== lanes) { requestEventTime(); - root.pingedLanes |= root.suspendedLanes & JSCompiler_inline_result; + root.pingedLanes |= root.suspendedLanes & prevExecutionContext; break; } root.timeoutHandle = scheduleTimeout( @@ -6722,14 +6787,14 @@ function performConcurrentWorkOnRoot(root, didTimeout) { markRootSuspended$1(root, lanes); if ((lanes & 4194240) === lanes) break; didTimeout = root.eventTimes; - for (JSCompiler_inline_result = -1; 0 < lanes; ) + for (prevExecutionContext = -1; 0 < lanes; ) (memoizedUpdaters = 31 - clz32(lanes)), (prevDispatcher = 1 << memoizedUpdaters), (memoizedUpdaters = didTimeout[memoizedUpdaters]), - memoizedUpdaters > JSCompiler_inline_result && - (JSCompiler_inline_result = memoizedUpdaters), + memoizedUpdaters > prevExecutionContext && + (prevExecutionContext = memoizedUpdaters), (lanes &= ~prevDispatcher); - lanes = JSCompiler_inline_result; + lanes = prevExecutionContext; lanes = now() - lanes; lanes = (120 > lanes @@ -6766,6 +6831,48 @@ function performConcurrentWorkOnRoot(root, didTimeout) { ? performConcurrentWorkOnRoot.bind(null, root) : null; } +function recoverFromConcurrentError(root, errorRetryLanes) { + var prevExecutionContext = executionContext; + executionContext |= 8; + root.hydrate && ((root.hydrate = !1), shim(root.containerInfo)); + root = renderRootSync(root, errorRetryLanes); + executionContext = prevExecutionContext; + return root; +} +function isRenderConsistentWithExternalStores(finishedWork) { + for (var node = finishedWork; ; ) { + if (node.flags & 8192) { + var updateQueue = node.updateQueue; + if ( + null !== updateQueue && + ((updateQueue = updateQueue.stores), null !== updateQueue) + ) + for (var i = 0; i < updateQueue.length; i++) { + var check = updateQueue[i], + getSnapshot = check.getSnapshot; + check = check.value; + try { + if (!objectIs(getSnapshot(), check)) return !1; + } catch (error) { + return !1; + } + } + } + updateQueue = node.child; + if (node.subtreeFlags & 8192 && null !== updateQueue) + (updateQueue.return = node), (node = updateQueue); + else { + if (node === finishedWork) break; + for (; null === node.sibling; ) { + if (null === node.return || node.return === finishedWork) return !0; + node = node.return; + } + node.sibling.return = node.return; + node = node.sibling; + } + } + return !0; +} function markRootSuspended$1(root, suspendedLanes) { suspendedLanes &= ~workInProgressRootPingedLanes; suspendedLanes &= ~workInProgressRootUpdatedLanes; @@ -6917,7 +7024,7 @@ function handleError(root$jscomp$0, thrownValue) { sourceFiber = erroredWork, value = thrownValue; thrownValue = workInProgressRootRenderLanes; - sourceFiber.flags |= 8192; + sourceFiber.flags |= 16384; isDevToolsPresent && restorePendingUpdaters(root, thrownValue); if ( null !== value && @@ -6969,8 +7076,8 @@ function handleError(root$jscomp$0, thrownValue) { workInProgress$32 !== returnFiber ) { workInProgress$32.flags |= 128; - sourceFiber.flags |= 32768; - sourceFiber.flags &= -10053; + sourceFiber.flags |= 65536; + sourceFiber.flags &= -26437; if ( enablePersistentOffscreenHostContainer && null === workInProgress$32.alternate @@ -7016,7 +7123,7 @@ function handleError(root$jscomp$0, thrownValue) { isDevToolsPresent && restorePendingUpdaters(root, sourceFiber); wakeable.then(ping, ping); } - workInProgress$32.flags |= 16384; + workInProgress$32.flags |= 32768; workInProgress$32.lanes = thrownValue; break a; } @@ -7035,7 +7142,7 @@ function handleError(root$jscomp$0, thrownValue) { switch (workInProgress$32.tag) { case 3: root = value; - workInProgress$32.flags |= 16384; + workInProgress$32.flags |= 32768; thrownValue &= -thrownValue; workInProgress$32.lanes |= thrownValue; var update$33 = createRootErrorUpdate( @@ -7057,7 +7164,7 @@ function handleError(root$jscomp$0, thrownValue) { (null === legacyErrorBoundariesThatAlreadyFailed || !legacyErrorBoundariesThatAlreadyFailed.has(instance)))) ) { - workInProgress$32.flags |= 16384; + workInProgress$32.flags |= 32768; thrownValue &= -thrownValue; workInProgress$32.lanes |= thrownValue; var update$36 = createClassErrorUpdate( @@ -7147,7 +7254,7 @@ function completeUnitOfWork(unitOfWork) { do { var current = completedWork.alternate; unitOfWork = completedWork.return; - if (0 === (completedWork.flags & 8192)) { + if (0 === (completedWork.flags & 16384)) { if (0 === (completedWork.mode & 2)) current = completeWork(current, completedWork, subtreeRenderLanes); else { @@ -7164,7 +7271,7 @@ function completeUnitOfWork(unitOfWork) { } else { current = unwindWork(completedWork); if (null !== current) { - current.flags &= 8191; + current.flags &= 16383; workInProgress = current; return; } @@ -7176,7 +7283,7 @@ function completeUnitOfWork(unitOfWork) { completedWork.actualDuration = current; } null !== unitOfWork && - ((unitOfWork.flags |= 8192), + ((unitOfWork.flags |= 16384), (unitOfWork.subtreeFlags = 0), (unitOfWork.deletions = null)); } @@ -7256,12 +7363,6 @@ function commitRootImpl(root, renderPriorityLevel) { (pendingPassiveEffectsLanes = lanes)); remainingLanes = root.pendingLanes; 0 === remainingLanes && (legacyErrorBoundariesThatAlreadyFailed = null); - 0 !== (remainingLanes & 1) - ? ((nestedUpdateScheduled = !0), - root === rootWithNestedUpdates - ? nestedUpdateCount++ - : ((nestedUpdateCount = 0), (rootWithNestedUpdates = root))) - : (nestedUpdateCount = 0); onCommitRoot(finishedWork.stateNode, renderPriorityLevel); isDevToolsPresent && root.memoizedUpdaters.clear(); ensureRootIsScheduled(root, now()); @@ -7273,6 +7374,13 @@ function commitRootImpl(root, renderPriorityLevel) { 0 !== (pendingPassiveEffectsLanes & 1) && 0 !== root.tag && flushPassiveEffects(); + remainingLanes = root.pendingLanes; + 0 !== (remainingLanes & 1) + ? ((nestedUpdateScheduled = !0), + root === rootWithNestedUpdates + ? nestedUpdateCount++ + : ((nestedUpdateCount = 0), (rootWithNestedUpdates = root))) + : (nestedUpdateCount = 0); flushSyncCallbacks(); return null; } @@ -7311,9 +7419,9 @@ function flushPassiveEffects() { case 15: current.mode & 2 ? ((passiveEffectStartTime = now$1()), - commitHookEffectListUnmount(4, current, fiber), + commitHookEffectListUnmount(8, current, fiber), recordPassiveEffectDuration(current)) - : commitHookEffectListUnmount(4, current, fiber); + : commitHookEffectListUnmount(8, current, fiber); } var child$jscomp$0 = fiber$jscomp$0.child; if (null !== child$jscomp$0) @@ -7365,9 +7473,9 @@ function flushPassiveEffects() { case 15: i.mode & 2 ? ((passiveEffectStartTime = now$1()), - commitHookEffectListUnmount(5, i, i.return), + commitHookEffectListUnmount(9, i, i.return), recordPassiveEffectDuration(i)) - : commitHookEffectListUnmount(5, i, i.return); + : commitHookEffectListUnmount(9, i, i.return); } var sibling$jscomp$0 = fiber.sibling; if (null !== sibling$jscomp$0) { @@ -7396,11 +7504,11 @@ function flushPassiveEffects() { if (fiberToDelete.mode & 2) { passiveEffectStartTime = now$1(); try { - commitHookEffectListMount(5, fiberToDelete); + commitHookEffectListMount(9, fiberToDelete); } finally { recordPassiveEffectDuration(fiberToDelete); } - } else commitHookEffectListMount(5, fiberToDelete); + } else commitHookEffectListMount(9, fiberToDelete); } } catch (error) { captureCommitPhaseError(deletions, deletions.return, error); @@ -7595,7 +7703,7 @@ beginWork$1 = function(current, workInProgress, renderLanes) { renderLanes ) ); - didReceiveUpdate = 0 !== (current.flags & 32768) ? !0 : !1; + didReceiveUpdate = 0 !== (current.flags & 65536) ? !0 : !1; } else didReceiveUpdate = !1; workInProgress.lanes = 0; @@ -8076,7 +8184,7 @@ function createWorkInProgress(current, pendingProps) { (workInProgress.deletions = null), (workInProgress.actualDuration = 0), (workInProgress.actualStartTime = -1)); - workInProgress.flags = current.flags & 1835008; + workInProgress.flags = current.flags & 3670016; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; workInProgress.child = current.child; @@ -8440,10 +8548,10 @@ batchedUpdatesImpl = function(fn, a) { } }; var roots = new Map(), - devToolsConfig$jscomp$inline_983 = { + devToolsConfig$jscomp$inline_980 = { findFiberByHostInstance: getInstanceFromInstance, bundleType: 0, - version: "18.0.0-95d762e40-20210908", + version: "18.0.0-e8feb11b6-20210915", rendererPackageName: "react-native-renderer", rendererConfig: { getInspectorDataForViewTag: function() { @@ -8458,11 +8566,11 @@ var roots = new Map(), }.bind(null, findNodeHandle) } }; -var internals$jscomp$inline_1244 = { - bundleType: devToolsConfig$jscomp$inline_983.bundleType, - version: devToolsConfig$jscomp$inline_983.version, - rendererPackageName: devToolsConfig$jscomp$inline_983.rendererPackageName, - rendererConfig: devToolsConfig$jscomp$inline_983.rendererConfig, +var internals$jscomp$inline_1241 = { + bundleType: devToolsConfig$jscomp$inline_980.bundleType, + version: devToolsConfig$jscomp$inline_980.version, + rendererPackageName: devToolsConfig$jscomp$inline_980.rendererPackageName, + rendererConfig: devToolsConfig$jscomp$inline_980.rendererConfig, overrideHookState: null, overrideHookStateDeletePath: null, overrideHookStateRenamePath: null, @@ -8478,7 +8586,7 @@ var internals$jscomp$inline_1244 = { return null === fiber ? null : fiber.stateNode; }, findFiberByHostInstance: - devToolsConfig$jscomp$inline_983.findFiberByHostInstance || + devToolsConfig$jscomp$inline_980.findFiberByHostInstance || emptyFindFiberByHostInstance, findHostInstancesForRefresh: null, scheduleRefresh: null, @@ -8486,19 +8594,19 @@ var internals$jscomp$inline_1244 = { setRefreshHandler: null, getCurrentFiber: null, getIsStrictMode: null, - reconcilerVersion: "18.0.0-95d762e40-20210908" + reconcilerVersion: "18.0.0-e8feb11b6-20210915" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { - var hook$jscomp$inline_1245 = __REACT_DEVTOOLS_GLOBAL_HOOK__; + var hook$jscomp$inline_1242 = __REACT_DEVTOOLS_GLOBAL_HOOK__; if ( - !hook$jscomp$inline_1245.isDisabled && - hook$jscomp$inline_1245.supportsFiber + !hook$jscomp$inline_1242.isDisabled && + hook$jscomp$inline_1242.supportsFiber ) try { - (rendererID = hook$jscomp$inline_1245.inject( - internals$jscomp$inline_1244 + (rendererID = hook$jscomp$inline_1242.inject( + internals$jscomp$inline_1241 )), - (injectedHook = hook$jscomp$inline_1245); + (injectedHook = hook$jscomp$inline_1242); } catch (err) {} } exports.createPortal = function(children, containerTag) { diff --git a/Libraries/Renderer/implementations/ReactNativeRenderer-dev.fb.js b/Libraries/Renderer/implementations/ReactNativeRenderer-dev.fb.js index 9f9b38a152..0f1cae0b61 100644 --- a/Libraries/Renderer/implementations/ReactNativeRenderer-dev.fb.js +++ b/Libraries/Renderer/implementations/ReactNativeRenderer-dev.fb.js @@ -7,7 +7,7 @@ * @noflow * @nolint * @preventMunge - * @generated SignedSource<<0aa534c810fde3c5289b4c849e474d9e>> + * @generated SignedSource<> */ 'use strict'; @@ -3140,6 +3140,7 @@ var enableProfilerTimer = true; var enableProfilerCommitHooks = true; var enableLazyElements = false; var warnAboutStringRefs = false; +var warnOnSubscriptionInsideStartTransition = false; var enableNewReconciler = false; var enableLazyContextPropagation = false; @@ -3190,21 +3191,25 @@ var HydratingAndUpdate = var Visibility = /* */ 4096; -var LifecycleEffectMask = Passive | Update | Callback | Ref | Snapshot; // Union of all commit flags (flags with the lifetime of a particular commit) +var StoreConsistency = + /* */ + 8192; +var LifecycleEffectMask = + Passive | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit) var HostEffectMask = /* */ - 8191; // These are not really side effects, but we still reuse this field. + 16383; // These are not really side effects, but we still reuse this field. var Incomplete = /* */ - 8192; + 16384; var ShouldCapture = /* */ - 16384; + 32768; var ForceUpdateForLegacySuspense = /* */ - 32768; + 65536; // e.g. a fiber uses a passive effect (even if there are no updates on this particular render). // This enables us to defer more work in the unmount case, // since we can defer traversing the tree during layout to look for Passive effects, @@ -3212,22 +3217,22 @@ var ForceUpdateForLegacySuspense = var RefStatic = /* */ - 262144; + 524288; var LayoutStatic = /* */ - 524288; + 1048576; var PassiveStatic = /* */ - 1048576; // These flags allow us to traverse to fibers that have effects on mount + 2097152; // These flags allow us to traverse to fibers that have effects on mount // without traversing the entire tree after every commit for // double invoking var MountLayoutDev = /* */ - 2097152; + 4194304; var MountPassiveDev = /* */ - 4194304; // Groups of flags that are used in the commit phase to skip over trees that + 8388608; // Groups of flags that are used in the commit phase to skip over trees that // don't contain effects, by checking subtreeFlags. var BeforeMutationMask = // TODO: Remove Update flag from before mutation phase by re-landing Visibility @@ -4742,16 +4747,10 @@ function includesOnlyRetries(lanes) { function includesOnlyTransitions(lanes) { return (lanes & TransitionLanes) === lanes; } -function shouldTimeSlice(root, lanes) { - if ((lanes & root.expiredLanes) !== NoLanes) { - // At least one of these lanes expired. To prevent additional starvation, - // finish rendering without yielding execution. - return false; - } - +function includesBlockingLane(root, lanes) { if ((root.current.mode & ConcurrentUpdatesByDefaultMode) !== NoMode) { // Concurrent updates by default always use time slicing. - return true; + return false; } var SyncDefaultLanes = @@ -4759,7 +4758,12 @@ function shouldTimeSlice(root, lanes) { InputContinuousLane | DefaultHydrationLane | DefaultLane; - return (lanes & SyncDefaultLanes) === NoLanes; + return (lanes & SyncDefaultLanes) !== NoLanes; +} +function includesExpiredLane(root, lanes) { + // This is a separate check from includesBlockingLane because a lane can + // expire after a render has already started. + return (lanes & root.expiredLanes) !== NoLanes; } function isTransitionLane(lane) { return (lane & TransitionLanes) !== 0; @@ -6069,6 +6073,18 @@ function findCurrentUnmaskedContext(fiber) { var LegacyRoot = 0; var ConcurrentRoot = 1; +/** + * inlined Object.is polyfill to avoid requiring consumers ship their own + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is + */ +function is(x, y) { + return ( + (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare + ); +} + +var objectIs = typeof Object.is === "function" ? Object.is : is; + var syncQueue = null; var includesLegacySyncCallbacks = false; var isFlushingSyncQueue = false; @@ -6138,7 +6154,7 @@ function flushSyncCallbacks() { return null; } -var ReactVersion = "18.0.0-95d762e40-20210908"; +var ReactVersion = "18.0.0-e8feb11b6-20210915"; var ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig; var NoTransition = 0; @@ -6146,18 +6162,6 @@ function requestCurrentTransition() { return ReactCurrentBatchConfig.transition; } -/** - * inlined Object.is polyfill to avoid requiring consumers ship their own - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is - */ -function is(x, y) { - return ( - (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare - ); -} - -var objectIs = typeof Object.is === "function" ? Object.is : is; - /** * Performs equality by iterating through keys on an object and returning false * when any key has values which are not strictly equal between the arguments. @@ -10092,19 +10096,22 @@ function findFirstSuspended(row) { } var NoFlags$1 = - /* */ + /* */ 0; // Represents whether effect should fire. var HasEffect = /* */ 1; // Represents the phase in which the effect (not the clean-up) fires. +var Insertion = + /* */ + 2; var Layout = /* */ - 2; + 4; var Passive$1 = /* */ - 4; + 8; var isHydrating = false; @@ -10667,7 +10674,8 @@ function updateWorkInProgressHook() { function createFunctionComponentUpdateQueue() { return { - lastEffect: null + lastEffect: null, + stores: null }; } @@ -11196,6 +11204,7 @@ function updateMutableSource(source, getSnapshot, subscribe) { } function mountSyncExternalStore(subscribe, getSnapshot) { + var fiber = currentlyRenderingFiber$1; var hook = mountWorkInProgressHook(); // Read the current snapshot from the store on every render. This breaks the // normal rules of React, and only works because store updates are // always synchronous. @@ -11219,11 +11228,43 @@ function mountSyncExternalStore(subscribe, getSnapshot) { value: nextSnapshot, getSnapshot: getSnapshot }; - hook.queue = inst; - return useSyncExternalStore(hook, inst, subscribe, getSnapshot, nextSnapshot); + hook.queue = inst; // Schedule an effect to subscribe to the store. + + mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); // Schedule an effect to update the mutable instance fields. We will update + // this whenever subscribe, getSnapshot, or value changes. Because there's no + // clean-up function, and we track the deps correctly, we can call pushEffect + // directly, without storing any additional state. For the same reason, we + // don't need to set a static flag, either. + // TODO: We can move this to the passive phase once we add a pre-commit + // consistency check. See the next comment. + + fiber.flags |= Passive; + pushEffect( + HasEffect | Passive$1, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + undefined, + null + ); // Unless we're rendering a blocking lane, schedule a consistency check. Right + // before committing, we will walk the tree and check if any of the stores + // were mutated. + + var root = getWorkInProgressRoot(); + + if (!(root !== null)) { + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + } + + if (!includesBlockingLane(root, renderLanes)) { + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + + return nextSnapshot; } function updateSyncExternalStore(subscribe, getSnapshot) { + var fiber = currentlyRenderingFiber$1; var hook = updateWorkInProgressHook(); // Read the current snapshot from the store on every render. This breaks the // normal rules of React, and only works because store updates are // always synchronous. @@ -11243,69 +11284,102 @@ function updateSyncExternalStore(subscribe, getSnapshot) { } var prevSnapshot = hook.memoizedState; + var snapshotChanged = !objectIs(prevSnapshot, nextSnapshot); - if (!objectIs(prevSnapshot, nextSnapshot)) { + if (snapshotChanged) { hook.memoizedState = nextSnapshot; markWorkInProgressReceivedUpdate(); } var inst = hook.queue; - return useSyncExternalStore(hook, inst, subscribe, getSnapshot, nextSnapshot); + updateEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [ + subscribe + ]); // Whenever getSnapshot or subscribe changes, we need to check in the + // commit phase if there was an interleaved mutation. In concurrent mode + // this can happen all the time, but even in synchronous mode, an earlier + // effect may have mutated the store. + + if ( + inst.getSnapshot !== getSnapshot || + snapshotChanged || // Check if the susbcribe function changed. We can save some memory by + // checking whether we scheduled a subscription effect above. + (workInProgressHook !== null && + workInProgressHook.memoizedState.tag & HasEffect) + ) { + fiber.flags |= Passive; + pushEffect( + HasEffect | Passive$1, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + undefined, + null + ); // Unless we're rendering a blocking lane, schedule a consistency check. + // Right before committing, we will walk the tree and check if any of the + // stores were mutated. + + var root = getWorkInProgressRoot(); + + if (!(root !== null)) { + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + } + + if (!includesBlockingLane(root, renderLanes)) { + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + } + + return nextSnapshot; } -function useSyncExternalStore( - hook, - inst, - subscribe, - getSnapshot, - nextSnapshot -) { - var fiber = currentlyRenderingFiber$1; - var dispatcher = ReactCurrentDispatcher$1.current; // Track the latest getSnapshot function with a ref. This needs to be updated - // in the layout phase so we can access it during the tearing check that - // happens on subscribe. - // TODO: Circumvent SSR warning +function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) { + fiber.flags |= StoreConsistency; + var check = { + getSnapshot: getSnapshot, + value: renderedSnapshot + }; + var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue; - dispatcher.useLayoutEffect( - function() { - inst.value = nextSnapshot; - inst.getSnapshot = getSnapshot; // Whenever getSnapshot or subscribe changes, we need to check in the - // commit phase if there was an interleaved mutation. In concurrent mode - // this can happen all the time, but even in synchronous mode, an earlier - // effect may have mutated the store. - // TODO: Move the tearing checks to an earlier, pre-commit phase so that the - // layout effects always observe a consistent tree. + if (componentUpdateQueue === null) { + componentUpdateQueue = createFunctionComponentUpdateQueue(); + currentlyRenderingFiber$1.updateQueue = componentUpdateQueue; + componentUpdateQueue.stores = [check]; + } else { + var stores = componentUpdateQueue.stores; - if (checkIfSnapshotChanged(inst)) { - // Force a re-render. - forceStoreRerender(fiber); - } - }, - [subscribe, nextSnapshot, getSnapshot] - ); - dispatcher.useEffect( - function() { - var handleStoreChange = function() { - // TODO: Because there is no cross-renderer API for batching updates, it's - // up to the consumer of this library to wrap their subscription event - // with unstable_batchedUpdates. Should we try to detect when this isn't - // the case and print a warning in development? - // The store changed. Check if the snapshot changed since the last time we - // read from the store. - if (checkIfSnapshotChanged(inst)) { - // Force a re-render. - forceStoreRerender(fiber); - } - }; // Check for changes right before subscribing. Subsequent changes will be - // detected in the subscription handler. + if (stores === null) { + componentUpdateQueue.stores = [check]; + } else { + stores.push(check); + } + } +} - handleStoreChange(); // Subscribe to the store and return a clean-up function. +function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) { + // These are updated in the passive phase + inst.value = nextSnapshot; + inst.getSnapshot = getSnapshot; // Something may have been mutated in between render and commit. This could + // have been in an event that fired before the passive effects, or it could + // have been in a layout effect. In that case, we would have used the old + // snapsho and getSnapshot values to bail out. We need to check one more time. - return subscribe(handleStoreChange); - }, - [subscribe] - ); - return nextSnapshot; + if (checkIfSnapshotChanged(inst)) { + // Force a re-render. + forceStoreRerender(fiber); + } +} + +function subscribeToStore(fiber, inst, subscribe) { + var handleStoreChange = function() { + // The store changed. Check if the snapshot changed since the last time we + // read from the store. + if (checkIfSnapshotChanged(inst)) { + // Force a re-render. + forceStoreRerender(fiber); + } + }; // Subscribe to the store and return a clean-up function. + + return subscribe(handleStoreChange); } function checkIfSnapshotChanged(inst) { @@ -11477,6 +11551,14 @@ function updateEffect(create, deps) { return updateEffectImpl(Passive, Passive$1, create, deps); } +function mountInsertionEffect(create, deps) { + return mountEffectImpl(Update, Insertion, create, deps); +} + +function updateInsertionEffect(create, deps) { + return updateEffectImpl(Update, Insertion, create, deps); +} + function mountLayoutEffect(create, deps) { var fiberFlags = Update; @@ -11712,6 +11794,26 @@ function startTransition(setPending, callback) { } finally { setCurrentUpdatePriority(previousPriority); ReactCurrentBatchConfig$1.transition = prevTransition; + + { + if ( + prevTransition !== 1 && + warnOnSubscriptionInsideStartTransition && + ReactCurrentBatchConfig$1._updatedFibers + ) { + var updatedFibersCount = ReactCurrentBatchConfig$1._updatedFibers.size; + + if (updatedFibersCount > 10) { + warn( + "Detected a large number of updates inside startTransition. " + + "If this is due to a subscription please re-write it to use React provided hooks. " + + "Otherwise concurrent mode guarantees are off the table." + ); + } + + ReactCurrentBatchConfig$1._updatedFibers.clear(); + } + } } } @@ -11941,6 +12043,7 @@ var ContextOnlyDispatcher = { useContext: throwInvalidHookError, useEffect: throwInvalidHookError, useImperativeHandle: throwInvalidHookError, + useInsertionEffect: throwInvalidHookError, useLayoutEffect: throwInvalidHookError, useMemo: throwInvalidHookError, useReducer: throwInvalidHookError, @@ -12009,6 +12112,12 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; checkDepsAreArrayDev(deps); return mountImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + mountHookTypesDev(); + checkDepsAreArrayDev(deps); + return mountInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; mountHookTypesDev(); @@ -12114,6 +12223,11 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return mountImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + updateHookTypesDev(); + return mountInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; updateHookTypesDev(); @@ -12217,6 +12331,11 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return updateImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + updateHookTypesDev(); + return updateInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; updateHookTypesDev(); @@ -12320,6 +12439,11 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return updateImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + updateHookTypesDev(); + return updateInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; updateHookTypesDev(); @@ -12428,6 +12552,12 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; mountHookTypesDev(); return mountImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + warnInvalidHookAccess(); + mountHookTypesDev(); + return mountInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; warnInvalidHookAccess(); @@ -12547,6 +12677,12 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return updateImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; warnInvalidHookAccess(); @@ -12666,6 +12802,12 @@ var InvalidNestedHooksDispatcherOnRerenderInDEV = null; updateHookTypesDev(); return updateImperativeHandle(ref, create, deps); }, + useInsertionEffect: function(create, deps) { + currentHookNameInDev = "useInsertionEffect"; + warnInvalidHookAccess(); + updateHookTypesDev(); + return updateInsertionEffect(create, deps); + }, useLayoutEffect: function(create, deps) { currentHookNameInDev = "useLayoutEffect"; warnInvalidHookAccess(); @@ -17437,6 +17579,8 @@ function safelyDetachRef(current, nearestMountedAncestor) { if (ref !== null) { if (typeof ref === "function") { + var retVal; + try { if ( enableProfilerTimer && @@ -17445,12 +17589,12 @@ function safelyDetachRef(current, nearestMountedAncestor) { ) { try { startLayoutEffectTimer(); - ref(null); + retVal = ref(null); } finally { recordLayoutEffectDuration(current); } } else { - ref(null); + retVal = ref(null); } } catch (error) { reportUncaughtErrorInDEV(error); @@ -17680,6 +17824,16 @@ function commitHookEffectListMount(tag, finishedWork) { var destroy = effect.destroy; if (destroy !== undefined && typeof destroy !== "function") { + var hookName = void 0; + + if ((effect.tag & Layout) !== NoFlags) { + hookName = "useLayoutEffect"; + } else if ((effect.tag & Insertion) !== NoFlags) { + hookName = "useInsertionEffect"; + } else { + hookName = "useEffect"; + } + var addendum = void 0; if (destroy === null) { @@ -17688,10 +17842,13 @@ function commitHookEffectListMount(tag, finishedWork) { "up, return undefined (or nothing)."; } else if (typeof destroy.then === "function") { addendum = - "\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. " + + "\n\nIt looks like you wrote " + + hookName + + "(async () => ...) or returned a Promise. " + "Instead, write the async function inside your effect " + "and call it immediately:\n\n" + - "useEffect(() => {\n" + + hookName + + "(() => {\n" + " async function fetchData() {\n" + " // You can await here\n" + " const response = await MyAPI.getData(someId);\n" + @@ -17705,8 +17862,9 @@ function commitHookEffectListMount(tag, finishedWork) { } error( - "An effect function must not return anything besides a function, " + + "%s must not return anything besides a function, " + "which is used for clean-up.%s", + hookName, addendum ); } @@ -18181,15 +18339,17 @@ function commitAttachRef(finishedWork) { } // Moved outside to ensure DCE works with this flag if (typeof ref === "function") { + var retVal; + if (finishedWork.mode & ProfileMode) { try { startLayoutEffectTimer(); - ref(instanceToUse); + retVal = ref(instanceToUse); } finally { recordLayoutEffectDuration(finishedWork); } } else { - ref(instanceToUse); + retVal = ref(instanceToUse); } } else { { @@ -18253,7 +18413,10 @@ function commitUnmount(finishedRoot, current, nearestMountedAncestor) { tag = _effect.tag; if (destroy !== undefined) { - if ((tag & Layout) !== NoFlags$1) { + if ( + (tag & Insertion) !== NoFlags$1 || + (tag & Layout) !== NoFlags$1 + ) { if (current.mode & ProfileMode) { startLayoutEffectTimer(); safelyCallDestroy(current, nearestMountedAncestor, destroy); @@ -18728,11 +18891,17 @@ function commitWork(current, finishedWork) { case ForwardRef: case MemoComponent: case SimpleMemoComponent: { - // Layout effects are destroyed during the mutation phase so that all + commitHookEffectListUnmount( + Insertion | HasEffect, + finishedWork, + finishedWork.return + ); + commitHookEffectListMount(Insertion | HasEffect, finishedWork); // Layout effects are destroyed during the mutation phase so that all // destroy functions for all fibers are called before any create functions. // This prevents sibling component effects from interfering with each other, // e.g. a destroy function in one component should never override a ref set // by a create function in another component during the same commit. + if (finishedWork.mode & ProfileMode) { try { startLayoutEffectTimer(); @@ -19654,13 +19823,13 @@ function requestUpdateLane(fiber) { var isTransition = requestCurrentTransition() !== NoTransition; if (isTransition) { - // The algorithm for assigning an update to a lane should be stable for all // updates at the same priority within the same event. To do this, the // inputs to the algorithm must be the same. // // The trick we use is to cache the first of each of these inputs within an // event. Then reset the cached values once we can be sure the event is // over. Our heuristic for that is whenever we enter a concurrent work loop. + if (currentEventTransitionLane === NoLane) { // All transitions within the same event are assigned the same lane. currentEventTransitionLane = claimNextTransitionLane(); @@ -19990,38 +20159,26 @@ function performConcurrentWorkOnRoot(root, didTimeout) { // bug we're still investigating. Once the bug in Scheduler is fixed, // we can remove this, since we track expiration ourselves. - var exitStatus = - shouldTimeSlice(root, lanes) && !didTimeout - ? renderRootConcurrent(root, lanes) - : renderRootSync(root, lanes); + var shouldTimeSlice = + !includesBlockingLane(root, lanes) && + !includesExpiredLane(root, lanes) && + !didTimeout; + var exitStatus = shouldTimeSlice + ? renderRootConcurrent(root, lanes) + : renderRootSync(root, lanes); if (exitStatus !== RootIncomplete) { if (exitStatus === RootErrored) { - var prevExecutionContext = executionContext; - executionContext |= RetryAfterError; // If an error occurred during hydration, - // discard server response and fall back to client side render. - - if (root.hydrate) { - root.hydrate = false; - - { - errorHydratingContainer(root.containerInfo); - } - - clearContainer(root.containerInfo); - } // If something threw an error, try rendering one more time. We'll render - // synchronously to block concurrent data mutations, and we'll includes - // all pending updates are included. If it still fails after the second - // attempt, we'll give up and commit the resulting tree. - + // If something threw an error, try rendering one more time. We'll + // render synchronously to block concurrent data mutations, and we'll + // includes all pending updates are included. If it still fails after + // the second attempt, we'll give up and commit the resulting tree. var errorRetryLanes = getLanesToRetrySynchronouslyOnError(root); if (errorRetryLanes !== NoLanes) { lanes = errorRetryLanes; - exitStatus = renderRootSync(root, errorRetryLanes); + exitStatus = recoverFromConcurrentError(root, errorRetryLanes); } - - executionContext = prevExecutionContext; } if (exitStatus === RootFatalErrored) { @@ -20030,10 +20187,43 @@ function performConcurrentWorkOnRoot(root, didTimeout) { markRootSuspended$1(root, lanes); ensureRootIsScheduled(root, now()); throw fatalError; + } // Check if this render may have yielded to a concurrent event, and if so, + // confirm that any newly rendered stores are consistent. + // TODO: It's possible that even a concurrent render may never have yielded + // to the main thread, if it was fast enough, or if it expired. We could + // skip the consistency check in that case, too. + + var renderWasConcurrent = !includesBlockingLane(root, lanes); + var finishedWork = root.current.alternate; + + if ( + renderWasConcurrent && + !isRenderConsistentWithExternalStores(finishedWork) + ) { + // A store was mutated in an interleaved event. Render again, + // synchronously, to block further mutations. + exitStatus = renderRootSync(root, lanes); // We need to check again if something threw + + if (exitStatus === RootErrored) { + var _errorRetryLanes = getLanesToRetrySynchronouslyOnError(root); + + if (_errorRetryLanes !== NoLanes) { + lanes = _errorRetryLanes; + exitStatus = recoverFromConcurrentError(root, _errorRetryLanes); // We assume the tree is now consistent because we didn't yield to any + // concurrent events. + } + } + + if (exitStatus === RootFatalErrored) { + var _fatalError = workInProgressRootFatalError; + prepareFreshStack(root, NoLanes); + markRootSuspended$1(root, lanes); + ensureRootIsScheduled(root, now()); + throw _fatalError; + } } // We now have a consistent tree. The next step is either to commit it, // or, if something suspended, wait to commit it after a timeout. - var finishedWork = root.current.alternate; root.finishedWork = finishedWork; root.finishedLanes = lanes; finishConcurrentRender(root, exitStatus, lanes); @@ -20050,6 +20240,26 @@ function performConcurrentWorkOnRoot(root, didTimeout) { return null; } +function recoverFromConcurrentError(root, errorRetryLanes) { + var prevExecutionContext = executionContext; + executionContext |= RetryAfterError; // If an error occurred during hydration, discard server response and fall + // back to client side render. + + if (root.hydrate) { + root.hydrate = false; + + { + errorHydratingContainer(root.containerInfo); + } + + clearContainer(root.containerInfo); + } + + var exitStatus = renderRootSync(root, errorRetryLanes); + executionContext = prevExecutionContext; + return exitStatus; +} + function finishConcurrentRender(root, exitStatus, lanes) { switch (exitStatus) { case RootIncomplete: @@ -20168,6 +20378,68 @@ function finishConcurrentRender(root, exitStatus, lanes) { } } +function isRenderConsistentWithExternalStores(finishedWork) { + // Search the rendered tree for external store reads, and check whether the + // stores were mutated in a concurrent event. Intentionally using a iterative + // loop instead of recursion so we can exit early. + var node = finishedWork; + + while (true) { + if (node.flags & StoreConsistency) { + var updateQueue = node.updateQueue; + + if (updateQueue !== null) { + var checks = updateQueue.stores; + + if (checks !== null) { + for (var i = 0; i < checks.length; i++) { + var check = checks[i]; + var getSnapshot = check.getSnapshot; + var renderedValue = check.value; + + try { + if (!objectIs(getSnapshot(), renderedValue)) { + // Found an inconsistent store. + return false; + } + } catch (error) { + // If `getSnapshot` throws, return `false`. This will schedule + // a re-render, and the error will be rethrown during render. + return false; + } + } + } + } + } + + var child = node.child; + + if (node.subtreeFlags & StoreConsistency && child !== null) { + child.return = node; + node = child; + continue; + } + + if (node === finishedWork) { + return true; + } + + while (node.sibling === null) { + if (node.return === null || node.return === finishedWork) { + return true; + } + + node = node.return; + } + + node.sibling.return = node.return; + node = node.sibling; + } // Flow doesn't know this is unreachable, but eslint does + // eslint-disable-next-line no-unreachable + + return true; +} + function markRootSuspended$1(root, suspendedLanes) { // When suspending, we should always exclude lanes that were pinged or (more // rarely, since we try to avoid it) updated during the render phase. @@ -20912,6 +21184,15 @@ function commitRootImpl(root, renderPriorityLevel) { } // Read this again, since an effect might have updated it remainingLanes = root.pendingLanes; // Check if there's remaining work on this root + // TODO: This is part of the `componentDidCatch` implementation. Its purpose + // is to detect whether something might have called setState inside + // `componentDidCatch`. The mechanism is known to be flawed because `setState` + // inside `componentDidCatch` is itself flawed — that's why we recommend + // `getDerivedStateFromError` instead. However, it could be improved by + // checking if remainingLanes includes Sync work, instead of whether there's + // any work remaining at all (which would also include stuff like Suspense + // retries or transitions). It's been like this for a while, though, so fixing + // it probably isn't that urgent. if (remainingLanes === NoLanes) { // If there's no remaining work, we can clear the set of already failed @@ -20925,22 +21206,6 @@ function commitRootImpl(root, renderPriorityLevel) { } } - if (includesSomeLane(remainingLanes, SyncLane)) { - { - markNestedUpdateScheduled(); - } // Count the number of times the root synchronously re-renders without - // finishing. If there are too many, it indicates an infinite update loop. - - if (root === rootWithNestedUpdates) { - nestedUpdateCount++; - } else { - nestedUpdateCount = 0; - rootWithNestedUpdates = root; - } - } else { - nestedUpdateCount = 0; - } - onCommitRoot(finishedWork.stateNode, renderPriorityLevel); { @@ -20971,6 +21236,24 @@ function commitRootImpl(root, renderPriorityLevel) { root.tag !== LegacyRoot ) { flushPassiveEffects(); + } // Read this again, since a passive effect might have updated it + + remainingLanes = root.pendingLanes; + + if (includesSomeLane(remainingLanes, SyncLane)) { + { + markNestedUpdateScheduled(); + } // Count the number of times the root synchronously re-renders without + // finishing. If there are too many, it indicates an infinite update loop. + + if (root === rootWithNestedUpdates) { + nestedUpdateCount++; + } else { + nestedUpdateCount = 0; + rootWithNestedUpdates = root; + } + } else { + nestedUpdateCount = 0; } // If layout work was scheduled, flush it now. flushSyncCallbacks(); diff --git a/Libraries/Renderer/implementations/ReactNativeRenderer-prod.fb.js b/Libraries/Renderer/implementations/ReactNativeRenderer-prod.fb.js index 1bd87b0db7..e748bf48d9 100644 --- a/Libraries/Renderer/implementations/ReactNativeRenderer-prod.fb.js +++ b/Libraries/Renderer/implementations/ReactNativeRenderer-prod.fb.js @@ -7,7 +7,7 @@ * @noflow * @nolint * @preventMunge - * @generated SignedSource<<5efc4e18a211a1edf88bf8bd3a184783>> + * @generated SignedSource<<5c359ba9bcb80e09d414cf763d94b5d5>> */ "use strict"; @@ -930,7 +930,7 @@ eventPluginOrder = Array.prototype.slice.call([ "ReactNativeBridgeEventPlugin" ]); recomputePluginOrdering(); -var injectedNamesToPlugins$jscomp$inline_224 = { +var injectedNamesToPlugins$jscomp$inline_225 = { ResponderEventPlugin: ResponderEventPlugin, ReactNativeBridgeEventPlugin: { eventTypes: {}, @@ -965,34 +965,34 @@ var injectedNamesToPlugins$jscomp$inline_224 = { } } }, - isOrderingDirty$jscomp$inline_225 = !1, - pluginName$jscomp$inline_226; -for (pluginName$jscomp$inline_226 in injectedNamesToPlugins$jscomp$inline_224) + isOrderingDirty$jscomp$inline_226 = !1, + pluginName$jscomp$inline_227; +for (pluginName$jscomp$inline_227 in injectedNamesToPlugins$jscomp$inline_225) if ( - injectedNamesToPlugins$jscomp$inline_224.hasOwnProperty( - pluginName$jscomp$inline_226 + injectedNamesToPlugins$jscomp$inline_225.hasOwnProperty( + pluginName$jscomp$inline_227 ) ) { - var pluginModule$jscomp$inline_227 = - injectedNamesToPlugins$jscomp$inline_224[pluginName$jscomp$inline_226]; + var pluginModule$jscomp$inline_228 = + injectedNamesToPlugins$jscomp$inline_225[pluginName$jscomp$inline_227]; if ( - !namesToPlugins.hasOwnProperty(pluginName$jscomp$inline_226) || - namesToPlugins[pluginName$jscomp$inline_226] !== - pluginModule$jscomp$inline_227 + !namesToPlugins.hasOwnProperty(pluginName$jscomp$inline_227) || + namesToPlugins[pluginName$jscomp$inline_227] !== + pluginModule$jscomp$inline_228 ) { - if (namesToPlugins[pluginName$jscomp$inline_226]) + if (namesToPlugins[pluginName$jscomp$inline_227]) throw Error( "EventPluginRegistry: Cannot inject two different event plugins using the same name, `" + - pluginName$jscomp$inline_226 + + pluginName$jscomp$inline_227 + "`." ); namesToPlugins[ - pluginName$jscomp$inline_226 - ] = pluginModule$jscomp$inline_227; - isOrderingDirty$jscomp$inline_225 = !0; + pluginName$jscomp$inline_227 + ] = pluginModule$jscomp$inline_228; + isOrderingDirty$jscomp$inline_226 = !0; } } -isOrderingDirty$jscomp$inline_225 && recomputePluginOrdering(); +isOrderingDirty$jscomp$inline_226 && recomputePluginOrdering(); var instanceCache = new Map(), instanceProps = new Map(); function getInstanceFromTag(tag) { @@ -1841,6 +1841,9 @@ function getLanesToRetrySynchronouslyOnError(root) { root = root.pendingLanes & -1073741825; return 0 !== root ? root : root & 1073741824 ? 1073741824 : 0; } +function includesBlockingLane(root, lanes) { + return 0 !== (root.current.mode & 32) ? !1 : 0 !== (lanes & 30); +} function createLaneMap(initial) { for (var laneMap = [], i = 0; 31 > i; i++) laneMap.push(initial); return laneMap; @@ -2044,7 +2047,11 @@ function invalidateContextProvider(workInProgress, type, didChange) { : pop(didPerformWorkStackCursor); push(didPerformWorkStackCursor, didChange); } -var syncQueue = null, +function is(x, y) { + return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); +} +var objectIs = "function" === typeof Object.is ? Object.is : is, + syncQueue = null, includesLegacySyncCallbacks = !1, isFlushingSyncQueue = !1; function flushSyncCallbacks() { @@ -2073,10 +2080,6 @@ function flushSyncCallbacks() { return null; } var ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig; -function is(x, y) { - return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); -} -var objectIs = "function" === typeof Object.is ? Object.is : is; function shallowEqual(objA, objB) { if (objectIs(objA, objB)) return !0; if ( @@ -2373,7 +2376,7 @@ function processUpdateQueue( newState = workInProgress; break a; case 3: - workInProgress.flags = (workInProgress.flags & -16385) | 128; + workInProgress.flags = (workInProgress.flags & -32769) | 128; case 0: workInProgress = update.payload; updateLane = @@ -3604,42 +3607,52 @@ function updateMutableSource(source, getSnapshot, subscribe) { return useMutableSource(hook, source, getSnapshot, subscribe); } function mountSyncExternalStore(subscribe, getSnapshot) { - var hook = mountWorkInProgressHook(), + var fiber = currentlyRenderingFiber$1, + hook = mountWorkInProgressHook(), nextSnapshot = getSnapshot(); hook.memoizedState = nextSnapshot; var inst = { value: nextSnapshot, getSnapshot: getSnapshot }; hook.queue = inst; - return useSyncExternalStore(hook, inst, subscribe, getSnapshot, nextSnapshot); -} -function useSyncExternalStore( - hook, - inst, - subscribe, - getSnapshot, - nextSnapshot -) { - var fiber = currentlyRenderingFiber$1; - hook = ReactCurrentDispatcher$1.current; - hook.useLayoutEffect( - function() { - inst.value = nextSnapshot; - inst.getSnapshot = getSnapshot; - checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); - }, - [subscribe, nextSnapshot, getSnapshot] - ); - hook.useEffect( - function() { - function handleStoreChange() { - checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); - } - handleStoreChange(); - return subscribe(handleStoreChange); - }, - [subscribe] + mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); + fiber.flags |= 1024; + pushEffect( + 9, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + void 0, + null ); + subscribe = workInProgressRoot; + if (null === subscribe) + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + includesBlockingLane(subscribe, renderLanes) || + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); return nextSnapshot; } +function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) { + fiber.flags |= 8192; + fiber = { getSnapshot: getSnapshot, value: renderedSnapshot }; + getSnapshot = currentlyRenderingFiber$1.updateQueue; + null === getSnapshot + ? ((getSnapshot = { lastEffect: null, stores: null }), + (currentlyRenderingFiber$1.updateQueue = getSnapshot), + (getSnapshot.stores = [fiber])) + : ((renderedSnapshot = getSnapshot.stores), + null === renderedSnapshot + ? (getSnapshot.stores = [fiber]) + : renderedSnapshot.push(fiber)); +} +function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) { + inst.value = nextSnapshot; + inst.getSnapshot = getSnapshot; + checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); +} +function subscribeToStore(fiber, inst, subscribe) { + return subscribe(function() { + checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); + }); +} function checkIfSnapshotChanged(inst) { var latestGetSnapshot = inst.getSnapshot; inst = inst.value; @@ -3674,7 +3687,7 @@ function pushEffect(tag, create, destroy, deps) { tag = { tag: tag, create: create, destroy: destroy, deps: deps, next: null }; create = currentlyRenderingFiber$1.updateQueue; null === create - ? ((create = { lastEffect: null }), + ? ((create = { lastEffect: null, stores: null }), (currentlyRenderingFiber$1.updateQueue = create), (create.lastEffect = tag.next = tag)) : ((destroy = create.lastEffect), @@ -3715,13 +3728,16 @@ function updateEffectImpl(fiberFlags, hookFlags, create, deps) { hook.memoizedState = pushEffect(1 | hookFlags, create, destroy, deps); } function mountEffect(create, deps) { - return mountEffectImpl(1049600, 4, create, deps); + return mountEffectImpl(2098176, 8, create, deps); } function updateEffect(create, deps) { - return updateEffectImpl(1024, 4, create, deps); + return updateEffectImpl(1024, 8, create, deps); +} +function updateInsertionEffect(create, deps) { + return updateEffectImpl(4, 2, create, deps); } function updateLayoutEffect(create, deps) { - return updateEffectImpl(4, 2, create, deps); + return updateEffectImpl(4, 4, create, deps); } function imperativeHandleEffect(create, ref) { if ("function" === typeof ref) @@ -3745,7 +3761,7 @@ function updateImperativeHandle(ref, create, deps) { deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null; return updateEffectImpl( 4, - 2, + 4, imperativeHandleEffect.bind(null, create, ref), deps ); @@ -3863,6 +3879,7 @@ var ContextOnlyDispatcher = { useContext: throwInvalidHookError, useEffect: throwInvalidHookError, useImperativeHandle: throwInvalidHookError, + useInsertionEffect: throwInvalidHookError, useLayoutEffect: throwInvalidHookError, useMemo: throwInvalidHookError, useReducer: throwInvalidHookError, @@ -3891,12 +3908,15 @@ var ContextOnlyDispatcher = { deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null; return mountEffectImpl( 4, - 2, + 4, imperativeHandleEffect.bind(null, create, ref), deps ); }, useLayoutEffect: function(create, deps) { + return mountEffectImpl(4, 4, create, deps); + }, + useInsertionEffect: function(create, deps) { return mountEffectImpl(4, 2, create, deps); }, useMemo: function(nextCreate, deps) { @@ -3979,6 +3999,7 @@ var ContextOnlyDispatcher = { useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, + useInsertionEffect: updateInsertionEffect, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: updateReducer, @@ -4012,17 +4033,44 @@ var ContextOnlyDispatcher = { }, useMutableSource: updateMutableSource, useSyncExternalStore: function(subscribe, getSnapshot) { - var hook = updateWorkInProgressHook(), - nextSnapshot = getSnapshot(); - objectIs(hook.memoizedState, nextSnapshot) || + var fiber = currentlyRenderingFiber$1, + hook = updateWorkInProgressHook(), + nextSnapshot = getSnapshot(), + snapshotChanged = !objectIs(hook.memoizedState, nextSnapshot); + snapshotChanged && ((hook.memoizedState = nextSnapshot), (didReceiveUpdate = !0)); - return useSyncExternalStore( - hook, - hook.queue, - subscribe, - getSnapshot, - nextSnapshot - ); + hook = hook.queue; + updateEffect(subscribeToStore.bind(null, fiber, hook, subscribe), [ + subscribe + ]); + if ( + hook.getSnapshot !== getSnapshot || + snapshotChanged || + (null !== workInProgressHook && + workInProgressHook.memoizedState.tag & 1) + ) { + fiber.flags |= 1024; + pushEffect( + 9, + updateStoreInstance.bind( + null, + fiber, + hook, + nextSnapshot, + getSnapshot + ), + void 0, + null + ); + subscribe = workInProgressRoot; + if (null === subscribe) + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + includesBlockingLane(subscribe, renderLanes) || + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + return nextSnapshot; }, useOpaqueIdentifier: function() { return updateReducer(basicStateReducer)[0]; @@ -4035,6 +4083,7 @@ var ContextOnlyDispatcher = { useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, + useInsertionEffect: updateInsertionEffect, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: rerenderReducer, @@ -4204,8 +4253,8 @@ function bubbleProperties(completedWork) { if (didBailout) for (var child$39 = completedWork.child; null !== child$39; ) (newChildLanes |= child$39.lanes | child$39.childLanes), - (subtreeFlags |= child$39.subtreeFlags & 1835008), - (subtreeFlags |= child$39.flags & 1835008), + (subtreeFlags |= child$39.subtreeFlags & 3670016), + (subtreeFlags |= child$39.flags & 3670016), (child$39.return = completedWork), (child$39 = child$39.sibling); else @@ -4419,7 +4468,7 @@ function completeWork(current, workInProgress, renderLanes) { for (newProps = workInProgress.child; null !== newProps; ) (renderLanes = newProps), (type = current), - (renderLanes.flags &= 1835010), + (renderLanes.flags &= 3670018), (updatePayload = renderLanes.alternate), null === updatePayload ? ((renderLanes.childLanes = 0), @@ -4649,7 +4698,7 @@ function updateSimpleMemoComponent( current.ref === workInProgress.ref ) if (((didReceiveUpdate = !1), 0 !== (current.lanes & renderLanes))) - 0 !== (current.flags & 32768) && (didReceiveUpdate = !0); + 0 !== (current.flags & 65536) && (didReceiveUpdate = !0); else return ( (workInProgress.lanes = current.lanes), @@ -5193,7 +5242,7 @@ function updateSuspenseFallbackChildren( (primaryChildren.pendingProps = primaryChildProps), (workInProgress.deletions = null)) : ((primaryChildren = createWorkInProgress(current, primaryChildProps)), - (primaryChildren.subtreeFlags = current.subtreeFlags & 1835008)); + (primaryChildren.subtreeFlags = current.subtreeFlags & 3670016)); null !== currentFallbackChildFragment ? (fallbackChildren = createWorkInProgress( currentFallbackChildFragment, @@ -5421,8 +5470,8 @@ function unwindWork(workInProgress) { case 1: isContextProvider(workInProgress.type) && popContext(); var flags = workInProgress.flags; - return flags & 16384 - ? ((workInProgress.flags = (flags & -16385) | 128), workInProgress) + return flags & 32768 + ? ((workInProgress.flags = (flags & -32769) | 128), workInProgress) : null; case 3: popHostContainer(); @@ -5434,7 +5483,7 @@ function unwindWork(workInProgress) { throw Error( "The root failed to unmount after an error. This is likely a bug in React. Please file an issue." ); - workInProgress.flags = (flags & -16385) | 128; + workInProgress.flags = (flags & -32769) | 128; return workInProgress; case 5: return popHostContext(workInProgress), null; @@ -5442,8 +5491,8 @@ function unwindWork(workInProgress) { return ( pop(suspenseStackCursor), (flags = workInProgress.flags), - flags & 16384 - ? ((workInProgress.flags = (flags & -16385) | 128), workInProgress) + flags & 32768 + ? ((workInProgress.flags = (flags & -32769) | 128), workInProgress) : null ); case 19: @@ -5596,7 +5645,10 @@ function commitUnmount(finishedRoot, current, nearestMountedAncestor$jscomp$0) { var _effect = effect, destroy = _effect.destroy; _effect = _effect.tag; - if (void 0 !== destroy && 0 !== (_effect & 2)) { + if ( + void 0 !== destroy && + (0 !== (_effect & 2) || 0 !== (_effect & 4)) + ) { _effect = current; var nearestMountedAncestor = nearestMountedAncestor$jscomp$0; try { @@ -5923,6 +5975,8 @@ function commitWork(current, finishedWork) { case 14: case 15: commitHookEffectListUnmount(3, finishedWork, finishedWork.return); + commitHookEffectListMount(3, finishedWork); + commitHookEffectListUnmount(5, finishedWork, finishedWork.return); return; case 1: return; @@ -6157,7 +6211,7 @@ function commitLayoutEffects(finishedWork) { case 0: case 11: case 15: - commitHookEffectListMount(3, firstChild); + commitHookEffectListMount(5, firstChild); break; case 1: var instance = firstChild.stateNode; @@ -6421,15 +6475,15 @@ function performConcurrentWorkOnRoot(root, didTimeout) { root === workInProgressRoot ? workInProgressRootRenderLanes : 0 ); if (0 === lanes) return null; - var JSCompiler_inline_result = - 0 !== (lanes & root.expiredLanes) - ? !1 - : 0 !== (root.current.mode & 32) - ? !0 - : 0 === (lanes & 30); - if (JSCompiler_inline_result && !didTimeout) { + if ( + includesBlockingLane(root, lanes) || + 0 !== (lanes & root.expiredLanes) || + didTimeout + ) + didTimeout = renderRootSync(root, lanes); + else { didTimeout = lanes; - JSCompiler_inline_result = executionContext; + var prevExecutionContext = executionContext; executionContext |= 2; var prevDispatcher = pushDispatcher(); if ( @@ -6448,30 +6502,44 @@ function performConcurrentWorkOnRoot(root, didTimeout) { while (1); resetContextDependencies(); ReactCurrentDispatcher$2.current = prevDispatcher; - executionContext = JSCompiler_inline_result; + executionContext = prevExecutionContext; null !== workInProgress ? (didTimeout = 0) : ((workInProgressRoot = null), (workInProgressRootRenderLanes = 0), (didTimeout = workInProgressRootExitStatus)); - } else didTimeout = renderRootSync(root, lanes); + } if (0 !== didTimeout) { 2 === didTimeout && - ((JSCompiler_inline_result = executionContext), - (executionContext |= 8), - root.hydrate && (root.hydrate = !1), - (prevDispatcher = getLanesToRetrySynchronouslyOnError(root)), - 0 !== prevDispatcher && - ((lanes = prevDispatcher), - (didTimeout = renderRootSync(root, prevDispatcher))), - (executionContext = JSCompiler_inline_result)); + ((prevExecutionContext = getLanesToRetrySynchronouslyOnError(root)), + 0 !== prevExecutionContext && + ((lanes = prevExecutionContext), + (didTimeout = recoverFromConcurrentError(root, prevExecutionContext)))); if (1 === didTimeout) throw ((originalCallbackNode = workInProgressRootFatalError), prepareFreshStack(root, 0), markRootSuspended$1(root, lanes), ensureRootIsScheduled(root, now()), originalCallbackNode); - root.finishedWork = root.current.alternate; + prevDispatcher = !includesBlockingLane(root, lanes); + prevExecutionContext = root.current.alternate; + if ( + prevDispatcher && + !isRenderConsistentWithExternalStores(prevExecutionContext) && + ((didTimeout = renderRootSync(root, lanes)), + 2 === didTimeout && + ((prevDispatcher = getLanesToRetrySynchronouslyOnError(root)), + 0 !== prevDispatcher && + ((lanes = prevDispatcher), + (didTimeout = recoverFromConcurrentError(root, prevDispatcher)))), + 1 === didTimeout) + ) + throw ((originalCallbackNode = workInProgressRootFatalError), + prepareFreshStack(root, 0), + markRootSuspended$1(root, lanes), + ensureRootIsScheduled(root, now()), + originalCallbackNode); + root.finishedWork = prevExecutionContext; root.finishedLanes = lanes; switch (didTimeout) { case 0: @@ -6488,10 +6556,10 @@ function performConcurrentWorkOnRoot(root, didTimeout) { 10 < didTimeout) ) { if (0 !== getNextLanes(root, 0)) break; - JSCompiler_inline_result = root.suspendedLanes; - if ((JSCompiler_inline_result & lanes) !== lanes) { + prevExecutionContext = root.suspendedLanes; + if ((prevExecutionContext & lanes) !== lanes) { requestEventTime(); - root.pingedLanes |= root.suspendedLanes & JSCompiler_inline_result; + root.pingedLanes |= root.suspendedLanes & prevExecutionContext; break; } root.timeoutHandle = scheduleTimeout( @@ -6506,15 +6574,14 @@ function performConcurrentWorkOnRoot(root, didTimeout) { markRootSuspended$1(root, lanes); if ((lanes & 4194240) === lanes) break; didTimeout = root.eventTimes; - for (JSCompiler_inline_result = -1; 0 < lanes; ) { + for (prevExecutionContext = -1; 0 < lanes; ) { var index$5 = 31 - clz32(lanes); prevDispatcher = 1 << index$5; index$5 = didTimeout[index$5]; - index$5 > JSCompiler_inline_result && - (JSCompiler_inline_result = index$5); + index$5 > prevExecutionContext && (prevExecutionContext = index$5); lanes &= ~prevDispatcher; } - lanes = JSCompiler_inline_result; + lanes = prevExecutionContext; lanes = now() - lanes; lanes = (120 > lanes @@ -6551,6 +6618,48 @@ function performConcurrentWorkOnRoot(root, didTimeout) { ? performConcurrentWorkOnRoot.bind(null, root) : null; } +function recoverFromConcurrentError(root, errorRetryLanes) { + var prevExecutionContext = executionContext; + executionContext |= 8; + root.hydrate && (root.hydrate = !1); + root = renderRootSync(root, errorRetryLanes); + executionContext = prevExecutionContext; + return root; +} +function isRenderConsistentWithExternalStores(finishedWork) { + for (var node = finishedWork; ; ) { + if (node.flags & 8192) { + var updateQueue = node.updateQueue; + if ( + null !== updateQueue && + ((updateQueue = updateQueue.stores), null !== updateQueue) + ) + for (var i = 0; i < updateQueue.length; i++) { + var check = updateQueue[i], + getSnapshot = check.getSnapshot; + check = check.value; + try { + if (!objectIs(getSnapshot(), check)) return !1; + } catch (error) { + return !1; + } + } + } + updateQueue = node.child; + if (node.subtreeFlags & 8192 && null !== updateQueue) + (updateQueue.return = node), (node = updateQueue); + else { + if (node === finishedWork) break; + for (; null === node.sibling; ) { + if (null === node.return || node.return === finishedWork) return !0; + node = node.return; + } + node.sibling.return = node.return; + node = node.sibling; + } + } + return !0; +} function markRootSuspended$1(root, suspendedLanes) { suspendedLanes &= ~workInProgressRootPingedLanes; suspendedLanes &= ~workInProgressRootUpdatedLanes; @@ -6698,7 +6807,7 @@ function handleError(root$jscomp$0, thrownValue) { sourceFiber = erroredWork, value = thrownValue; thrownValue = workInProgressRootRenderLanes; - sourceFiber.flags |= 8192; + sourceFiber.flags |= 16384; if ( null !== value && "object" === typeof value && @@ -6749,8 +6858,8 @@ function handleError(root$jscomp$0, thrownValue) { workInProgress$32 !== returnFiber ) { workInProgress$32.flags |= 128; - sourceFiber.flags |= 32768; - sourceFiber.flags &= -10053; + sourceFiber.flags |= 65536; + sourceFiber.flags &= -26437; if (1 === sourceFiber.tag) if (null === sourceFiber.alternate) sourceFiber.tag = 17; else { @@ -6781,7 +6890,7 @@ function handleError(root$jscomp$0, thrownValue) { ); wakeable.then(ping, ping); } - workInProgress$32.flags |= 16384; + workInProgress$32.flags |= 32768; workInProgress$32.lanes = thrownValue; break a; } @@ -6800,7 +6909,7 @@ function handleError(root$jscomp$0, thrownValue) { switch (workInProgress$32.tag) { case 3: root = value; - workInProgress$32.flags |= 16384; + workInProgress$32.flags |= 32768; thrownValue &= -thrownValue; workInProgress$32.lanes |= thrownValue; var update$33 = createRootErrorUpdate( @@ -6822,7 +6931,7 @@ function handleError(root$jscomp$0, thrownValue) { (null === legacyErrorBoundariesThatAlreadyFailed || !legacyErrorBoundariesThatAlreadyFailed.has(instance)))) ) { - workInProgress$32.flags |= 16384; + workInProgress$32.flags |= 32768; thrownValue &= -thrownValue; workInProgress$32.lanes |= thrownValue; var update$36 = createClassErrorUpdate( @@ -6896,7 +7005,7 @@ function completeUnitOfWork(unitOfWork) { do { var current = completedWork.alternate; unitOfWork = completedWork.return; - if (0 === (completedWork.flags & 8192)) { + if (0 === (completedWork.flags & 16384)) { if ( ((current = completeWork(current, completedWork, subtreeRenderLanes)), null !== current) @@ -6907,12 +7016,12 @@ function completeUnitOfWork(unitOfWork) { } else { current = unwindWork(completedWork); if (null !== current) { - current.flags &= 8191; + current.flags &= 16383; workInProgress = current; return; } null !== unitOfWork && - ((unitOfWork.flags |= 8192), + ((unitOfWork.flags |= 16384), (unitOfWork.subtreeFlags = 0), (unitOfWork.deletions = null)); } @@ -6991,11 +7100,6 @@ function commitRootImpl(root, renderPriorityLevel) { (pendingPassiveEffectsLanes = lanes)); remainingLanes = root.pendingLanes; 0 === remainingLanes && (legacyErrorBoundariesThatAlreadyFailed = null); - 0 !== (remainingLanes & 1) - ? root === rootWithNestedUpdates - ? nestedUpdateCount++ - : ((nestedUpdateCount = 0), (rootWithNestedUpdates = root)) - : (nestedUpdateCount = 0); onCommitRoot(finishedWork.stateNode, renderPriorityLevel); ensureRootIsScheduled(root, now()); if (hasUncaughtError) @@ -7006,6 +7110,12 @@ function commitRootImpl(root, renderPriorityLevel) { 0 !== (pendingPassiveEffectsLanes & 1) && 0 !== root.tag && flushPassiveEffects(); + remainingLanes = root.pendingLanes; + 0 !== (remainingLanes & 1) + ? root === rootWithNestedUpdates + ? nestedUpdateCount++ + : ((nestedUpdateCount = 0), (rootWithNestedUpdates = root)) + : (nestedUpdateCount = 0); flushSyncCallbacks(); return null; } @@ -7041,7 +7151,7 @@ function flushPassiveEffects() { case 0: case 11: case 15: - commitHookEffectListUnmount(4, fiber$jscomp$0, fiber); + commitHookEffectListUnmount(8, fiber$jscomp$0, fiber); } var child$jscomp$0 = fiber$jscomp$0.child; if (null !== child$jscomp$0) @@ -7091,7 +7201,7 @@ function flushPassiveEffects() { case 0: case 11: case 15: - commitHookEffectListUnmount(5, fiber, fiber.return); + commitHookEffectListUnmount(9, fiber, fiber.return); } var sibling$jscomp$0 = fiber.sibling; if (null !== sibling$jscomp$0) { @@ -7117,7 +7227,7 @@ function flushPassiveEffects() { case 0: case 11: case 15: - commitHookEffectListMount(5, deletions); + commitHookEffectListMount(9, deletions); } } catch (error) { captureCommitPhaseError(deletions, deletions.return, error); @@ -7263,7 +7373,7 @@ beginWork$1 = function(current, workInProgress, renderLanes) { renderLanes ) ); - didReceiveUpdate = 0 !== (current.flags & 32768) ? !0 : !1; + didReceiveUpdate = 0 !== (current.flags & 65536) ? !0 : !1; } else didReceiveUpdate = !1; workInProgress.lanes = 0; @@ -7729,7 +7839,7 @@ function createWorkInProgress(current, pendingProps) { (workInProgress.flags = 0), (workInProgress.subtreeFlags = 0), (workInProgress.deletions = null)); - workInProgress.flags = current.flags & 1835008; + workInProgress.flags = current.flags & 3670016; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; workInProgress.child = current.child; @@ -8086,10 +8196,10 @@ batchedUpdatesImpl = function(fn, a) { } }; var roots = new Map(), - devToolsConfig$jscomp$inline_989 = { + devToolsConfig$jscomp$inline_986 = { findFiberByHostInstance: getInstanceFromTag, bundleType: 0, - version: "18.0.0-95d762e40-20210908", + version: "18.0.0-e8feb11b6-20210915", rendererPackageName: "react-native-renderer", rendererConfig: { getInspectorDataForViewTag: function() { @@ -8104,11 +8214,11 @@ var roots = new Map(), }.bind(null, findNodeHandle) } }; -var internals$jscomp$inline_1242 = { - bundleType: devToolsConfig$jscomp$inline_989.bundleType, - version: devToolsConfig$jscomp$inline_989.version, - rendererPackageName: devToolsConfig$jscomp$inline_989.rendererPackageName, - rendererConfig: devToolsConfig$jscomp$inline_989.rendererConfig, +var internals$jscomp$inline_1239 = { + bundleType: devToolsConfig$jscomp$inline_986.bundleType, + version: devToolsConfig$jscomp$inline_986.version, + rendererPackageName: devToolsConfig$jscomp$inline_986.rendererPackageName, + rendererConfig: devToolsConfig$jscomp$inline_986.rendererConfig, overrideHookState: null, overrideHookStateDeletePath: null, overrideHookStateRenamePath: null, @@ -8124,7 +8234,7 @@ var internals$jscomp$inline_1242 = { return null === fiber ? null : fiber.stateNode; }, findFiberByHostInstance: - devToolsConfig$jscomp$inline_989.findFiberByHostInstance || + devToolsConfig$jscomp$inline_986.findFiberByHostInstance || emptyFindFiberByHostInstance, findHostInstancesForRefresh: null, scheduleRefresh: null, @@ -8132,19 +8242,19 @@ var internals$jscomp$inline_1242 = { setRefreshHandler: null, getCurrentFiber: null, getIsStrictMode: null, - reconcilerVersion: "18.0.0-95d762e40-20210908" + reconcilerVersion: "18.0.0-e8feb11b6-20210915" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { - var hook$jscomp$inline_1243 = __REACT_DEVTOOLS_GLOBAL_HOOK__; + var hook$jscomp$inline_1240 = __REACT_DEVTOOLS_GLOBAL_HOOK__; if ( - !hook$jscomp$inline_1243.isDisabled && - hook$jscomp$inline_1243.supportsFiber + !hook$jscomp$inline_1240.isDisabled && + hook$jscomp$inline_1240.supportsFiber ) try { - (rendererID = hook$jscomp$inline_1243.inject( - internals$jscomp$inline_1242 + (rendererID = hook$jscomp$inline_1240.inject( + internals$jscomp$inline_1239 )), - (injectedHook = hook$jscomp$inline_1243); + (injectedHook = hook$jscomp$inline_1240); } catch (err) {} } exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { diff --git a/Libraries/Renderer/implementations/ReactNativeRenderer-profiling.fb.js b/Libraries/Renderer/implementations/ReactNativeRenderer-profiling.fb.js index 30a8adbbe0..6dc64c74dc 100644 --- a/Libraries/Renderer/implementations/ReactNativeRenderer-profiling.fb.js +++ b/Libraries/Renderer/implementations/ReactNativeRenderer-profiling.fb.js @@ -7,7 +7,7 @@ * @noflow * @nolint * @preventMunge - * @generated SignedSource<> + * @generated SignedSource<> */ "use strict"; @@ -930,7 +930,7 @@ eventPluginOrder = Array.prototype.slice.call([ "ReactNativeBridgeEventPlugin" ]); recomputePluginOrdering(); -var injectedNamesToPlugins$jscomp$inline_232 = { +var injectedNamesToPlugins$jscomp$inline_233 = { ResponderEventPlugin: ResponderEventPlugin, ReactNativeBridgeEventPlugin: { eventTypes: {}, @@ -965,34 +965,34 @@ var injectedNamesToPlugins$jscomp$inline_232 = { } } }, - isOrderingDirty$jscomp$inline_233 = !1, - pluginName$jscomp$inline_234; -for (pluginName$jscomp$inline_234 in injectedNamesToPlugins$jscomp$inline_232) + isOrderingDirty$jscomp$inline_234 = !1, + pluginName$jscomp$inline_235; +for (pluginName$jscomp$inline_235 in injectedNamesToPlugins$jscomp$inline_233) if ( - injectedNamesToPlugins$jscomp$inline_232.hasOwnProperty( - pluginName$jscomp$inline_234 + injectedNamesToPlugins$jscomp$inline_233.hasOwnProperty( + pluginName$jscomp$inline_235 ) ) { - var pluginModule$jscomp$inline_235 = - injectedNamesToPlugins$jscomp$inline_232[pluginName$jscomp$inline_234]; + var pluginModule$jscomp$inline_236 = + injectedNamesToPlugins$jscomp$inline_233[pluginName$jscomp$inline_235]; if ( - !namesToPlugins.hasOwnProperty(pluginName$jscomp$inline_234) || - namesToPlugins[pluginName$jscomp$inline_234] !== - pluginModule$jscomp$inline_235 + !namesToPlugins.hasOwnProperty(pluginName$jscomp$inline_235) || + namesToPlugins[pluginName$jscomp$inline_235] !== + pluginModule$jscomp$inline_236 ) { - if (namesToPlugins[pluginName$jscomp$inline_234]) + if (namesToPlugins[pluginName$jscomp$inline_235]) throw Error( "EventPluginRegistry: Cannot inject two different event plugins using the same name, `" + - pluginName$jscomp$inline_234 + + pluginName$jscomp$inline_235 + "`." ); namesToPlugins[ - pluginName$jscomp$inline_234 - ] = pluginModule$jscomp$inline_235; - isOrderingDirty$jscomp$inline_233 = !0; + pluginName$jscomp$inline_235 + ] = pluginModule$jscomp$inline_236; + isOrderingDirty$jscomp$inline_234 = !0; } } -isOrderingDirty$jscomp$inline_233 && recomputePluginOrdering(); +isOrderingDirty$jscomp$inline_234 && recomputePluginOrdering(); var instanceCache = new Map(), instanceProps = new Map(); function getInstanceFromTag(tag) { @@ -1859,6 +1859,9 @@ function getLanesToRetrySynchronouslyOnError(root) { root = root.pendingLanes & -1073741825; return 0 !== root ? root : root & 1073741824 ? 1073741824 : 0; } +function includesBlockingLane(root, lanes) { + return 0 !== (root.current.mode & 32) ? !1 : 0 !== (lanes & 30); +} function createLaneMap(initial) { for (var laneMap = [], i = 0; 31 > i; i++) laneMap.push(initial); return laneMap; @@ -2092,7 +2095,11 @@ function invalidateContextProvider(workInProgress, type, didChange) { : pop(didPerformWorkStackCursor); push(didPerformWorkStackCursor, didChange); } -var syncQueue = null, +function is(x, y) { + return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); +} +var objectIs = "function" === typeof Object.is ? Object.is : is, + syncQueue = null, includesLegacySyncCallbacks = !1, isFlushingSyncQueue = !1; function flushSyncCallbacks() { @@ -2121,10 +2128,6 @@ function flushSyncCallbacks() { return null; } var ReactCurrentBatchConfig = ReactSharedInternals.ReactCurrentBatchConfig; -function is(x, y) { - return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y); -} -var objectIs = "function" === typeof Object.is ? Object.is : is; function shallowEqual(objA, objB) { if (objectIs(objA, objB)) return !0; if ( @@ -2421,7 +2424,7 @@ function processUpdateQueue( newState = workInProgress; break a; case 3: - workInProgress.flags = (workInProgress.flags & -16385) | 128; + workInProgress.flags = (workInProgress.flags & -32769) | 128; case 0: workInProgress = update.payload; updateLane = @@ -3652,42 +3655,52 @@ function updateMutableSource(source, getSnapshot, subscribe) { return useMutableSource(hook, source, getSnapshot, subscribe); } function mountSyncExternalStore(subscribe, getSnapshot) { - var hook = mountWorkInProgressHook(), + var fiber = currentlyRenderingFiber$1, + hook = mountWorkInProgressHook(), nextSnapshot = getSnapshot(); hook.memoizedState = nextSnapshot; var inst = { value: nextSnapshot, getSnapshot: getSnapshot }; hook.queue = inst; - return useSyncExternalStore(hook, inst, subscribe, getSnapshot, nextSnapshot); -} -function useSyncExternalStore( - hook, - inst, - subscribe, - getSnapshot, - nextSnapshot -) { - var fiber = currentlyRenderingFiber$1; - hook = ReactCurrentDispatcher$1.current; - hook.useLayoutEffect( - function() { - inst.value = nextSnapshot; - inst.getSnapshot = getSnapshot; - checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); - }, - [subscribe, nextSnapshot, getSnapshot] - ); - hook.useEffect( - function() { - function handleStoreChange() { - checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); - } - handleStoreChange(); - return subscribe(handleStoreChange); - }, - [subscribe] + mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]); + fiber.flags |= 1024; + pushEffect( + 9, + updateStoreInstance.bind(null, fiber, inst, nextSnapshot, getSnapshot), + void 0, + null ); + subscribe = workInProgressRoot; + if (null === subscribe) + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + includesBlockingLane(subscribe, renderLanes) || + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); return nextSnapshot; } +function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) { + fiber.flags |= 8192; + fiber = { getSnapshot: getSnapshot, value: renderedSnapshot }; + getSnapshot = currentlyRenderingFiber$1.updateQueue; + null === getSnapshot + ? ((getSnapshot = { lastEffect: null, stores: null }), + (currentlyRenderingFiber$1.updateQueue = getSnapshot), + (getSnapshot.stores = [fiber])) + : ((renderedSnapshot = getSnapshot.stores), + null === renderedSnapshot + ? (getSnapshot.stores = [fiber]) + : renderedSnapshot.push(fiber)); +} +function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) { + inst.value = nextSnapshot; + inst.getSnapshot = getSnapshot; + checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); +} +function subscribeToStore(fiber, inst, subscribe) { + return subscribe(function() { + checkIfSnapshotChanged(inst) && scheduleUpdateOnFiber(fiber, 1, -1); + }); +} function checkIfSnapshotChanged(inst) { var latestGetSnapshot = inst.getSnapshot; inst = inst.value; @@ -3722,7 +3735,7 @@ function pushEffect(tag, create, destroy, deps) { tag = { tag: tag, create: create, destroy: destroy, deps: deps, next: null }; create = currentlyRenderingFiber$1.updateQueue; null === create - ? ((create = { lastEffect: null }), + ? ((create = { lastEffect: null, stores: null }), (currentlyRenderingFiber$1.updateQueue = create), (create.lastEffect = tag.next = tag)) : ((destroy = create.lastEffect), @@ -3763,13 +3776,16 @@ function updateEffectImpl(fiberFlags, hookFlags, create, deps) { hook.memoizedState = pushEffect(1 | hookFlags, create, destroy, deps); } function mountEffect(create, deps) { - return mountEffectImpl(1049600, 4, create, deps); + return mountEffectImpl(2098176, 8, create, deps); } function updateEffect(create, deps) { - return updateEffectImpl(1024, 4, create, deps); + return updateEffectImpl(1024, 8, create, deps); +} +function updateInsertionEffect(create, deps) { + return updateEffectImpl(4, 2, create, deps); } function updateLayoutEffect(create, deps) { - return updateEffectImpl(4, 2, create, deps); + return updateEffectImpl(4, 4, create, deps); } function imperativeHandleEffect(create, ref) { if ("function" === typeof ref) @@ -3793,7 +3809,7 @@ function updateImperativeHandle(ref, create, deps) { deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null; return updateEffectImpl( 4, - 2, + 4, imperativeHandleEffect.bind(null, create, ref), deps ); @@ -3911,6 +3927,7 @@ var ContextOnlyDispatcher = { useContext: throwInvalidHookError, useEffect: throwInvalidHookError, useImperativeHandle: throwInvalidHookError, + useInsertionEffect: throwInvalidHookError, useLayoutEffect: throwInvalidHookError, useMemo: throwInvalidHookError, useReducer: throwInvalidHookError, @@ -3939,12 +3956,15 @@ var ContextOnlyDispatcher = { deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null; return mountEffectImpl( 4, - 2, + 4, imperativeHandleEffect.bind(null, create, ref), deps ); }, useLayoutEffect: function(create, deps) { + return mountEffectImpl(4, 4, create, deps); + }, + useInsertionEffect: function(create, deps) { return mountEffectImpl(4, 2, create, deps); }, useMemo: function(nextCreate, deps) { @@ -4027,6 +4047,7 @@ var ContextOnlyDispatcher = { useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, + useInsertionEffect: updateInsertionEffect, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: updateReducer, @@ -4060,17 +4081,44 @@ var ContextOnlyDispatcher = { }, useMutableSource: updateMutableSource, useSyncExternalStore: function(subscribe, getSnapshot) { - var hook = updateWorkInProgressHook(), - nextSnapshot = getSnapshot(); - objectIs(hook.memoizedState, nextSnapshot) || + var fiber = currentlyRenderingFiber$1, + hook = updateWorkInProgressHook(), + nextSnapshot = getSnapshot(), + snapshotChanged = !objectIs(hook.memoizedState, nextSnapshot); + snapshotChanged && ((hook.memoizedState = nextSnapshot), (didReceiveUpdate = !0)); - return useSyncExternalStore( - hook, - hook.queue, - subscribe, - getSnapshot, - nextSnapshot - ); + hook = hook.queue; + updateEffect(subscribeToStore.bind(null, fiber, hook, subscribe), [ + subscribe + ]); + if ( + hook.getSnapshot !== getSnapshot || + snapshotChanged || + (null !== workInProgressHook && + workInProgressHook.memoizedState.tag & 1) + ) { + fiber.flags |= 1024; + pushEffect( + 9, + updateStoreInstance.bind( + null, + fiber, + hook, + nextSnapshot, + getSnapshot + ), + void 0, + null + ); + subscribe = workInProgressRoot; + if (null === subscribe) + throw Error( + "Expected a work-in-progress root. This is a bug in React. Please file an issue." + ); + includesBlockingLane(subscribe, renderLanes) || + pushStoreConsistencyCheck(fiber, getSnapshot, nextSnapshot); + } + return nextSnapshot; }, useOpaqueIdentifier: function() { return updateReducer(basicStateReducer)[0]; @@ -4083,6 +4131,7 @@ var ContextOnlyDispatcher = { useContext: readContext, useEffect: updateEffect, useImperativeHandle: updateImperativeHandle, + useInsertionEffect: updateInsertionEffect, useLayoutEffect: updateLayoutEffect, useMemo: updateMemo, useReducer: rerenderReducer, @@ -4316,8 +4365,8 @@ function bubbleProperties(completedWork) { ) (newChildLanes |= child$43.lanes | child$43.childLanes), - (subtreeFlags |= child$43.subtreeFlags & 1835008), - (subtreeFlags |= child$43.flags & 1835008), + (subtreeFlags |= child$43.subtreeFlags & 3670016), + (subtreeFlags |= child$43.flags & 3670016), (treeBaseDuration$42 += child$43.treeBaseDuration), (child$43 = child$43.sibling); completedWork.treeBaseDuration = treeBaseDuration$42; @@ -4329,8 +4378,8 @@ function bubbleProperties(completedWork) { ) (newChildLanes |= treeBaseDuration$42.lanes | treeBaseDuration$42.childLanes), - (subtreeFlags |= treeBaseDuration$42.subtreeFlags & 1835008), - (subtreeFlags |= treeBaseDuration$42.flags & 1835008), + (subtreeFlags |= treeBaseDuration$42.subtreeFlags & 3670016), + (subtreeFlags |= treeBaseDuration$42.flags & 3670016), (treeBaseDuration$42.return = completedWork), (treeBaseDuration$42 = treeBaseDuration$42.sibling); else if (0 !== (completedWork.mode & 2)) { @@ -4572,7 +4621,7 @@ function completeWork(current, workInProgress, renderLanes) { for (newProps = workInProgress.child; null !== newProps; ) (renderLanes = newProps), (updatePayload = current), - (renderLanes.flags &= 1835010), + (renderLanes.flags &= 3670018), (type = renderLanes.alternate), null === type ? ((renderLanes.childLanes = 0), @@ -4804,7 +4853,7 @@ function updateSimpleMemoComponent( current.ref === workInProgress.ref ) if (((didReceiveUpdate = !1), 0 !== (current.lanes & renderLanes))) - 0 !== (current.flags & 32768) && (didReceiveUpdate = !0); + 0 !== (current.flags & 65536) && (didReceiveUpdate = !0); else return ( (workInProgress.lanes = current.lanes), @@ -5362,7 +5411,7 @@ function updateSuspenseFallbackChildren( (primaryChildren.treeBaseDuration = current.treeBaseDuration)), (workInProgress.deletions = null)) : ((primaryChildren = createWorkInProgress(current, primaryChildProps)), - (primaryChildren.subtreeFlags = current.subtreeFlags & 1835008)); + (primaryChildren.subtreeFlags = current.subtreeFlags & 3670016)); null !== currentFallbackChildFragment ? (fallbackChildren = createWorkInProgress( currentFallbackChildFragment, @@ -5598,8 +5647,8 @@ function unwindWork(workInProgress) { case 1: isContextProvider(workInProgress.type) && popContext(); var flags = workInProgress.flags; - return flags & 16384 - ? ((workInProgress.flags = (flags & -16385) | 128), + return flags & 32768 + ? ((workInProgress.flags = (flags & -32769) | 128), 0 !== (workInProgress.mode & 2) && transferActualDuration(workInProgress), workInProgress) @@ -5614,7 +5663,7 @@ function unwindWork(workInProgress) { throw Error( "The root failed to unmount after an error. This is likely a bug in React. Please file an issue." ); - workInProgress.flags = (flags & -16385) | 128; + workInProgress.flags = (flags & -32769) | 128; return workInProgress; case 5: return popHostContext(workInProgress), null; @@ -5622,8 +5671,8 @@ function unwindWork(workInProgress) { return ( pop(suspenseStackCursor), (flags = workInProgress.flags), - flags & 16384 - ? ((workInProgress.flags = (flags & -16385) | 128), + flags & 32768 + ? ((workInProgress.flags = (flags & -32769) | 128), 0 !== (workInProgress.mode & 2) && transferActualDuration(workInProgress), workInProgress) @@ -5787,8 +5836,8 @@ function commitUnmount(finishedRoot, current, nearestMountedAncestor) { var _effect = effect, destroy = _effect.destroy; _effect = _effect.tag; - void 0 !== destroy && - 0 !== (_effect & 2) && + void 0 === destroy || + (0 === (_effect & 2) && 0 === (_effect & 4)) || (current.mode & 2 ? (startLayoutEffectTimer(), safelyCallDestroy(current, nearestMountedAncestor, destroy), @@ -6111,14 +6160,16 @@ function commitWork(current, finishedWork) { case 11: case 14: case 15: + commitHookEffectListUnmount(3, finishedWork, finishedWork.return); + commitHookEffectListMount(3, finishedWork); if (finishedWork.mode & 2) try { startLayoutEffectTimer(), - commitHookEffectListUnmount(3, finishedWork, finishedWork.return); + commitHookEffectListUnmount(5, finishedWork, finishedWork.return); } finally { recordLayoutEffectDuration(finishedWork); } - else commitHookEffectListUnmount(3, finishedWork, finishedWork.return); + else commitHookEffectListUnmount(5, finishedWork, finishedWork.return); return; case 1: return; @@ -6379,11 +6430,11 @@ function commitLayoutEffects(finishedWork, root, committedLanes) { if (committedLanes.mode & 2) try { startLayoutEffectTimer(), - commitHookEffectListMount(3, committedLanes); + commitHookEffectListMount(5, committedLanes); } finally { recordLayoutEffectDuration(committedLanes); } - else commitHookEffectListMount(3, committedLanes); + else commitHookEffectListMount(5, committedLanes); break; case 1: var instance = committedLanes.stateNode; @@ -6722,15 +6773,15 @@ function performConcurrentWorkOnRoot(root, didTimeout) { root === workInProgressRoot ? workInProgressRootRenderLanes : 0 ); if (0 === lanes) return null; - var JSCompiler_inline_result = - 0 !== (lanes & root.expiredLanes) - ? !1 - : 0 !== (root.current.mode & 32) - ? !0 - : 0 === (lanes & 30); - if (JSCompiler_inline_result && !didTimeout) { + if ( + includesBlockingLane(root, lanes) || + 0 !== (lanes & root.expiredLanes) || + didTimeout + ) + didTimeout = renderRootSync(root, lanes); + else { didTimeout = lanes; - JSCompiler_inline_result = executionContext; + var prevExecutionContext = executionContext; executionContext |= 2; var prevDispatcher = pushDispatcher(); if ( @@ -6757,30 +6808,44 @@ function performConcurrentWorkOnRoot(root, didTimeout) { while (1); resetContextDependencies(); ReactCurrentDispatcher$2.current = prevDispatcher; - executionContext = JSCompiler_inline_result; + executionContext = prevExecutionContext; null !== workInProgress ? (didTimeout = 0) : ((workInProgressRoot = null), (workInProgressRootRenderLanes = 0), (didTimeout = workInProgressRootExitStatus)); - } else didTimeout = renderRootSync(root, lanes); + } if (0 !== didTimeout) { 2 === didTimeout && - ((JSCompiler_inline_result = executionContext), - (executionContext |= 8), - root.hydrate && (root.hydrate = !1), - (prevDispatcher = getLanesToRetrySynchronouslyOnError(root)), - 0 !== prevDispatcher && - ((lanes = prevDispatcher), - (didTimeout = renderRootSync(root, prevDispatcher))), - (executionContext = JSCompiler_inline_result)); + ((prevExecutionContext = getLanesToRetrySynchronouslyOnError(root)), + 0 !== prevExecutionContext && + ((lanes = prevExecutionContext), + (didTimeout = recoverFromConcurrentError(root, prevExecutionContext)))); if (1 === didTimeout) throw ((originalCallbackNode = workInProgressRootFatalError), prepareFreshStack(root, 0), markRootSuspended$1(root, lanes), ensureRootIsScheduled(root, now()), originalCallbackNode); - root.finishedWork = root.current.alternate; + prevDispatcher = !includesBlockingLane(root, lanes); + prevExecutionContext = root.current.alternate; + if ( + prevDispatcher && + !isRenderConsistentWithExternalStores(prevExecutionContext) && + ((didTimeout = renderRootSync(root, lanes)), + 2 === didTimeout && + ((prevDispatcher = getLanesToRetrySynchronouslyOnError(root)), + 0 !== prevDispatcher && + ((lanes = prevDispatcher), + (didTimeout = recoverFromConcurrentError(root, prevDispatcher)))), + 1 === didTimeout) + ) + throw ((originalCallbackNode = workInProgressRootFatalError), + prepareFreshStack(root, 0), + markRootSuspended$1(root, lanes), + ensureRootIsScheduled(root, now()), + originalCallbackNode); + root.finishedWork = prevExecutionContext; root.finishedLanes = lanes; switch (didTimeout) { case 0: @@ -6797,10 +6862,10 @@ function performConcurrentWorkOnRoot(root, didTimeout) { 10 < didTimeout) ) { if (0 !== getNextLanes(root, 0)) break; - JSCompiler_inline_result = root.suspendedLanes; - if ((JSCompiler_inline_result & lanes) !== lanes) { + prevExecutionContext = root.suspendedLanes; + if ((prevExecutionContext & lanes) !== lanes) { requestEventTime(); - root.pingedLanes |= root.suspendedLanes & JSCompiler_inline_result; + root.pingedLanes |= root.suspendedLanes & prevExecutionContext; break; } root.timeoutHandle = scheduleTimeout( @@ -6815,14 +6880,14 @@ function performConcurrentWorkOnRoot(root, didTimeout) { markRootSuspended$1(root, lanes); if ((lanes & 4194240) === lanes) break; didTimeout = root.eventTimes; - for (JSCompiler_inline_result = -1; 0 < lanes; ) + for (prevExecutionContext = -1; 0 < lanes; ) (memoizedUpdaters = 31 - clz32(lanes)), (prevDispatcher = 1 << memoizedUpdaters), (memoizedUpdaters = didTimeout[memoizedUpdaters]), - memoizedUpdaters > JSCompiler_inline_result && - (JSCompiler_inline_result = memoizedUpdaters), + memoizedUpdaters > prevExecutionContext && + (prevExecutionContext = memoizedUpdaters), (lanes &= ~prevDispatcher); - lanes = JSCompiler_inline_result; + lanes = prevExecutionContext; lanes = now() - lanes; lanes = (120 > lanes @@ -6859,6 +6924,48 @@ function performConcurrentWorkOnRoot(root, didTimeout) { ? performConcurrentWorkOnRoot.bind(null, root) : null; } +function recoverFromConcurrentError(root, errorRetryLanes) { + var prevExecutionContext = executionContext; + executionContext |= 8; + root.hydrate && (root.hydrate = !1); + root = renderRootSync(root, errorRetryLanes); + executionContext = prevExecutionContext; + return root; +} +function isRenderConsistentWithExternalStores(finishedWork) { + for (var node = finishedWork; ; ) { + if (node.flags & 8192) { + var updateQueue = node.updateQueue; + if ( + null !== updateQueue && + ((updateQueue = updateQueue.stores), null !== updateQueue) + ) + for (var i = 0; i < updateQueue.length; i++) { + var check = updateQueue[i], + getSnapshot = check.getSnapshot; + check = check.value; + try { + if (!objectIs(getSnapshot(), check)) return !1; + } catch (error) { + return !1; + } + } + } + updateQueue = node.child; + if (node.subtreeFlags & 8192 && null !== updateQueue) + (updateQueue.return = node), (node = updateQueue); + else { + if (node === finishedWork) break; + for (; null === node.sibling; ) { + if (null === node.return || node.return === finishedWork) return !0; + node = node.return; + } + node.sibling.return = node.return; + node = node.sibling; + } + } + return !0; +} function markRootSuspended$1(root, suspendedLanes) { suspendedLanes &= ~workInProgressRootPingedLanes; suspendedLanes &= ~workInProgressRootUpdatedLanes; @@ -7010,7 +7117,7 @@ function handleError(root$jscomp$0, thrownValue) { sourceFiber = erroredWork, value = thrownValue; thrownValue = workInProgressRootRenderLanes; - sourceFiber.flags |= 8192; + sourceFiber.flags |= 16384; isDevToolsPresent && restorePendingUpdaters(root, thrownValue); if ( null !== value && @@ -7062,8 +7169,8 @@ function handleError(root$jscomp$0, thrownValue) { workInProgress$34 !== returnFiber ) { workInProgress$34.flags |= 128; - sourceFiber.flags |= 32768; - sourceFiber.flags &= -10053; + sourceFiber.flags |= 65536; + sourceFiber.flags &= -26437; if (1 === sourceFiber.tag) if (null === sourceFiber.alternate) sourceFiber.tag = 17; else { @@ -7095,7 +7202,7 @@ function handleError(root$jscomp$0, thrownValue) { isDevToolsPresent && restorePendingUpdaters(root, sourceFiber); wakeable.then(ping, ping); } - workInProgress$34.flags |= 16384; + workInProgress$34.flags |= 32768; workInProgress$34.lanes = thrownValue; break a; } @@ -7114,7 +7221,7 @@ function handleError(root$jscomp$0, thrownValue) { switch (workInProgress$34.tag) { case 3: root = value; - workInProgress$34.flags |= 16384; + workInProgress$34.flags |= 32768; thrownValue &= -thrownValue; workInProgress$34.lanes |= thrownValue; var update$35 = createRootErrorUpdate( @@ -7136,7 +7243,7 @@ function handleError(root$jscomp$0, thrownValue) { (null === legacyErrorBoundariesThatAlreadyFailed || !legacyErrorBoundariesThatAlreadyFailed.has(instance)))) ) { - workInProgress$34.flags |= 16384; + workInProgress$34.flags |= 32768; thrownValue &= -thrownValue; workInProgress$34.lanes |= thrownValue; var update$38 = createClassErrorUpdate( @@ -7226,7 +7333,7 @@ function completeUnitOfWork(unitOfWork) { do { var current = completedWork.alternate; unitOfWork = completedWork.return; - if (0 === (completedWork.flags & 8192)) { + if (0 === (completedWork.flags & 16384)) { if (0 === (completedWork.mode & 2)) current = completeWork(current, completedWork, subtreeRenderLanes); else { @@ -7243,7 +7350,7 @@ function completeUnitOfWork(unitOfWork) { } else { current = unwindWork(completedWork); if (null !== current) { - current.flags &= 8191; + current.flags &= 16383; workInProgress = current; return; } @@ -7255,7 +7362,7 @@ function completeUnitOfWork(unitOfWork) { completedWork.actualDuration = current; } null !== unitOfWork && - ((unitOfWork.flags |= 8192), + ((unitOfWork.flags |= 16384), (unitOfWork.subtreeFlags = 0), (unitOfWork.deletions = null)); } @@ -7335,12 +7442,6 @@ function commitRootImpl(root, renderPriorityLevel) { (pendingPassiveEffectsLanes = lanes)); remainingLanes = root.pendingLanes; 0 === remainingLanes && (legacyErrorBoundariesThatAlreadyFailed = null); - 0 !== (remainingLanes & 1) - ? ((nestedUpdateScheduled = !0), - root === rootWithNestedUpdates - ? nestedUpdateCount++ - : ((nestedUpdateCount = 0), (rootWithNestedUpdates = root))) - : (nestedUpdateCount = 0); onCommitRoot(finishedWork.stateNode, renderPriorityLevel); isDevToolsPresent && root.memoizedUpdaters.clear(); ensureRootIsScheduled(root, now()); @@ -7352,6 +7453,13 @@ function commitRootImpl(root, renderPriorityLevel) { 0 !== (pendingPassiveEffectsLanes & 1) && 0 !== root.tag && flushPassiveEffects(); + remainingLanes = root.pendingLanes; + 0 !== (remainingLanes & 1) + ? ((nestedUpdateScheduled = !0), + root === rootWithNestedUpdates + ? nestedUpdateCount++ + : ((nestedUpdateCount = 0), (rootWithNestedUpdates = root))) + : (nestedUpdateCount = 0); flushSyncCallbacks(); return null; } @@ -7390,9 +7498,9 @@ function flushPassiveEffects() { case 15: current.mode & 2 ? ((passiveEffectStartTime = now$1()), - commitHookEffectListUnmount(4, current, fiber), + commitHookEffectListUnmount(8, current, fiber), recordPassiveEffectDuration(current)) - : commitHookEffectListUnmount(4, current, fiber); + : commitHookEffectListUnmount(8, current, fiber); } var child$jscomp$0 = fiber$jscomp$0.child; if (null !== child$jscomp$0) @@ -7444,9 +7552,9 @@ function flushPassiveEffects() { case 15: i.mode & 2 ? ((passiveEffectStartTime = now$1()), - commitHookEffectListUnmount(5, i, i.return), + commitHookEffectListUnmount(9, i, i.return), recordPassiveEffectDuration(i)) - : commitHookEffectListUnmount(5, i, i.return); + : commitHookEffectListUnmount(9, i, i.return); } var sibling$jscomp$0 = fiber.sibling; if (null !== sibling$jscomp$0) { @@ -7475,11 +7583,11 @@ function flushPassiveEffects() { if (fiberToDelete.mode & 2) { passiveEffectStartTime = now$1(); try { - commitHookEffectListMount(5, fiberToDelete); + commitHookEffectListMount(9, fiberToDelete); } finally { recordPassiveEffectDuration(fiberToDelete); } - } else commitHookEffectListMount(5, fiberToDelete); + } else commitHookEffectListMount(9, fiberToDelete); } } catch (error) { captureCommitPhaseError(deletions, deletions.return, error); @@ -7674,7 +7782,7 @@ beginWork$1 = function(current, workInProgress, renderLanes) { renderLanes ) ); - didReceiveUpdate = 0 !== (current.flags & 32768) ? !0 : !1; + didReceiveUpdate = 0 !== (current.flags & 65536) ? !0 : !1; } else didReceiveUpdate = !1; workInProgress.lanes = 0; @@ -8155,7 +8263,7 @@ function createWorkInProgress(current, pendingProps) { (workInProgress.deletions = null), (workInProgress.actualDuration = 0), (workInProgress.actualStartTime = -1)); - workInProgress.flags = current.flags & 1835008; + workInProgress.flags = current.flags & 3670016; workInProgress.childLanes = current.childLanes; workInProgress.lanes = current.lanes; workInProgress.child = current.child; @@ -8519,10 +8627,10 @@ batchedUpdatesImpl = function(fn, a) { } }; var roots = new Map(), - devToolsConfig$jscomp$inline_1019 = { + devToolsConfig$jscomp$inline_1016 = { findFiberByHostInstance: getInstanceFromTag, bundleType: 0, - version: "18.0.0-95d762e40-20210908", + version: "18.0.0-e8feb11b6-20210915", rendererPackageName: "react-native-renderer", rendererConfig: { getInspectorDataForViewTag: function() { @@ -8537,11 +8645,11 @@ var roots = new Map(), }.bind(null, findNodeHandle) } }; -var internals$jscomp$inline_1292 = { - bundleType: devToolsConfig$jscomp$inline_1019.bundleType, - version: devToolsConfig$jscomp$inline_1019.version, - rendererPackageName: devToolsConfig$jscomp$inline_1019.rendererPackageName, - rendererConfig: devToolsConfig$jscomp$inline_1019.rendererConfig, +var internals$jscomp$inline_1289 = { + bundleType: devToolsConfig$jscomp$inline_1016.bundleType, + version: devToolsConfig$jscomp$inline_1016.version, + rendererPackageName: devToolsConfig$jscomp$inline_1016.rendererPackageName, + rendererConfig: devToolsConfig$jscomp$inline_1016.rendererConfig, overrideHookState: null, overrideHookStateDeletePath: null, overrideHookStateRenamePath: null, @@ -8557,7 +8665,7 @@ var internals$jscomp$inline_1292 = { return null === fiber ? null : fiber.stateNode; }, findFiberByHostInstance: - devToolsConfig$jscomp$inline_1019.findFiberByHostInstance || + devToolsConfig$jscomp$inline_1016.findFiberByHostInstance || emptyFindFiberByHostInstance, findHostInstancesForRefresh: null, scheduleRefresh: null, @@ -8565,19 +8673,19 @@ var internals$jscomp$inline_1292 = { setRefreshHandler: null, getCurrentFiber: null, getIsStrictMode: null, - reconcilerVersion: "18.0.0-95d762e40-20210908" + reconcilerVersion: "18.0.0-e8feb11b6-20210915" }; if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) { - var hook$jscomp$inline_1293 = __REACT_DEVTOOLS_GLOBAL_HOOK__; + var hook$jscomp$inline_1290 = __REACT_DEVTOOLS_GLOBAL_HOOK__; if ( - !hook$jscomp$inline_1293.isDisabled && - hook$jscomp$inline_1293.supportsFiber + !hook$jscomp$inline_1290.isDisabled && + hook$jscomp$inline_1290.supportsFiber ) try { - (rendererID = hook$jscomp$inline_1293.inject( - internals$jscomp$inline_1292 + (rendererID = hook$jscomp$inline_1290.inject( + internals$jscomp$inline_1289 )), - (injectedHook = hook$jscomp$inline_1293); + (injectedHook = hook$jscomp$inline_1290); } catch (err) {} } exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = { diff --git a/Libraries/Text/TextInput/RCTBaseTextInputView.m b/Libraries/Text/TextInput/RCTBaseTextInputView.m index f95717e833..695c18cc95 100644 --- a/Libraries/Text/TextInput/RCTBaseTextInputView.m +++ b/Libraries/Text/TextInput/RCTBaseTextInputView.m @@ -288,20 +288,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame) @"streetAddressLine2": UITextContentTypeStreetAddressLine2, @"sublocality": UITextContentTypeSublocality, @"telephoneNumber": UITextContentTypeTelephoneNumber, + @"username": UITextContentTypeUsername, + @"password": UITextContentTypePassword, }; - #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - if (@available(iOS 11.0, *)) { - NSDictionary * iOS11extras = @{@"username": UITextContentTypeUsername, - @"password": UITextContentTypePassword}; - - NSMutableDictionary * iOS11baseMap = [contentTypeMap mutableCopy]; - [iOS11baseMap addEntriesFromDictionary:iOS11extras]; - - contentTypeMap = [iOS11baseMap copy]; - } - #endif - #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120000 /* __IPHONE_12_0 */ if (@available(iOS 12.0, *)) { NSDictionary * iOS12extras = @{@"newPassword": UITextContentTypeNewPassword, diff --git a/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m b/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m index 6916657e5c..2e9af2acc7 100644 --- a/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m +++ b/Libraries/Text/TextInput/RCTInputAccessoryViewContent.m @@ -30,21 +30,17 @@ _heightConstraint = [_safeAreaContainer.heightAnchor constraintEqualToConstant:0]; _heightConstraint.active = YES; -#if !TARGET_OS_OSX // TODO(macOS GH#774) - if (@available(iOS 11.0, *)) { - [_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES; - [_safeAreaContainer.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES; - [_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor].active = YES; - [_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor].active = YES; - } else { -#endif // TODO(macOS GH#774) - [_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES; - [_safeAreaContainer.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES; - [_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = YES; - [_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = YES; -#if !TARGET_OS_OSX // TODO(macOS GH#774) - } -#endif // TODO(macOS GH#774) +#if !TARGET_OS_OSX // [TODO(macOS GH#774) + [_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES; + [_safeAreaContainer.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES; + [_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leadingAnchor].active = YES; + [_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.trailingAnchor].active = YES; +#else // TODO(macOS GH#774) + [_safeAreaContainer.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES; + [_safeAreaContainer.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES; + [_safeAreaContainer.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = YES; + [_safeAreaContainer.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = YES; +#endif // ]TODO(macOS GH#774) } return self; } diff --git a/React/Base/RCTConstants.h b/React/Base/RCTConstants.h index 06fbc7597c..c92fc1041c 100644 --- a/React/Base/RCTConstants.h +++ b/React/Base/RCTConstants.h @@ -16,12 +16,6 @@ RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollec RCT_EXTERN BOOL RCTExperimentGetPreemptiveViewAllocationDisabled(void); RCT_EXTERN void RCTExperimentSetPreemptiveViewAllocationDisabled(BOOL value); -/* - * Initial maximum surface size - */ -RCT_EXTERN BOOL RCTGetInitialMaxSizeEnabled(void); -RCT_EXTERN void RCTSetInitialMaxSizeEnabled(BOOL value); - /* * Remove clipped subviews */ diff --git a/React/Base/RCTConstants.m b/React/Base/RCTConstants.m index f734a3384a..38947bb170 100644 --- a/React/Base/RCTConstants.m +++ b/React/Base/RCTConstants.m @@ -25,21 +25,6 @@ void RCTExperimentSetPreemptiveViewAllocationDisabled(BOOL value) RCTExperimentPreemptiveViewAllocationDisabled = value; } -/* - * Initial maximum surface size - */ -static BOOL RCTInitialMaxSizeEnabled = NO; - -BOOL RCTGetInitialMaxSizeEnabled() -{ - return RCTInitialMaxSizeEnabled; -} - -void RCTSetInitialMaxSizeEnabled(BOOL value) -{ - RCTInitialMaxSizeEnabled = value; -} - /* * Remove clipped subviews */ diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index e3c2621ba1..e692b44af3 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -76,7 +76,7 @@ typedef NSURL RCTFileURL; #endif #if TARGET_OS_IPHONE -+ (WKDataDetectorTypes)WKDataDetectorTypes:(id)json API_AVAILABLE(ios(10.0)); ++ (WKDataDetectorTypes)WKDataDetectorTypes:(id)json; #endif + (UIViewContentMode)UIViewContentMode:(id)json; diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m index d0bf1af0f3..9b8f3f80ce 100644 --- a/React/Base/RCTTouchHandler.m +++ b/React/Base/RCTTouchHandler.m @@ -41,10 +41,6 @@ __weak RCTUIView *_cachedRootView; // TODO(macOS GH#774) - // See Touch.h and usage. This gives us a time-basis for a monotonic - // clock that acts like a timestamp of milliseconds elapsed since UNIX epoch. - NSTimeInterval _unixEpochBasisTime; - uint16_t _coalescingKey; #if TARGET_OS_OSX// [TODO(macOS GH#774) BOOL _shouldSendMouseUpOnSystemBehalf; @@ -62,9 +58,6 @@ _reactTouches = [NSMutableArray new]; _touchViews = [NSMutableArray new]; - // Get a UNIX epoch basis time: - _unixEpochBasisTime = [[NSDate date] timeIntervalSince1970] - [NSProcessInfo processInfo].systemUptime; - #if !TARGET_OS_OSX // TODO(macOS GH#774) // `cancelsTouchesInView` and `delaysTouches*` are needed in order to be used as a top level // event delegated recognizer. Otherwise, lower-level components not built @@ -236,7 +229,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)coder) reactTouch[@"pageY"] = @(RCTSanitizeNaNValue(rootViewLocation.y, @"touchEvent.pageY")); reactTouch[@"locationX"] = @(RCTSanitizeNaNValue(touchViewLocation.x, @"touchEvent.locationX")); reactTouch[@"locationY"] = @(RCTSanitizeNaNValue(touchViewLocation.y, @"touchEvent.locationY")); - reactTouch[@"timestamp"] = @((_unixEpochBasisTime + nativeTouch.timestamp) * 1000); // in ms, for JS + reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS #if !TARGET_OS_OSX // TODO(macOS GH#774) // TODO: force for a 'normal' touch is usually 1.0; diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h index 9c5ace441f..8d243f2375 100644 --- a/React/Base/RCTUtils.h +++ b/React/Base/RCTUtils.h @@ -40,6 +40,9 @@ RCT_EXTERN void RCTExecuteOnMainQueue(dispatch_block_t block); // Please do not use this unless you know what you're doing. RCT_EXTERN void RCTUnsafeExecuteOnMainQueueSync(dispatch_block_t block); +// Get screen scale, can be only used on main +RCT_EXTERN void RCTComputeScreenScale(void); + // Get screen metrics in a thread-safe way RCT_EXTERN CGFloat RCTScreenScale(void); RCT_EXTERN CGFloat RCTFontSizeMultiplier(void); diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m index 4ad23a8a3c..01b92dc00f 100644 --- a/React/Base/RCTUtils.m +++ b/React/Base/RCTUtils.m @@ -297,16 +297,23 @@ static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, disp } #if !TARGET_OS_OSX // TODO(macOS GH#774) +static dispatch_once_t onceTokenScreenScale; +static CGFloat screenScale; + +void RCTComputeScreenScale() +{ + dispatch_once(&onceTokenScreenScale, ^{ + screenScale = [UIScreen mainScreen].scale; + }); +} + CGFloat RCTScreenScale() { - static dispatch_once_t onceToken; - static CGFloat scale; - - RCTUnsafeExecuteOnMainQueueOnceSync(&onceToken, ^{ - scale = [UIScreen mainScreen].scale; + RCTUnsafeExecuteOnMainQueueOnceSync(&onceTokenScreenScale, ^{ + screenScale = [UIScreen mainScreen].scale; }); - return scale; + return screenScale; } CGFloat RCTFontSizeMultiplier() @@ -335,7 +342,7 @@ CGFloat RCTFontSizeMultiplier() CGSize RCTScreenSize() { - // FIXME: this caches the bounds at app start, whatever those were, and then + // FIXME: this caches whatever the bounds were when it was first called, and then // doesn't update when the device is rotated. We need to find another thread- // safe way to get the screen size. diff --git a/React/CoreModules/RCTActionSheetManager.mm b/React/CoreModules/RCTActionSheetManager.mm index 0bdfea14ae..ada2cfd078 100644 --- a/React/CoreModules/RCTActionSheetManager.mm +++ b/React/CoreModules/RCTActionSheetManager.mm @@ -120,6 +120,8 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions #if !TARGET_OS_OSX // TODO(macOS GH#774) UIViewController *controller = RCTPresentedViewController(); UIColor *tintColor = [RCTConvert UIColor:options.tintColor() ? @(*options.tintColor()) : nil]; + UIColor *cancelButtonTintColor = + [RCTConvert UIColor:options.cancelButtonTintColor() ? @(*options.cancelButtonTintColor()) : nil]; if (controller == nil) { RCTLogError( @@ -131,6 +133,7 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions @"destructiveButtonIndices" : destructiveButtonIndices ?: [NSNull null], @"anchor" : anchor ?: [NSNull null], @"tintColor" : tintColor ?: [NSNull null], + @"cancelButtonTintColor" : cancelButtonTintColor ?: [NSNull null], @"disabledButtonIndices" : disabledButtonIndices ?: [NSNull null], }); /* // TODO(macOS GH#774): nil check our dict values before inserting them or we may crash ] */ return; @@ -149,20 +152,26 @@ RCT_EXPORT_METHOD(showActionSheetWithOptions preferredStyle:UIAlertControllerStyleActionSheet]; NSInteger index = 0; + bool isCancelButtonIndex = false; for (NSString *option in buttons) { UIAlertActionStyle style = UIAlertActionStyleDefault; if ([destructiveButtonIndices containsObject:@(index)]) { style = UIAlertActionStyleDestructive; } else if (index == cancelButtonIndex) { style = UIAlertActionStyleCancel; + isCancelButtonIndex = true; } NSInteger localIndex = index; - [alertController addAction:[UIAlertAction actionWithTitle:option - style:style - handler:^(__unused UIAlertAction *action) { - callback(@[ @(localIndex) ]); - }]]; + UIAlertAction *actionButton = [UIAlertAction actionWithTitle:option + style:style + handler:^(__unused UIAlertAction *action) { + callback(@[ @(localIndex) ]); + }]; + if (isCancelButtonIndex) { + [actionButton setValue:cancelButtonTintColor forKey:@"titleTextColor"]; + } + [alertController addAction:actionButton]; index++; } diff --git a/React/CoreModules/RCTDevLoadingView.mm b/React/CoreModules/RCTDevLoadingView.mm index 020f3ab8c5..4a8f6c22b1 100644 --- a/React/CoreModules/RCTDevLoadingView.mm +++ b/React/CoreModules/RCTDevLoadingView.mm @@ -128,16 +128,11 @@ RCT_EXPORT_MODULE() #if !TARGET_OS_OSX // TODO(macOS GH#774) CGSize screenSize = [UIScreen mainScreen].bounds.size; - if (@available(iOS 11.0, *)) { - UIWindow *window = RCTSharedApplication().keyWindow; - self->_window = - [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, window.safeAreaInsets.top + 10)]; - self->_label = - [[UILabel alloc] initWithFrame:CGRectMake(0, window.safeAreaInsets.top - 10, screenSize.width, 20)]; - } else { - self->_window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, 20)]; - self->_label = [[UILabel alloc] initWithFrame:self->_window.bounds]; - } + UIWindow *window = RCTSharedApplication().keyWindow; + self->_window = + [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, window.safeAreaInsets.top + 10)]; + self->_label = + [[UILabel alloc] initWithFrame:CGRectMake(0, window.safeAreaInsets.top - 10, screenSize.width, 20)]; [self->_window addSubview:self->_label]; self->_window.windowLevel = UIWindowLevelStatusBar + 1; diff --git a/React/CoreModules/RCTRedBox.mm b/React/CoreModules/RCTRedBox.mm index 6af0ea8e75..bbb8444677 100644 --- a/React/CoreModules/RCTRedBox.mm +++ b/React/CoreModules/RCTRedBox.mm @@ -206,11 +206,7 @@ - (NSInteger)bottomSafeViewHeight { - if (@available(iOS 11.0, *)) { - return RCTSharedApplication().delegate.window.safeAreaInsets.bottom; - } else { - return 0; - } + return RCTSharedApplication().delegate.window.safeAreaInsets.bottom; } RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) diff --git a/React/DevSupport/RCTPackagerConnection.h b/React/DevSupport/RCTPackagerConnection.h index 7f1bbd1d0e..795a3fcad5 100644 --- a/React/DevSupport/RCTPackagerConnection.h +++ b/React/DevSupport/RCTPackagerConnection.h @@ -59,6 +59,9 @@ typedef void (^RCTConnectedHandler)(void); /** Disconnects and removes all handlers. */ - (void)stop; +/** Reconnect with given packager server. */ +- (void)reconnect:(NSString *)packagerServerHostPort; + /** * Historically no distinction was made between notification and request * handlers. If you use this method, it will be registered as *both* a diff --git a/React/DevSupport/RCTPackagerConnection.mm b/React/DevSupport/RCTPackagerConnection.mm index 11623b6190..29e839f018 100644 --- a/React/DevSupport/RCTPackagerConnection.mm +++ b/React/DevSupport/RCTPackagerConnection.mm @@ -125,28 +125,32 @@ static RCTReconnectingWebSocket *socketForLocation(NSString *const serverHostPor _requestRegistrations.clear(); } -- (void)bundleURLSettingsChanged +- (void)reconnect:(NSString *)packagerServerHostPort { std::lock_guard l(_mutex); if (_socket == nil) { return; // already stopped } - NSString *const serverHostPort = [[RCTBundleURLProvider sharedSettings] packagerServerHostPort]; NSString *const serverScheme = [[RCTBundleURLProvider sharedSettings] packagerScheme]; - if ([serverHostPort isEqual:_serverHostPortForSocket] && [serverScheme isEqual:_serverSchemeForSocket]) { + if ([packagerServerHostPort isEqual:_serverHostPortForSocket] && [serverScheme isEqual:_serverSchemeForSocket]) { return; // unchanged } _socket.delegate = nil; [_socket stop]; - _serverHostPortForSocket = serverHostPort; + _serverHostPortForSocket = packagerServerHostPort; _serverSchemeForSocket = serverScheme; - _socket = socketForLocation(serverHostPort, serverScheme); + _socket = socketForLocation(packagerServerHostPort, serverScheme); _socket.delegate = self; [_socket start]; } +- (void)bundleURLSettingsChanged +{ + [self reconnect:[[RCTBundleURLProvider sharedSettings] packagerServerHostPort]]; +} + - (RCTHandlerToken)addNotificationHandler:(RCTNotificationHandler)handler queue:(dispatch_queue_t)queue forMethod:(NSString *)method diff --git a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm index 37468a85b1..603b76a7f6 100644 --- a/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm @@ -133,15 +133,6 @@ static void RCTSendPaperScrollEvent_DEPRECATED(UIScrollView *scrollView, NSInteg [self.scrollViewDelegateSplitter removeAllDelegates]; } -- (void)layoutSubviews -{ - [super layoutSubviews]; - - if (_subviewClippingEnabled) { - [self _remountChildren]; - } -} - - (RCTGenericDelegateSplitter> *)scrollViewDelegateSplitter { return ((RCTEnhancedScrollView *)_scrollView).delegateSplitter; @@ -611,6 +602,11 @@ static void RCTSendPaperScrollEvent_DEPRECATED(UIScrollView *scrollView, NSInteg #pragma mark - Child views mounting +- (void)updateClippedSubviewsWithClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView +{ + // Do nothing. ScrollView manages its subview clipping individually in `_remountChildren`. +} + - (void)_remountChildrenIfNeeded { CGPoint contentOffset = _scrollView.contentOffset; diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 3136efccc6..2b9d5381ad 100644 --- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -331,11 +331,7 @@ using namespace facebook::react; // `accessibilityIgnoresInvertColors` if (oldViewProps.accessibilityIgnoresInvertColors != newViewProps.accessibilityIgnoresInvertColors) { -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - if (@available(iOS 11.0, *)) { - self.accessibilityIgnoresInvertColors = newViewProps.accessibilityIgnoresInvertColors; - } -#endif + self.accessibilityIgnoresInvertColors = newViewProps.accessibilityIgnoresInvertColors; } // `accessibilityValue` diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index 7887d2f4f6..389ae00f1b 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -122,8 +122,8 @@ class LayoutAnimationDelegateProxy : public LayoutAnimationStatusDelegate, publi if (reactNativeConfig->getBool("react_fabric:enabled_layout_animations_ios")) { _layoutAnimationDelegateProxy = std::make_shared((__bridge void *)self); - _animationDriver = - std::make_shared(toolbox.runtimeExecutor, _layoutAnimationDelegateProxy.get()); + _animationDriver = std::make_shared( + toolbox.runtimeExecutor, toolbox.contextContainer, _layoutAnimationDelegateProxy.get()); if (reactNativeConfig->getBool("react_fabric:enabled_skip_invalidated_key_frames_ios")) { _animationDriver->enableSkipInvalidatedKeyFrames(); } diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index 82fdc0978f..63f065b2a6 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -261,10 +261,6 @@ static BackgroundExecutor RCTGetBackgroundExecutor() RCTSetRemoveClippedSubviewsEnabled(YES); } - if (reactNativeConfig && reactNativeConfig->getBool("react_fabric:enable_initial_max_size_ios")) { - RCTSetInitialMaxSizeEnabled(YES); - } - auto componentRegistryFactory = [factory = wrapManagedObject(_mountingManager.componentViewRegistry.componentViewFactory)]( EventDispatcher::Weak const &eventDispatcher, ContextContainer::Shared const &contextContainer) { diff --git a/React/Fabric/RCTSurfaceTouchHandler.mm b/React/Fabric/RCTSurfaceTouchHandler.mm index ba64bef7c1..eb5d126eda 100644 --- a/React/Fabric/RCTSurfaceTouchHandler.mm +++ b/React/Fabric/RCTSurfaceTouchHandler.mm @@ -10,7 +10,6 @@ #import #import #import -#import #import "RCTConversions.h" #import "RCTTouchableComponentViewProtocol.h" @@ -83,8 +82,7 @@ static void UpdateActiveTouchWithUITouch( ActiveTouch &activeTouch, UITouch *uiTouch, UIView *rootComponentView, - CGPoint rootViewOriginOffset, - NSTimeInterval unixTimestampBasis) + CGPoint rootViewOriginOffset) { CGPoint offsetPoint = [uiTouch locationInView:activeTouch.componentView]; CGPoint screenPoint = [uiTouch locationInView:uiTouch.window]; @@ -95,18 +93,14 @@ static void UpdateActiveTouchWithUITouch( activeTouch.touch.screenPoint = RCTPointFromCGPoint(screenPoint); activeTouch.touch.pagePoint = RCTPointFromCGPoint(pagePoint); - activeTouch.touch.timestamp = unixTimestampBasis + uiTouch.timestamp; + activeTouch.touch.timestamp = uiTouch.timestamp; if (RCTForceTouchAvailable()) { activeTouch.touch.force = RCTZeroIfNaN(uiTouch.force / uiTouch.maximumPossibleForce); } } -static ActiveTouch CreateTouchWithUITouch( - UITouch *uiTouch, - UIView *rootComponentView, - CGPoint rootViewOriginOffset, - NSTimeInterval unixTimestampBasis) +static ActiveTouch CreateTouchWithUITouch(UITouch *uiTouch, UIView *rootComponentView, CGPoint rootViewOriginOffset) { ActiveTouch activeTouch = {}; @@ -123,7 +117,7 @@ static ActiveTouch CreateTouchWithUITouch( componentView = componentView.superview; } - UpdateActiveTouchWithUITouch(activeTouch, uiTouch, rootComponentView, rootViewOriginOffset, unixTimestampBasis); + UpdateActiveTouchWithUITouch(activeTouch, uiTouch, rootComponentView, rootViewOriginOffset); return activeTouch; } @@ -173,12 +167,6 @@ struct PointerHasher { */ __weak UIView *_rootComponentView; IdentifierPool<11> _identifierPool; - - /* - * See Touch.h and usage. This gives us a time-basis for a monotonic - * clock that acts like a timestamp of milliseconds elapsed since UNIX epoch. - */ - NSTimeInterval _unixEpochBasisTime; } - (instancetype)init @@ -193,8 +181,6 @@ struct PointerHasher { self.delaysTouchesEnded = NO; self.delegate = self; - - _unixEpochBasisTime = [[NSDate date] timeIntervalSince1970] - [NSProcessInfo processInfo].systemUptime; } return self; @@ -222,7 +208,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act - (void)_registerTouches:(NSSet *)touches { for (UITouch *touch in touches) { - auto activeTouch = CreateTouchWithUITouch(touch, _rootComponentView, _viewOriginOffset, _unixEpochBasisTime); + auto activeTouch = CreateTouchWithUITouch(touch, _rootComponentView, _viewOriginOffset); activeTouch.touch.identifier = _identifierPool.dequeue(); _activeTouches.emplace(touch, activeTouch); } @@ -237,7 +223,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithTarget : (id)target action : (SEL)act continue; } - UpdateActiveTouchWithUITouch(iterator->second, touch, _rootComponentView, _viewOriginOffset, _unixEpochBasisTime); + UpdateActiveTouchWithUITouch(iterator->second, touch, _rootComponentView, _viewOriginOffset); } } diff --git a/React/Fabric/Surface/RCTFabricSurface.mm b/React/Fabric/Surface/RCTFabricSurface.mm index e18ee5a5f2..686e7794c4 100644 --- a/React/Fabric/Surface/RCTFabricSurface.mm +++ b/React/Fabric/Surface/RCTFabricSurface.mm @@ -61,9 +61,7 @@ using namespace facebook::react; [_surfacePresenter registerSurface:self]; - if (RCTGetInitialMaxSizeEnabled()) { - [self setMinimumSize:CGSizeZero maximumSize:RCTViewportSize()]; - } + [self setMinimumSize:CGSizeZero maximumSize:RCTViewportSize()]; [self _updateLayoutContext]; diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index afb548dd21..364c085889 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -344,12 +344,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused) if (bundle) { NSURL *url = [bundle URLForResource:@"Localizable" withExtension:@"strings"]; - if (@available(iOS 11.0, *)) { - rolesAndStatesDescription = [NSDictionary dictionaryWithContentsOfURL:url error:nil]; - } else { - // Fallback on earlier versions - rolesAndStatesDescription = [NSDictionary dictionaryWithContentsOfURL:url]; - } + rolesAndStatesDescription = [NSDictionary dictionaryWithContentsOfURL:url error:nil]; } if (rolesAndStatesDescription == nil) { // Falling back to hardcoded English list. diff --git a/React/Views/SafeAreaView/RCTSafeAreaView.m b/React/Views/SafeAreaView/RCTSafeAreaView.m index c60b3d8797..9b912560a2 100644 --- a/React/Views/SafeAreaView/RCTSafeAreaView.m +++ b/React/Views/SafeAreaView/RCTSafeAreaView.m @@ -54,13 +54,9 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame) - (UIEdgeInsets)safeAreaInsetsIfSupportedAndEnabled { -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ if (self.isSupportedByOS) { - if (@available(iOS 11.0, *)) { - return self.safeAreaInsets; - } + return self.safeAreaInsets; } -#endif return self.emulateUnlessSupported ? self.emulatedSafeAreaInsets : UIEdgeInsetsZero; } diff --git a/React/Views/ScrollView/RCTScrollView.m b/React/Views/ScrollView/RCTScrollView.m index 5ae6d4bd4e..107c3eb186 100644 --- a/React/Views/ScrollView/RCTScrollView.m +++ b/React/Views/ScrollView/RCTScrollView.m @@ -276,10 +276,8 @@ self.contentOffset = originalOffset; } else { #if !TARGET_OS_OSX // [TODO(macOS GH#774) - if (@available(iOS 11.0, *)) { - if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, self.adjustedContentInset)) { - contentInset = self.adjustedContentInset; - } + if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, self.adjustedContentInset)) { + contentInset = self.adjustedContentInset; } #endif // [TODO(macOS GH#774) CGSize boundsSize = self.bounds.size; @@ -409,17 +407,15 @@ #pragma clang diagnostic push // TODO(OSS Candidate ISS#2710739) #pragma clang diagnostic ignored "-Wunguarded-availability" // TODO(OSS Candidate ISS#2710739) -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ +#if !TARGET_OS_OSX // [TODO(macOS GH#774) // `contentInsetAdjustmentBehavior` is only available since iOS 11. // We set the default behavior to "never" so that iOS // doesn't do weird things to UIScrollView insets automatically // and keeps it as an opt-in behavior. if ([_scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { - if (@available(iOS 11.0, *)) { - _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; - } + _scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } -#endif +#endif // ]TODO(macOS GH#774) #pragma clang diagnostic pop // TODO(OSS Candidate ISS#2710739) _automaticallyAdjustContentInsets = YES; @@ -1302,19 +1298,17 @@ RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, scrollIndicatorInsets, UIE } #endif -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ -- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior API_AVAILABLE(ios(11.0)) +#if !TARGET_OS_OSX // [TODO(macOS GH#774) +- (void)setContentInsetAdjustmentBehavior:(UIScrollViewContentInsetAdjustmentBehavior)behavior { // `contentInsetAdjustmentBehavior` is available since iOS 11. if ([_scrollView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { CGPoint contentOffset = _scrollView.contentOffset; - if (@available(iOS 11.0, *)) { - _scrollView.contentInsetAdjustmentBehavior = behavior; - } + _scrollView.contentInsetAdjustmentBehavior = behavior; _scrollView.contentOffset = contentOffset; } } -#endif +#endif // ]TODO(macOS GH#774) #pragma clang diagnostic pop // TODO(OSS Candidate ISS#2710739) - (void)sendScrollEventWithName:(NSString *)eventName diff --git a/React/Views/ScrollView/RCTScrollViewManager.m b/React/Views/ScrollView/RCTScrollViewManager.m index 8c5c28a064..94ce9d8e79 100644 --- a/React/Views/ScrollView/RCTScrollViewManager.m +++ b/React/Views/ScrollView/RCTScrollViewManager.m @@ -37,9 +37,6 @@ RCT_ENUM_CONVERTER( UIScrollViewIndicatorStyleDefault, integerValue) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunguarded-availability-new" -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ RCT_ENUM_CONVERTER( UIScrollViewContentInsetAdjustmentBehavior, (@{ @@ -50,8 +47,6 @@ RCT_ENUM_CONVERTER( }), UIScrollViewContentInsetAdjustmentNever, integerValue) -#endif -#pragma clang diagnostic pop @end #endif // TODO(OSS Candidate ISS#2710739) @@ -109,9 +104,7 @@ RCT_EXPORT_VIEW_PROPERTY(inverted, BOOL) #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 /* __IPHONE_13_0 */ RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustsScrollIndicatorInsets, BOOL) #endif -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior) -#endif // overflow is used both in css-layout as well as by react-native. In css-layout // we always want to treat overflow as scroll but depending on what the overflow diff --git a/React/Views/UIView+React.m b/React/Views/UIView+React.m index 14a58f1cf7..0420aa7a54 100644 --- a/React/Views/UIView+React.m +++ b/React/Views/UIView+React.m @@ -47,21 +47,12 @@ - (BOOL)shouldAccessibilityIgnoresInvertColors { -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - if (@available(iOS 11.0, *)) { - return self.accessibilityIgnoresInvertColors; - } -#endif - return NO; + return self.accessibilityIgnoresInvertColors; } - (void)setShouldAccessibilityIgnoresInvertColors:(BOOL)shouldAccessibilityIgnoresInvertColors { -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ - if (@available(iOS 11.0, *)) { - self.accessibilityIgnoresInvertColors = shouldAccessibilityIgnoresInvertColors; - } -#endif + self.accessibilityIgnoresInvertColors = shouldAccessibilityIgnoresInvertColors; } - (BOOL)isReactRootView diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 72f0e37965..559ebb01a2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -219,6 +219,7 @@ public class ReactInstanceManager { @Nullable String jsMainModulePath, List packages, boolean useDeveloperSupport, + DevSupportManagerFactory devSupportManagerFactory, boolean requireActivity, @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, LifecycleState initialLifecycleState, @@ -250,7 +251,7 @@ public class ReactInstanceManager { Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstanceManager.initDevSupportManager"); mDevSupportManager = - DevSupportManagerFactory.create( + devSupportManagerFactory.create( applicationContext, createDevHelperInterface(), mJSMainModulePath, @@ -1225,7 +1226,8 @@ public class ReactInstanceManager { // If we can't get a UIManager something has probably gone horribly wrong if (uiManager == null) { throw new IllegalStateException( - "Unable to attach a rootView to ReactInstance when UIManager is not properly initialized."); + "Unable to attach a rootView to ReactInstance when UIManager is not properly" + + " initialized."); } @Nullable Bundle initialProperties = reactRoot.getAppProperties(); @@ -1366,10 +1368,6 @@ public class ReactInstanceManager { } } - if (ReactFeatureFlags.enableRuntimeScheduler) { - catalystInstance.installRuntimeScheduler(); - } - if (mJSIModulePackage != null) { catalystInstance.addJSIModules( mJSIModulePackage.getJSIModules( diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java index 36f700e497..0064ca021c 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java @@ -22,6 +22,8 @@ import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.NativeModuleCallExceptionHandler; import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener; import com.facebook.react.common.LifecycleState; +import com.facebook.react.devsupport.DefaultDevSupportManagerFactory; +import com.facebook.react.devsupport.DevSupportManagerFactory; import com.facebook.react.devsupport.RedBoxHandler; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; @@ -45,6 +47,7 @@ public class ReactInstanceManagerBuilder { private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; private @Nullable Application mApplication; private boolean mUseDeveloperSupport; + private @Nullable DevSupportManagerFactory mDevSupportManagerFactory; private boolean mRequireActivity; private @Nullable LifecycleState mInitialLifecycleState; private @Nullable UIImplementationProvider mUIImplementationProvider; @@ -172,6 +175,16 @@ public class ReactInstanceManagerBuilder { return this; } + /** + * Set the custom {@link DevSupportManagerFactory}. If not set, will use {@link + * DefaultDevSupportManagerFactory}. + */ + public ReactInstanceManagerBuilder setDevSupportManagerFactory( + final DevSupportManagerFactory devSupportManagerFactory) { + mDevSupportManagerFactory = devSupportManagerFactory; + return this; + } + /** * When {@code false}, indicates that correct usage of React Native will NOT involve an Activity. * For the vast majority of Android apps in the ecosystem, this will not need to change. Unless @@ -294,6 +307,9 @@ public class ReactInstanceManagerBuilder { mJSMainModulePath, mPackages, mUseDeveloperSupport, + mDevSupportManagerFactory == null + ? new DefaultDevSupportManagerFactory() + : mDevSupportManagerFactory, mRequireActivity, mBridgeIdleDebugListener, Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java index dc313d33bc..68b1f3e367 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java @@ -15,6 +15,7 @@ import com.facebook.react.bridge.JavaScriptExecutorFactory; import com.facebook.react.bridge.ReactMarker; import com.facebook.react.bridge.ReactMarkerConstants; import com.facebook.react.common.LifecycleState; +import com.facebook.react.devsupport.DevSupportManagerFactory; import com.facebook.react.devsupport.RedBoxHandler; import com.facebook.react.uimanager.UIImplementationProvider; import java.util.List; @@ -68,6 +69,7 @@ public abstract class ReactNativeHost { .setApplication(mApplication) .setJSMainModulePath(getJSMainModuleName()) .setUseDeveloperSupport(getUseDeveloperSupport()) + .setDevSupportManagerFactory(getDevSupportManagerFactory()) .setRequireActivity(getShouldRequireActivity()) .setRedBoxHandler(getRedBoxHandler()) .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory()) @@ -160,6 +162,11 @@ public abstract class ReactNativeHost { /** Returns whether dev mode should be enabled. This enables e.g. the dev menu. */ public abstract boolean getUseDeveloperSupport(); + /** Get the {@link DevSupportManagerFactory}. Override this to use a custom dev support manager */ + protected @Nullable DevSupportManagerFactory getDevSupportManagerFactory() { + return null; + } + /** * Returns a list of {@link ReactPackage} used by the app. You'll most likely want to return at * least the {@code MainReactPackage}. If your app uses additional views or modules besides the diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 13074f8963..5bfdf9aafd 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -16,8 +16,10 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; +import android.os.Build; import android.os.Bundle; import android.util.AttributeSet; +import android.view.DisplayCutout; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; @@ -647,6 +649,11 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot { mJSTouchDispatcher = new JSTouchDispatcher(this); } + @VisibleForTesting + /* package */ void simulateCheckForKeyboardForTesting() { + getCustomGlobalLayoutListener().checkForKeyboardEvents(); + } + private CustomGlobalLayoutListener getCustomGlobalLayoutListener() { if (mCustomGlobalLayoutListener == null) { mCustomGlobalLayoutListener = new CustomGlobalLayoutListener(); @@ -766,8 +773,17 @@ public class ReactRootView extends FrameLayout implements RootView, ReactRoot { private void checkForKeyboardEvents() { getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea); + int notchHeight = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + DisplayCutout displayCutout = getRootView().getRootWindowInsets().getDisplayCutout(); + if (displayCutout != null) { + notchHeight = displayCutout.getSafeInsetTop(); + } + } final int heightDiff = - DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels - mVisibleViewArea.bottom; + DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels + - mVisibleViewArea.bottom + + notchHeight; boolean isKeyboardShowingOrKeyboardHeightChanged = mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected; diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java index 09eb2c7018..ddd1bee4f0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java @@ -113,8 +113,6 @@ public interface CatalystInstance RuntimeScheduler getRuntimeScheduler(); - void installRuntimeScheduler(); - void addJSIModules(List jsiModules); /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java index d5a9b97571..74992ad17a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java @@ -108,7 +108,8 @@ public class CatalystInstanceImpl implements CatalystInstance { // C++ parts private final HybridData mHybridData; - private static native HybridData initHybrid(); + private static native HybridData initHybrid( + boolean enableRuntimeScheduler, boolean enableRuntimeSchedulerInTurboModule); public native CallInvokerHolderImpl getJSCallInvokerHolder(); @@ -123,7 +124,15 @@ public class CatalystInstanceImpl implements CatalystInstance { FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge."); Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstanceImpl"); - mHybridData = initHybrid(); + if (ReactFeatureFlags.enableRuntimeSchedulerInTurboModule + && !ReactFeatureFlags.enableRuntimeScheduler) { + Assertions.assertUnreachable(); + } + + mHybridData = + initHybrid( + ReactFeatureFlags.enableRuntimeScheduler, + ReactFeatureFlags.enableRuntimeSchedulerInTurboModule); mReactQueueConfiguration = ReactQueueConfigurationImpl.create( @@ -560,8 +569,6 @@ public class CatalystInstanceImpl implements CatalystInstance { public native RuntimeScheduler getRuntimeScheduler(); - public native void installRuntimeScheduler(); - @Override public void addJSIModules(List jsiModules) { mJSIModuleRegistry.registerModules(jsiModules); diff --git a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK index 02246b5339..710a6095cb 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/common/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/common/BUCK @@ -1,4 +1,4 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "react_native_dep", "rn_android_build_config", "rn_android_library") +load("//tools/build_defs/oss:rn_defs.bzl", "HERMES_BYTECODE_VERSION", "react_native_dep", "rn_android_build_config", "rn_android_library") SUB_PROJECTS = [ "network/**/*", @@ -41,7 +41,7 @@ rn_android_build_config( package = "com.facebook.react", values = [ "boolean IS_INTERNAL_BUILD = true", - "int HERMES_BYTECODE_VERSION = 0", + "int HERMES_BYTECODE_VERSION = {}".format(HERMES_BYTECODE_VERSION), ], visibility = [ "PUBLIC", diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index 077eb5fd7b..bb625143c2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -72,6 +72,8 @@ public class ReactFeatureFlags { public static boolean enableRuntimeScheduler = false; + public static boolean enableRuntimeSchedulerInTurboModule = false; + /** Enables a more aggressive cleanup during destruction of ReactContext */ public static boolean enableReactContextCleanupFix = false; @@ -95,4 +97,6 @@ public class ReactFeatureFlags { public static boolean enableLockFreeEventDispatcher = false; public static boolean enableAggressiveEventEmitterCleanup = false; + + public static boolean insertZReorderBarriersOnViewGroupChildren = true; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.java new file mode 100644 index 0000000000..3b0233ace2 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.devsupport; + +import android.content.Context; +import androidx.annotation.Nullable; +import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; +import com.facebook.react.devsupport.interfaces.DevSupportManager; +import com.facebook.react.packagerconnection.RequestHandler; +import java.lang.reflect.Constructor; +import java.util.Map; + +/** + * A simple factory that creates instances of {@link DevSupportManager} implementations. Uses + * reflection to create BridgeDevSupportManager if it exists. This allows ProGuard to strip that + * class and its dependencies in release builds. If the class isn't found, {@link + * DisabledDevSupportManager} is returned instead. + */ +public class DefaultDevSupportManagerFactory implements DevSupportManagerFactory { + + private static final String DEVSUPPORT_IMPL_PACKAGE = "com.facebook.react.devsupport"; + private static final String DEVSUPPORT_IMPL_CLASS = "BridgeDevSupportManager"; + + public DevSupportManager create( + Context applicationContext, + ReactInstanceDevHelper reactInstanceDevHelper, + @Nullable String packagerPathForJSBundleName, + boolean enableOnCreate, + int minNumShakes) { + + return create( + applicationContext, + reactInstanceDevHelper, + packagerPathForJSBundleName, + enableOnCreate, + null, + null, + minNumShakes, + null); + } + + @Override + public DevSupportManager create( + Context applicationContext, + ReactInstanceDevHelper reactInstanceManagerHelper, + @Nullable String packagerPathForJSBundleName, + boolean enableOnCreate, + @Nullable RedBoxHandler redBoxHandler, + @Nullable DevBundleDownloadListener devBundleDownloadListener, + int minNumShakes, + @Nullable Map customPackagerCommandHandlers) { + if (!enableOnCreate) { + return new DisabledDevSupportManager(); + } + try { + // ProGuard is surprisingly smart in this case and will keep a class if it detects a call to + // Class.forName() with a static string. So instead we generate a quasi-dynamic string to + // confuse it. + String className = + new StringBuilder(DEVSUPPORT_IMPL_PACKAGE) + .append(".") + .append(DEVSUPPORT_IMPL_CLASS) + .toString(); + Class devSupportManagerClass = Class.forName(className); + Constructor constructor = + devSupportManagerClass.getConstructor( + Context.class, + ReactInstanceDevHelper.class, + String.class, + boolean.class, + RedBoxHandler.class, + DevBundleDownloadListener.class, + int.class, + Map.class); + return (DevSupportManager) + constructor.newInstance( + applicationContext, + reactInstanceManagerHelper, + packagerPathForJSBundleName, + true, + redBoxHandler, + devBundleDownloadListener, + minNumShakes, + customPackagerCommandHandlers); + } catch (Exception e) { + throw new RuntimeException( + "Requested enabled DevSupportManager, but BridgeDevSupportManager class was not found" + + " or could not be created", + e); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java index 24ef6b3d3b..e279b6b047 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java @@ -12,39 +12,10 @@ import androidx.annotation.Nullable; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.packagerconnection.RequestHandler; -import java.lang.reflect.Constructor; import java.util.Map; -/** - * A simple factory that creates instances of {@link DevSupportManager} implementations. Uses - * reflection to create BridgeDevSupportManager if it exists. This allows ProGuard to strip that - * class and its dependencies in release builds. If the class isn't found, {@link - * DisabledDevSupportManager} is returned instead. - */ -public class DevSupportManagerFactory { - - private static final String DEVSUPPORT_IMPL_PACKAGE = "com.facebook.react.devsupport"; - private static final String DEVSUPPORT_IMPL_CLASS = "BridgeDevSupportManager"; - - public static DevSupportManager create( - Context applicationContext, - ReactInstanceDevHelper reactInstanceDevHelper, - @Nullable String packagerPathForJSBundleName, - boolean enableOnCreate, - int minNumShakes) { - - return create( - applicationContext, - reactInstanceDevHelper, - packagerPathForJSBundleName, - enableOnCreate, - null, - null, - minNumShakes, - null); - } - - public static DevSupportManager create( +public interface DevSupportManagerFactory { + DevSupportManager create( Context applicationContext, ReactInstanceDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, @@ -52,45 +23,5 @@ public class DevSupportManagerFactory { @Nullable RedBoxHandler redBoxHandler, @Nullable DevBundleDownloadListener devBundleDownloadListener, int minNumShakes, - @Nullable Map customPackagerCommandHandlers) { - if (!enableOnCreate) { - return new DisabledDevSupportManager(); - } - try { - // ProGuard is surprisingly smart in this case and will keep a class if it detects a call to - // Class.forName() with a static string. So instead we generate a quasi-dynamic string to - // confuse it. - String className = - new StringBuilder(DEVSUPPORT_IMPL_PACKAGE) - .append(".") - .append(DEVSUPPORT_IMPL_CLASS) - .toString(); - Class devSupportManagerClass = Class.forName(className); - Constructor constructor = - devSupportManagerClass.getConstructor( - Context.class, - ReactInstanceDevHelper.class, - String.class, - boolean.class, - RedBoxHandler.class, - DevBundleDownloadListener.class, - int.class, - Map.class); - return (DevSupportManager) - constructor.newInstance( - applicationContext, - reactInstanceManagerHelper, - packagerPathForJSBundleName, - true, - redBoxHandler, - devBundleDownloadListener, - minNumShakes, - customPackagerCommandHandlers); - } catch (Exception e) { - throw new RuntimeException( - "Requested enabled DevSupportManager, but BridgeDevSupportManager class was not found" - + " or could not be created", - e); - } - } + @Nullable Map customPackagerCommandHandlers); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/AsyncEventBeatV2.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/AsyncEventBeatV2.cpp new file mode 100644 index 0000000000..1d9b2dd27f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/AsyncEventBeatV2.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include + +#include "AsyncEventBeatV2.h" + +namespace facebook::react { + +AsyncEventBeatV2::AsyncEventBeatV2( + EventBeat::SharedOwnerBox const &ownerBox, + EventBeatManager *eventBeatManager, + RuntimeExecutor runtimeExecutor, + jni::global_ref javaUIManager) + : EventBeat(ownerBox), + eventBeatManager_(eventBeatManager), + runtimeExecutor_(runtimeExecutor), + javaUIManager_(javaUIManager) { + eventBeatManager->addObserver(*this); +} + +AsyncEventBeatV2::~AsyncEventBeatV2() { + eventBeatManager_->removeObserver(*this); +} + +void AsyncEventBeatV2::tick() const { + if (!isRequested_ || isBeatCallbackScheduled_) { + return; + } + + isRequested_ = false; + isBeatCallbackScheduled_ = true; + + runtimeExecutor_([this, ownerBox = ownerBox_](jsi::Runtime &runtime) { + isBeatCallbackScheduled_ = false; + auto owner = ownerBox->owner.lock(); + if (!owner) { + return; + } + + if (beatCallback_) { + beatCallback_(runtime); + } + }); +} + +void AsyncEventBeatV2::induce() const { + tick(); +} + +void AsyncEventBeatV2::request() const { + bool alreadyRequested = isRequested_; + EventBeat::request(); + if (!alreadyRequested) { + // Notifies java side that an event will be dispatched (e.g. LayoutEvent) + static auto onRequestEventBeat = + jni::findClassStatic("com/facebook/react/fabric/FabricUIManager") + ->getMethod("onRequestEventBeat"); + onRequestEventBeat(javaUIManager_); + } +} + +} // namespace facebook::react diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/AsyncEventBeatV2.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/AsyncEventBeatV2.h new file mode 100644 index 0000000000..acd682f4cd --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/AsyncEventBeatV2.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 + +#include "EventBeatManager.h" + +namespace facebook::react { + +class AsyncEventBeatV2 final : public EventBeat, + public EventBeatManagerObserver { + public: + AsyncEventBeatV2( + EventBeat::SharedOwnerBox const &ownerBox, + EventBeatManager *eventBeatManager, + RuntimeExecutor runtimeExecutor, + jni::global_ref javaUIManager); + + ~AsyncEventBeatV2() override; + + void tick() const override; + + void induce() const override; + + void request() const override; + + private: + EventBeatManager *eventBeatManager_; + RuntimeExecutor runtimeExecutor_; + jni::global_ref javaUIManager_; + mutable std::atomic isBeatCallbackScheduled_{false}; +}; + +} // namespace facebook::react diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp index 7c9d43bee0..1a4bf7b58b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.cpp @@ -7,6 +7,7 @@ #include "Binding.h" #include "AsyncEventBeat.h" +#include "AsyncEventBeatV2.h" #include "EventEmitterWrapper.h" #include "ReactNativeConfigHolder.h" #include "StateWrapperImpl.h" @@ -529,7 +530,7 @@ void Binding::installFabricUIManager( auto runtimeExecutor = runtimeExecutorHolder->cthis()->get(); if (runtimeSchedulerHolder) { - auto runtimeScheduler = runtimeSchedulerHolder->cthis()->get(); + auto runtimeScheduler = runtimeSchedulerHolder->cthis()->get().lock(); if (runtimeScheduler) { runtimeScheduler->setEnableYielding(config->getBool( "react_native_new_architecture:runtimescheduler_enable_yielding_android")); @@ -541,22 +542,39 @@ void Binding::installFabricUIManager( } } + auto enableV2AsynchronousEventBeat = + config->getBool("react_fabric:enable_asynchronous_event_beat_v2_android"); + // TODO: T31905686 Create synchronous Event Beat jni::global_ref localJavaUIManager = javaUIManager_; EventBeat::Factory synchronousBeatFactory = - [eventBeatManager, runtimeExecutor, localJavaUIManager]( - EventBeat::SharedOwnerBox const &ownerBox) + [eventBeatManager, + runtimeExecutor, + localJavaUIManager, + enableV2AsynchronousEventBeat](EventBeat::SharedOwnerBox const &ownerBox) -> std::unique_ptr { - return std::make_unique( - ownerBox, eventBeatManager, runtimeExecutor, localJavaUIManager); + if (enableV2AsynchronousEventBeat) { + return std::make_unique( + ownerBox, eventBeatManager, runtimeExecutor, localJavaUIManager); + } else { + return std::make_unique( + ownerBox, eventBeatManager, runtimeExecutor, localJavaUIManager); + } }; EventBeat::Factory asynchronousBeatFactory = - [eventBeatManager, runtimeExecutor, localJavaUIManager]( - EventBeat::SharedOwnerBox const &ownerBox) + [eventBeatManager, + runtimeExecutor, + localJavaUIManager, + enableV2AsynchronousEventBeat](EventBeat::SharedOwnerBox const &ownerBox) -> std::unique_ptr { - return std::make_unique( - ownerBox, eventBeatManager, runtimeExecutor, localJavaUIManager); + if (enableV2AsynchronousEventBeat) { + return std::make_unique( + ownerBox, eventBeatManager, runtimeExecutor, localJavaUIManager); + } else { + return std::make_unique( + ownerBox, eventBeatManager, runtimeExecutor, localJavaUIManager); + } }; contextContainer->insert("ReactNativeConfig", config); @@ -587,8 +605,8 @@ void Binding::installFabricUIManager( toolbox.backgroundExecutor = backgroundExecutor_->get(); } - animationDriver_ = - std::make_shared(runtimeExecutor, this); + animationDriver_ = std::make_shared( + runtimeExecutor, contextContainer, this); scheduler_ = std::make_shared( toolbox, (animationDriver_ ? animationDriver_.get() : nullptr), this); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java index c4e41b2cee..4436ceda2b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java @@ -17,7 +17,9 @@ import androidx.annotation.Nullable; import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.touch.ReactHitSlopView; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.List; /** * Class responsible for identifying which react view should handle a given {@link MotionEvent}. It @@ -81,7 +83,7 @@ public class TouchTargetHelper { // Store eventCoords in array so that they are modified to be relative to the targetView found. viewCoords[0] = eventX; viewCoords[1] = eventY; - View nativeTargetView = findTouchTargetViewWithPointerEvents(viewCoords, viewGroup); + View nativeTargetView = findTouchTargetViewWithPointerEvents(viewCoords, viewGroup, null); if (nativeTargetView != null) { View reactTargetView = findClosestReactAncestor(nativeTargetView); if (reactTargetView != null) { @@ -94,6 +96,39 @@ public class TouchTargetHelper { return targetTag; } + /** + * Find touch event target view within the provided container given the coordinates provided via + * {@link MotionEvent}. + * + * @param eventX the X screen coordinate of the touch location + * @param eventY the Y screen coordinate of the touch location + * @param viewGroup the container view to traverse + * @param viewCoords an out parameter that will return the X,Y value in the target view + * @return If a target was found, returns a path through the view tree of all react tags that are + * a container for the touch target, ordered from target to root (last element) + */ + public static List findTargetPathAndCoordinatesForTouch( + float eventX, float eventY, ViewGroup viewGroup, float[] viewCoords) { + UiThreadUtil.assertOnUiThread(); + + // Store eventCoords in array so that they are modified to be relative to the targetView found. + viewCoords[0] = eventX; + viewCoords[1] = eventY; + + List pathAccumulator = new ArrayList<>(); + View targetView = findTouchTargetViewWithPointerEvents(viewCoords, viewGroup, pathAccumulator); + + if (targetView != null) { + View reactTargetView = findClosestReactAncestor(targetView); + int targetTag = getTouchTargetForView(reactTargetView, viewCoords[0], viewCoords[1]); + if (targetTag != pathAccumulator.get(0)) { + pathAccumulator.add(0, targetTag); + } + } + + return pathAccumulator; + } + private static View findClosestReactAncestor(View view) { while (view != null && view.getId() <= 0) { view = (View) view.getParent(); @@ -121,7 +156,10 @@ public class TouchTargetHelper { * relative to the targetView found. */ private static View findTouchTargetView( - float[] eventCoords, View view, EnumSet allowReturnTouchTargetTypes) { + float[] eventCoords, + View view, + EnumSet allowReturnTouchTargetTypes, + List pathAccumulator) { // We prefer returning a child, so we check for a child that can handle the touch first if (allowReturnTouchTargetTypes.contains(TouchTargetReturnType.CHILD) && view instanceof ViewGroup) { @@ -143,7 +181,7 @@ public class TouchTargetHelper { float restoreY = eventCoords[1]; eventCoords[0] = childPoint.x; eventCoords[1] = childPoint.y; - View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child); + View targetView = findTouchTargetViewWithPointerEvents(eventCoords, child, pathAccumulator); if (targetView != null) { // We don't allow touches on views that are outside the bounds of an `overflow: hidden` @@ -224,7 +262,7 @@ public class TouchTargetHelper { * its descendants are the touch target. */ private static @Nullable View findTouchTargetViewWithPointerEvents( - float eventCoords[], View view) { + float eventCoords[], View view, @Nullable List pathAccumulator) { PointerEvents pointerEvents = view instanceof ReactPointerEventsView ? ((ReactPointerEventsView) view).getPointerEvents() @@ -247,13 +285,23 @@ public class TouchTargetHelper { } else if (pointerEvents == PointerEvents.BOX_ONLY) { // This view may be the target, its children don't matter - return findTouchTargetView(eventCoords, view, EnumSet.of(TouchTargetReturnType.SELF)); + View targetView = + findTouchTargetView( + eventCoords, view, EnumSet.of(TouchTargetReturnType.SELF), pathAccumulator); + if (targetView != null && pathAccumulator != null) { + pathAccumulator.add(view.getId()); + } + return targetView; } else if (pointerEvents == PointerEvents.BOX_NONE) { // This view can't be the target, but its children might. View targetView = - findTouchTargetView(eventCoords, view, EnumSet.of(TouchTargetReturnType.CHILD)); + findTouchTargetView( + eventCoords, view, EnumSet.of(TouchTargetReturnType.CHILD), pathAccumulator); if (targetView != null) { + if (pathAccumulator != null) { + pathAccumulator.add(view.getId()); + } return targetView; } @@ -266,8 +314,11 @@ public class TouchTargetHelper { if (view instanceof ReactCompoundView && isTouchPointInView(eventCoords[0], eventCoords[1], view)) { int reactTag = ((ReactCompoundView) view).reactTagForTouch(eventCoords[0], eventCoords[1]); + // make sure we exclude the View itself because of the PointerEvents.BOX_NONE if (reactTag != view.getId()) { - // make sure we exclude the View itself because of the PointerEvents.BOX_NONE + if (pathAccumulator != null) { + pathAccumulator.add(view.getId()); + } return view; } } @@ -279,10 +330,22 @@ public class TouchTargetHelper { if (view instanceof ReactCompoundViewGroup && isTouchPointInView(eventCoords[0], eventCoords[1], view) && ((ReactCompoundViewGroup) view).interceptsTouchEvent(eventCoords[0], eventCoords[1])) { + if (pathAccumulator != null) { + pathAccumulator.add(view.getId()); + } return view; } - return findTouchTargetView( - eventCoords, view, EnumSet.of(TouchTargetReturnType.SELF, TouchTargetReturnType.CHILD)); + + View result = + findTouchTargetView( + eventCoords, + view, + EnumSet.of(TouchTargetReturnType.SELF, TouchTargetReturnType.CHILD), + pathAccumulator); + if (result != null && pathAccumulator != null) { + pathAccumulator.add(view.getId()); + } + return result; } else { throw new JSApplicationIllegalArgumentException( diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java index a93c0a82d8..909a564433 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java @@ -40,15 +40,6 @@ public abstract class Event { private long mTimestampMs; private int mUniqueID = sUniqueID++; - // Android native Event times use 'uptimeMillis', and historically we've used `uptimeMillis` - // throughout this Event class as the coalescing key for events, and for other purposes. - // To get an accurate(ish) absolute UNIX time for the event, we store the initial clock time here. - // uptimeMillis can then be added to this to get an accurate UNIX time. - // However, we still default to uptimeMillis: you must explicitly request UNIX time if you want - // that; see `getUnixTimestampMs`. - public static final long sInitialClockTimeUnixOffset = - SystemClock.currentTimeMillis() - SystemClock.uptimeMillis(); - protected Event() {} @Deprecated @@ -65,8 +56,16 @@ public abstract class Event { init(-1, viewTag); } - /** This method needs to be called before event is sent to event dispatcher. */ protected void init(int surfaceId, int viewTag) { + init(surfaceId, viewTag, SystemClock.uptimeMillis()); + } + + /** + * This method needs to be called before event is sent to event dispatcher. Event timestamps can + * optionally be dated/backdated to a custom time: for example, touch events should be dated with + * the system event time. + */ + protected void init(int surfaceId, int viewTag, long timestampMs) { mSurfaceId = surfaceId; mViewTag = viewTag; @@ -82,9 +81,7 @@ public abstract class Event { // At some point it would be great to pass the SurfaceContext here instead. mUIManagerType = (surfaceId == -1 ? UIManagerType.DEFAULT : UIManagerType.FABRIC); - // This is a *relative* time. See `getUnixTimestampMs`. - mTimestampMs = SystemClock.uptimeMillis(); - + mTimestampMs = timestampMs; mInitialized = true; } @@ -106,11 +103,6 @@ public abstract class Event { return mTimestampMs; } - /** @return the time at which the event happened as a UNIX timestamp, in milliseconds. */ - public final long getUnixTimestampMs() { - return sInitialClockTimeUnixOffset + mTimestampMs; - } - /** @return false if this Event can *never* be coalesced */ public boolean canCoalesce() { return true; @@ -174,7 +166,7 @@ public abstract class Event { WritableMap eventData = getEventData(); if (eventData == null) { throw new IllegalViewOperationException( - "Event: you must return a valid, non-null value from `getEventData`, or override `dispatch` and `disatchModern`. Event: " + "Event: you must return a valid, non-null value from `getEventData`, or override `dispatch` and `dispatchModern`. Event: " + getEventName()); } rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); @@ -224,7 +216,7 @@ public abstract class Event { getEventName(), canCoalesce(), getCoalescingKey(), - getEventData()); + eventData); return; } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/LockFreeEventDispatcherImpl.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/LockFreeEventDispatcherImpl.java index 3ff83f3080..a16f43494a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/LockFreeEventDispatcherImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/LockFreeEventDispatcherImpl.java @@ -52,7 +52,7 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public class LockFreeEventDispatcherImpl implements EventDispatcher, LifecycleEventListener { - private final boolean DEBUG_MODE = true && ReactBuildConfig.DEBUG; + private final boolean DEBUG_MODE = ReactBuildConfig.DEBUG; private final String TAG = LockFreeEventDispatcherImpl.class.getSimpleName(); private final ReactApplicationContext mReactContext; @@ -77,7 +77,7 @@ public class LockFreeEventDispatcherImpl implements EventDispatcher, LifecycleEv Assertions.assertNotNull(mReactEventEmitter); if (DEBUG_MODE) { - FLog.d(TAG, "dispatchEvent: " + event.toString()); + FLog.v(TAG, "dispatchEvent: " + event.toString()); } for (EventDispatcherListener listener : mListeners) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java index c2b02f42dd..c99cb277ba 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/ReactEventEmitter.java @@ -61,15 +61,10 @@ public class ReactEventEmitter implements RCTModernEventEmitter { @Override public void receiveEvent( - int surfaceId, - int targetTag, - String eventName, - boolean canCoalesceEvent, - int customCoalesceKey, - @Nullable WritableMap event) { + int surfaceId, int targetTag, String eventName, @Nullable WritableMap event) { // The two additional params here, `canCoalesceEvent` and `customCoalesceKey`, have no // meaning outside of Fabric. - receiveEvent(surfaceId, targetTag, eventName, event); + receiveEvent(surfaceId, targetTag, eventName, false, 0, event); } @Override @@ -120,10 +115,16 @@ public class ReactEventEmitter implements RCTModernEventEmitter { @Override public void receiveEvent( - int surfaceId, int targetReactTag, String eventName, @Nullable WritableMap event) { + int surfaceId, + int targetReactTag, + String eventName, + boolean canCoalesceEvent, + int customCoalesceKey, + @Nullable WritableMap event) { @UIManagerType int uiManagerType = ViewUtil.getUIManagerType(targetReactTag); if (uiManagerType == UIManagerType.FABRIC && mFabricEventEmitter != null) { - mFabricEventEmitter.receiveEvent(surfaceId, targetReactTag, eventName, event); + mFabricEventEmitter.receiveEvent( + surfaceId, targetReactTag, eventName, canCoalesceEvent, customCoalesceKey, event); } else if (uiManagerType == UIManagerType.DEFAULT && getEventEmitter(targetReactTag) != null) { mRCTEventEmitter.receiveEvent(targetReactTag, eventName, event); } else { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java index 554456cccc..de64e8c8f9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchEvent.java @@ -96,7 +96,7 @@ public class TouchEvent extends Event { float viewX, float viewY, TouchEventCoalescingKeyHelper touchEventCoalescingKeyHelper) { - super.init(surfaceId, viewTag); + super.init(surfaceId, viewTag, motionEventToCopy.getEventTime()); SoftAssertions.assertCondition( gestureStartTime != UNSET, "Gesture start time must be initialized"); diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java index b67f6e3eff..187e612116 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java @@ -64,7 +64,7 @@ public class TouchesHelper { touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY)); touch.putInt(TARGET_SURFACE_KEY, surfaceId); touch.putInt(TARGET_KEY, reactTarget); - touch.putDouble(TIMESTAMP_KEY, event.getUnixTimestampMs()); + touch.putDouble(TIMESTAMP_KEY, event.getTimestampMs()); touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index)); touches.pushMap(touch); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/CanvasUtil.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/CanvasUtil.java new file mode 100644 index 0000000000..f65353bb8d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/CanvasUtil.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.view; + +import android.annotation.SuppressLint; +import android.graphics.Canvas; +import android.os.Build; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.annotation.Nullable; + +/** + * Copied from + * Compose canvas utils + */ +public class CanvasUtil { + private CanvasUtil() {} + + private @Nullable static Method mReorderBarrierMethod = null; + private @Nullable static Method mInorderBarrierMethod = null; + private static boolean mOrderMethodsFetched = false; + + /** + * Enables Z support for the Canvas. The method is publicly available starting from API 29 and was + * hidden before, so we have to resort to reflection tricks to ensure we can use this API. + */ + @SuppressLint({"SoonBlockedPrivateApi", "PrivateApi"}) + public static void enableZ(Canvas canvas, boolean enable) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return; + } + + if (Build.VERSION.SDK_INT >= 29) { + if (enable) { + canvas.enableZ(); + } else { + canvas.disableZ(); + } + } else { + fetchOrderMethods(); + try { + if (enable && mReorderBarrierMethod != null) { + mReorderBarrierMethod.invoke(canvas); + } + if (!enable && mInorderBarrierMethod != null) { + mInorderBarrierMethod.invoke(canvas); + } + } catch (IllegalAccessException | InvocationTargetException ignore) { + // Do nothing + } + } + } + + private static void fetchOrderMethods() { + if (!mOrderMethodsFetched) { + try { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { + // use double reflection to avoid grey list on P + Method getDeclaredMethod = + Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); + mReorderBarrierMethod = + (Method) getDeclaredMethod.invoke(Canvas.class, "insertReorderBarrier", new Class[0]); + mInorderBarrierMethod = + (Method) getDeclaredMethod.invoke(Canvas.class, "insertInorderBarrier", new Class[0]); + } else { + mReorderBarrierMethod = Canvas.class.getDeclaredMethod("insertReorderBarrier"); + mInorderBarrierMethod = Canvas.class.getDeclaredMethod("insertInorderBarrier"); + } + + if (mReorderBarrierMethod == null || mInorderBarrierMethod == null) { + return; + } + + mReorderBarrierMethod.setAccessible(true); + mInorderBarrierMethod.setAccessible(true); + } catch (IllegalAccessException ignore) { + // Do nothing + } catch (InvocationTargetException ignore) { + // Do nothing + } catch (NoSuchMethodException ignore) { + // Do nothing + } + mOrderMethodsFetched = true; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 03f374a7c3..c0bf085c8f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -33,6 +33,7 @@ import com.facebook.react.bridge.ReactNoCrashSoftException; import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.touch.OnInterceptTouchEventListener; import com.facebook.react.touch.ReactHitSlopView; @@ -759,6 +760,23 @@ public class ReactViewGroup extends ViewGroup } } + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean drawWithZ = + child.getElevation() > 0 && ReactFeatureFlags.insertZReorderBarriersOnViewGroupChildren; + + if (drawWithZ) { + CanvasUtil.enableZ(canvas, false); + } + + boolean result = super.drawChild(canvas, child, drawingTime); + + if (drawWithZ) { + CanvasUtil.enableZ(canvas, true); + } + return result; + } + private void dispatchOverflowDraw(Canvas canvas) { if (mOverflow != null) { switch (mOverflow) { diff --git a/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp b/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp index e5b36b30dd..00d947649a 100644 --- a/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp +++ b/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -92,12 +93,21 @@ class JInstanceCallback : public InstanceCallback { } // namespace jni::local_ref -CatalystInstanceImpl::initHybrid(jni::alias_ref) { - return makeCxxInstance(); +CatalystInstanceImpl::initHybrid( + jni::alias_ref, + bool enableRuntimeScheduler, + bool enableRuntimeSchedulerInTurboModule) { + return makeCxxInstance( + enableRuntimeScheduler, enableRuntimeSchedulerInTurboModule); } -CatalystInstanceImpl::CatalystInstanceImpl() - : instance_(std::make_unique()) {} +CatalystInstanceImpl::CatalystInstanceImpl( + bool enableRuntimeScheduler, + bool enableRuntimeSchedulerInTurboModule) + : instance_(std::make_unique()), + enableRuntimeScheduler_(enableRuntimeScheduler), + enableRuntimeSchedulerInTurboModule_( + enableRuntimeScheduler && enableRuntimeSchedulerInTurboModule) {} void CatalystInstanceImpl::warnOnLegacyNativeModuleSystemUse() { CxxNativeModule::setShouldWarnOnUse(true); @@ -140,9 +150,6 @@ void CatalystInstanceImpl::registerNatives() { "getRuntimeExecutor", CatalystInstanceImpl::getRuntimeExecutor), makeNativeMethod( "getRuntimeScheduler", CatalystInstanceImpl::getRuntimeScheduler), - makeNativeMethod( - "installRuntimeScheduler", - CatalystInstanceImpl::installRuntimeScheduler), makeNativeMethod( "warnOnLegacyNativeModuleSystemUse", CatalystInstanceImpl::warnOnLegacyNativeModuleSystemUse), @@ -347,10 +354,18 @@ void CatalystInstanceImpl::handleMemoryPressure(int pressureLevel) { jni::alias_ref CatalystInstanceImpl::getJSCallInvokerHolder() { if (!jsCallInvokerHolder_) { - jsCallInvokerHolder_ = jni::make_global( - CallInvokerHolder::newObjectCxxArgs(instance_->getJSCallInvoker())); + if (enableRuntimeSchedulerInTurboModule_) { + auto runtimeScheduler = getRuntimeScheduler(); + auto runtimeSchedulerCallInvoker = + std::make_shared( + runtimeScheduler->cthis()->get()); + jsCallInvokerHolder_ = jni::make_global( + CallInvokerHolder::newObjectCxxArgs(runtimeSchedulerCallInvoker)); + } else { + jsCallInvokerHolder_ = jni::make_global( + CallInvokerHolder::newObjectCxxArgs(instance_->getJSCallInvoker())); + } } - return jsCallInvokerHolder_; } @@ -397,11 +412,7 @@ CatalystInstanceImpl::getRuntimeExecutor() { jni::alias_ref CatalystInstanceImpl::getRuntimeScheduler() { - return runtimeScheduler_; -} - -void CatalystInstanceImpl::installRuntimeScheduler() { - if (!runtimeScheduler_) { + if (enableRuntimeScheduler_ && !runtimeScheduler_) { auto runtimeExecutor = instance_->getRuntimeExecutor(); auto runtimeScheduler = std::make_shared(runtimeExecutor); @@ -413,6 +424,8 @@ void CatalystInstanceImpl::installRuntimeScheduler() { runtime, runtimeScheduler); }); } + + return runtimeScheduler_; } } // namespace react diff --git a/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.h b/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.h index 66daec3e34..219669714d 100644 --- a/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.h +++ b/ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.h @@ -37,7 +37,10 @@ class CatalystInstanceImpl : public jni::HybridClass { static constexpr auto kJavaDescriptor = "Lcom/facebook/react/bridge/CatalystInstanceImpl;"; - static jni::local_ref initHybrid(jni::alias_ref); + static jni::local_ref initHybrid( + jni::alias_ref, + bool enableRuntimeScheduler, + bool enableRuntimeSchedulerInTurboModule); static void registerNatives(); @@ -48,7 +51,9 @@ class CatalystInstanceImpl : public jni::HybridClass { private: friend HybridBase; - CatalystInstanceImpl(); + CatalystInstanceImpl( + bool enableRuntimeScheduler, + bool enableRuntimeSchedulerInTurboModule); void initializeBridge( jni::alias_ref callback, @@ -100,11 +105,12 @@ class CatalystInstanceImpl : public jni::HybridClass { jni::alias_ref getNativeCallInvokerHolder(); jni::alias_ref getRuntimeExecutor(); jni::alias_ref getRuntimeScheduler(); - void installRuntimeScheduler(); void setGlobalVariable(std::string propName, std::string &&jsonValue); jlong getJavaScriptContext(); void handleMemoryPressure(int pressureLevel); + void createAndInstallRuntimeSchedulerIfNecessary(); + // This should be the only long-lived strong reference, but every C++ class // will have a weak reference. std::shared_ptr instance_; @@ -114,6 +120,9 @@ class CatalystInstanceImpl : public jni::HybridClass { jni::global_ref nativeCallInvokerHolder_; jni::global_ref runtimeExecutor_; jni::global_ref runtimeScheduler_; + + bool const enableRuntimeScheduler_; + bool const enableRuntimeSchedulerInTurboModule_; }; } // namespace react diff --git a/ReactAndroid/src/main/jni/react/jni/JRuntimeScheduler.cpp b/ReactAndroid/src/main/jni/react/jni/JRuntimeScheduler.cpp index 3b059ec89c..1176d21f31 100644 --- a/ReactAndroid/src/main/jni/react/jni/JRuntimeScheduler.cpp +++ b/ReactAndroid/src/main/jni/react/jni/JRuntimeScheduler.cpp @@ -11,10 +11,10 @@ namespace facebook { namespace react { JRuntimeScheduler::JRuntimeScheduler( - std::shared_ptr const &runtimeScheduler) + std::weak_ptr runtimeScheduler) : runtimeScheduler_(runtimeScheduler) {} -std::shared_ptr JRuntimeScheduler::get() { +std::weak_ptr JRuntimeScheduler::get() { return runtimeScheduler_; } diff --git a/ReactAndroid/src/main/jni/react/jni/JRuntimeScheduler.h b/ReactAndroid/src/main/jni/react/jni/JRuntimeScheduler.h index fb58c7ef77..3f3f7e857d 100644 --- a/ReactAndroid/src/main/jni/react/jni/JRuntimeScheduler.h +++ b/ReactAndroid/src/main/jni/react/jni/JRuntimeScheduler.h @@ -18,12 +18,12 @@ class JRuntimeScheduler : public jni::HybridClass { static auto constexpr kJavaDescriptor = "Lcom/facebook/react/bridge/RuntimeScheduler;"; - std::shared_ptr get(); + std::weak_ptr get(); private: friend HybridBase; - JRuntimeScheduler(std::shared_ptr const &runtimeScheduler); - std::shared_ptr runtimeScheduler_; + JRuntimeScheduler(std::weak_ptr runtimeScheduler); + std::weak_ptr runtimeScheduler_; }; } // namespace react diff --git a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java index c0a28af8f8..6389c29d71 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.java @@ -16,6 +16,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.graphics.Rect; import android.view.MotionEvent; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.CatalystInstance; @@ -25,7 +26,9 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactTestHelper; import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.SystemClock; +import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter; import com.facebook.react.uimanager.DisplayMetricsHolder; import com.facebook.react.uimanager.UIManagerModule; import com.facebook.react.uimanager.events.Event; @@ -37,6 +40,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; @@ -209,4 +213,43 @@ public class RootViewTest { rootView.unmountReactApplication(); rootView.startReactApplication(instanceManager, ""); } + + @Test + public void testCheckForKeyboardEvents() { + ReactInstanceManager instanceManager = mock(ReactInstanceManager.class); + RCTDeviceEventEmitter eventEmitterModuleMock = mock(RCTDeviceEventEmitter.class); + + when(instanceManager.getCurrentReactContext()).thenReturn(mReactContext); + when(mReactContext.getJSModule(RCTDeviceEventEmitter.class)).thenReturn(eventEmitterModuleMock); + + ReactRootView rootView = + new ReactRootView(mReactContext) { + @Override + public void getWindowVisibleDisplayFrame(Rect outRect) { + if (outRect.bottom == 0) { + outRect.bottom += 100; + outRect.right += 370; + } else { + outRect.bottom += 370; + } + } + }; + + rootView.startReactApplication(instanceManager, ""); + rootView.simulateCheckForKeyboardForTesting(); + + WritableMap params = Arguments.createMap(); + WritableMap endCoordinates = Arguments.createMap(); + double screenHeight = 470.0; + double keyboardHeight = 100.0; + params.putDouble("duration", 0.0); + endCoordinates.putDouble("width", screenHeight - keyboardHeight); + endCoordinates.putDouble("screenX", 0.0); + endCoordinates.putDouble("height", screenHeight - keyboardHeight); + endCoordinates.putDouble("screenY", keyboardHeight); + params.putMap("endCoordinates", endCoordinates); + params.putString("easing", "keyboard"); + + verify(eventEmitterModuleMock, Mockito.times(1)).emit("keyboardDidShow", params); + } } diff --git a/ReactCommon/callinvoker/BUCK b/ReactCommon/callinvoker/BUCK index c10ced16f2..b86dfb7c9a 100644 --- a/ReactCommon/callinvoker/BUCK +++ b/ReactCommon/callinvoker/BUCK @@ -1,4 +1,4 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "rn_xplat_cxx_library", "subdir_glob") +load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "CXX", "rn_xplat_cxx_library", "subdir_glob") rn_xplat_cxx_library( name = "callinvoker", @@ -17,7 +17,7 @@ rn_xplat_cxx_library( "-Wall", ], labels = ["supermodule:xplat/default/public.react_native.infra"], - platforms = (ANDROID, APPLE), + platforms = (ANDROID, APPLE, CXX), preferred_linkage = "static", preprocessor_flags = [ "-DLOG_TAG=\"ReactNative\"", diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationDriver.h b/ReactCommon/react/renderer/animations/LayoutAnimationDriver.h index c5960fa50d..428eb08eca 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationDriver.h +++ b/ReactCommon/react/renderer/animations/LayoutAnimationDriver.h @@ -18,8 +18,12 @@ class LayoutAnimationDriver : public LayoutAnimationKeyFrameManager { public: LayoutAnimationDriver( RuntimeExecutor runtimeExecutor, + ContextContainer::Shared &contextContainer, LayoutAnimationStatusDelegate *delegate) - : LayoutAnimationKeyFrameManager(runtimeExecutor, delegate) {} + : LayoutAnimationKeyFrameManager( + runtimeExecutor, + contextContainer, + delegate) {} protected: virtual void animationMutationsForFrame( diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp index 5161a51058..13e2f11155 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp @@ -94,8 +94,10 @@ interpolateFloats(float coefficient, float oldValue, float newValue) { LayoutAnimationKeyFrameManager::LayoutAnimationKeyFrameManager( RuntimeExecutor runtimeExecutor, + ContextContainer::Shared &contextContainer, LayoutAnimationStatusDelegate *delegate) : runtimeExecutor_(runtimeExecutor), + contextContainer_(contextContainer), layoutAnimationStatusDelegate_(delegate), now_([]() { return std::chrono::duration_cast( @@ -228,14 +230,7 @@ LayoutAnimationKeyFrameManager::pullTransaction( LOG(ERROR) << "BEGINNING DONE DISPLAYING ONGOING inflightAnimations_!"; #endif - // Stub PropsParserContext used for cloneProps. - // This is/should be safe because cloning doesn't actually need to - // parse props, and just copies them; therefore there should be no - // need to actually use anything in the PropsParserContext. - // If this ever changes, the LayoutAnimations API will need to change - // to pass in a real PropsParserContext. - ContextContainer contextContainer{}; - PropsParserContext propsParserContext{surfaceId, contextContainer}; + PropsParserContext propsParserContext{surfaceId, *contextContainer_}; // What to do if we detect a conflict? Get current value and make // that the baseline of the next animation. Scale the remaining time @@ -1131,15 +1126,6 @@ ShadowView LayoutAnimationKeyFrameManager::createInterpolatedShadowView( return finalView; } - // Stub PropsParserContext used for interpolateProps. - // This is/should be safe because interpolating doesn't actually need to - // parse props, and just copies them; therefore there should be no - // need to actually use anything in the PropsParserContext. - // If this ever changes, the LayoutAnimations API will need to change - // to pass in a real PropsParserContext. - ContextContainer contextContainer{}; - PropsParserContext propsParserContext{-1, contextContainer}; - ComponentDescriptor const &componentDescriptor = getComponentDescriptorForShadowView(startingView); @@ -1160,6 +1146,8 @@ ShadowView LayoutAnimationKeyFrameManager::createInterpolatedShadowView( } // Animate opacity or scale/transform + PropsParserContext propsParserContext{ + finalView.surfaceId, *contextContainer_}; mutatedShadowView.props = componentDescriptor.interpolateProps( propsParserContext, progress, startingView.props, finalView.props); react_native_assert(mutatedShadowView.props != nullptr); @@ -1202,7 +1190,7 @@ void LayoutAnimationKeyFrameManager::queueFinalMutationsForCompletedKeyFrame( AnimationKeyFrame const &keyframe, ShadowViewMutation::List &mutationsList, bool interrupted, - std::string logPrefix) const { + const std::string &logPrefix) const { if (skipInvalidatedKeyFrames_ && keyframe.invalidated) { return; } diff --git a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h index 8f9c3ccb77..528fe052da 100644 --- a/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h +++ b/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.h @@ -41,6 +41,7 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, public: LayoutAnimationKeyFrameManager( RuntimeExecutor runtimeExecutor, + ContextContainer::Shared &contextContainer, LayoutAnimationStatusDelegate *delegate); #pragma mark - UIManagerAnimationDelegate methods @@ -138,10 +139,12 @@ class LayoutAnimationKeyFrameManager : public UIManagerAnimationDelegate, AnimationKeyFrame const &keyframe, ShadowViewMutation::List &mutationsList, bool interrupted, - std::string logPrefix) const; + const std::string &logPrefix) const; private: RuntimeExecutor runtimeExecutor_; + ContextContainer::Shared contextContainer_; + mutable std::mutex layoutAnimationStatusDelegateMutex_; mutable LayoutAnimationStatusDelegate *layoutAnimationStatusDelegate_{}; mutable std::mutex surfaceIdsToStopMutex_; diff --git a/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp b/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp index 52e07d9d87..33e72268f1 100644 --- a/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp +++ b/ReactCommon/react/renderer/animations/tests/LayoutAnimationTest.cpp @@ -51,7 +51,7 @@ static void testShadowNodeTreeLifeCycleLayoutAnimations( auto entropy = seed == 0 ? Entropy() : Entropy(seed); auto eventDispatcher = EventDispatcher::Shared{}; - auto contextContainer = std::make_shared(); + auto contextContainer = std::make_shared(); auto componentDescriptorParameters = ComponentDescriptorParameters{eventDispatcher, contextContainer, nullptr}; auto viewComponentDescriptor = @@ -77,8 +77,8 @@ static void testShadowNodeTreeLifeCycleLayoutAnimations( concreteComponentDescriptorProvider()); // Create Animation Driver - auto animationDriver = - std::make_shared(runtimeExecutor, nullptr); + auto animationDriver = std::make_shared( + runtimeExecutor, contextContainer, nullptr); animationDriver->setComponentDescriptorRegistry(componentDescriptorRegistry); // Mock animation timers diff --git a/ReactCommon/react/renderer/components/view/Touch.h b/ReactCommon/react/renderer/components/view/Touch.h index 5a150b955e..91b6db7522 100644 --- a/ReactCommon/react/renderer/components/view/Touch.h +++ b/ReactCommon/react/renderer/components/view/Touch.h @@ -52,38 +52,7 @@ struct Touch { Float force; /* - * The time in seconds (with fractional milliseconds) when the touch occurred - * or when it was last mutated. - * - * Whenever possible this should be computed as: - * 1. Pick MONO_CLOCK_NOW, a monotonic system clock. Generally, something like - * `systemUptimeMillis`. - * 2. BASIS_TIME = unix timestamp from unix epoch (ms) - MONO_CLOCK_NOW - * 3. Then assign timestamp = BASIS_TIME + MONO_CLOCK_NOW - * - * The effect should be UNIX timestamp from UNIX epoch, but as a monotonic - * clock (if you just assign to current system time, it can move backwards due - * to clock adjustements, leap seconds, etc etc). So the vast majority of the - * time it will look identical to current UNIX time, but there are some - * edge-cases where it can drift. - * - * If you are not able to use the scheme above for some reason, prefer to use - * a monotonic clock. This timestamp MUST be monotonic. Do NOT just pass along - * system time. - * - * The goal is to allow touch latency to be computed in JS. JS does not have - * access to something like `systemUptimeMillis`, it generally can only access - * the current system time. This *does* mean that the touch latency could be - * computed incorrectly in cases of clock drift, so you should only use this - * as telemetry to get a decent, but not totally perfect, idea of performance. - * Do not use this latency information for anything "mission critical". You - * can assume it's probably reasonably accurate 99% of the time. - * - * Note that we attempt to adhere to the spec of timestamp here: - * https://dom.spec.whatwg.org/#dom-event-timestamp - * Notably, since `global` is not a Window object in React Native, we have - * some flexibility in how we define the time origin: - * https://w3c.github.io/hr-time/#dfn-time-origin + * The time in seconds when the touch occurred or when it was last mutated. */ Float timestamp; diff --git a/ReactCommon/react/renderer/core/Constants.cpp b/ReactCommon/react/renderer/core/Constants.cpp deleted file mode 100644 index 150473be28..0000000000 --- a/ReactCommon/react/renderer/core/Constants.cpp +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#include "Constants.h" - -namespace facebook { -namespace react { - -static bool isPropsForwardingEnabled = false; - -void Constants::setPropsForwardingEnabled(bool propsForwardingEnabled) { - isPropsForwardingEnabled = propsForwardingEnabled; -} - -bool Constants::getPropsForwardingEnabled() { - return isPropsForwardingEnabled; -} - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/react/renderer/core/Constants.h b/ReactCommon/react/renderer/core/Constants.h deleted file mode 100644 index 04b2ba57bc..0000000000 --- a/ReactCommon/react/renderer/core/Constants.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#pragma once - -namespace facebook { -namespace react { - -struct Constants { - /* - Flag controlling props forwarding when shadow node is cloned on Android. - Has no effect on iOS. - */ - static void setPropsForwardingEnabled(bool propsForwardingEnabled); - static bool getPropsForwardingEnabled(); -}; - -} // namespace react -} // namespace facebook diff --git a/ReactCommon/react/renderer/core/ShadowNode.cpp b/ReactCommon/react/renderer/core/ShadowNode.cpp index 846170e006..4c7258c4cb 100644 --- a/ReactCommon/react/renderer/core/ShadowNode.cpp +++ b/ReactCommon/react/renderer/core/ShadowNode.cpp @@ -6,7 +6,6 @@ */ #include "ShadowNode.h" -#include "Constants.h" #include "DynamicPropsUtilities.h" #include "ShadowNodeFragment.h" @@ -39,15 +38,13 @@ SharedProps ShadowNode::propsForClonedShadowNode( ShadowNode const &sourceShadowNode, Props::Shared const &props) { #ifdef ANDROID - if (Constants::getPropsForwardingEnabled()) { - bool hasBeenMounted = sourceShadowNode.hasBeenMounted_; - bool sourceNodeHasRawProps = !sourceShadowNode.getProps()->rawProps.empty(); - if (!hasBeenMounted && sourceNodeHasRawProps && props) { - auto &castedProps = const_cast(*props); - castedProps.rawProps = mergeDynamicProps( - sourceShadowNode.getProps()->rawProps, props->rawProps); - return props; - } + bool hasBeenMounted = sourceShadowNode.hasBeenMounted_; + bool sourceNodeHasRawProps = !sourceShadowNode.getProps()->rawProps.empty(); + if (!hasBeenMounted && sourceNodeHasRawProps && props) { + auto &castedProps = const_cast(*props); + castedProps.rawProps = mergeDynamicProps( + sourceShadowNode.getProps()->rawProps, props->rawProps); + return props; } #endif return props ? props : sourceShadowNode.getProps(); diff --git a/ReactCommon/react/renderer/runtimescheduler/Android.mk b/ReactCommon/react/renderer/runtimescheduler/Android.mk index 010cced384..b67a8e6329 100644 --- a/ReactCommon/react/renderer/runtimescheduler/Android.mk +++ b/ReactCommon/react/renderer/runtimescheduler/Android.mk @@ -15,7 +15,7 @@ LOCAL_SRC_FILES := $(wildcard $(LOCAL_PATH)/*.cpp) LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../../../ -LOCAL_SHARED_LIBRARIES := libruntimeexecutor libreact_render_core libreact_debug libjsi +LOCAL_SHARED_LIBRARIES := libruntimeexecutor libreact_render_core libreact_debug libjsi callinvoker LOCAL_CFLAGS := \ -DLOG_TAG=\"Fabric\" @@ -25,3 +25,4 @@ LOCAL_CFLAGS += -fexceptions -frtti -std=c++17 -Wall include $(BUILD_SHARED_LIBRARY) $(call import-module,runtimeexecutor) +$(call import-module,callinvoker) diff --git a/ReactCommon/react/renderer/runtimescheduler/BUCK b/ReactCommon/react/renderer/runtimescheduler/BUCK index 8303728982..d313bb670a 100644 --- a/ReactCommon/react/renderer/runtimescheduler/BUCK +++ b/ReactCommon/react/renderer/runtimescheduler/BUCK @@ -53,6 +53,7 @@ rn_xplat_cxx_library( react_native_xplat_target("runtimeexecutor:runtimeexecutor"), react_native_xplat_target("react/renderer/debug:debug"), react_native_xplat_target("better:better"), + react_native_xplat_target("callinvoker:callinvoker"), ], ) diff --git a/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.cpp b/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.cpp new file mode 100644 index 0000000000..70f02ca132 --- /dev/null +++ b/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "RuntimeSchedulerCallInvoker.h" + +namespace facebook { +namespace react { + +RuntimeSchedulerCallInvoker::RuntimeSchedulerCallInvoker( + std::weak_ptr runtimeScheduler) + : runtimeScheduler_(runtimeScheduler) {} + +void RuntimeSchedulerCallInvoker::invokeAsync(std::function &&func) { + if (auto runtimeScheduler = runtimeScheduler_.lock()) { + runtimeScheduler->scheduleWork( + [func = std::move(func)](jsi::Runtime &) { func(); }); + } +} + +void RuntimeSchedulerCallInvoker::invokeSync(std::function &&func) { + if (auto runtimeScheduler = runtimeScheduler_.lock()) { + runtimeScheduler->executeNowOnTheSameThread( + [func = std::move(func)](jsi::Runtime &) { func(); }); + } +} + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h b/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h new file mode 100644 index 0000000000..b40e0cad72 --- /dev/null +++ b/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerCallInvoker.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) Facebook, Inc. and its 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 +#include + +namespace facebook { +namespace react { + +/* + * Exposes RuntimeScheduler to native modules. All calls invonked on JavaScript + * queue from native modules will be funneled through RuntimeScheduler. + */ +class RuntimeSchedulerCallInvoker : public CallInvoker { + public: + RuntimeSchedulerCallInvoker(std::weak_ptr runtimeScheduler); + + void invokeAsync(std::function &&func) override; + void invokeSync(std::function &&func) override; + + private: + /* + * RuntimeScheduler is retained by the runtime. It must not be + * retained by anything beyond the runtime. + */ + std::weak_ptr runtimeScheduler_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 2d5e153505..00d13bcb53 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #include @@ -119,8 +118,6 @@ Scheduler::Scheduler( #ifdef ANDROID removeOutstandingSurfacesOnDestruction_ = reactNativeConfig_->getBool( "react_fabric:remove_outstanding_surfaces_on_destruction_android"); - Constants::setPropsForwardingEnabled(reactNativeConfig_->getBool( - "react_fabric:enable_props_forwarding_android")); #else removeOutstandingSurfacesOnDestruction_ = reactNativeConfig_->getBool( "react_fabric:remove_outstanding_surfaces_on_destruction_ios"); diff --git a/build.gradle.kts b/build.gradle.kts index 8c17661ba0..4318c6d00e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,6 @@ buildscript { repositories { - mavenLocal() google() mavenCentral() } @@ -31,7 +30,6 @@ allprojects { // All of Detox's artifacts are provided via the npm module url = uri("$rootDir/node_modules/detox/Detox-android") } - mavenLocal() google() mavenCentral() } diff --git a/package.json b/package.json index c491f7fabe..70159cff57 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "scripts/compose-source-maps.js", "scripts/find-node.sh", "scripts/fixmacscripts.sh", - "scripts/generate-specs.sh", "scripts/generate-specs-cli.js", "scripts/ios-configure-glog.sh", "scripts/launchPackager.bat", @@ -153,7 +152,7 @@ "eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-native": "^3.11.0", "eslint-plugin-relay": "1.8.1", - "flow-bin": "^0.159.0", + "flow-bin": "^0.160.2", "jest": "^26.6.3", "jest-junit": "^10.0.0", "jscodeshift": "^0.11.0", diff --git a/packages/babel-plugin-codegen/__tests__/__snapshots__/index-test.js.snap b/packages/babel-plugin-codegen/__tests__/__snapshots__/index-test.js.snap index 54d4e46fd9..c324b68b28 100644 --- a/packages/babel-plugin-codegen/__tests__/__snapshots__/index-test.js.snap +++ b/packages/babel-plugin-codegen/__tests__/__snapshots__/index-test.js.snap @@ -22,7 +22,7 @@ interface NativeCommands { +scrollTo: (viewRef: React.ElementRef, y: Int32, animated: boolean) => void, } -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); const { dispatchCommand @@ -84,7 +84,7 @@ interface NativeCommands { +scrollTo: (viewRef: React.ElementRef, y: Int32, animated: boolean) => void, } -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); const { dispatchCommand diff --git a/packages/react-native-codegen/package.json b/packages/react-native-codegen/package.json index 4578b6ce99..21b086c8b0 100644 --- a/packages/react-native-codegen/package.json +++ b/packages/react-native-codegen/package.json @@ -1,6 +1,6 @@ { "name": "react-native-codegen", - "version": "0.0.7", + "version": "0.0.8", "description": "⚛️ Code generation tools for React Native", "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/react-native-codegen", "repository": { diff --git a/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js b/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js index a95cce35fe..8a16c67427 100644 --- a/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js +++ b/packages/react-native-codegen/src/generators/components/GenerateViewConfigJs.js @@ -177,7 +177,7 @@ function buildViewConfig( switch (extendProps.knownTypeName) { case 'ReactNativeCoreViewProps': imports.add( - "const NativeComponentRegistry = require('NativeComponentRegistry');", + "const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry');", ); return; diff --git a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap index 099c42e270..cf218e0b23 100644 --- a/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap +++ b/packages/react-native-codegen/src/generators/components/__tests__/__snapshots__/GenerateViewConfigJs-test.js.snap @@ -16,7 +16,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'ArrayPropsNativeComponent'; @@ -61,7 +61,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'ArrayPropsNativeComponent'; @@ -92,7 +92,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'BooleanPropNativeComponent'; @@ -123,7 +123,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'ColorPropNativeComponent'; @@ -156,7 +156,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); const {dispatchCommand} = require(\\"react-native/Libraries/Renderer/shims/ReactNative\\"); let nativeComponentName = 'CommandNativeComponent'; @@ -195,7 +195,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); const {dispatchCommand} = require(\\"react-native/Libraries/Renderer/shims/ReactNative\\"); let nativeComponentName = 'CommandNativeComponent'; @@ -237,7 +237,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'DoublePropNativeComponent'; @@ -273,7 +273,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'EventsNestedObjectNativeComponent'; @@ -314,7 +314,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'EventsNativeComponent'; @@ -375,7 +375,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'RCTInterfaceOnlyComponent'; @@ -433,7 +433,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'ExcludedAndroidComponent'; @@ -461,7 +461,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'ExcludedAndroidIosComponent'; @@ -489,7 +489,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'FloatPropNativeComponent'; @@ -525,7 +525,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'ImagePropNativeComponent'; @@ -558,7 +558,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'InsetsPropNativeComponent'; @@ -591,7 +591,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'Int32EnumPropsNativeComponent'; @@ -622,7 +622,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'IntegerPropNativeComponent'; @@ -655,7 +655,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'RCTInterfaceOnlyComponent'; @@ -696,7 +696,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'ImageColorPropNativeComponent'; @@ -741,7 +741,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'NoPropsNoEventsComponent'; @@ -769,7 +769,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'ObjectProps'; @@ -800,7 +800,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'PointPropNativeComponent'; @@ -833,7 +833,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'StringEnumPropsNativeComponent'; @@ -864,7 +864,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'StringPropComponent'; @@ -896,7 +896,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'MultiFile1NativeComponent'; @@ -937,7 +937,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); let nativeComponentName = 'MultiComponent1NativeComponent'; @@ -978,7 +978,7 @@ Map { 'use strict'; -const NativeComponentRegistry = require('NativeComponentRegistry'); +const NativeComponentRegistry = require('react-native/Libraries/NativeComponent/NativeComponentRegistry'); const {UIManager} = require(\\"react-native\\") let nativeComponentName = 'NativeComponentName'; diff --git a/packages/react-native-gradle-plugin/README.md b/packages/react-native-gradle-plugin/README.md new file mode 100644 index 0000000000..dbe677a574 --- /dev/null +++ b/packages/react-native-gradle-plugin/README.md @@ -0,0 +1,16 @@ +# react-native-gradle-plugin + +[![Version][version-badge]][package] + +A Gradle Plugin used to support development of React Native applications for Android. + +## Installation + +``` +yarn add react-native-gradle-plugin +``` + +*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like* + +[version-badge]: https://img.shields.io/npm/v/react-native-gradle-plugin?style=flat-square +[package]: https://www.npmjs.com/package/react-native-gradle-plugin diff --git a/packages/react-native-gradle-plugin/build.gradle.kts b/packages/react-native-gradle-plugin/build.gradle.kts index 60c1b3766c..88d9dc7774 100644 --- a/packages/react-native-gradle-plugin/build.gradle.kts +++ b/packages/react-native-gradle-plugin/build.gradle.kts @@ -20,9 +20,9 @@ repositories { gradlePlugin { plugins { - create("reactApp") { + create("react") { id = "com.facebook.react" - implementationClass = "com.facebook.react.ReactAppPlugin" + implementationClass = "com.facebook.react.ReactPlugin" } } } diff --git a/packages/react-native-gradle-plugin/package.json b/packages/react-native-gradle-plugin/package.json new file mode 100644 index 0000000000..25cdbff2b9 --- /dev/null +++ b/packages/react-native-gradle-plugin/package.json @@ -0,0 +1,30 @@ +{ + "name": "react-native-gradle-plugin", + "version": "0.0.1", + "description": "⚛️ Gradle Plugin for React Native", + "homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/react-native-gradle-plugin", + "repository": { + "type": "git", + "url": "git@github.com:facebook/react-native.git", + "directory": "packages/react-native-gradle-plugin" + }, + "scripts": { + "build": "./gradlew build", + "clean": "./gradlew clean", + "test": "./gradlew check" + }, + "license": "MIT", + "files": [ + "settings.gradle.kts", + "build.gradle.kts", + "gradle", + "gradlew", + "gradlew.bat", + "src", + "README.md" + ], + "dependencies": { + "react-native-codegen": "*" + }, + "devDependencies": {} +} diff --git a/packages/react-native-gradle-plugin/src/main/java/com/facebook/react/codegen/generator/JavaGenerator.java b/packages/react-native-gradle-plugin/src/main/java/com/facebook/react/codegen/generator/JavaGenerator.java index c0abf23bbb..feed3fc21e 100644 --- a/packages/react-native-gradle-plugin/src/main/java/com/facebook/react/codegen/generator/JavaGenerator.java +++ b/packages/react-native-gradle-plugin/src/main/java/com/facebook/react/codegen/generator/JavaGenerator.java @@ -30,7 +30,7 @@ import java.util.stream.Collectors; * generator is isolated to a single schema, and a single Java package output. */ public final class JavaGenerator { - public static String LICENSE_HEADER = + public static final String LICENSE_HEADER = "/*\n" + " * Copyright (c) Facebook, Inc. and its affiliates.\n" + " *\n" diff --git a/packages/react-native-gradle-plugin/src/main/java/com/facebook/react/codegen/plugin/CodegenPlugin.java b/packages/react-native-gradle-plugin/src/main/java/com/facebook/react/codegen/plugin/CodegenPlugin.java deleted file mode 100644 index 26800e283c..0000000000 --- a/packages/react-native-gradle-plugin/src/main/java/com/facebook/react/codegen/plugin/CodegenPlugin.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.codegen.plugin; - -import com.android.build.gradle.BaseExtension; -import com.facebook.react.ReactExtension; -import com.facebook.react.codegen.generator.JavaGenerator; -import com.facebook.react.tasks.BuildCodegenCLITask; -import com.facebook.react.tasks.GenerateCodegenSchemaTask; -import com.facebook.react.utils.GradleUtils; -import com.facebook.react.utils.PathUtils; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import java.io.File; -import org.gradle.api.GradleException; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.tasks.Exec; -import org.gradle.api.tasks.TaskProvider; - -/** - * A Gradle plugin to enable react-native-codegen in Gradle environment. See the Gradle API docs for - * more information: https://docs.gradle.org/6.5.1/javadoc/org/gradle/api/Project.html - */ -public class CodegenPlugin { - - public void apply(final Project project) { - final ReactExtension extension = - GradleUtils.createOrGet(project.getExtensions(), "react", ReactExtension.class, project); - - // 1. Set up build dir. - final File generatedSrcDir = new File(project.getBuildDir(), "generated/source/codegen"); - final File generatedSchemaFile = new File(generatedSrcDir, "schema.json"); - - // 2. Task: produce schema from JS files. - String os = System.getProperty("os.name").toLowerCase(); - - TaskProvider buildCodegenTask = - project - .getTasks() - .register( - "buildCodegenCLI", - BuildCodegenCLITask.class, - task -> { - task.getCodegenDir().set(extension.getCodegenDir()); - String bashWindowsHome = (String) project.findProperty("REACT_WINDOWS_BASH"); - task.getBashWindowsHome().set(bashWindowsHome); - }); - - TaskProvider generateCodegenSchemaTask = - project - .getTasks() - .register( - "generateCodegenSchemaFromJavaScript", - GenerateCodegenSchemaTask.class, - task -> { - task.getJsRootDir().set(extension.getJsRootDir()); - task.getNodeExecutableAndArgs().set(extension.getNodeExecutableAndArgs()); - task.getCodegenDir().set(extension.getCodegenDir()); - task.getGeneratedSrcDir().set(generatedSrcDir); - task.dependsOn(buildCodegenTask); - }); - - // 3. Task: generate Java code from schema. - project - .getTasks() - .register( - "generateCodegenArtifactsFromSchema", - Exec.class, - task -> { - task.dependsOn(generateCodegenSchemaTask); - - task.getInputs() - .files( - project.fileTree( - ImmutableMap.of("dir", extension.getCodegenDir().getAsFile().get()))); - task.getInputs() - .files(PathUtils.codegenGenerateSchemaCLI(extension).getAbsolutePath()); - task.getInputs().files(generatedSchemaFile); - task.getOutputs().dir(generatedSrcDir); - - if (extension.getUseJavaGenerator().get()) { - task.doLast( - s -> { - generateJavaFromSchemaWithJavaGenerator( - generatedSchemaFile, - extension.getCodegenJavaPackageName().get(), - generatedSrcDir); - }); - } - - ImmutableList execCommands = - new ImmutableList.Builder() - .add(os.contains("windows") ? "yarn.cmd" : "yarn") - .addAll(ImmutableList.copyOf(extension.getNodeExecutableAndArgs().get())) - .add( - PathUtils.codegenGenerateNativeModuleSpecsCLI(extension) - .getAbsolutePath()) - .add("android") - .add(generatedSchemaFile.getAbsolutePath()) - .add(generatedSrcDir.getAbsolutePath()) - .add(extension.getLibraryName().get()) - .add(extension.getCodegenJavaPackageName().get()) - .build(); - task.commandLine(execCommands); - }); - - // 4. Add dependencies & generated sources to the project. - // Note: This last step needs to happen after the project has been evaluated. - project.afterEvaluate( - s -> { - // `preBuild` is one of the base tasks automatically registered by Gradle. - // This will invoke the codegen before compiling the entire project. - Task preBuild = project.getTasks().findByName("preBuild"); - if (preBuild != null) { - preBuild.dependsOn("generateCodegenArtifactsFromSchema"); - } - - /** - * Finally, update the android configuration to include the generated sources. This - * equivalent to this DSL: - * - *

android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } } - * - *

See documentation at - * https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.BaseExtension.html. - */ - BaseExtension android = (BaseExtension) project.getExtensions().getByName("android"); - android - .getSourceSets() - .getByName("main") - .getJava() - .srcDir(new File(generatedSrcDir, "java")); - }); - } - - // Use Java-based generator implementation to produce the source files, instead of using the - // JS-based generator. - private void generateJavaFromSchemaWithJavaGenerator( - final File schemaFile, final String javaPackageName, final File outputDir) { - final JavaGenerator generator = new JavaGenerator(schemaFile, javaPackageName, outputDir); - try { - generator.build(); - } catch (final Exception ex) { - throw new GradleException("Failed to generate Java from schema.", ex); - } - } -} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt deleted file mode 100644 index 84912209f3..0000000000 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactAppPlugin.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react - -import com.android.build.gradle.AppExtension -import com.android.build.gradle.BaseExtension -import com.android.build.gradle.LibraryExtension -import com.facebook.react.codegen.plugin.CodegenPlugin -import com.facebook.react.utils.GradleUtils.createOrGet -import org.gradle.api.Plugin -import org.gradle.api.Project - -class ReactAppPlugin : Plugin { - override fun apply(project: Project) { - applyAppPlugin(project) - applyCodegenPlugin(project) - } - - private fun applyAppPlugin(project: Project) { - val config = project.extensions.createOrGet("react", ReactExtension::class.java, project) - - if (config.applyAppPlugin.getOrElse(false)) { - project.afterEvaluate { - val androidConfiguration = project.extensions.getByType(BaseExtension::class.java) - project.configureDevPorts(androidConfiguration) - - val isAndroidLibrary = project.plugins.hasPlugin("com.android.library") - val variants = - if (isAndroidLibrary) { - project.extensions.getByType(LibraryExtension::class.java).libraryVariants - } else { - project.extensions.getByType(AppExtension::class.java).applicationVariants - } - variants.all { project.configureReactTasks(variant = it, config = config) } - } - } - } - - private fun applyCodegenPlugin(project: Project) { - CodegenPlugin().apply(project) - } -} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt new file mode 100644 index 0000000000..ebad04dab8 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt @@ -0,0 +1,109 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.BaseExtension +import com.android.build.gradle.LibraryExtension +import com.android.build.gradle.internal.tasks.factory.dependsOn +import com.facebook.react.tasks.BuildCodegenCLITask +import com.facebook.react.tasks.GenerateCodegenArtifactsTask +import com.facebook.react.tasks.GenerateCodegenSchemaTask +import java.io.File +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task + +class ReactPlugin : Plugin { + override fun apply(project: Project) { + val extension = project.extensions.create("react", ReactExtension::class.java, project) + applyAppPlugin(project, extension) + applyCodegenPlugin(project, extension) + } + + private fun applyAppPlugin(project: Project, config: ReactExtension) { + if (config.applyAppPlugin.getOrElse(false)) { + project.afterEvaluate { + val androidConfiguration = project.extensions.getByType(BaseExtension::class.java) + project.configureDevPorts(androidConfiguration) + + val isAndroidLibrary = project.plugins.hasPlugin("com.android.library") + val variants = + if (isAndroidLibrary) { + project.extensions.getByType(LibraryExtension::class.java).libraryVariants + } else { + project.extensions.getByType(AppExtension::class.java).applicationVariants + } + variants.all { project.configureReactTasks(variant = it, config = config) } + } + } + } + + /** + * A plugin to enable react-native-codegen in Gradle environment. See the Gradle API docs for more + * information: https://docs.gradle.org/6.5.1/javadoc/org/gradle/api/Project.html + */ + private fun applyCodegenPlugin(project: Project, extension: ReactExtension) { + // 1. Set up build dir. + val generatedSrcDir = File(project.buildDir, "generated/source/codegen") + + val buildCodegenTask = + project.tasks.register("buildCodegenCLI", BuildCodegenCLITask::class.java) { + it.codegenDir.set(extension.codegenDir) + val bashWindowsHome = project.findProperty("REACT_WINDOWS_BASH") as String? + it.bashWindowsHome.set(bashWindowsHome) + } + + // 2. Task: produce schema from JS files. + val generateCodegenSchemaTask = + project.tasks.register( + "generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) { + it.dependsOn(buildCodegenTask) + it.jsRootDir.set(extension.jsRootDir) + it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs) + it.codegenDir.set(extension.codegenDir) + it.generatedSrcDir.set(generatedSrcDir) + } + + // 3. Task: generate Java code from schema. + val generateCodegenArtifactsTask = + project.tasks.register( + "generateCodegenArtifactsFromSchema", GenerateCodegenArtifactsTask::class.java) { + it.dependsOn(generateCodegenSchemaTask) + it.reactRoot.set(extension.reactRoot) + it.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs) + it.codegenDir.set(extension.codegenDir) + it.useJavaGenerator.set(extension.useJavaGenerator) + it.codegenJavaPackageName.set(extension.codegenJavaPackageName) + it.libraryName.set(extension.libraryName) + it.generatedSrcDir.set(generatedSrcDir) + } + + // 4. Add dependencies & generated sources to the project. + // Note: This last step needs to happen after the project has been evaluated. + project.afterEvaluate { + + // `preBuild` is one of the base tasks automatically registered by Gradle. + // This will invoke the codegen before compiling the entire project. + project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask) + + /** + * Finally, update the android configuration to include the generated sources. This equivalent + * to this DSL: + * + * android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } } + * + * See documentation at + * https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.BaseExtension.html. + */ + val android = project.extensions.getByName("android") as BaseExtension + + android.sourceSets.getByName("main").java.srcDir(File(generatedSrcDir, "java")) + } + } +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt index 2be6c7f606..ad0f73c5ce 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/TaskConfiguration.kt @@ -19,7 +19,9 @@ import com.facebook.react.utils.detectedHermesCommand import java.io.File import java.util.* import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.TaskProvider private const val REACT_GROUP = "react" @@ -134,6 +136,8 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactExte is LibraryVariant -> variant.packageLibraryProvider else -> tasks.named("package$targetName") } + val stripDebugSymbolsTask: TaskProvider? = tasks.named("strip${targetName}DebugSymbols") + val mergeNativeLibsTask: TaskProvider? = tasks.named("merge${targetName}NativeLibs") val mergeResourcesTask = variant.mergeResourcesProvider val mergeAssetsTask = variant.mergeAssetsProvider @@ -160,7 +164,25 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactExte packageTask.configure { if (config.enableVmCleanup.get()) { - it.doFirst { cleanupVMFiles(enableHermes, isRelease, targetPath) } + val libDir = "$buildDir/intermediates/transforms/" + val targetVariant = ".*/transforms/[^/]*/$targetPath/.*".toRegex() + it.doFirst { cleanupVMFiles(libDir, targetVariant, enableHermes, isRelease) } + } + } + + stripDebugSymbolsTask?.configure { + if (config.enableVmCleanup.get()) { + val libDir = "$buildDir/intermediates/stripped_native_libs/${targetPath}/out/lib/" + val targetVariant = ".*/stripped_native_libs/$targetPath/out/lib/.*".toRegex() + it.doLast { cleanupVMFiles(libDir, targetVariant, enableHermes, isRelease) } + } + } + + mergeNativeLibsTask?.configure { + if (config.enableVmCleanup.get()) { + val libDir = "$buildDir/intermediates/merged_native_libs/${targetPath}/out/lib/" + val targetVariant = ".*/merged_native_libs/$targetPath/out/lib/.*".toRegex() + it.doLast { cleanupVMFiles(libDir, targetVariant, enableHermes, isRelease) } } } @@ -191,14 +213,18 @@ internal fun Project.configureReactTasks(variant: BaseVariant, config: ReactExte preBundleTask.dependsOn(currentAssetsCopyTask) } -private fun Project.cleanupVMFiles(enableHermes: Boolean, isRelease: Boolean, targetPath: String) { +private fun Project.cleanupVMFiles( + libDir: String, + targetVariant: Regex, + enableHermes: Boolean, + isRelease: Boolean +) { // Delete the VM related libraries that this build doesn't need. // The application can manage this manually by setting 'enableVmCleanup: false' // // This should really be done by packaging all Hermes related libs into // two separate HermesDebug and HermesRelease AARs, but until then we'll // kludge it by deleting the .so files out of the /transforms/ directory. - val libDir = "$buildDir/intermediates/transforms/" fileTree(libDir) { if (enableHermes) { // For Hermes, delete all the libjsc* files @@ -208,18 +234,23 @@ private fun Project.cleanupVMFiles(enableHermes: Boolean, isRelease: Boolean, ta // Reduce size by deleting the debugger/inspector it.include("**/libhermes-inspector.so") it.include("**/libhermes-executor-debug.so") + it.include("**/libhermes-executor-common-debug.so") } else { // Release libs take precedence and must be removed // to allow debugging it.include("**/libhermes-executor-release.so") + it.include("**/libhermes-executor-common-release.so") } } else { // For JSC, delete all the libhermes* files it.include("**/libhermes*.so") + // Delete the libjscexecutor from release build + if (isRelease) { + it.include("**/libjscexecutor.so") + } } } .visit { visit -> - val targetVariant = ".*/transforms/[^/]*/$targetPath/.*".toRegex() val path = visit.file.absolutePath.replace(File.separatorChar, '/') if (path.matches(targetVariant) && visit.file.isFile) { visit.file.delete() diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BuildCodegenCLITask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BuildCodegenCLITask.kt index cafdad393d..1b295cdec5 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BuildCodegenCLITask.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BuildCodegenCLITask.kt @@ -27,10 +27,12 @@ abstract class BuildCodegenCLITask : Exec() { @get:Internal abstract val bashWindowsHome: Property @get:InputFiles - val input: FileCollection = - codegenDir.files("scripts", "src", "package.json", ".babelrc", ".prettierrc") + val input: FileCollection by lazy { + codegenDir.get().files("scripts", "src", "package.json", ".babelrc", ".prettierrc") + } - @get:OutputFiles val output: FileCollection = codegenDir.files("lib", "node_modules") + @get:OutputDirectories + val output: FileCollection by lazy { codegenDir.get().files("lib", "node_modules") } init { // We need this condition as we want a single instance of BuildCodegenCLITask to execute diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt new file mode 100644 index 0000000000..d6890c310e --- /dev/null +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTask.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.tasks + +import com.facebook.react.codegen.generator.JavaGenerator +import com.facebook.react.utils.windowsAwareYarn +import org.gradle.api.GradleException +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* + +abstract class GenerateCodegenArtifactsTask : Exec() { + + @get:Internal abstract val reactRoot: DirectoryProperty + + @get:Internal abstract val codegenDir: DirectoryProperty + + @get:Internal abstract val generatedSrcDir: DirectoryProperty + + @get:Input abstract val nodeExecutableAndArgs: ListProperty + + @get:Input abstract val useJavaGenerator: Property + + @get:Input abstract val codegenJavaPackageName: Property + + @get:Input abstract val libraryName: Property + + @get:InputFile + val combineJsToSchemaCli: Provider = + codegenDir.file("lib/cli/combine/combine-js-to-schema-cli.js") + + @get:InputFile + val generatedSchemaFile: Provider = generatedSrcDir.file("schema.json") + + @get:OutputDirectory val generatedJavaFiles: Provider = generatedSrcDir.dir("java") + + @get:OutputDirectory val generatedJniFiles: Provider = generatedSrcDir.dir("jni") + + override fun exec() { + setupCommandLine() + super.exec() + } + + internal fun setupCommandLine() { + if (useJavaGenerator.getOrElse(false)) { + // Use Java-based generator implementation to produce the source files, + // instead of using the JS-based generator. + try { + JavaGenerator( + generatedSchemaFile.get().asFile, + codegenJavaPackageName.get(), + generatedSrcDir.get().asFile) + .build() + } catch (e: Exception) { + throw GradleException("Failed to generate Java from schema.", e) + } + commandLine("echo", "Used JavaGenerator to generate files instead of generate-specs-cli.js") + } else { + commandLine( + windowsAwareYarn( + *nodeExecutableAndArgs.get().toTypedArray(), + reactRoot.file("scripts/generate-specs-cli.js").get().asFile.absolutePath, + "android", + generatedSchemaFile.get().asFile.absolutePath, + generatedSrcDir.get().asFile.absolutePath, + libraryName.get(), + codegenJavaPackageName.get())) + } + } +} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt index 0c42f7929e..59855d63e0 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTask.kt @@ -35,11 +35,19 @@ abstract class GenerateCodegenSchemaTask : Exec() { val generatedSchemaFile: Provider = generatedSrcDir.file("schema.json") override fun exec() { + wipeOutputDir() + setupCommandLine() + super.exec() + } + + internal fun wipeOutputDir() { generatedSrcDir.asFile.get().apply { - delete() + deleteRecursively() mkdirs() } + } + internal fun setupCommandLine() { commandLine( windowsAwareYarn( *nodeExecutableAndArgs.get().toTypedArray(), @@ -51,6 +59,5 @@ abstract class GenerateCodegenSchemaTask : Exec() { generatedSchemaFile.get().asFile.absolutePath, jsRootDir.asFile.get().absolutePath, )) - super.exec() } } diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/GradleUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/GradleUtils.kt deleted file mode 100644 index a182b64659..0000000000 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/GradleUtils.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.utils - -import org.gradle.api.Project -import org.gradle.api.plugins.ExtensionContainer - -object GradleUtils { - - @JvmStatic - fun ExtensionContainer.createOrGet(name: String, type: Class, target: Project): T = - this.findByType(type) ?: this.create(name, type, target) -} diff --git a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt index d3c318b75b..4a215f22fc 100644 --- a/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt +++ b/packages/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/utils/PathUtils.kt @@ -122,9 +122,3 @@ internal fun projectPathToLibraryName(projectPath: String): String = .split(':', '-', '_', '.') .joinToString("") { it.capitalize(Locale.ROOT) } .plus("Spec") - -fun codegenGenerateSchemaCLI(config: ReactExtension): File = - config.codegenDir.file("lib/cli/combine/combine-js-to-schema-cli.js").get().asFile - -fun codegenGenerateNativeModuleSpecsCLI(config: ReactExtension): File = - config.reactRoot.file("scripts/generate-specs-cli.js").get().asFile diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BuildCodegenCLITaskTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BuildCodegenCLITaskTest.kt new file mode 100644 index 0000000000..240d0c11d2 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/BuildCodegenCLITaskTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.tasks + +import com.facebook.react.tests.createTestTask +import java.io.File +import org.gradle.api.tasks.* +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class BuildCodegenCLITaskTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @Test + fun buildCodegenCli_input_isSetCorrectly() { + val task = createTestTask { it.codegenDir.set(tempFolder.root) } + + assertTrue(task.input.contains(File(tempFolder.root, "scripts"))) + assertTrue(task.input.contains(File(tempFolder.root, "src"))) + assertTrue(task.input.contains(File(tempFolder.root, "package.json"))) + assertTrue(task.input.contains(File(tempFolder.root, ".babelrc"))) + assertTrue(task.input.contains(File(tempFolder.root, ".prettierrc"))) + } + + @Test + fun buildCodegenCli_output_isSetCorrectly() { + val task = createTestTask { it.codegenDir.set(tempFolder.root) } + + assertTrue(task.output.contains(File(tempFolder.root, "lib"))) + assertTrue(task.output.contains(File(tempFolder.root, "node_modules"))) + } + + @Test + fun buildCodegenCli_bashWindowsHome_isSetCorrectly() { + val bashPath = tempFolder.newFile("bash").absolutePath + val task = createTestTask { it.bashWindowsHome.set(bashPath) } + + assertEquals(bashPath, task.bashWindowsHome.get()) + } + + @Test + fun buildCodegenCli_onlyIf_withMissingDirectory_isSatisfied() { + File(tempFolder.root, "lib/cli/").apply { mkdirs() } + val task = createTestTask { it.codegenDir.set(tempFolder.root) } + + assertTrue(task.onlyIf.isSatisfiedBy(task)) + } + + @Test + fun buildCodegenCli_onlyIf_withEmptyDirectory_isSatisfied() { + File(tempFolder.root, "lib/cli/").apply { mkdirs() } + val task = createTestTask { it.codegenDir.set(tempFolder.root) } + + assertTrue(task.onlyIf.isSatisfiedBy(task)) + } + + @Test + fun buildCodegenCli_onlyIf_withExistingDirtyDirectory_isNotSatisfied() { + File(tempFolder.root, "lib/cli/a-file").apply { + parentFile.mkdirs() + writeText("¯\\_(ツ)_/¯") + } + val task = createTestTask { it.codegenDir.set(tempFolder.root) } + + assertFalse(task.onlyIf.isSatisfiedBy(task)) + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTaskTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTaskTest.kt new file mode 100644 index 0000000000..b07244f49e --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenArtifactsTaskTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.tasks + +import com.facebook.react.tests.OS +import com.facebook.react.tests.OsRule +import com.facebook.react.tests.WithOs +import com.facebook.react.tests.createTestTask +import java.io.File +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class GenerateCodegenArtifactsTaskTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @get:Rule val osRule = OsRule() + + @Test + fun generateCodegenSchema_inputFiles_areSetCorrectly() { + val codegenDir = tempFolder.newFolder("codegen") + val outputDir = tempFolder.newFolder("output") + + val task = + createTestTask { + it.codegenDir.set(codegenDir) + it.generatedSrcDir.set(outputDir) + } + + assertEquals( + File(codegenDir, "lib/cli/combine/combine-js-to-schema-cli.js"), + task.combineJsToSchemaCli.get().asFile) + assertEquals(File(outputDir, "schema.json"), task.generatedSchemaFile.get().asFile) + } + + @Test + fun generateCodegenSchema_outputFile_isSetCorrectly() { + val codegenDir = tempFolder.newFolder("codegen") + val outputDir = tempFolder.newFolder("output") + + val task = + createTestTask { + it.codegenDir.set(codegenDir) + it.generatedSrcDir.set(outputDir) + } + + assertEquals(File(outputDir, "java"), task.generatedJavaFiles.get().asFile) + assertEquals(File(outputDir, "jni"), task.generatedJniFiles.get().asFile) + } + + @Test + fun generateCodegenSchema_simpleProperties_areInsideInput() { + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("npm", "help")) + it.useJavaGenerator.set(true) + it.codegenJavaPackageName.set("com.example.test") + it.libraryName.set("example-test") + } + + assertEquals(listOf("npm", "help"), task.nodeExecutableAndArgs.get()) + assertEquals(true, task.useJavaGenerator.get()) + assertEquals("com.example.test", task.codegenJavaPackageName.get()) + assertEquals("example-test", task.libraryName.get()) + assertTrue(task.inputs.properties.containsKey("nodeExecutableAndArgs")) + assertTrue(task.inputs.properties.containsKey("useJavaGenerator")) + assertTrue(task.inputs.properties.containsKey("codegenJavaPackageName")) + assertTrue(task.inputs.properties.containsKey("libraryName")) + } + + @Test + @WithOs(OS.UNIX) + fun setupCommandLine_withoutJavaGenerator_willSetupCorrectly() { + val reactRoot = tempFolder.newFolder("node_modules/react-native/") + val codegenDir = tempFolder.newFolder("codegen") + val outputDir = tempFolder.newFolder("output") + + val task = + createTestTask { + it.reactRoot.set(reactRoot) + it.codegenDir.set(codegenDir) + it.generatedSrcDir.set(outputDir) + it.nodeExecutableAndArgs.set(listOf("--verbose")) + it.codegenJavaPackageName.set("com.example.test") + it.libraryName.set("example-test") + } + + task.setupCommandLine() + + assertEquals( + listOf( + "yarn", + "--verbose", + File(reactRoot, "scripts/generate-specs-cli.js").toString(), + "android", + File(outputDir, "schema.json").toString(), + outputDir.toString(), + "example-test", + "com.example.test", + ), + task.commandLine.toMutableList()) + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTaskTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTaskTest.kt new file mode 100644 index 0000000000..cb092dfc6e --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateCodegenSchemaTaskTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.tasks + +import com.facebook.react.tests.OS +import com.facebook.react.tests.OsRule +import com.facebook.react.tests.WithOs +import com.facebook.react.tests.createTestTask +import java.io.File +import org.gradle.api.tasks.* +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class GenerateCodegenSchemaTaskTest { + + @get:Rule val tempFolder = TemporaryFolder() + + @get:Rule val osRule = OsRule() + + @Test + fun generateCodegenSchema_inputFiles_areSetCorrectly() { + val jsRootDir = + tempFolder.newFolder("js").apply { + File(this, "file.js").createNewFile() + File(this, "ignore.txt").createNewFile() + } + + val task = createTestTask { it.jsRootDir.set(jsRootDir) } + + assertEquals(jsRootDir, task.jsInputFiles.dir) + assertEquals(setOf("**/*.js"), task.jsInputFiles.includes) + assertEquals(1, task.jsInputFiles.files.size) + assertEquals(setOf(File(jsRootDir, "file.js")), task.jsInputFiles.files) + } + + @Test + fun generateCodegenSchema_outputFile_isSetCorrectly() { + val outputDir = tempFolder.newFolder("output") + + val task = createTestTask { it.generatedSrcDir.set(outputDir) } + + assertEquals(File(outputDir, "schema.json"), task.generatedSchemaFile.get().asFile) + } + + @Test + fun generateCodegenSchema_nodeExecutablesArgs_areInsideInput() { + val task = + createTestTask { + it.nodeExecutableAndArgs.set(listOf("npm", "help")) + } + + assertEquals(listOf("npm", "help"), task.nodeExecutableAndArgs.get()) + assertTrue(task.inputs.properties.containsKey("nodeExecutableAndArgs")) + } + + @Test + fun wipeOutputDir_willCreateOutputDir() { + val task = + createTestTask { + it.generatedSrcDir.set(File(tempFolder.root, "output")) + } + + task.wipeOutputDir() + + assertTrue(File(tempFolder.root, "output").exists()) + assertEquals(0, File(tempFolder.root, "output").listFiles()?.size) + } + + @Test + fun wipeOutputDir_willWipeOutputDir() { + val outputDir = + tempFolder.newFolder("output").apply { File(this, "some-generated-file").createNewFile() } + + val task = createTestTask { it.generatedSrcDir.set(outputDir) } + + task.wipeOutputDir() + + assertTrue(outputDir.exists()) + assertEquals(0, outputDir.listFiles()?.size) + } + + @Test + @WithOs(OS.UNIX) + fun setupCommandLine_willSetupCorrectly() { + val codegenDir = tempFolder.newFolder("codegen") + val jsRootDir = tempFolder.newFolder("js") + val outputDir = tempFolder.newFolder("output") + + val task = + createTestTask { + it.codegenDir.set(codegenDir) + it.jsRootDir.set(jsRootDir) + it.generatedSrcDir.set(outputDir) + it.nodeExecutableAndArgs.set(listOf("--verbose")) + } + + task.setupCommandLine() + + assertEquals( + listOf( + "yarn", + "--verbose", + File(codegenDir, "lib/cli/combine/combine-js-to-schema-cli.js").toString(), + File(outputDir, "schema.json").toString(), + jsRootDir.toString(), + ), + task.commandLine.toMutableList()) + } +} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/GradleUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/GradleUtilsTest.kt deleted file mode 100644 index 8b3c4b21f0..0000000000 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/GradleUtilsTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.tests - -import com.facebook.react.ReactExtension -import com.facebook.react.utils.GradleUtils.createOrGet -import org.gradle.testfixtures.ProjectBuilder -import org.junit.Assert.* -import org.junit.Test - -class GradleUtilsTest { - - @Test - fun createOrGet_createsNewExtension() { - val project = ProjectBuilder.builder().build() - - assertNull(project.extensions.findByType(ReactExtension::class.java)) - - project.extensions.createOrGet("testExtension", ReactExtension::class.java, project) - - assertNotNull(project.extensions.findByType(ReactExtension::class.java)) - } - - @Test - fun createOrGet_returnsExistingExtension() { - val project = ProjectBuilder.builder().build() - val expected = project.extensions.create("testExtension", ReactExtension::class.java, project) - - assertEquals( - expected, - project.extensions.createOrGet("testExtension", ReactExtension::class.java, project)) - } -} diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/TaskTestUtils.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/TaskTestUtils.kt new file mode 100644 index 0000000000..67e89e1fa1 --- /dev/null +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/TaskTestUtils.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.tests + +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.testfixtures.ProjectBuilder + +internal fun createProject(): Project { + with(ProjectBuilder.builder().build()) { + plugins.apply("com.facebook.react") + return this + } +} + +internal inline fun createTestTask( + project: Project = createProject(), + taskName: String = T::class.java.simpleName, + crossinline block: (T) -> Unit = {} +): T = project.tasks.register(taskName, T::class.java) { block(it) }.get() diff --git a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/PathUtilsTest.kt b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt similarity index 82% rename from packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/PathUtilsTest.kt rename to packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt index 0dbae619df..8c9b443469 100644 --- a/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tests/PathUtilsTest.kt +++ b/packages/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/utils/PathUtilsTest.kt @@ -5,10 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -package com.facebook.react.tests +package com.facebook.react.utils import com.facebook.react.TestReactExtension -import com.facebook.react.utils.* import java.io.File import org.gradle.testfixtures.ProjectBuilder import org.junit.Assert.* @@ -142,30 +141,4 @@ class PathUtilsTest { fun projectPathToLibraryName_withDotsAndUnderscores() { assertEquals("SampleAndroidAppSpec", projectPathToLibraryName("sample_android.app")) } - - @Test - fun codegenGenerateSchemaCLI_worksCorrectly() { - val extension = TestReactExtension(ProjectBuilder.builder().build()) - extension.codegenDir.set(tempFolder.root) - val expected = - File(tempFolder.root, "lib/cli/combine/combine-js-to-schema-cli.js").apply { - parentFile.mkdirs() - createNewFile() - } - - assertEquals(expected, codegenGenerateSchemaCLI(extension)) - } - - @Test - fun codegenGenerateNativeModuleSpecsCLI_worksCorrectly() { - val extension = TestReactExtension(ProjectBuilder.builder().build()) - extension.reactRoot.set(tempFolder.root) - val expected = - File(tempFolder.root, "scripts/generate-specs-cli.js").apply { - parentFile.mkdirs() - createNewFile() - } - - assertEquals(expected, codegenGenerateNativeModuleSpecsCLI(extension)) - } } diff --git a/packages/rn-tester/README.md b/packages/rn-tester/README.md index 8b0ea4becc..087f0cf358 100644 --- a/packages/rn-tester/README.md +++ b/packages/rn-tester/README.md @@ -12,6 +12,12 @@ Before running the app, make sure you ran: ### Running on iOS +If you are testing non-fabric component, modify [the fabric_enabled flag in RNTester's Podfile](https://github.com/facebook/react-native/blob/main/packages/rn-tester/Podfile#L21). + +```ruby +fabric_enabled = false +``` + Both macOS and Xcode are required. - `cd packages/rn-tester` - Install [Bundler](https://bundler.io/): `gem install bundler`. We use bundler to install the right version of [CocoaPods](https://cocoapods.org/) locally. diff --git a/packages/rn-tester/js/examples/ActionSheetIOS/ActionSheetIOSExample.js b/packages/rn-tester/js/examples/ActionSheetIOS/ActionSheetIOSExample.js index 917cba05c0..dae3c572a5 100644 --- a/packages/rn-tester/js/examples/ActionSheetIOS/ActionSheetIOSExample.js +++ b/packages/rn-tester/js/examples/ActionSheetIOS/ActionSheetIOSExample.js @@ -93,6 +93,41 @@ class ActionSheetTintExample extends React.Component< }; } +class ActionSheetCancelButtonTintExample extends React.Component< + $FlowFixMeProps, + $FlowFixMeState, +> { + state = { + clicked: 'none', + }; + + render() { + return ( + + + Click to show the ActionSheet + + Clicked button: {this.state.clicked} + + ); + } + + showActionSheet = () => { + ActionSheetIOS.showActionSheetWithOptions( + { + options: BUTTONS, + cancelButtonIndex: CANCEL_INDEX, + destructiveButtonIndex: DESTRUCTIVE_INDEX, + tintColor: 'green', + cancelButtonTintColor: 'brown', + }, + buttonIndex => { + this.setState({clicked: BUTTONS[buttonIndex]}); + }, + ); + }; +} + class ActionSheetAnchorExample extends React.Component< $FlowFixMeProps, $FlowFixMeState, @@ -342,6 +377,12 @@ exports.examples = [ return ; }, }, + { + title: 'Show Action Sheet with cancel tinted button', + render(): React.Element { + return ; + }, + }, { title: 'Show Action Sheet with anchor', render(): React.Element { diff --git a/react.gradle b/react.gradle index 867a06e14b..812d11e43e 100644 --- a/react.gradle +++ b/react.gradle @@ -329,8 +329,8 @@ afterEvaluate { // two separate HermesDebug and HermesRelease AARs, but until then we'll // kludge it by deleting the .so files out of the /transforms/ directory. def isRelease = targetName.toLowerCase().contains("release") - def libDir = "$buildDir/intermediates/transforms/" - def vmSelectionAction = { + + def vmSelectionAction = { libDir -> fileTree(libDir).matching { if (enableHermes) { // For Hermes, delete all the libjsc* files @@ -340,26 +340,48 @@ afterEvaluate { // Reduce size by deleting the debugger/inspector include '**/libhermes-inspector.so' include '**/libhermes-executor-debug.so' + include '**/libhermes-executor-common-debug.so' } else { // Release libs take precedence and must be removed // to allow debugging include '**/libhermes-executor-release.so' + include '**/libhermes-executor-common-release.so' } } else { // For JSC, delete all the libhermes* files include "**/libhermes*.so" + // Delete the libjscexecutor from release build + if (isRelease) { + include "**/libjscexecutor.so" + } } }.visit { details -> - def targetVariant = ".*/transforms/[^/]*/${targetPath}/.*" + def targetVariant1 = ".*/transforms/[^/]*/${targetPath}/.*" + def targetVariant2 = ".*/merged_native_libs/${targetPath}/out/lib/.*" + def targetVariant3 = ".*/stripped_native_libs/${targetPath}/out/lib/.*" def path = details.file.getAbsolutePath().replace(File.separatorChar, '/' as char) - if (path.matches(targetVariant) && details.file.isFile()) { + if ((path.matches(targetVariant1) || path.matches(targetVariant2) || path.matches(targetVariant3)) && details.file.isFile()) { details.file.delete() } } } if (enableVmCleanup) { - packageTask.doFirst(vmSelectionAction) + def task = tasks.findByName("package${targetName}") + def transformsLibDir = "$buildDir/intermediates/transforms/" + task.doFirst { vmSelectionAction(transformsLibDir) } + + def sTask = tasks.findByName("strip${targetName}DebugSymbols") + if (sTask != null) { + def strippedLibDir = "$buildDir/intermediates/stripped_native_libs/${targetPath}/out/lib/" + sTask.doLast { vmSelectionAction(strippedLibDir) } + } + + def mTask = tasks.findByName("merge${targetName}NativeLibs") + if (mTask != null) { + def mergedLibDir = "$buildDir/intermediates/merged_native_libs/${targetPath}/out/lib/" + mTask.doLast { vmSelectionAction(mergedLibDir) } + } } } } diff --git a/repo-config/package.json b/repo-config/package.json index 7d68c10aba..105224bf19 100644 --- a/repo-config/package.json +++ b/repo-config/package.json @@ -36,7 +36,7 @@ "eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-native": "^3.11.0", "eslint-plugin-relay": "^1.8.2", - "flow-bin": "^0.159.0", + "flow-bin": "^0.160.2", "jest": "^26.6.3", "jest-junit": "^10.0.0", "jscodeshift": "^0.11.0", @@ -44,6 +44,7 @@ "mkdirp": "^0.5.1", "prettier": "1.19.1", "react": "17.0.2", + "react-native-codegen": "^0.0.8", "react-shallow-renderer": "16.14.1", "react-test-renderer": "17.0.2", "shelljs": "^0.8.5", diff --git a/scripts/bump-oss-version.js b/scripts/bump-oss-version.js index 59d09174d2..548580e68a 100755 --- a/scripts/bump-oss-version.js +++ b/scripts/bump-oss-version.js @@ -53,6 +53,8 @@ const autogenerateVersionNumber = argv.autogenerateVersionNumber; const nightlyBuild = argv.nightly; + // Nightly builds don't need an update as main will already be up-to-date. + const updatePodfileLock = !nightlyBuild; let version = argv.toVersion; if (!version) { @@ -193,6 +195,15 @@ // Change react-native version in the template's package.json exec(`node scripts/set-rn-template-version.js ${version}`); + if (updatePodfileLock) { + echo('Updating RNTester Podfile.lock...'); + if (exec('source scripts/update_podfile_lock.sh && update_pods').code) { + echo('Failed to update RNTester Podfile.lock.'); + echo('Fix the issue, revert and try again.'); + exit(1); + } + } + // Verify that files changed, we just do a git diff and check how many times version is added across files const filesToValidate = [ 'package.json', diff --git a/scripts/find-node.sh b/scripts/find-node.sh index c498ba617a..d5793ed61a 100755 --- a/scripts/find-node.sh +++ b/scripts/find-node.sh @@ -37,3 +37,12 @@ if [[ ! -x node && -d ${HOME}/.anyenv/bin ]]; then eval "$(anyenv init -)" fi fi + +# Set up asdf-vm if present +if [[ -f "$HOME/.asdf/asdf.sh" ]]; then + # shellcheck source=/dev/null + . "$HOME/.asdf/asdf.sh" +elif [[ -x "$(command -v brew)" && -f "$(brew --prefix asdf)/asdf.sh" ]]; then + # shellcheck source=/dev/null + . "$(brew --prefix asdf)/asdf.sh" +fi diff --git a/scripts/publish-npm.js b/scripts/publish-npm.js index bfd325d2c4..295b785cbf 100644 --- a/scripts/publish-npm.js +++ b/scripts/publish-npm.js @@ -53,9 +53,6 @@ const {exec, echo, exit, test} = require('shelljs'); const yargs = require('yargs'); const {parseVersion} = require('./version-utils'); -const buildTag = process.env.CIRCLE_TAG; -const otp = process.env.NPM_CONFIG_OTP; - const argv = yargs .option('n', { alias: 'nightly', @@ -69,6 +66,9 @@ const argv = yargs }).argv; const nightlyBuild = argv.nightly; const dryRunBuild = argv.dryRun; +const buildFromMain = nightlyBuild || dryRunBuild; +const buildTag = process.env.CIRCLE_TAG; +const otp = process.env.NPM_CONFIG_OTP; // 34c034298dc9cad5a4553964a5a324450fda0385 const currentCommit = exec('git rev-parse HEAD', { @@ -113,7 +113,7 @@ if (dryRunBuild) { // Bump version number in various files (package.json, gradle.properties etc) // For stable, pre-release releases, we manually call bump-oss-version on release branch -if (nightlyBuild || dryRunBuild) { +if (buildFromMain) { if ( exec( `node scripts/bump-oss-version.js --nightly --to-version ${releaseVersion}`, @@ -153,35 +153,41 @@ artifacts.forEach(name => { if (dryRunBuild) { echo('Skipping `npm publish` because --dry-run is set.'); - exit(0); -} - -// Running to see if this commit has been git tagged as `latest` -const latestCommit = exec("git rev-list -n 1 'latest'", { - silent: true, -}).stdout.replace('\n', ''); -const isLatest = currentCommit === latestCommit; - -const releaseBranch = `${major}.${minor}-stable`; - -// Set the right tag for nightly and prerelease builds -// If a release is not git-tagged as `latest` we use `releaseBranch` to prevent -// npm from overriding the current `latest` version tag, which it will do if no tag is set. -const tagFlag = nightlyBuild - ? '--tag nightly' - : prerelease != null - ? '--tag next' - : isLatest - ? '--tag latest' - : `--tag ${releaseBranch}`; - -// use otp from envvars if available -const otpFlag = otp ? `--otp ${otp}` : ''; - -if (exec(`npm publish ${tagFlag} ${otpFlag}`).code) { - echo('Failed to publish package to npm'); - exit(1); + if (exec('mkdir -p build && npm pack --pack-destination build').code) { + echo('Failed to build release package.'); + exit(1); + } else { + echo('The release was built successfully.'); + exit(0); + } } else { - echo(`Published to npm ${releaseVersion}`); - exit(0); + // Running to see if this commit has been git tagged as `latest` + const latestCommit = exec("git rev-list -n 1 'latest'", { + silent: true, + }).stdout.replace('\n', ''); + const isLatest = currentCommit === latestCommit; + + const releaseBranch = `${major}.${minor}-stable`; + + // Set the right tag for nightly and prerelease builds + // If a release is not git-tagged as `latest` we use `releaseBranch` to prevent + // npm from overriding the current `latest` version tag, which it will do if no tag is set. + const tagFlag = nightlyBuild + ? '--tag nightly' + : prerelease != null + ? '--tag next' + : isLatest + ? '--tag latest' + : `--tag ${releaseBranch}`; + + // use otp from envvars if available + const otpFlag = otp ? `--otp ${otp}` : ''; + + if (exec(`npm publish ${tagFlag} ${otpFlag}`).code) { + echo('Failed to publish package to npm'); + exit(1); + } else { + echo(`Published to npm ${releaseVersion}`); + exit(0); + } } diff --git a/scripts/react-native-xcode.sh b/scripts/react-native-xcode.sh index 26743dac06..1d060dcf90 100755 --- a/scripts/react-native-xcode.sh +++ b/scripts/react-native-xcode.sh @@ -119,7 +119,7 @@ fi BUNDLE_FILE="$CONFIGURATION_BUILD_DIR/main.jsbundle" -EXTRA_ARGS= +EXTRA_ARGS=() case "$PLATFORM_NAME" in "macosx") @@ -146,12 +146,12 @@ if [[ $EMIT_SOURCEMAP == true ]]; then else PACKAGER_SOURCEMAP_FILE="$SOURCEMAP_FILE" fi - EXTRA_ARGS="$EXTRA_ARGS --sourcemap-output $PACKAGER_SOURCEMAP_FILE" + EXTRA_ARGS+=("--sourcemap-output" "$PACKAGER_SOURCEMAP_FILE") fi # Hermes doesn't require JS minification. if [[ $USE_HERMES == true && $DEV == false ]]; then - EXTRA_ARGS="$EXTRA_ARGS --minify false" + EXTRA_ARGS+=("--minify" "false") fi "$NODE_BINARY" $NODE_ARGS "$CLI_PATH" $BUNDLE_COMMAND \ @@ -162,8 +162,8 @@ fi --reset-cache \ --bundle-output "$BUNDLE_FILE" \ --assets-dest "$DEST" \ - $EXTRA_ARGS \ - $EXTRA_PACKAGER_ARGS + "${EXTRA_ARGS[@]}" \ + "${EXTRA_PACKAGER_ARGS[@]}" if [[ $USE_HERMES != true ]]; then cp "$BUNDLE_FILE" "$DEST/" diff --git a/settings.gradle.kts b/settings.gradle.kts index 2075d4e969..045b88e9ac 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,7 +8,6 @@ pluginManagement { repositories { gradlePluginPortal() - mavenLocal() google() } } diff --git a/template/_flowconfig b/template/_flowconfig index 50317312eb..725a212209 100644 --- a/template/_flowconfig +++ b/template/_flowconfig @@ -65,4 +65,4 @@ untyped-import untyped-type-import [version] -^0.159.0 +^0.160.2 diff --git a/template/android/build.gradle b/template/android/build.gradle index 650989507d..758d1d448a 100644 --- a/template/android/build.gradle +++ b/template/android/build.gradle @@ -22,7 +22,6 @@ buildscript { allprojects { repositories { mavenCentral() - mavenLocal() maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm url("$rootDir/../node_modules/react-native/android") diff --git a/yarn.lock b/yarn.lock index 0f0874469e..75a715cef0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3993,10 +3993,10 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== -flow-bin@^0.159.0: - version "0.159.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.159.0.tgz#f788480d54db6da021e3440648c1dbb5dbc8aee8" - integrity sha512-5SugX7mOdfruzn2/+42DF74bxJO2SGmSEzCvTH9wOoBi98Ie87D5Hmb9OoGfwAE5SnXJmB6OCwN2WDiJe4lI+w== +flow-bin@^0.160.2: + version "0.160.2" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.160.2.tgz#4d579f6bbd48ca975e13237b66e503e0c519c8b9" + integrity sha512-vrsiPwcqdaBsuQmPZXjF0tdKqgoVc2wH+nHamvqFzvgnQLJMLN+N0seqaOMH8t/3YKnAEbY/uZ440mALE/+WLw== flow-parser@0.*: version "0.176.2"