main
js 495 lines 13.8 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
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 * @flow
8 */
9
10 import type {Lane, Lanes} from './ReactFiberLane';
11 import type {Fiber, FiberRoot} from './ReactInternalTypes';
12 import type {ReactNodeList, Wakeable} from 'shared/ReactTypes';
13 import type {EventPriority} from './ReactEventPriorities';
14 // import type {DevToolsProfilingHooks} from 'react-devtools-shared/src/backend/types';
15 // TODO: This import doesn't work because the DevTools depend on the DOM version of React
16 // and to properly type check against DOM React we can't also type check again non-DOM
17 // React which this hook might be in.
18 type DevToolsProfilingHooks = any;
19
20 import {DidCapture} from './ReactFiberFlags';
21 import {
22 enableProfilerTimer,
23 enableSchedulingProfiler,
24 } from 'shared/ReactFeatureFlags';
25 import {
26 DiscreteEventPriority,
27 ContinuousEventPriority,
28 DefaultEventPriority,
29 IdleEventPriority,
30 } from './ReactEventPriorities';
31 import {
32 ImmediatePriority as ImmediateSchedulerPriority,
33 UserBlockingPriority as UserBlockingSchedulerPriority,
34 NormalPriority as NormalSchedulerPriority,
35 IdlePriority as IdleSchedulerPriority,
36 log,
37 unstable_setDisableYieldValue,
38 } from './Scheduler';
39
40 declare const __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void;
41
42 let rendererID = null;
43 let injectedHook = null;
44 let injectedProfilingHooks: DevToolsProfilingHooks | null = null;
45 let hasLoggedError = false;
46
47 export const isDevToolsPresent =
48 typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined';
49
50 export function injectInternals(internals: Object): boolean {
51 if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
52 // No DevTools
53 return false;
54 }
55 const hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
56 if (hook.isDisabled) {
57 // This isn't a real property on the hook, but it can be set to opt out
58 // of DevTools integration and associated warnings and logs.
59 // https://github.com/facebook/react/issues/3877
60 return true;
61 }
62 if (!hook.supportsFiber) {
63 if (__DEV__) {
64 console.error(
65 'The installed version of React DevTools is too old and will not work ' +
66 'with the current version of React. Please update React DevTools. ' +
67 'https://react.dev/link/react-devtools',
68 );
69 }
70 // DevTools exists, even though it doesn't support Fiber.
71 return true;
72 }
73 try {
74 rendererID = hook.inject(internals);
75
76 // We have successfully injected, so now it is safe to set up hooks.
77 injectedHook = hook;
78 } catch (err) {
79 // Catch all errors because it is unsafe to throw during initialization.
80 if (__DEV__) {
81 console.error('React instrumentation encountered an error: %o.', err);
82 }
83 }
84 if (hook.checkDCE) {
85 // This is the real DevTools.
86 return true;
87 } else {
88 // This is likely a hook installed by Fast Refresh runtime.
89 return false;
90 }
91 }
92
93 export function onScheduleRoot(root: FiberRoot, children: ReactNodeList) {
94 if (__DEV__) {
95 if (
96 injectedHook &&
97 typeof injectedHook.onScheduleFiberRoot === 'function'
98 ) {
99 try {
100 injectedHook.onScheduleFiberRoot(rendererID, root, children);
101 } catch (err) {
102 if (__DEV__ && !hasLoggedError) {
103 hasLoggedError = true;
104 console.error('React instrumentation encountered an error: %o', err);
105 }
106 }
107 }
108 }
109 }
110
111 export function onCommitRoot(root: FiberRoot, eventPriority: EventPriority) {
112 if (injectedHook && typeof injectedHook.onCommitFiberRoot === 'function') {
113 try {
114 const didError = (root.current.flags & DidCapture) === DidCapture;
115 if (enableProfilerTimer) {
116 let schedulerPriority;
117 switch (eventPriority) {
118 case DiscreteEventPriority:
119 schedulerPriority = ImmediateSchedulerPriority;
120 break;
121 case ContinuousEventPriority:
122 schedulerPriority = UserBlockingSchedulerPriority;
123 break;
124 case DefaultEventPriority:
125 schedulerPriority = NormalSchedulerPriority;
126 break;
127 case IdleEventPriority:
128 schedulerPriority = IdleSchedulerPriority;
129 break;
130 default:
131 schedulerPriority = NormalSchedulerPriority;
132 break;
133 }
134 injectedHook.onCommitFiberRoot(
135 rendererID,
136 root,
137 schedulerPriority,
138 didError,
139 );
140 } else {
141 injectedHook.onCommitFiberRoot(rendererID, root, undefined, didError);
142 }
143 } catch (err) {
144 if (__DEV__) {
145 if (!hasLoggedError) {
146 hasLoggedError = true;
147 console.error('React instrumentation encountered an error: %o', err);
148 }
149 }
150 }
151 }
152 }
153
154 export function onPostCommitRoot(root: FiberRoot) {
155 if (
156 injectedHook &&
157 typeof injectedHook.onPostCommitFiberRoot === 'function'
158 ) {
159 try {
160 injectedHook.onPostCommitFiberRoot(rendererID, root);
161 } catch (err) {
162 if (__DEV__) {
163 if (!hasLoggedError) {
164 hasLoggedError = true;
165 console.error('React instrumentation encountered an error: %o', err);
166 }
167 }
168 }
169 }
170 }
171
172 export function onCommitUnmount(fiber: Fiber) {
173 if (injectedHook && typeof injectedHook.onCommitFiberUnmount === 'function') {
174 try {
175 injectedHook.onCommitFiberUnmount(rendererID, fiber);
176 } catch (err) {
177 if (__DEV__) {
178 if (!hasLoggedError) {
179 hasLoggedError = true;
180 console.error('React instrumentation encountered an error: %o', err);
181 }
182 }
183 }
184 }
185 }
186
187 export function setIsStrictModeForDevtools(newIsStrictMode: boolean) {
188 if (typeof log === 'function') {
189 // We're in a test because Scheduler.log only exists
190 // in SchedulerMock. To reduce the noise in strict mode tests,
191 // suppress warnings and disable scheduler yielding during the double render
192 unstable_setDisableYieldValue(newIsStrictMode);
193 }
194
195 if (injectedHook && typeof injectedHook.setStrictMode === 'function') {
196 try {
197 injectedHook.setStrictMode(rendererID, newIsStrictMode);
198 } catch (err) {
199 if (__DEV__) {
200 if (!hasLoggedError) {
201 hasLoggedError = true;
202 console.error('React instrumentation encountered an error: %o', err);
203 }
204 }
205 }
206 }
207 }
208
209 // Profiler API hooks
210
211 export function injectProfilingHooks(
212 profilingHooks: DevToolsProfilingHooks,
213 ): void {
214 injectedProfilingHooks = profilingHooks;
215 }
216
217 export function markCommitStarted(lanes: Lanes): void {
218 if (enableSchedulingProfiler) {
219 if (
220 injectedProfilingHooks !== null &&
221 typeof injectedProfilingHooks.markCommitStarted === 'function'
222 ) {
223 injectedProfilingHooks.markCommitStarted(lanes);
224 }
225 }
226 }
227
228 export function markCommitStopped(): void {
229 if (enableSchedulingProfiler) {
230 if (
231 injectedProfilingHooks !== null &&
232 typeof injectedProfilingHooks.markCommitStopped === 'function'
233 ) {
234 injectedProfilingHooks.markCommitStopped();
235 }
236 }
237 }
238
239 export function markComponentRenderStarted(fiber: Fiber): void {
240 if (enableSchedulingProfiler) {
241 if (
242 injectedProfilingHooks !== null &&
243 typeof injectedProfilingHooks.markComponentRenderStarted === 'function'
244 ) {
245 injectedProfilingHooks.markComponentRenderStarted(fiber);
246 }
247 }
248 }
249
250 export function markComponentRenderStopped(): void {
251 if (enableSchedulingProfiler) {
252 if (
253 injectedProfilingHooks !== null &&
254 typeof injectedProfilingHooks.markComponentRenderStopped === 'function'
255 ) {
256 injectedProfilingHooks.markComponentRenderStopped();
257 }
258 }
259 }
260
261 export function markComponentPassiveEffectMountStarted(fiber: Fiber): void {
262 if (enableSchedulingProfiler) {
263 if (
264 injectedProfilingHooks !== null &&
265 typeof injectedProfilingHooks.markComponentPassiveEffectMountStarted ===
266 'function'
267 ) {
268 injectedProfilingHooks.markComponentPassiveEffectMountStarted(fiber);
269 }
270 }
271 }
272
273 export function markComponentPassiveEffectMountStopped(): void {
274 if (enableSchedulingProfiler) {
275 if (
276 injectedProfilingHooks !== null &&
277 typeof injectedProfilingHooks.markComponentPassiveEffectMountStopped ===
278 'function'
279 ) {
280 injectedProfilingHooks.markComponentPassiveEffectMountStopped();
281 }
282 }
283 }
284
285 export function markComponentPassiveEffectUnmountStarted(fiber: Fiber): void {
286 if (enableSchedulingProfiler) {
287 if (
288 injectedProfilingHooks !== null &&
289 typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStarted ===
290 'function'
291 ) {
292 injectedProfilingHooks.markComponentPassiveEffectUnmountStarted(fiber);
293 }
294 }
295 }
296
297 export function markComponentPassiveEffectUnmountStopped(): void {
298 if (enableSchedulingProfiler) {
299 if (
300 injectedProfilingHooks !== null &&
301 typeof injectedProfilingHooks.markComponentPassiveEffectUnmountStopped ===
302 'function'
303 ) {
304 injectedProfilingHooks.markComponentPassiveEffectUnmountStopped();
305 }
306 }
307 }
308
309 export function markComponentLayoutEffectMountStarted(fiber: Fiber): void {
310 if (enableSchedulingProfiler) {
311 if (
312 injectedProfilingHooks !== null &&
313 typeof injectedProfilingHooks.markComponentLayoutEffectMountStarted ===
314 'function'
315 ) {
316 injectedProfilingHooks.markComponentLayoutEffectMountStarted(fiber);
317 }
318 }
319 }
320
321 export function markComponentLayoutEffectMountStopped(): void {
322 if (enableSchedulingProfiler) {
323 if (
324 injectedProfilingHooks !== null &&
325 typeof injectedProfilingHooks.markComponentLayoutEffectMountStopped ===
326 'function'
327 ) {
328 injectedProfilingHooks.markComponentLayoutEffectMountStopped();
329 }
330 }
331 }
332
333 export function markComponentLayoutEffectUnmountStarted(fiber: Fiber): void {
334 if (enableSchedulingProfiler) {
335 if (
336 injectedProfilingHooks !== null &&
337 typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStarted ===
338 'function'
339 ) {
340 injectedProfilingHooks.markComponentLayoutEffectUnmountStarted(fiber);
341 }
342 }
343 }
344
345 export function markComponentLayoutEffectUnmountStopped(): void {
346 if (enableSchedulingProfiler) {
347 if (
348 injectedProfilingHooks !== null &&
349 typeof injectedProfilingHooks.markComponentLayoutEffectUnmountStopped ===
350 'function'
351 ) {
352 injectedProfilingHooks.markComponentLayoutEffectUnmountStopped();
353 }
354 }
355 }
356
357 export function markComponentErrored(
358 fiber: Fiber,
359 thrownValue: mixed,
360 lanes: Lanes,
361 ): void {
362 if (enableSchedulingProfiler) {
363 if (
364 injectedProfilingHooks !== null &&
365 typeof injectedProfilingHooks.markComponentErrored === 'function'
366 ) {
367 injectedProfilingHooks.markComponentErrored(fiber, thrownValue, lanes);
368 }
369 }
370 }
371
372 export function markComponentSuspended(
373 fiber: Fiber,
374 wakeable: Wakeable,
375 lanes: Lanes,
376 ): void {
377 if (enableSchedulingProfiler) {
378 if (
379 injectedProfilingHooks !== null &&
380 typeof injectedProfilingHooks.markComponentSuspended === 'function'
381 ) {
382 injectedProfilingHooks.markComponentSuspended(fiber, wakeable, lanes);
383 }
384 }
385 }
386
387 export function markLayoutEffectsStarted(lanes: Lanes): void {
388 if (enableSchedulingProfiler) {
389 if (
390 injectedProfilingHooks !== null &&
391 typeof injectedProfilingHooks.markLayoutEffectsStarted === 'function'
392 ) {
393 injectedProfilingHooks.markLayoutEffectsStarted(lanes);
394 }
395 }
396 }
397
398 export function markLayoutEffectsStopped(): void {
399 if (enableSchedulingProfiler) {
400 if (
401 injectedProfilingHooks !== null &&
402 typeof injectedProfilingHooks.markLayoutEffectsStopped === 'function'
403 ) {
404 injectedProfilingHooks.markLayoutEffectsStopped();
405 }
406 }
407 }
408
409 export function markPassiveEffectsStarted(lanes: Lanes): void {
410 if (enableSchedulingProfiler) {
411 if (
412 injectedProfilingHooks !== null &&
413 typeof injectedProfilingHooks.markPassiveEffectsStarted === 'function'
414 ) {
415 injectedProfilingHooks.markPassiveEffectsStarted(lanes);
416 }
417 }
418 }
419
420 export function markPassiveEffectsStopped(): void {
421 if (enableSchedulingProfiler) {
422 if (
423 injectedProfilingHooks !== null &&
424 typeof injectedProfilingHooks.markPassiveEffectsStopped === 'function'
425 ) {
426 injectedProfilingHooks.markPassiveEffectsStopped();
427 }
428 }
429 }
430
431 export function markRenderStarted(lanes: Lanes): void {
432 if (enableSchedulingProfiler) {
433 if (
434 injectedProfilingHooks !== null &&
435 typeof injectedProfilingHooks.markRenderStarted === 'function'
436 ) {
437 injectedProfilingHooks.markRenderStarted(lanes);
438 }
439 }
440 }
441
442 export function markRenderYielded(): void {
443 if (enableSchedulingProfiler) {
444 if (
445 injectedProfilingHooks !== null &&
446 typeof injectedProfilingHooks.markRenderYielded === 'function'
447 ) {
448 injectedProfilingHooks.markRenderYielded();
449 }
450 }
451 }
452
453 export function markRenderStopped(): void {
454 if (enableSchedulingProfiler) {
455 if (
456 injectedProfilingHooks !== null &&
457 typeof injectedProfilingHooks.markRenderStopped === 'function'
458 ) {
459 injectedProfilingHooks.markRenderStopped();
460 }
461 }
462 }
463
464 export function markRenderScheduled(lane: Lane): void {
465 if (enableSchedulingProfiler) {
466 if (
467 injectedProfilingHooks !== null &&
468 typeof injectedProfilingHooks.markRenderScheduled === 'function'
469 ) {
470 injectedProfilingHooks.markRenderScheduled(lane);
471 }
472 }
473 }
474
475 export function markForceUpdateScheduled(fiber: Fiber, lane: Lane): void {
476 if (enableSchedulingProfiler) {
477 if (
478 injectedProfilingHooks !== null &&
479 typeof injectedProfilingHooks.markForceUpdateScheduled === 'function'
480 ) {
481 injectedProfilingHooks.markForceUpdateScheduled(fiber, lane);
482 }
483 }
484 }
485
486 export function markStateUpdateScheduled(fiber: Fiber, lane: Lane): void {
487 if (enableSchedulingProfiler) {
488 if (
489 injectedProfilingHooks !== null &&
490 typeof injectedProfilingHooks.markStateUpdateScheduled === 'function'
491 ) {
492 injectedProfilingHooks.markStateUpdateScheduled(fiber, lane);
493 }
494 }
495 }