[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',
Iterator: 'readonly',
AsyncIterator: 'readonly',
IntervalID: 'readonly',
IteratorResult: 'readonly',
JSONValue: 'readonly',
JSResourceReference: 'readonly',

View File

@ -6,7 +6,7 @@ const contentScriptsToInject = [
js: ['build/proxy.js'],
matches: ['<all_urls>'],
persistAcrossSessions: true,
runAt: 'document_end',
runAt: 'document_start',
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 */
export function executeScriptInIsolatedWorld({target, files}) {
export function executeScriptInIsolatedWorld({
target,
files,
}: {
files: any,
target: any,
}): Promise<void> {
return chrome.scripting.executeScript({
target,
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({
target,
files,
injectImmediately,
world: chrome.scripting.ExecutionWorld.MAIN,
});
}

View File

@ -1,5 +1,6 @@
/* global chrome */
import {__DEBUG__} from 'react-devtools-shared/src/constants';
import setExtensionIconAndPopup from './setExtensionIconAndPopup';
import {executeScriptInMainWorld} from './executeScript';
@ -25,6 +26,7 @@ export function handleBackendManagerMessage(message, sender) {
payload.versions.forEach(version => {
if (EXTENSION_CONTAINED_VERSIONS.includes(version)) {
executeScriptInMainWorld({
injectImmediately: true,
target: {tabId: sender.tab.id},
files: [`/build/react_devtools_backend_${version}.js`],
});
@ -79,9 +81,19 @@ export function handleDevToolsPageMessage(message) {
}
executeScriptInMainWorld({
injectImmediately: true,
target: {tabId},
files: ['/build/backendManager.js'],
});
}).then(
() => {
if (__DEBUG__) {
console.log('Successfully injected backend manager');
}
},
reason => {
console.error('Failed to inject backend manager:', reason);
},
);
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 */
'use strict';
window.addEventListener('pageshow', function ({target}) {
function injectProxy({target}: {target: any}) {
// 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__) {
@ -14,7 +22,7 @@ window.addEventListener('pageshow', function ({target}) {
// 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 = setInterval(() => {
const intervalID: IntervalID = setInterval(() => {
if (backendInitialized) {
clearInterval(intervalID);
} else {
@ -22,7 +30,11 @@ window.addEventListener('pageshow', function ({target}) {
}
}, 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}) {
if (target !== window.document) {
@ -45,7 +57,7 @@ function sayHelloToBackendManager() {
);
}
function handleMessageFromDevtools(message) {
function handleMessageFromDevtools(message: any) {
window.postMessage(
{
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) {
return;
}
@ -65,6 +77,7 @@ function handleMessageFromPage(event) {
case 'react-devtools-bridge': {
backendInitialized = true;
// $FlowFixMe[incompatible-use]
port.postMessage(event.data.payload);
break;
}
@ -99,6 +112,8 @@ function connectPort() {
window.addEventListener('message', handleMessageFromPage);
// $FlowFixMe[incompatible-use]
port.onMessage.addListener(handleMessageFromDevtools);
// $FlowFixMe[incompatible-use]
port.onDisconnect.addListener(handleDisconnect);
}