/** * 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. */ import { needsStateRestore, restoreStateIfNeeded, } from './ReactControlledComponent'; import {enableFlareAPI} from 'shared/ReactFeatureFlags'; import {invokeGuardedCallbackAndCatchFirstError} from 'shared/ReactErrorUtils'; // Used as a way to call batchedUpdates when we don't have a reference to // the renderer. Such as when we're dispatching events or if third party // libraries need to call batchedUpdates. Eventually, this API will go away when // everything is batched by default. We'll then have a similar API to opt-out of // scheduled work and instead do synchronous work. // Defaults let batchedUpdatesImpl = function(fn, bookkeeping) { return fn(bookkeeping); }; let discreteUpdatesImpl = function(fn, a, b, c) { return fn(a, b, c); }; let flushDiscreteUpdatesImpl = function() {}; let batchedEventUpdatesImpl = batchedUpdatesImpl; let isInsideEventHandler = false; let isBatchingEventUpdates = false; function finishEventHandler() { // Here we wait until all updates have propagated, which is important // when using controlled components within layers: // https://github.com/facebook/react/issues/1698 // Then we restore state of any controlled component. const controlledComponentsHavePendingUpdates = needsStateRestore(); if (controlledComponentsHavePendingUpdates) { // If a controlled event was fired, we may need to restore the state of // the DOM node back to the controlled value. This is necessary when React // bails out of the update without touching the DOM. flushDiscreteUpdatesImpl(); restoreStateIfNeeded(); } } export function batchedUpdates(fn, bookkeeping) { if (isInsideEventHandler) { // If we are currently inside another batch, we need to wait until it // fully completes before restoring state. return fn(bookkeeping); } isInsideEventHandler = true; try { return batchedUpdatesImpl(fn, bookkeeping); } finally { isInsideEventHandler = false; finishEventHandler(); } } export function batchedEventUpdates(fn, a, b) { if (isBatchingEventUpdates) { // If we are currently inside another batch, we need to wait until it // fully completes before restoring state. return fn(a, b); } isBatchingEventUpdates = true; try { return batchedEventUpdatesImpl(fn, a, b); } finally { isBatchingEventUpdates = false; finishEventHandler(); } } // This is for the React Flare event system export function executeUserEventHandler(fn: any => void, value: any): void { const previouslyInEventHandler = isInsideEventHandler; try { isInsideEventHandler = true; const type = typeof value === 'object' && value !== null ? value.type : ''; invokeGuardedCallbackAndCatchFirstError(type, fn, undefined, value); } finally { isInsideEventHandler = previouslyInEventHandler; } } export function discreteUpdates(fn, a, b, c) { const prevIsInsideEventHandler = isInsideEventHandler; isInsideEventHandler = true; try { return discreteUpdatesImpl(fn, a, b, c); } finally { isInsideEventHandler = prevIsInsideEventHandler; if (!isInsideEventHandler) { finishEventHandler(); } } } let lastFlushedEventTimeStamp = 0; export function flushDiscreteUpdatesIfNeeded(timeStamp: number) { // event.timeStamp isn't overly reliable due to inconsistencies in // how different browsers have historically provided the time stamp. // Some browsers provide high-resolution time stamps for all events, // some provide low-resolution time stamps for all events. FF < 52 // even mixes both time stamps together. Some browsers even report // negative time stamps or time stamps that are 0 (iOS9) in some cases. // Given we are only comparing two time stamps with equality (!==), // we are safe from the resolution differences. If the time stamp is 0 // we bail-out of preventing the flush, which can affect semantics, // such as if an earlier flush removes or adds event listeners that // are fired in the subsequent flush. However, this is the same // behaviour as we had before this change, so the risks are low. if ( !isInsideEventHandler && (!enableFlareAPI || (timeStamp === 0 || lastFlushedEventTimeStamp !== timeStamp)) ) { lastFlushedEventTimeStamp = timeStamp; flushDiscreteUpdatesImpl(); } } export function setBatchingImplementation( _batchedUpdatesImpl, _discreteUpdatesImpl, _flushDiscreteUpdatesImpl, _batchedEventUpdatesImpl, ) { batchedUpdatesImpl = _batchedUpdatesImpl; discreteUpdatesImpl = _discreteUpdatesImpl; flushDiscreteUpdatesImpl = _flushDiscreteUpdatesImpl; batchedEventUpdatesImpl = _batchedEventUpdatesImpl; }