Skip to content
Merged
Show file tree
Hide file tree
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
16 changes: 16 additions & 0 deletions packages/react-debug-tools/src/ReactDebugHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
import {
REACT_MEMO_CACHE_SENTINEL,
REACT_CONTEXT_TYPE,
REACT_RECOVERABLE_TYPE,
} from 'shared/ReactSymbols';
import hasOwnProperty from 'shared/hasOwnProperty';

Expand Down Expand Up @@ -110,6 +111,11 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: null,
} as any);
const recoverable = new Error();
Object.defineProperty(recoverable as any, '$$typeof', {
value: REACT_RECOVERABLE_TYPE,
});
Dispatcher.use(recoverable as any);
Dispatcher.use({
then() {},
status: 'fulfilled',
Expand Down Expand Up @@ -240,6 +246,16 @@ function use<T>(usable: Usable<T>): T {
dispatcherHookName: 'Use',
});
throw SuspenseException;
} else if (usable.$$typeof === REACT_RECOVERABLE_TYPE) {
hookLog.push({
displayName: null,
primitive: 'Recoverable',
stackError: new Error(),
value: undefined,
debugInfo: null,
dispatcherHookName: 'Use',
});
return undefined as any;
} else if (usable.$$typeof === REACT_CONTEXT_TYPE) {
const context: ReactContext<T> = usable as any;
const value = readContext(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,37 @@ import {

let welcomeHasInitialized = false;
const requiredBackends = new Set<string>();
const activeBackendsShutdownCallbacks = new Set<() => void>();
let cleanupBackendManagerSetup: (() => void) | null = null;
let hasShutdownBackendManager = false;

function finishBackendManagerShutdown() {
if (hasShutdownBackendManager) {
return;
}
hasShutdownBackendManager = true;

window.removeEventListener('message', welcome);
window.removeEventListener('pagehide', handlePageHide);

const cleanup = cleanupBackendManagerSetup;
cleanupBackendManagerSetup = null;
cleanup?.();

delete window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__;
}

function handlePageHide() {
// A document in the back-forward cache keeps its JavaScript heap but loses
// its extension messaging port. Shut down locally while the document is
// still active so a restored page can attach a new Agent and replay its tree.
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const shutdownBackend of activeBackendsShutdownCallbacks) {
shutdownBackend();
}

finishBackendManagerShutdown();
}

function welcome(event: $FlowFixMe) {
if (
Expand Down Expand Up @@ -89,11 +120,26 @@ function setup(hook: ?DevToolsHook) {
},
);

const unsubscribeShutdownListener: () => void = hook.sub('shutdown', () => {
let didCleanup = false;
let unsubscribeShutdownListener: (() => void) | null = null;
const cleanup = () => {
if (didCleanup) {
return;
}
didCleanup = true;

unsubscribeRendererListener();
unsubscribeBackendInstallationListener();
unsubscribeShutdownListener();
});
unsubscribeShutdownListener?.();
unsubscribeShutdownListener = null;

if (cleanupBackendManagerSetup === cleanup) {
cleanupBackendManagerSetup = null;
}
};

unsubscribeShutdownListener = hook.sub('shutdown', cleanup);
cleanupBackendManagerSetup = cleanup;
}

function registerRenderer(renderer: ReactRenderer, hook: DevToolsHook) {
Expand All @@ -115,6 +161,7 @@ function activateBackend(version: string, hook: DevToolsHook) {
}

const {Agent, Bridge, initBackend, setupNativeStyleEditor} = backend;
let shouldSendMessages = true;
const bridge = new Bridge({
listen(fn) {
const listener = (event: $FlowFixMe) => {
Expand All @@ -134,6 +181,10 @@ function activateBackend(version: string, hook: DevToolsHook) {
};
},
send(event: string, payload: mixed, transferable?: $ReadOnlyArray<mixed>) {
if (!shouldSendMessages) {
return;
}

window.postMessage(
{
source: 'react-devtools-bridge',
Expand All @@ -154,11 +205,28 @@ function activateBackend(version: string, hook: DevToolsHook) {
// Clean up flags, so that next reload won't start profiling
onReloadAndProfileFlagsReset();

let hasShutdownBackend = false;
const shutdownBackend = () => {
if (hasShutdownBackend) {
return;
}
hasShutdownBackend = true;
shouldSendMessages = false;

bridge.shutdown();
};
activeBackendsShutdownCallbacks.add(shutdownBackend);

agent.addListener('shutdown', () => {
// If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down,
// and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here.
hasShutdownBackend = true;
shouldSendMessages = false;
activeBackendsShutdownCallbacks.delete(shutdownBackend);

hook.emit('shutdown');
delete window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__;

if (activeBackendsShutdownCallbacks.size === 0) {
finishBackendManagerShutdown();
}
});

initBackend(hook, agent, window, getIsReloadAndProfileSupported());
Expand Down Expand Up @@ -207,4 +275,5 @@ if (!window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__) {
window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__ = true;

window.addEventListener('message', welcome);
window.addEventListener('pagehide', handlePageHide);
}
29 changes: 25 additions & 4 deletions packages/react-devtools-extensions/src/contentScripts/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
} from '../constants';

function injectProxy() {
isTransportActive = true;

// Firefox's behaviour for injecting this content script can be unpredictable
// While navigating the history, some content scripts might not be re-injected and still be alive
if (!window.__REACT_DEVTOOLS_PROXY_INJECTED__) {
Expand All @@ -29,9 +31,9 @@ function injectProxy() {
// The backend waits to install the global hook until notified by the content script.
// In the event of a page reload, the content script might be loaded before the backend manager is injected.
// Because of this we need to poll the backend manager until it has been initialized.
const intervalID: IntervalID = setInterval(() => {
backendManagerHelloIntervalID = setInterval(() => {
if (backendInitialized) {
clearInterval(intervalID);
stopPollingForBackendManager();
} else {
sayHelloToBackendManager();
}
Expand Down Expand Up @@ -62,14 +64,29 @@ window.addEventListener('pagehide', function ({target}) {
return;
}

isTransportActive = false;
backendInitialized = false;
isBridgeConnected = false;
pendingMessages.length = 0;
stopPollingForBackendManager();

delete window.__REACT_DEVTOOLS_PROXY_INJECTED__;
});

let port: ExtensionRuntimePort | null = null;
let isTransportActive: boolean = true;
let backendInitialized: boolean = false;
let isBridgeConnected: boolean = false;
let isListeningToMessagesFromBackend: boolean = false;
const pendingMessages: Array<mixed> = [];
let backendManagerHelloIntervalID: IntervalID | null = null;

function stopPollingForBackendManager() {
if (backendManagerHelloIntervalID !== null) {
clearInterval(backendManagerHelloIntervalID);
backendManagerHelloIntervalID = null;
}
}

function listenToMessagesFromBackend() {
if (!isListeningToMessagesFromBackend) {
Expand Down Expand Up @@ -116,7 +133,7 @@ function handleMessageFromDevtools(
sourcePort: ExtensionRuntimePort,
message: mixed,
) {
if (port !== sourcePort) {
if (!isTransportActive || port !== sourcePort) {
return;
}

Expand Down Expand Up @@ -153,7 +170,7 @@ function handleMessageFromDevtools(
}

function handleMessageFromPage(event: any) {
if (event.source !== window || !event.data) {
if (!isTransportActive || event.source !== window || !event.data) {
return;
}

Expand Down Expand Up @@ -206,6 +223,10 @@ function handleDisconnect(disconnectedPort: ExtensionRuntimePort) {
// Creates port from application page to the React DevTools' service worker
// Which then connects it with extension port
function connectPort() {
if (!isTransportActive) {
return;
}

isBridgeConnected = false;
const nextPort = chrome.runtime.connect({
name: 'proxy',
Expand Down
Loading
Loading