Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 99 additions & 39 deletions src/renderers/shared/ReactDebugTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactDebugTool
* @flow
*/

'use strict';
Expand All @@ -20,6 +21,62 @@ var ExecutionEnvironment = require('ExecutionEnvironment');
var performanceNow = require('performanceNow');
var warning = require('warning');

import type { ReactElement } from 'ReactElementType';
import type { DebugID } from 'ReactInstanceType';

type Hook = any;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created those two types in order to name them in the code even though they are not properly typed yet.

type Payload = mixed;

type TimerType =
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will probably need to be moved to a different place later. @cpojer told me during his jest flowification hackathon that it's better to put them inside of the file you're working on and when you need it in another place, then extract them, instead of doing it the other way.

'ctor' |
'render' |
'componentWillMount' |
'componentWillUnmount' |
'componentWillReceiveProps' |
'shouldComponentUpdate' |
'componentWillUpdate' |
'componentDidUpdate' |
'componentDidMount';

type HostOperationType =
'mount' |
'insert child' |
'move child' |
'remove child' |
'replace children' |
'replace text' |
'replace with';

type Operation = {
instanceID: DebugID,
type: string,
payload: Payload,
};

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,
};

var hooks = [];
var didHookThrowForEvent = {};

Expand Down Expand Up @@ -51,11 +108,11 @@ var isProfiling = false;
var flushHistory = [];
var lifeCycleTimerStack = [];
var currentFlushNesting = 0;
var currentFlushMeasurements = null;
var currentFlushStartTime = null;
var currentFlushMeasurements = [];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very happy with this change but I can't think of a better way that wouldn't involve a massive change.

https://www.facebook.com/groups/flowtype/permalink/1132169456831668/

var currentFlushStartTime = 0;
var currentTimerDebugID = null;
var currentTimerStartTime = null;
var currentTimerNestedFlushDuration = null;
var currentTimerStartTime = 0;
var currentTimerNestedFlushDuration = 0;
var currentTimerType = null;

var lifeCycleTimerHasWarned = false;
Expand All @@ -75,7 +132,9 @@ function getTreeSnapshot(registeredIDs) {
updateCount: ReactComponentTreeHook.getUpdateCount(id),
childIDs: ReactComponentTreeHook.getChildIDs(id),
// Text nodes don't have owners but this is close enough.
ownerID: ownerID || ReactComponentTreeHook.getOwnerID(parentID),
ownerID: ownerID ||
parentID && ReactComponentTreeHook.getOwnerID(parentID) ||
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to add an invariant saying that if ownerID is null, enforce that parentID is not null, but it throws in tons of places in tests.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both can be null potentially.

0,
parentID,
};
return tree;
Expand All @@ -84,12 +143,12 @@ function getTreeSnapshot(registeredIDs) {

function resetMeasurements() {
var previousStartTime = currentFlushStartTime;
var previousMeasurements = currentFlushMeasurements || [];
var previousMeasurements = currentFlushMeasurements;
var previousOperations = ReactHostOperationHistoryHook.getHistory();

if (currentFlushNesting === 0) {
currentFlushStartTime = null;
currentFlushMeasurements = null;
currentFlushStartTime = 0;
currentFlushMeasurements = [];
clearHistory();
return;
}
Expand Down Expand Up @@ -163,8 +222,8 @@ function endLifeCycleTimer(debugID, timerType) {
duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration,
});
}
currentTimerStartTime = null;
currentTimerNestedFlushDuration = null;
currentTimerStartTime = 0;
currentTimerNestedFlushDuration = 0;
currentTimerDebugID = null;
currentTimerType = null;
}
Expand All @@ -177,8 +236,8 @@ function pauseCurrentLifeCycleTimer() {
timerType: currentTimerType,
};
lifeCycleTimerStack.push(currentTimer);
currentTimerStartTime = null;
currentTimerNestedFlushDuration = null;
currentTimerStartTime = 0;
currentTimerNestedFlushDuration = 0;
currentTimerDebugID = null;
currentTimerType = null;
}
Expand All @@ -192,8 +251,9 @@ function resumeCurrentLifeCycleTimer() {
currentTimerType = timerType;
}

var lastMarkTimeStamp = null;
var canUsePerformanceMeasure =
var lastMarkTimeStamp = 0;
var canUsePerformanceMeasure: bool =
// $FlowFixMe https://github.com/facebook/flow/issues/2345
typeof performance !== 'undefined' &&
typeof performance.mark === 'function' &&
typeof performance.clearMarks === 'function' &&
Expand Down Expand Up @@ -231,7 +291,7 @@ function markEnd(debugID, markType) {
}

var markName = `${debugID}::${markType}`;
var displayName = ReactComponentTreeHook.getDisplayName(debugID);
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
Expand All @@ -250,21 +310,21 @@ function markEnd(debugID, markType) {
}

