2017-05-03 15:35:00 +02:00
/ * *
* 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 .
*
*
* /
'use strict' ;
var ReactInvalidSetStateWarningHook = require ( './ReactInvalidSetStateWarningHook' ) ;
var ReactHostOperationHistoryHook = require ( './ReactHostOperationHistoryHook' ) ;
var ReactComponentTreeHook = require ( 'react/lib/ReactComponentTreeHook' ) ;
var ExecutionEnvironment = require ( 'fbjs/lib/ExecutionEnvironment' ) ;
var performanceNow = require ( 'fbjs/lib/performanceNow' ) ;
var warning = require ( 'fbjs/lib/warning' ) ;
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 ) {
process . env . NODE _ENV !== 'production' ? warning ( didHookThrowForEvent [ event ] , 'Exception thrown by hook while handling %s: %s' , event , e + '\n' + e . stack ) : void 0 ;
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 ( function ( 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 : 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 ) {
var allowRoot = arguments . length > 1 && arguments [ 1 ] !== undefined ? arguments [ 1 ] : false ;
if ( allowRoot && debugID === 0 ) {
return ;
}
if ( ! debugID ) {
process . env . NODE _ENV !== 'production' ? warning ( false , 'ReactDebugTool: debugID may not be empty.' ) : void 0 ;
}
}
function beginLifeCycleTimer ( debugID , timerType ) {
if ( currentFlushNesting === 0 ) {
return ;
}
if ( currentTimerType && ! lifeCycleTimerHasWarned ) {
process . env . NODE _ENV !== 'production' ? 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' ) : void 0 ;
lifeCycleTimerHasWarned = true ;
}
currentTimerStartTime = performanceNow ( ) ;
currentTimerNestedFlushDuration = 0 ;
currentTimerDebugID = debugID ;
currentTimerType = timerType ;
}
function endLifeCycleTimer ( debugID , timerType ) {
if ( currentFlushNesting === 0 ) {
return ;
}
if ( currentTimerType !== timerType && ! lifeCycleTimerHasWarned ) {
process . env . NODE _ENV !== 'production' ? 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' ) : void 0 ;
lifeCycleTimerHasWarned = true ;
}
if ( isProfiling ) {
currentFlushMeasurements . push ( {
timerType : 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 _lifeCycleTimerStack$ = lifeCycleTimerStack . pop ( ) ,
startTime = _lifeCycleTimerStack$ . startTime ,
nestedFlushStartTime = _lifeCycleTimerStack$ . nestedFlushStartTime ,
debugID = _lifeCycleTimerStack$ . debugID ,
timerType = _lifeCycleTimerStack$ . timerType ;
var nestedFlushDuration = performanceNow ( ) - nestedFlushStartTime ;
currentTimerStartTime = startTime ;
currentTimerNestedFlushDuration += nestedFlushDuration ;
currentTimerDebugID = debugID ;
currentTimerType = timerType ;
}
var lastMarkTimeStamp = 0 ;
var canUsePerformanceMeasure = 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 ) ;
2017-08-14 05:01:11 +02:00
if ( measurementName ) {
performance . clearMeasures ( measurementName ) ;
}
2017-05-03 15:35:00 +02:00
}
var ReactDebugTool = {
addHook : function ( hook ) {
hooks . push ( hook ) ;
} ,
removeHook : function ( hook ) {
for ( var i = 0 ; i < hooks . length ; i ++ ) {
if ( hooks [ i ] === hook ) {
hooks . splice ( i , 1 ) ;
i -- ;
}
}
} ,
isProfiling : function ( ) {
return isProfiling ;
} ,
beginProfiling : function ( ) {
if ( isProfiling ) {
return ;
}
isProfiling = true ;
flushHistory . length = 0 ;
resetMeasurements ( ) ;
ReactDebugTool . addHook ( ReactHostOperationHistoryHook ) ;
} ,
endProfiling : function ( ) {
if ( ! isProfiling ) {
return ;
}
isProfiling = false ;
resetMeasurements ( ) ;
ReactDebugTool . removeHook ( ReactHostOperationHistoryHook ) ;
} ,
getFlushHistory : function ( ) {
return flushHistory ;
} ,
onBeginFlush : function ( ) {
currentFlushNesting ++ ;
resetMeasurements ( ) ;
pauseCurrentLifeCycleTimer ( ) ;
emitEvent ( 'onBeginFlush' ) ;
} ,
onEndFlush : function ( ) {
resetMeasurements ( ) ;
currentFlushNesting -- ;
resumeCurrentLifeCycleTimer ( ) ;
emitEvent ( 'onEndFlush' ) ;
} ,
onBeginLifeCycleTimer : function ( debugID , timerType ) {
checkDebugID ( debugID ) ;
emitEvent ( 'onBeginLifeCycleTimer' , debugID , timerType ) ;
markBegin ( debugID , timerType ) ;
beginLifeCycleTimer ( debugID , timerType ) ;
} ,
onEndLifeCycleTimer : function ( debugID , timerType ) {
checkDebugID ( debugID ) ;
endLifeCycleTimer ( debugID , timerType ) ;
markEnd ( debugID , timerType ) ;
emitEvent ( 'onEndLifeCycleTimer' , debugID , timerType ) ;
} ,
onBeginProcessingChildContext : function ( ) {
emitEvent ( 'onBeginProcessingChildContext' ) ;
} ,
onEndProcessingChildContext : function ( ) {
emitEvent ( 'onEndProcessingChildContext' ) ;
} ,
onHostOperation : function ( operation ) {
checkDebugID ( operation . instanceID ) ;
emitEvent ( 'onHostOperation' , operation ) ;
} ,
onSetState : function ( ) {
emitEvent ( 'onSetState' ) ;
} ,
onSetChildren : function ( debugID , childDebugIDs ) {
checkDebugID ( debugID ) ;
childDebugIDs . forEach ( checkDebugID ) ;
emitEvent ( 'onSetChildren' , debugID , childDebugIDs ) ;
} ,
onBeforeMountComponent : function ( debugID , element , parentDebugID ) {
checkDebugID ( debugID ) ;
checkDebugID ( parentDebugID , true ) ;
emitEvent ( 'onBeforeMountComponent' , debugID , element , parentDebugID ) ;
markBegin ( debugID , 'mount' ) ;
} ,
onMountComponent : function ( debugID ) {
checkDebugID ( debugID ) ;
markEnd ( debugID , 'mount' ) ;
emitEvent ( 'onMountComponent' , debugID ) ;
} ,
onBeforeUpdateComponent : function ( debugID , element ) {
checkDebugID ( debugID ) ;
emitEvent ( 'onBeforeUpdateComponent' , debugID , element ) ;
markBegin ( debugID , 'update' ) ;
} ,
onUpdateComponent : function ( debugID ) {
checkDebugID ( debugID ) ;
markEnd ( debugID , 'update' ) ;
emitEvent ( 'onUpdateComponent' , debugID ) ;
} ,
onBeforeUnmountComponent : function ( debugID ) {
checkDebugID ( debugID ) ;
emitEvent ( 'onBeforeUnmountComponent' , debugID ) ;
markBegin ( debugID , 'unmount' ) ;
} ,
onUnmountComponent : function ( debugID ) {
checkDebugID ( debugID ) ;
markEnd ( debugID , 'unmount' ) ;
emitEvent ( 'onUnmountComponent' , debugID ) ;
} ,
onTestEvent : function ( ) {
emitEvent ( 'onTestEvent' ) ;
}
} ;
// TODO remove these when RN/www gets updated
ReactDebugTool . addDevtool = ReactDebugTool . addHook ;
ReactDebugTool . 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 ;