UNPKG

11.5 kBJavaScriptView Raw
1/**
2 * Copyright (c) 2016-present, Facebook, Inc.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 *
8 */
9
10'use strict';
11
12var ReactInvalidSetStateWarningHook = require('./ReactInvalidSetStateWarningHook');
13var ReactHostOperationHistoryHook = require('./ReactHostOperationHistoryHook');
14var ReactComponentTreeHook = require('react/lib/ReactComponentTreeHook');
15var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
16
17var performanceNow = require('fbjs/lib/performanceNow');
18var warning = require('fbjs/lib/warning');
19
20var hooks = [];
21var didHookThrowForEvent = {};
22
23function callHook(event, fn, context, arg1, arg2, arg3, arg4, arg5) {
24 try {
25 fn.call(context, arg1, arg2, arg3, arg4, arg5);
26 } catch (e) {
27 process.env.NODE_ENV !== 'production' ? warning(didHookThrowForEvent[event], 'Exception thrown by hook while handling %s: %s', event, e + '\n' + e.stack) : void 0;
28 didHookThrowForEvent[event] = true;
29 }
30}
31
32function emitEvent(event, arg1, arg2, arg3, arg4, arg5) {
33 for (var i = 0; i < hooks.length; i++) {
34 var hook = hooks[i];
35 var fn = hook[event];
36 if (fn) {
37 callHook(event, fn, hook, arg1, arg2, arg3, arg4, arg5);
38 }
39 }
40}
41
42var isProfiling = false;
43var flushHistory = [];
44var lifeCycleTimerStack = [];
45var currentFlushNesting = 0;
46var currentFlushMeasurements = [];
47var currentFlushStartTime = 0;
48var currentTimerDebugID = null;
49var currentTimerStartTime = 0;
50var currentTimerNestedFlushDuration = 0;
51var currentTimerType = null;
52
53var lifeCycleTimerHasWarned = false;
54
55function clearHistory() {
56 ReactComponentTreeHook.purgeUnmountedComponents();
57 ReactHostOperationHistoryHook.clearHistory();
58}
59
60function getTreeSnapshot(registeredIDs) {
61 return registeredIDs.reduce(function (tree, id) {
62 var ownerID = ReactComponentTreeHook.getOwnerID(id);
63 var parentID = ReactComponentTreeHook.getParentID(id);
64 tree[id] = {
65 displayName: ReactComponentTreeHook.getDisplayName(id),
66 text: ReactComponentTreeHook.getText(id),
67 updateCount: ReactComponentTreeHook.getUpdateCount(id),
68 childIDs: ReactComponentTreeHook.getChildIDs(id),
69 // Text nodes don't have owners but this is close enough.
70 ownerID: ownerID || parentID && ReactComponentTreeHook.getOwnerID(parentID) || 0,
71 parentID: parentID
72 };
73 return tree;
74 }, {});
75}
76
77function resetMeasurements() {
78 var previousStartTime = currentFlushStartTime;
79 var previousMeasurements = currentFlushMeasurements;
80 var previousOperations = ReactHostOperationHistoryHook.getHistory();
81
82 if (currentFlushNesting === 0) {
83 currentFlushStartTime = 0;
84 currentFlushMeasurements = [];
85 clearHistory();
86 return;
87 }
88
89 if (previousMeasurements.length || previousOperations.length) {
90 var registeredIDs = ReactComponentTreeHook.getRegisteredIDs();
91 flushHistory.push({
92 duration: performanceNow() - previousStartTime,
93 measurements: previousMeasurements || [],
94 operations: previousOperations || [],
95 treeSnapshot: getTreeSnapshot(registeredIDs)
96 });
97 }
98
99 clearHistory();
100 currentFlushStartTime = performanceNow();
101 currentFlushMeasurements = [];
102}
103
104function checkDebugID(debugID) {
105 var allowRoot = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
106
107 if (allowRoot && debugID === 0) {
108 return;
109 }
110 if (!debugID) {
111 process.env.NODE_ENV !== 'production' ? warning(false, 'ReactDebugTool: debugID may not be empty.') : void 0;
112 }
113}
114
115function beginLifeCycleTimer(debugID, timerType) {
116 if (currentFlushNesting === 0) {
117 return;
118 }
119 if (currentTimerType && !lifeCycleTimerHasWarned) {
120 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;
121 lifeCycleTimerHasWarned = true;
122 }
123 currentTimerStartTime = performanceNow();
124 currentTimerNestedFlushDuration = 0;
125 currentTimerDebugID = debugID;
126 currentTimerType = timerType;
127}
128
129function endLifeCycleTimer(debugID, timerType) {
130 if (currentFlushNesting === 0) {
131 return;
132 }
133 if (currentTimerType !== timerType && !lifeCycleTimerHasWarned) {
134 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;
135 lifeCycleTimerHasWarned = true;
136 }
137 if (isProfiling) {
138 currentFlushMeasurements.push({
139 timerType: timerType,
140 instanceID: debugID,
141 duration: performanceNow() - currentTimerStartTime - currentTimerNestedFlushDuration
142 });
143 }
144 currentTimerStartTime = 0;
145 currentTimerNestedFlushDuration = 0;
146 currentTimerDebugID = null;
147 currentTimerType = null;
148}
149
150function pauseCurrentLifeCycleTimer() {
151 var currentTimer = {
152 startTime: currentTimerStartTime,
153 nestedFlushStartTime: performanceNow(),
154 debugID: currentTimerDebugID,
155 timerType: currentTimerType
156 };
157 lifeCycleTimerStack.push(currentTimer);
158 currentTimerStartTime = 0;
159 currentTimerNestedFlushDuration = 0;
160 currentTimerDebugID = null;
161 currentTimerType = null;
162}
163
164function resumeCurrentLifeCycleTimer() {
165 var _lifeCycleTimerStack$ = lifeCycleTimerStack.pop(),
166 startTime = _lifeCycleTimerStack$.startTime,
167 nestedFlushStartTime = _lifeCycleTimerStack$.nestedFlushStartTime,
168 debugID = _lifeCycleTimerStack$.debugID,
169 timerType = _lifeCycleTimerStack$.timerType;
170
171 var nestedFlushDuration = performanceNow() - nestedFlushStartTime;
172 currentTimerStartTime = startTime;
173 currentTimerNestedFlushDuration += nestedFlushDuration;
174 currentTimerDebugID = debugID;
175 currentTimerType = timerType;
176}
177
178var lastMarkTimeStamp = 0;
179var canUsePerformanceMeasure = typeof performance !== 'undefined' && typeof performance.mark === 'function' && typeof performance.clearMarks === 'function' && typeof performance.measure === 'function' && typeof performance.clearMeasures === 'function';
180
181function shouldMark(debugID) {
182 if (!isProfiling || !canUsePerformanceMeasure) {
183 return false;
184 }
185 var element = ReactComponentTreeHook.getElement(debugID);
186 if (element == null || typeof element !== 'object') {
187 return false;
188 }
189 var isHostElement = typeof element.type === 'string';
190 if (isHostElement) {
191 return false;
192 }
193 return true;
194}
195
196function markBegin(debugID, markType) {
197 if (!shouldMark(debugID)) {
198 return;
199 }
200
201 var markName = debugID + '::' + markType;
202 lastMarkTimeStamp = performanceNow();
203 performance.mark(markName);
204}
205
206function markEnd(debugID, markType) {
207 if (!shouldMark(debugID)) {
208 return;
209 }
210
211 var markName = debugID + '::' + markType;
212 var displayName = ReactComponentTreeHook.getDisplayName(debugID) || 'Unknown';
213
214 // Chrome has an issue of dropping markers recorded too fast:
215 // https://bugs.chromium.org/p/chromium/issues/detail?id=640652
216 // To work around this, we will not report very small measurements.
217 // I determined the magic number by tweaking it back and forth.
218 // 0.05ms was enough to prevent the issue, but I set it to 0.1ms to be safe.
219 // When the bug is fixed, we can `measure()` unconditionally if we want to.
220 var timeStamp = performanceNow();
221 if (timeStamp - lastMarkTimeStamp > 0.1) {
222 var measurementName = displayName + ' [' + markType + ']';
223 performance.measure(measurementName, markName);
224 }
225
226 performance.clearMarks(markName);
227 if (measurementName) {
228 performance.clearMeasures(measurementName);
229 }
230}
231
232var ReactDebugTool = {
233 addHook: function (hook) {
234 hooks.push(hook);
235 },
236 removeHook: function (hook) {
237 for (var i = 0; i < hooks.length; i++) {
238 if (hooks[i] === hook) {
239 hooks.splice(i, 1);
240 i--;
241 }
242 }
243 },
244 isProfiling: function () {
245 return isProfiling;
246 },
247 beginProfiling: function () {
248 if (isProfiling) {
249 return;
250 }
251
252 isProfiling = true;
253 flushHistory.length = 0;
254 resetMeasurements();
255 ReactDebugTool.addHook(ReactHostOperationHistoryHook);
256 },
257 endProfiling: function () {
258 if (!isProfiling) {
259 return;
260 }
261
262 isProfiling = false;
263 resetMeasurements();
264 ReactDebugTool.removeHook(ReactHostOperationHistoryHook);
265 },
266 getFlushHistory: function () {
267 return flushHistory;
268 },
269 onBeginFlush: function () {
270 currentFlushNesting++;
271 resetMeasurements();
272 pauseCurrentLifeCycleTimer();
273 emitEvent('onBeginFlush');
274 },
275 onEndFlush: function () {
276 resetMeasurements();
277 currentFlushNesting--;
278 resumeCurrentLifeCycleTimer();
279 emitEvent('onEndFlush');
280 },
281 onBeginLifeCycleTimer: function (debugID, timerType) {
282 checkDebugID(debugID);
283 emitEvent('onBeginLifeCycleTimer', debugID, timerType);
284 markBegin(debugID, timerType);
285 beginLifeCycleTimer(debugID, timerType);
286 },
287 onEndLifeCycleTimer: function (debugID, timerType) {
288 checkDebugID(debugID);
289 endLifeCycleTimer(debugID, timerType);
290 markEnd(debugID, timerType);
291 emitEvent('onEndLifeCycleTimer', debugID, timerType);
292 },
293 onBeginProcessingChildContext: function () {
294 emitEvent('onBeginProcessingChildContext');
295 },
296 onEndProcessingChildContext: function () {
297 emitEvent('onEndProcessingChildContext');
298 },
299 onHostOperation: function (operation) {
300 checkDebugID(operation.instanceID);
301 emitEvent('onHostOperation', operation);
302 },
303 onSetState: function () {
304 emitEvent('onSetState');
305 },
306 onSetChildren: function (debugID, childDebugIDs) {
307 checkDebugID(debugID);
308 childDebugIDs.forEach(checkDebugID);
309 emitEvent('onSetChildren', debugID, childDebugIDs);
310 },
311 onBeforeMountComponent: function (debugID, element, parentDebugID) {
312 checkDebugID(debugID);
313 checkDebugID(parentDebugID, true);
314 emitEvent('onBeforeMountComponent', debugID, element, parentDebugID);
315 markBegin(debugID, 'mount');
316 },
317 onMountComponent: function (debugID) {
318 checkDebugID(debugID);
319 markEnd(debugID, 'mount');
320 emitEvent('onMountComponent', debugID);
321 },
322 onBeforeUpdateComponent: function (debugID, element) {
323 checkDebugID(debugID);
324 emitEvent('onBeforeUpdateComponent', debugID, element);
325 markBegin(debugID, 'update');
326 },
327 onUpdateComponent: function (debugID) {
328 checkDebugID(debugID);
329 markEnd(debugID, 'update');
330 emitEvent('onUpdateComponent', debugID);
331 },
332 onBeforeUnmountComponent: function (debugID) {
333 checkDebugID(debugID);
334 emitEvent('onBeforeUnmountComponent', debugID);
335 markBegin(debugID, 'unmount');
336 },
337 onUnmountComponent: function (debugID) {
338 checkDebugID(debugID);
339 markEnd(debugID, 'unmount');
340 emitEvent('onUnmountComponent', debugID);
341 },
342 onTestEvent: function () {
343 emitEvent('onTestEvent');
344 }
345};
346
347// TODO remove these when RN/www gets updated
348ReactDebugTool.addDevtool = ReactDebugTool.addHook;
349ReactDebugTool.removeDevtool = ReactDebugTool.removeHook;
350
351ReactDebugTool.addHook(ReactInvalidSetStateWarningHook);
352ReactDebugTool.addHook(ReactComponentTreeHook);
353var url = ExecutionEnvironment.canUseDOM && window.location.href || '';
354if (/[?&]react_perf\b/.test(url)) {
355 ReactDebugTool.beginProfiling();
356}
357
358module.exports = ReactDebugTool;
\No newline at end of file