Skip to content

Commit f6a2054

Browse files
committed
implemented test and code changes to fix issue #20117, to enable handling of dev tools global hook being disabled
1 parent 6b28eb6 commit f6a2054

File tree

2 files changed

+119
-93
lines changed

2 files changed

+119
-93
lines changed

packages/react-refresh/src/ReactFreshRuntime.js

Lines changed: 104 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ export function injectIntoGlobalHook(globalObject: any): void {
447447
globalObject.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook = {
448448
renderers: new Map(),
449449
supportsFiber: true,
450+
isDisabled: false,
450451
inject(injected) {
451452
return nextID++;
452453
},
@@ -465,107 +466,117 @@ export function injectIntoGlobalHook(globalObject: any): void {
465466
};
466467
}
467468

468-
// Here, we just want to get a reference to scheduleRefresh.
469-
const oldInject = hook.inject;
470-
hook.inject = function(injected) {
471-
const id = oldInject.apply(this, arguments);
472-
if (
473-
typeof injected.scheduleRefresh === 'function' &&
474-
typeof injected.setRefreshHandler === 'function'
475-
) {
476-
// This version supports React Refresh.
477-
helpersByRendererID.set(id, ((injected: any): RendererHelpers));
478-
}
479-
return id;
480-
};
469+
if (hook.isDisabled) {
470+
// This isn't a real property on the hook, but it can be set to opt out
471+
// of DevTools integration and associated warnings and logs.
472+
// https://github.com/facebook/react/issues/20117
473+
console.error(
474+
'The installed version of React DevTools is disabled. ' +
475+
'https://reactjs.org/link/react-devtools',
476+
);
477+
} else {
478+
// Here, we just want to get a reference to scheduleRefresh.
479+
const oldInject = hook.inject;
480+
hook.inject = function(injected) {
481+
const id = oldInject.apply(this, arguments);
482+
if (
483+
typeof injected.scheduleRefresh === 'function' &&
484+
typeof injected.setRefreshHandler === 'function'
485+
) {
486+
// This version supports React Refresh.
487+
helpersByRendererID.set(id, ((injected: any): RendererHelpers));
488+
}
489+
return id;
490+
};
481491

482-
// Do the same for any already injected roots.
483-
// This is useful if ReactDOM has already been initialized.
484-
// https://github.com/facebook/react/issues/17626
485-
hook.renderers.forEach((injected, id) => {
486-
if (
487-
typeof injected.scheduleRefresh === 'function' &&
488-
typeof injected.setRefreshHandler === 'function'
489-
) {
490-
// This version supports React Refresh.
491-
helpersByRendererID.set(id, ((injected: any): RendererHelpers));
492-
}
493-
});
492+
// Do the same for any already injected roots.
493+
// This is useful if ReactDOM has already been initialized.
494+
// https://github.com/facebook/react/issues/17626
495+
hook.renderers.forEach((injected, id) => {
496+
if (
497+
typeof injected.scheduleRefresh === 'function' &&
498+
typeof injected.setRefreshHandler === 'function'
499+
) {
500+
// This version supports React Refresh.
501+
helpersByRendererID.set(id, ((injected: any): RendererHelpers));
502+
}
503+
});
494504

495-
// We also want to track currently mounted roots.
496-
const oldOnCommitFiberRoot = hook.onCommitFiberRoot;
497-
const oldOnScheduleFiberRoot = hook.onScheduleFiberRoot || (() => {});
498-
hook.onScheduleFiberRoot = function(
499-
id: number,
500-
root: FiberRoot,
501-
children: ReactNodeList,
502-
) {
503-
if (!isPerformingRefresh) {
504-
// If it was intentionally scheduled, don't attempt to restore.
505-
// This includes intentionally scheduled unmounts.
506-
failedRoots.delete(root);
507-
if (rootElements !== null) {
508-
rootElements.set(root, children);
505+
// We also want to track currently mounted roots.
506+
const oldOnCommitFiberRoot = hook.onCommitFiberRoot;
507+
const oldOnScheduleFiberRoot = hook.onScheduleFiberRoot || (() => {});
508+
hook.onScheduleFiberRoot = function(
509+
id: number,
510+
root: FiberRoot,
511+
children: ReactNodeList,
512+
) {
513+
if (!isPerformingRefresh) {
514+
// If it was intentionally scheduled, don't attempt to restore.
515+
// This includes intentionally scheduled unmounts.
516+
failedRoots.delete(root);
517+
if (rootElements !== null) {
518+
rootElements.set(root, children);
519+
}
509520
}
510-
}
511-
return oldOnScheduleFiberRoot.apply(this, arguments);
512-
};
513-
hook.onCommitFiberRoot = function(
514-
id: number,
515-
root: FiberRoot,
516-
maybePriorityLevel: mixed,
517-
didError: boolean,
518-
) {
519-
const helpers = helpersByRendererID.get(id);
520-
if (helpers !== undefined) {
521-
helpersByRoot.set(root, helpers);
522-
523-
const current = root.current;
524-
const alternate = current.alternate;
525-
526-
// We need to determine whether this root has just (un)mounted.
527-
// This logic is copy-pasted from similar logic in the DevTools backend.
528-
// If this breaks with some refactoring, you'll want to update DevTools too.
529-
530-
if (alternate !== null) {
531-
const wasMounted =
532-
alternate.memoizedState != null &&
533-
alternate.memoizedState.element != null;
534-
const isMounted =
535-
current.memoizedState != null &&
536-
current.memoizedState.element != null;
537-
538-
if (!wasMounted && isMounted) {
521+
return oldOnScheduleFiberRoot.apply(this, arguments);
522+
};
523+
hook.onCommitFiberRoot = function(
524+
id: number,
525+
root: FiberRoot,
526+
maybePriorityLevel: mixed,
527+
didError: boolean,
528+
) {
529+
const helpers = helpersByRendererID.get(id);
530+
if (helpers !== undefined) {
531+
helpersByRoot.set(root, helpers);
532+
533+
const current = root.current;
534+
const alternate = current.alternate;
535+
536+
// We need to determine whether this root has just (un)mounted.
537+
// This logic is copy-pasted from similar logic in the DevTools backend.
538+
// If this breaks with some refactoring, you'll want to update DevTools too.
539+
540+
if (alternate !== null) {
541+
const wasMounted =
542+
alternate.memoizedState != null &&
543+
alternate.memoizedState.element != null;
544+
const isMounted =
545+
current.memoizedState != null &&
546+
current.memoizedState.element != null;
547+
548+
if (!wasMounted && isMounted) {
549+
// Mount a new root.
550+
mountedRoots.add(root);
551+
failedRoots.delete(root);
552+
} else if (wasMounted && isMounted) {
553+
// Update an existing root.
554+
// This doesn't affect our mounted root Set.
555+
} else if (wasMounted && !isMounted) {
556+
// Unmount an existing root.
557+
mountedRoots.delete(root);
558+
if (didError) {
559+
// We'll remount it on future edits.
560+
failedRoots.add(root);
561+
} else {
562+
helpersByRoot.delete(root);
563+
}
564+
} else if (!wasMounted && !isMounted) {
565+
if (didError) {
566+
// We'll remount it on future edits.
567+
failedRoots.add(root);
568+
}
569+
}
570+
} else {
539571
// Mount a new root.
540572
mountedRoots.add(root);
541-
failedRoots.delete(root);
542-
} else if (wasMounted && isMounted) {
543-
// Update an existing root.
544-
// This doesn't affect our mounted root Set.
545-
} else if (wasMounted && !isMounted) {
546-
// Unmount an existing root.
547-
mountedRoots.delete(root);
548-
if (didError) {
549-
// We'll remount it on future edits.
550-
failedRoots.add(root);
551-
} else {
552-
helpersByRoot.delete(root);
553-
}
554-
} else if (!wasMounted && !isMounted) {
555-
if (didError) {
556-
// We'll remount it on future edits.
557-
failedRoots.add(root);
558-
}
559573
}
560-
} else {
561-
// Mount a new root.
562-
mountedRoots.add(root);
563574
}
564-
}
565575

566-
// Always call the decorated DevTools hook.
567-
return oldOnCommitFiberRoot.apply(this, arguments);
568-
};
576+
// Always call the decorated DevTools hook.
577+
return oldOnCommitFiberRoot.apply(this, arguments);
578+
};
579+
}
569580
} else {
570581
throw new Error(
571582
'Unexpected call to React Refresh in a production environment.',

packages/react-refresh/src/__tests__/ReactFresh-test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,21 @@ describe('ReactFresh', () => {
447447
}
448448
});
449449

450+
it('throws an error when react dev tools global hook is disabled to avoid crashing', () => {
451+
if (__DEV__) {
452+
global.__REACT_DEVTOOLS_GLOBAL_HOOK__.isDisabled = true;
453+
ReactFreshRuntime = require('react-refresh/runtime');
454+
455+
expect(() => {
456+
ReactFreshRuntime.injectIntoGlobalHook(global);
457+
}).toErrorDev(
458+
'The installed version of React DevTools is disabled. ' +
459+
'https://reactjs.org/link/react-devtools',
460+
{withoutStack: true},
461+
);
462+
}
463+
});
464+
450465
it('can update forwardRef render function in isolation', () => {
451466
if (__DEV__) {
452467
render(() => {

0 commit comments

Comments
 (0)