text: /**
   * Copyright 2013-2015, Facebook, Inc.
   * All rights reserved.
   *
   * This source code is licensed under the BSD-style license found in the
   * LICENSE file in the root directory of this source tree. An additional grant
   * of patent rights can be found in the PATENTS file in the same directory.
   *
   * @providesModule ReactUpdates
   */
  
  'use strict';
  
  var CallbackQueue = require('CallbackQueue');
  var PooledClass = require('PooledClass');
  var ReactCurrentOwner = require('ReactCurrentOwner');
  var ReactPerf = require('ReactPerf');
  var Transaction = require('Transaction');
  
  var assign = require('Object.assign');
  var invariant = require('invariant');
  var warning = require('warning');
  
  var dirtyComponents = [];
  var asapCallbackQueue = CallbackQueue.getPooled();
  var asapEnqueued = false;
  
  var batchingStrategy = null;
  
  function ensureInjected() {
    invariant(
      ReactUpdates.ReactReconcileTransaction && batchingStrategy,
      'ReactUpdates: must inject a reconcile transaction class and batching ' +
      'strategy'
    );
  }
  
  var NESTED_UPDATES = {
    initialize: function() {
      this.dirtyComponentsLength = dirtyComponents.length;
    },
    close: function() {
      if (this.dirtyComponentsLength !== dirtyComponents.length) {
        // Additional updates were enqueued by componentDidUpdate handlers or
        // similar; before our own UPDATE_QUEUEING wrapper closes, we want to run
        // these new updates so that if A's componentDidUpdate calls setState on
        // B, B will update before the callback A's updater provided when calling
        // setState.
        dirtyComponents.splice(0, this.dirtyComponentsLength);
        flushBatchedUpdates();
      } else {
        dirtyComponents.length = 0;
      }
    }
  };
  
  var UPDATE_QUEUEING = {
    initialize: function() {
      this.callbackQueue.reset();
    },
    close: function() {
      this.callbackQueue.notifyAll();
    }
  };
  
  var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
  
  function ReactUpdatesFlushTransaction() {
    this.reinitializeTransaction();
    this.dirtyComponentsLength = null;
    this.callbackQueue = CallbackQueue.getPooled();
    this.reconcileTransaction =
      ReactUpdates.ReactReconcileTransaction.getPooled();
  }
  
  assign(
    ReactUpdatesFlushTransaction.prototype,
    Transaction.Mixin, {
    getTransactionWrappers: function() {
      return TRANSACTION_WRAPPERS;
    },
  
    destructor: function() {
      this.dirtyComponentsLength = null;
      CallbackQueue.release(this.callbackQueue);
      this.callbackQueue = null;
      ReactUpdates.ReactReconcileTransaction.release(this.reconcileTransaction);
      this.reconcileTransaction = null;
    },
  
    perform: function(method, scope, a) {
      // Essentially calls `this.reconcileTransaction.perform(method, scope, a)`
      // with this transaction's wrappers around it.
      return Transaction.Mixin.perform.call(
        this,
        this.reconcileTransaction.perform,
        this.reconcileTransaction,
        method,
        scope,
        a
      );
    }
  });
  
  PooledClass.addPoolingTo(ReactUpdatesFlushTransaction);
  
  function batchedUpdates(callback, a, b, c, d) {
    ensureInjected();
    batchingStrategy.batchedUpdates(callback, a, b, c, d);
  }
  
  /**
   * Array comparator for ReactComponents by mount ordering.
   *
   * @param {ReactComponent} c1 first component you're comparing
   * @param {ReactComponent} c2 second component you're comparing
   * @return {number} Return value usable by Array.prototype.sort().
   */
  function mountOrderComparator(c1, c2) {
    return c1._mountOrder - c2._mountOrder;
  }
  
  function runBatchedUpdates(transaction) {
    var len = transaction.dirtyComponentsLength;
    invariant(
      len === dirtyComponents.length,
      'Expected flush transaction\'s stored dirty-components length (%s) to ' +
      'match dirty-components array length (%s).',
      len,
      dirtyComponents.length
    );
  
    // Since reconciling a component higher in the owner hierarchy usually (not
    // always -- see shouldComponentUpdate()) will reconcile children, reconcile
    // them before their children by sorting the array.
    dirtyComponents.sort(mountOrderComparator);
  
    for (var i = 0; i < len; i++) {
      // If a component is unmounted before pending changes apply, it will still
      // be here, but we assume that it has cleared its _pendingCallbacks and
      // that performUpdateIfNecessary is a noop.
      var component = dirtyComponents[i];
  
      // If performUpdateIfNecessary happens to enqueue any new updates, we
      // shouldn't execute the callbacks until the next render happens, so
      // stash the callbacks first
      var callbacks = component._pendingCallbacks;
      component._pendingCallbacks = null;
      component.performUpdateIfNecessary(transaction.reconcileTransaction);
  
      if (callbacks) {
        for (var j = 0; j < callbacks.length; j++) {
          transaction.callbackQueue.enqueue(
            callbacks[j],
            component.getPublicInstance()
          );
        }
      }
    }
  }
  
  var flushBatchedUpdates = function() {
    // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
    // array and perform any updates enqueued by mount-ready handlers (i.e.,
    // componentDidUpdate) but we need to check here too in order to catch
    // updates enqueued by setState callbacks and asap calls.
    while (dirtyComponents.length || asapEnqueued) {
      if (dirtyComponents.length) {
        var transaction = ReactUpdatesFlushTransaction.getPooled();
        transaction.perform(runBatchedUpdates, null, transaction);
        ReactUpdatesFlushTransaction.release(transaction);
      }
  
      if (asapEnqueued) {
        asapEnqueued = false;
        var queue = asapCallbackQueue;
        asapCallbackQueue = CallbackQueue.getPooled();
        queue.notifyAll();
        CallbackQueue.release(queue);
      }
    }
  };
  flushBatchedUpdates = ReactPerf.measure(
    'ReactUpdates',
    'flushBatchedUpdates',
    flushBatchedUpdates
  );
  
  /**
   * Mark a component as needing a rerender, adding an optional callback to a
   * list of functions which will be executed once the rerender occurs.
   */
  function enqueueUpdate(component) {
    ensureInjected();
  
    // Various parts of our code (such as ReactCompositeComponent's
    // _renderValidatedComponent) assume that calls to render aren't nested;
    // verify that that's the case. (This is called by each top-level update
    // function, like setProps, setState, forceUpdate, etc.; creation and
    // destruction of top-level components is guarded in ReactMount.)
    warning(
      ReactCurrentOwner.current == null,
      'enqueueUpdate(): Render methods should be a pure function of props ' +
      'and state; triggering nested component updates from render is not ' +
      'allowed. If necessary, trigger nested updates in ' +
      'componentDidUpdate.'
    );
  
    if (!batchingStrategy.isBatchingUpdates) {
      batchingStrategy.batchedUpdates(enqueueUpdate, component);
      return;
    }
  
    dirtyComponents.push(component);
  }
  
  /**
   * Enqueue a callback to be run at the end of the current batching cycle. Throws
   * if no updates are currently being performed.
   */
  function asap(callback, context) {
    invariant(
      batchingStrategy.isBatchingUpdates,
      'ReactUpdates.asap: Can\'t enqueue an asap callback in a context where' +
      'updates are not being batched.'
    );
    asapCallbackQueue.enqueue(callback, context);
    asapEnqueued = true;
  }
  
  var ReactUpdatesInjection = {
    injectReconcileTransaction: function(ReconcileTransaction) {
      invariant(
        ReconcileTransaction,
        'ReactUpdates: must provide a reconcile transaction class'
      );
      ReactUpdates.ReactReconcileTransaction = ReconcileTransaction;
    },
  
    injectBatchingStrategy: function(_batchingStrategy) {
      invariant(
        _batchingStrategy,
        'ReactUpdates: must provide a batching strategy'
      );
      invariant(
        typeof _batchingStrategy.batchedUpdates === 'function',
        'ReactUpdates: must provide a batchedUpdates() function'
      );
      invariant(
        typeof _batchingStrategy.isBatchingUpdates === 'boolean',
        'ReactUpdates: must provide an isBatchingUpdates boolean attribute'
      );
      batchingStrategy = _batchingStrategy;
    }
  };
  
  var ReactUpdates = {
    /**
     * React references `ReactReconcileTransaction` using this property in order
     * to allow dependency injection.
     *
     * @internal
     */
    ReactReconcileTransaction: null,
  
    batchedUpdates: batchedUpdates,
    enqueueUpdate: enqueueUpdate,
    flushBatchedUpdates: flushBatchedUpdates,
    injection: ReactUpdatesInjection,
    asap: asap
  };
  
  module.exports = ReactUpdates;
  