var ReactDebugTool = {
addHook(hook) {
addHook(hook: Hook): void {
hooks.push(hook);
},
removeHook(hook) {
removeHook(hook: Hook): void {
for (var i = 0; i < hooks.length; i++) {
if (hooks[i] === hook) {
hooks.splice(i, 1);
i--;
}
}
},
isProfiling() {
isProfiling(): bool {
return isProfiling;
},
beginProfiling() {
beginProfiling(): void {
if (isProfiling) {
return;
}
Expand All @@ -274,7 +334,7 @@ var ReactDebugTool = {
resetMeasurements();
ReactDebugTool.addHook(ReactHostOperationHistoryHook);
},
endProfiling() {
endProfiling(): void {
if (!isProfiling) {
return;
}
Expand All @@ -283,90 +343,90 @@ var ReactDebugTool = {
resetMeasurements();
ReactDebugTool.removeHook(ReactHostOperationHistoryHook);
},
getFlushHistory() {
getFlushHistory(): Array<HistoryItem> {
return flushHistory;
},
onBeginFlush() {
onBeginFlush(): void {
currentFlushNesting++;
resetMeasurements();
pauseCurrentLifeCycleTimer();
emitEvent('onBeginFlush');
},
onEndFlush() {
onEndFlush(): void {
resetMeasurements();
currentFlushNesting--;
resumeCurrentLifeCycleTimer();
emitEvent('onEndFlush');
},
onBeginLifeCycleTimer(debugID, timerType) {
onBeginLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
checkDebugID(debugID);
emitEvent('onBeginLifeCycleTimer', debugID, timerType);
markBegin(debugID, timerType);
beginLifeCycleTimer(debugID, timerType);
},
onEndLifeCycleTimer(debugID, timerType) {
onEndLifeCycleTimer(debugID: DebugID, timerType: TimerType): void {
checkDebugID(debugID);
endLifeCycleTimer(debugID, timerType);
markEnd(debugID, timerType);
emitEvent('onEndLifeCycleTimer', debugID, timerType);
},
onBeginProcessingChildContext() {
onBeginProcessingChildContext(): void {
emitEvent('onBeginProcessingChildContext');
},
onEndProcessingChildContext() {
onEndProcessingChildContext(): void {
emitEvent('onEndProcessingChildContext');
},
onHostOperation(debugID, type, payload) {
onHostOperation(debugID: DebugID, type: HostOperationType, payload: Payload): void {
checkDebugID(debugID);
emitEvent('onHostOperation', debugID, type, payload);
},
onSetState() {
onSetState(): void {
emitEvent('onSetState');
},
onSetChildren(debugID, childDebugIDs) {
onSetChildren(debugID: DebugID, childDebugIDs: Array<DebugID>) {
checkDebugID(debugID);
childDebugIDs.forEach(checkDebugID);
emitEvent('onSetChildren', debugID, childDebugIDs);
},
onBeforeMountComponent(debugID, element, parentDebugID) {
onBeforeMountComponent(debugID: DebugID, element: ReactElement, parentDebugID: DebugID): void {
checkDebugID(debugID);
checkDebugID(parentDebugID, true);
emitEvent('onBeforeMountComponent', debugID, element, parentDebugID);
markBegin(debugID, 'mount');
},
onMountComponent(debugID) {
onMountComponent(debugID: DebugID): void {
checkDebugID(debugID);
markEnd(debugID, 'mount');
emitEvent('onMountComponent', debugID);
},
onBeforeUpdateComponent(debugID, element) {
onBeforeUpdateComponent(debugID: DebugID, element: ReactElement): void {
checkDebugID(debugID);
emitEvent('onBeforeUpdateComponent', debugID, element);
markBegin(debugID, 'update');
},
onUpdateComponent(debugID) {
onUpdateComponent(debugID: DebugID): void {
checkDebugID(debugID);
markEnd(debugID, 'update');
emitEvent('onUpdateComponent', debugID);
},
onBeforeUnmountComponent(debugID) {
onBeforeUnmountComponent(debugID: DebugID): void {
checkDebugID(debugID);
emitEvent('onBeforeUnmountComponent', debugID);
markBegin(debugID, 'unmount');
},
onUnmountComponent(debugID) {
onUnmountComponent(debugID: DebugID): void {
checkDebugID(debugID);
markEnd(debugID, 'unmount');
emitEvent('onUnmountComponent', debugID);
},
onTestEvent() {
onTestEvent(): void {
emitEvent('onTestEvent');
},
};

// TODO remove these when RN/www gets updated
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if we can kill this now?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we kill them, people who use old RN with new React will have issues. Unfortunately it seems like a lot of people still do that so I'd keep until a major.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

ReactDebugTool.addDevtool = ReactDebugTool.addHook;
ReactDebugTool.removeDevtool = ReactDebugTool.removeHook;
(ReactDebugTool: any).addDevtool = ReactDebugTool.addHook;
(ReactDebugTool: any).removeDevtool = ReactDebugTool.removeHook;

ReactDebugTool.addHook(ReactInvalidSetStateWarningHook);
ReactDebugTool.addHook(ReactComponentTreeHook);
Expand Down