UNPKG

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