[DevTools] Allow inspection before streaming has finished in Chrome (#34360)

This commit is contained in:
Sebastian "Sebbie" Silbermann 2025-09-04 12:21:06 +02:00 committed by GitHub
parent ba6590dd7c
commit 5a31758ed6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 61 additions and 9 deletions

View File

@ -577,6 +577,7 @@ module.exports = {
$AsyncIterator: 'readonly', $AsyncIterator: 'readonly',
Iterator: 'readonly', Iterator: 'readonly',
AsyncIterator: 'readonly', AsyncIterator: 'readonly',
IntervalID: 'readonly',
IteratorResult: 'readonly', IteratorResult: 'readonly',
JSONValue: 'readonly', JSONValue: 'readonly',
JSResourceReference: 'readonly', JSResourceReference: 'readonly',

View File

@ -6,7 +6,7 @@ const contentScriptsToInject = [
js: ['build/proxy.js'], js: ['build/proxy.js'],
matches: ['<all_urls>'], matches: ['<all_urls>'],
persistAcrossSessions: true, persistAcrossSessions: true,
runAt: 'document_end', runAt: 'document_start',
world: chrome.scripting.ExecutionWorld.ISOLATED, world: chrome.scripting.ExecutionWorld.ISOLATED,
}, },
{ {

View File

@ -1,6 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/* global chrome */ /* global chrome */
export function executeScriptInIsolatedWorld({target, files}) { export function executeScriptInIsolatedWorld({
target,
files,
}: {
files: any,
target: any,
}): Promise<void> {
return chrome.scripting.executeScript({ return chrome.scripting.executeScript({
target, target,
files, files,
@ -8,10 +22,20 @@ export function executeScriptInIsolatedWorld({target, files}) {
}); });
} }
export function executeScriptInMainWorld({target, files}) { export function executeScriptInMainWorld({
target,
files,
injectImmediately,
}: {
files: any,
target: any,
// It's nice to have this required to make active choices.
injectImmediately: boolean,
}): Promise<void> {
return chrome.scripting.executeScript({ return chrome.scripting.executeScript({
target, target,
files, files,
injectImmediately,
world: chrome.scripting.ExecutionWorld.MAIN, world: chrome.scripting.ExecutionWorld.MAIN,
}); });
} }

View File

@ -1,5 +1,6 @@
/* global chrome */ /* global chrome */
import {__DEBUG__} from 'react-devtools-shared/src/constants';
import setExtensionIconAndPopup from './setExtensionIconAndPopup'; import setExtensionIconAndPopup from './setExtensionIconAndPopup';
import {executeScriptInMainWorld} from './executeScript'; import {executeScriptInMainWorld} from './executeScript';
@ -25,6 +26,7 @@ export function handleBackendManagerMessage(message, sender) {
payload.versions.forEach(version => { payload.versions.forEach(version => {
if (EXTENSION_CONTAINED_VERSIONS.includes(version)) { if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
executeScriptInMainWorld({ executeScriptInMainWorld({
injectImmediately: true,
target: {tabId: sender.tab.id}, target: {tabId: sender.tab.id},
files: [`/build/react_devtools_backend_${version}.js`], files: [`/build/react_devtools_backend_${version}.js`],
}); });
@ -79,9 +81,19 @@ export function handleDevToolsPageMessage(message) {
} }
executeScriptInMainWorld({ executeScriptInMainWorld({
injectImmediately: true,
target: {tabId}, target: {tabId},
files: ['/build/backendManager.js'], files: ['/build/backendManager.js'],
}); }).then(
() => {
if (__DEBUG__) {
console.log('Successfully injected backend manager');
}
},
reason => {
console.error('Failed to inject backend manager:', reason);
},
);
break; break;
} }

View File

@ -1,8 +1,16 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/* global chrome */ /* global chrome */
'use strict'; 'use strict';
window.addEventListener('pageshow', function ({target}) { function injectProxy({target}: {target: any}) {
// Firefox's behaviour for injecting this content script can be unpredictable // 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 // While navigating the history, some content scripts might not be re-injected and still be alive
if (!window.__REACT_DEVTOOLS_PROXY_INJECTED__) { if (!window.__REACT_DEVTOOLS_PROXY_INJECTED__) {
@ -14,7 +22,7 @@ window.addEventListener('pageshow', function ({target}) {
// The backend waits to install the global hook until notified by the content script. // 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. // 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. // Because of this we need to poll the backend manager until it has been initialized.
const intervalID = setInterval(() => { const intervalID: IntervalID = setInterval(() => {
if (backendInitialized) { if (backendInitialized) {
clearInterval(intervalID); clearInterval(intervalID);
} else { } else {
@ -22,7 +30,11 @@ window.addEventListener('pageshow', function ({target}) {
} }
}, 500); }, 500);
} }
}); }
window.addEventListener('pagereveal', injectProxy);
// For backwards compat with browsers not implementing `pagereveal` which is a fairly new event.
window.addEventListener('pageshow', injectProxy);
window.addEventListener('pagehide', function ({target}) { window.addEventListener('pagehide', function ({target}) {
if (target !== window.document) { if (target !== window.document) {
@ -45,7 +57,7 @@ function sayHelloToBackendManager() {
); );
} }
function handleMessageFromDevtools(message) { function handleMessageFromDevtools(message: any) {
window.postMessage( window.postMessage(
{ {
source: 'react-devtools-content-script', source: 'react-devtools-content-script',
@ -55,7 +67,7 @@ function handleMessageFromDevtools(message) {
); );
} }
function handleMessageFromPage(event) { function handleMessageFromPage(event: any) {
if (event.source !== window || !event.data) { if (event.source !== window || !event.data) {
return; return;
} }
@ -65,6 +77,7 @@ function handleMessageFromPage(event) {
case 'react-devtools-bridge': { case 'react-devtools-bridge': {
backendInitialized = true; backendInitialized = true;
// $FlowFixMe[incompatible-use]
port.postMessage(event.data.payload); port.postMessage(event.data.payload);
break; break;
} }
@ -99,6 +112,8 @@ function connectPort() {
window.addEventListener('message', handleMessageFromPage); window.addEventListener('message', handleMessageFromPage);
// $FlowFixMe[incompatible-use]
port.onMessage.addListener(handleMessageFromDevtools); port.onMessage.addListener(handleMessageFromDevtools);
// $FlowFixMe[incompatible-use]
port.onDisconnect.addListener(handleDisconnect); port.onDisconnect.addListener(handleDisconnect);
} }