text: /**
   * Copyright 2016-present, 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 ReactDebugTool
   * @flow
   */
  
  'use strict';
  
  var ReactInvalidSetStateWarningHook = require('ReactInvalidSetStateWarningHook');
  var ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook');
  var ReactComponentTreeHook = require('ReactComponentTreeHook');
  var ExecutionEnvironment = require('ExecutionEnvironment');
  
  var performanceNow = require('performanceNow');
  var warning = require('warning');
  
  import type {ReactElement} from 'ReactElementType';
  import type {DebugID} from 'ReactInstanceType';
  import type {Operation} from 'ReactHostOperationHistoryHook';
  
  type Hook = any;
  
  type TimerType =
    | 'ctor'
    | 'render'
    | 'componentWillMount'
    | 'componentWillUnmount'
    | 'componentWillReceiveProps'
    | 'shouldComponentUpdate'
    | 'componentWillUpdate'
    | 'componentDidUpdate'
    | 'componentDidMount';
  
  type Measurement = {
    timerType: TimerType,
    instanceID: DebugID,
    duration: number,
  };
  
  type TreeSnapshot = {
    [key: DebugID]: {
      displayName: string,
      text: string,
      updateCount: number,
      childIDs: Array<DebugID>,
      ownerID: DebugID,
      parentID: DebugID,
    },
  };
  
  type HistoryItem = {
    duration: number,
    measurements: Array<Measurement>,
    operations: Array<Operation>,
    treeSnapshot: TreeSnapshot,
  };
  
  export type FlushHistory = Array<HistoryItem>;
  
  var hooks = [];
  var didHookThrowForEvent = {};
  
  function callHook(event, fn, context, arg1, arg2, arg3, arg4, arg5) {
    try {
      fn.call(context, arg1, arg2, arg3, arg4, arg5);
    } catch (e) {
      warning(
        didHookThrowForEvent[event],
        'Exception thrown by hook while handling %s: %s',
        event,
        e + '\n' + e.stack,
      );
      didHookThrowForEvent[event] = true;
    }
  }
  
  function emitEvent(event, arg1, arg2, arg3, arg4, arg5) {
    for (var i = 0; i < hooks.length; i++) {
      var hook = hooks[i];
      var fn = hook[event];
      if (fn) {
        callHook(event, fn, hook, arg1, arg2, arg3, arg4, arg5);
      }
    }
  }
  
  var isProfiling = false;
  var flushHistory = [];
  var lifeCycleTimerStack = [];
  var currentFlushNesting = 0;
  var currentFlushMeasurements = [];
  var currentFlushStartTime = 0;
  var currentTimerDebugID = null;
  var currentTimerStartTime = 0;
  var currentTimerNestedFlushDuration = 0;
  var currentTimerType = null;
  
  var lifeCycleTimerHasWarned = false;
  
  function clearHistory() {
    ReactComponentTreeHook.purgeUnmountedComponents();
    ReactHostOperationHistoryHook.clearHistory();
  }
  
  function getTreeSnapshot(registeredIDs) {
    return registeredIDs.reduce((tree, id) => {
      var ownerID = ReactComponentTreeHook.getOwnerID(id);
      var parentID = ReactComponentTreeHook.getParentID(id);
      tree[id] = {
        displayName: ReactComponentTreeHook.getDisplayName(id),
        text: ReactComponentTreeHook.getText(id),
        updateCount: ReactComponentTreeHook.getUpdateCount(id),
        childIDs: ReactComponentTreeHook.getChildIDs(id),
        // Text nodes don't have owners but this is close enough.
        ownerID: ownerID ||
          (parentID && ReactComponentTreeHook.getOwnerID(parentID)) ||
          0,
        parentID,
      };
      return tree;
    }, {});
  }
  
  function resetMeasurements() {
    var previousStartTime = currentFlushStartTime;
    var previousMeasurements = currentFlushMeasurements;
    var previousOperations = ReactHostOperationHistoryHook.getHistory();
  
    if (currentFlushNesting === 0) {
      currentFlushStartTime = 0;
      currentFlushMeasurements = [];
      clearHistory();
      return;
    }
  
    if (previousMeasurements.length || previousOperations.length) {
      var registeredIDs = ReactComponentTreeHook.getRegisteredIDs();
      flushHistory.push({
        duration: performanceNow() - previousStartTime,
        measurements: previousMeasurements || [],
        operations: previousOperations || [],
        treeSnapshot: getTreeSnapshot(registeredIDs),
      });
    }
  
    clearHistory();
    currentFlushStartTime = performanceNow();
    currentFlushMeasurements = [];
  }
  
  function checkDebugID(debugID, allowRoot = false) {
    if (allowRoot && debugID === 0) {
      return;
    }
    if (!debugID) {
      warning(false, 'ReactDebugTool: debugID may not be empty.');
    }
  }
  
  function beginLifeCycleTimer(debugID, timerType) {
    if (currentFlushNesting === 0) {
      return;
    }
    if (currentTimerType && !lifeCycleTimerHasWarned) {
      warning(
        false,
        'There is an internal error in the React performance measurement code. ' +
          'Did not expect %s timer to start while %s timer is still in ' +
          'progress for %s instance.',
        timerType,
        currentTimerType || 'no',
        debugID === currentTimerDebugID ? 'the same' : 'another',
      );
      lifeCycleTimerHasWarned = true;
    }
    currentTimerStartTime = performanceNow();
    currentTimerNestedFlushDuration = 0;
    currentTimerDebugID = debugID;
    currentTimerType = timerType;
  }
  
  function endLifeCycleTimer(debugID, timerType) {
    if (currentFlushNesting === 0) {
      return;
    }
    if (currentTimerType !== timerType && !lifeCycleTimerHasWarned) {
      warning(
        false,
        'There is an internal error in the React performance measurement code. ' +
          'We did not expect %s timer to stop while %s timer is still in ' +
          'progress for %s instance. Please report this as a bug in React.',
        timerType,
        currentTimerType || 'no',
        debugID === currentTimerDebugID ? 'the same' : 'another',
      );
      lifeCycleTimerHasWarned = true;
    }
    if (isProfiling) {
      currentFlushMeasurements.push({
        timerType,
        instanceID: debugID,
        duration: performanceNow() -
          currentTimerStartTime -
          currentTimerNestedFlushDuration,
      });
    }
    currentTimerStartTime = 0;
    currentTimerNestedFlushDuration = 0;
    currentTimerDebugID = null;
    currentTimerType = null;
  }
  
  function pauseCurrentLifeCycleTimer() {
    var currentTimer = {
      startTime: currentTimerStartTime,
      nestedFlushStartTime: performanceNow(),
      debugID: currentTimerDebugID,
      timerType: currentTimerType,
    };
    lifeCycleTimerStack.push(currentTimer);
    currentTimerStartTime = 0;
    currentTimerNestedFlushDuration = 0;
    currentTimerDebugID = null;
    currentTimerType = null;
  }
  
  function resumeCurrentLifeCycleTimer() {
    var {
      startTime,
      nestedFlushStartTime,
      debugID,
      timerType,
    } = lifeCycleTimerStack.pop();
    var nestedFlushDuration = performanceNow() - nestedFlushStartTime;
    currentTimerStartTime = startTime;
    currentTimerNestedFlushDuration += nestedFlushDuration;
    currentTimerDebugID = debugID;
    currentTimerType = timerType;
  }
  
  var lastMarkTimeStamp = 0;
  var canUsePerformanceMeasure: boolean =
    typeof performance !== 'undefined' &&
    typeof performance.mark === 'function' &&
    typeof performance.clearMarks === 'function' &&
    typeof performance.measure === 'function' &&
    typeof performance.clearMeasures === 'function';
  
  function shouldMark(debugID) {
    if (!isProfiling || !canUsePerformanceMeasure) {
      return false;
    }
    var element = ReactComponentTreeHook.getElement(debugID);
    if (element == null || typeof element !== 'object') {
      return false;
    }
    var isHostElement = typeof element.type === 'string';
    if (isHostElement) {
      return false;
    }
    return true;
  }
  
  function markBegin(debugID, markType) {
    if (!shouldMark(debugID)) {
      return;
    }
  
    var markName = `${debugID}::${markType}`;
    lastMarkTimeStamp = performanceNow();
    performance.mark(markName);
  }
  
  function markEnd(debugID, markType) {
    if (!shouldMark(debugID)) {
      return;
    }
  
    var markName = `${debugID}::${markType}`;
    var displayName = ReactComponentTreeHook.getDisplayName(debugID) || 'Unknown';
  
    // Chrome has an issue of dropping markers recorded too fast:
    // https://bugs.chromium.org/p/chromium/issues/detail?id=640652
    // To work around this, we will not report very small measurements.
    // I determined the magic number by tweaking it back and forth.
    // 0.05ms was enough to prevent the issue, but I set it to 0.1ms to be safe.
    // When the bug is fixed, we can `measure()` unconditionally if we want to.
    var timeStamp = performanceNow();
    if (timeStamp - lastMarkTimeStamp > 0.1) {
      var measurementName = `${displayName} [${markType}]`;
      performance.measure(measurementName, markName);
    }
  
    performance.clearMarks(markName);
    if (measurementName) {
      performance.clearMeasures(measurementName);
    }
  }
  
  var ReactDebugTool = {
    addHook(hook: Hook): void {
      hooks.push(hook);
    },
    removeHook(hook: Hook): void {
      for (var i = 0; i < hooks.length; i++) {
        if (hooks[i] === hook) {
          hooks.splice(i, 1);
          i--;
        }
      }
    },
    isProfiling(): boolean {
      return isProfiling;
    },
    beginProfiling(): void {
      if (isProfiling) {
        return;
      }
  
      isProfiling = true;
      flushHistory.length = 0;
      resetMeasurements();
      ReactDebugTool.addHook(ReactHostOperationHistoryHook);
    },
    endProfiling(): void {
      if (!isProfiling) {
        return;
      }
  
      isProfiling = false;
      resetMeasurements();
      ReactDebugTool.removeHook(ReactHostOperationHistoryHook);
    },
    getFlushHistory(): FlushHistory {
      return flushHistory;
    },
    onBeginFlush(): void {
      currentFlushNesting++;
      resetMeasurements();
      pauseCurrentLifeCycleTimer();
      emitEvent('onBeginFlush');
    },
    onEndFlush(): void {
      resetMeasurements();
      currentFlushNesting--;
      resumeCurrentLifeCycleTimer();
      emitEvent('onEndFlush');
    },
    onBeginLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
      checkDebugID(debugID);
      emitEvent('onBeginLifeCycleTimer', debugID, timerType);
      markBegin(debugID, timerType);
      beginLifeCycleTimer(debugID, timerType);
    },
    onEndLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
      checkDebugID(debugID);
      endLifeCycleTimer(debugID, timerType);
      markEnd(debugID, timerType);
      emitEvent('onEndLifeCycleTimer', debugID, timerType);
    },
    onBeginProcessingChildContext(): void {
      emitEvent('onBeginProcessingChildContext');
    },
    onEndProcessingChildContext(): void {
      emitEvent('onEndProcessingChildContext');
    },
    onHostOperation(operation: Operation) {
      checkDebugID(operation.instanceID);
      emitEvent('onHostOperation', operation);
    },
    onSetState(): void {
      emitEvent('onSetState');
    },
    onSetChildren(debugID: DebugID, childDebugIDs: Array<DebugID>) {
      checkDebugID(debugID);
      childDebugIDs.forEach(checkDebugID);
      emitEvent('onSetChildren', debugID, childDebugIDs);
    },
    onBeforeMountComponent(
      debugID: DebugID,
      element: ReactElement,
      parentDebugID: DebugID,
    ): void {
      checkDebugID(debugID);
      checkDebugID(parentDebugID, true);
      emitEvent('onBeforeMountComponent', debugID, element, parentDebugID);
      markBegin(debugID, 'mount');
    },
    onMountComponent(debugID: DebugID): void {
      checkDebugID(debugID);
      markEnd(debugID, 'mount');
      emitEvent('onMountComponent', debugID);
    },
    onBeforeUpdateComponent(debugID: DebugID, element: ReactElement): void {
      checkDebugID(debugID);
      emitEvent('onBeforeUpdateComponent', debugID, element);
      markBegin(debugID, 'update');
    },
    onUpdateComponent(debugID: DebugID): void {
      checkDebugID(debugID);
      markEnd(debugID, 'update');
      emitEvent('onUpdateComponent', debugID);
    },
    onBeforeUnmountComponent(debugID: DebugID): void {
      checkDebugID(debugID);
      emitEvent('onBeforeUnmountComponent', debugID);
      markBegin(debugID, 'unmount');
    },
    onUnmountComponent(debugID: DebugID): void {
      checkDebugID(debugID);
      markEnd(debugID, 'unmount');
      emitEvent('onUnmountComponent', debugID);
    },
    onTestEvent(): void {
      emitEvent('onTestEvent');
    },
  };
  
  // TODO remove these when RN/www gets updated
  (ReactDebugTool: any).addDevtool = ReactDebugTool.addHook;
  (ReactDebugTool: any).removeDevtool = ReactDebugTool.removeHook;
  
  ReactDebugTool.addHook(ReactInvalidSetStateWarningHook);
  ReactDebugTool.addHook(ReactComponentTreeHook);
  var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
  if (/[?&]react_perf\b/.test(url)) {
    ReactDebugTool.beginProfiling();
  }
  
  module.exports = ReactDebugTool;
  