From a515c6e76d6deeebe7d484ac314d07179c58574a Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 13 Apr 2023 09:10:25 -0400 Subject: [PATCH 1/6] [DevTools] use backend manager to support multiple backends in extension --- .../react-devtools-extensions/src/backend.js | 134 ++++---------- .../src/backendManager.js | 164 ++++++++++++++++++ .../src/background.js | 38 +++- .../src/contentScripts/prepareInjection.js | 6 +- .../src/contentScripts/proxy.js | 29 ++-- .../react-devtools-extensions/src/main.js | 4 +- .../react-devtools-extensions/src/utils.js | 3 + .../webpack.backend.js | 2 +- .../webpack.config.js | 1 + .../setupNativeStyleEditor.js | 1 + .../src/backend/index.js | 17 ++ .../src/backend/types.js | 18 +- .../src/backend/utils.js | 6 + packages/react-devtools-shared/src/hook.js | 4 + 14 files changed, 297 insertions(+), 130 deletions(-) create mode 100644 packages/react-devtools-extensions/src/backendManager.js diff --git a/packages/react-devtools-extensions/src/backend.js b/packages/react-devtools-extensions/src/backend.js index 4a0dc99a97c2c..1e4f92e749288 100644 --- a/packages/react-devtools-extensions/src/backend.js +++ b/packages/react-devtools-extensions/src/backend.js @@ -1,109 +1,37 @@ -// Do not use imports or top-level requires here! -// Running module factories is intentionally delayed until we know the hook exists. -// This is to avoid issues like: https://github.com/facebook/react-devtools/issues/1039 - -// @flow strict-local - -'use strict'; - -let welcomeHasInitialized = false; - -// $FlowFixMe[missing-local-annot] -function welcome(event: $FlowFixMe) { - if ( - event.source !== window || - event.data.source !== 'react-devtools-content-script' - ) { - return; - } - - // In some circumstances, this method is called more than once for a single welcome message. - // The exact circumstances of this are unclear, though it seems related to 3rd party event batching code. - // - // Regardless, call this method multiple times can cause DevTools to add duplicate elements to the Store - // (and throw an error) or worse yet, choke up entirely and freeze the browser. - // - // The simplest solution is to ignore the duplicate events. - // To be clear, this SHOULD NOT BE NECESSARY, since we remove the event handler below. - // - // See https://github.com/facebook/react/issues/24162 - if (welcomeHasInitialized) { - console.warn( - 'React DevTools detected duplicate welcome "message" events from the content script.', - ); - return; - } - - welcomeHasInitialized = true; - - window.removeEventListener('message', welcome); - - setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__); -} - -window.addEventListener('message', welcome); - -function setup(hook: any) { +/** + * 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 + */ + +import type { + DevToolsBackend, + DevToolsHook, + ReactRenderer, +} from 'react-devtools-shared/src/backend/types'; + +import Agent from 'react-devtools-shared/src/backend/agent'; +import Bridge from 'react-devtools-shared/src/bridge'; +import {initBackend} from 'react-devtools-shared/src/backend'; +import setupNativeStyleEditor from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; + +import {COMPACT_VERSION_NAME} from './utils'; + +setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__); + +function setup(hook: ?DevToolsHook) { if (hook == null) { - // DevTools didn't get injected into this page (maybe b'c of the contentType). return; } - const Agent = require('react-devtools-shared/src/backend/agent').default; - const Bridge = require('react-devtools-shared/src/bridge').default; - const {initBackend} = require('react-devtools-shared/src/backend'); - const setupNativeStyleEditor = - require('react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor').default; - - const bridge = new Bridge<$FlowFixMe, $FlowFixMe>({ - listen(fn) { - const listener = (event: $FlowFixMe) => { - if ( - event.source !== window || - !event.data || - event.data.source !== 'react-devtools-content-script' || - !event.data.payload - ) { - return; - } - fn(event.data.payload); - }; - window.addEventListener('message', listener); - return () => { - window.removeEventListener('message', listener); - }; - }, - send(event: string, payload: any, transferable?: Array) { - window.postMessage( - { - source: 'react-devtools-bridge', - payload: {event, payload}, - }, - '*', - transferable, - ); - }, - }); - const agent = new Agent(bridge); - 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. - hook.emit('shutdown'); + hook.backends.set(COMPACT_VERSION_NAME, { + Agent, + Bridge, + initBackend, + setupNativeStyleEditor, }); - - initBackend(hook, agent, window); - - // Let the frontend know that the backend has attached listeners and is ready for messages. - // This covers the case of syncing saved values after reloading/navigating while DevTools remain open. - bridge.send('extensionBackendInitialized'); - - // Setup React Native style editor if a renderer like react-native-web has injected it. - if (hook.resolveRNStyle) { - setupNativeStyleEditor( - bridge, - agent, - hook.resolveRNStyle, - hook.nativeStyleEditorValidAttributes, - ); - } + hook.emit('devtools-backend-installed', COMPACT_VERSION_NAME); } diff --git a/packages/react-devtools-extensions/src/backendManager.js b/packages/react-devtools-extensions/src/backendManager.js new file mode 100644 index 0000000000000..0f59e1ca5f963 --- /dev/null +++ b/packages/react-devtools-extensions/src/backendManager.js @@ -0,0 +1,164 @@ +/** + * 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 + */ + +import type { + DevToolsBackend, + DevToolsHook, + ReactRenderer, +} from 'react-devtools-shared/src/backend/types'; +import {hasAssignedBackend} from 'react-devtools-shared/src/backend/utils'; +import {COMPACT_VERSION_NAME} from './utils'; + +let welcomeHasInitialized = false; + +// $FlowFixMe[missing-local-annot] +function welcome(event: $FlowFixMe) { + if ( + event.source !== window || + event.data.source !== 'react-devtools-content-script' + ) { + return; + } + + // In some circumstances, this method is called more than once for a single welcome message. + // The exact circumstances of this are unclear, though it seems related to 3rd party event batching code. + // + // Regardless, call this method multiple times can cause DevTools to add duplicate elements to the Store + // (and throw an error) or worse yet, choke up entirely and freeze the browser. + // + // The simplest solution is to ignore the duplicate events. + // To be clear, this SHOULD NOT BE NECESSARY, since we remove the event handler below. + // + // See https://github.com/facebook/react/issues/24162 + if (welcomeHasInitialized) { + console.warn( + 'React DevTools detected duplicate welcome "message" events from the content script.', + ); + return; + } + + welcomeHasInitialized = true; + + window.removeEventListener('message', welcome); + + setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__); +} + +window.addEventListener('message', welcome); + +function setup(hook: ?DevToolsHook) { + // this should not happen, but Chrome can be weird sometimes + if (hook == null) { + return; + } + + // register renderers that have already injected themselves. + hook.renderers.forEach(renderer => { + registerRenderer(renderer); + }); + updateRequiredBackends(); + + // register renderers that inject themselves later. + hook.sub('renderer', ({renderer}) => { + registerRenderer(renderer); + updateRequiredBackends(); + }); + + // listen for backend installations. + hook.sub('devtools-backend-installed', version => + activateBackend(version, hook), + ); +} + +const requiredBackends = new Set(); + +function registerRenderer(renderer: ReactRenderer) { + let version = renderer.version; + if (!hasAssignedBackend(renderer.version)) { + version = COMPACT_VERSION_NAME; + } + requiredBackends.add(version); +} + +function activateBackend(version: string, hook: DevToolsHook) { + const backend = hook.backends.get(version); + if (!backend) { + throw new Error(`Could not find backend for version "${version}"`); + } + const {Agent, Bridge, initBackend, setupNativeStyleEditor} = backend; + const bridge = new Bridge({ + listen(fn) { + const listener = (event: $FlowFixMe) => { + if ( + event.source !== window || + !event.data || + event.data.source !== 'react-devtools-content-script' || + !event.data.payload + ) { + return; + } + fn(event.data.payload); + }; + window.addEventListener('message', listener); + return () => { + window.removeEventListener('message', listener); + }; + }, + send(event: string, payload: any, transferable?: Array) { + window.postMessage( + { + source: 'react-devtools-bridge', + payload: {event, payload}, + }, + '*', + transferable, + ); + }, + }); + + const agent = new Agent(bridge); + 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. + hook.emit('shutdown'); + }); + + initBackend(hook, agent, window); + + // Setup React Native style editor if a renderer like react-native-web has injected it. + if (typeof setupNativeStyleEditor === 'function' && hook.resolveRNStyle) { + setupNativeStyleEditor( + bridge, + agent, + hook.resolveRNStyle, + hook.nativeStyleEditorValidAttributes, + ); + } + + // Let the frontend know that the backend has attached listeners and is ready for messages. + // This covers the case of syncing saved values after reloading/navigating while DevTools remain open. + bridge.send('extensionBackendInitialized'); + + // this backend is activated + requiredBackends.delete(version); +} + +// tell the service worker which versions of backends are needed for the current page +function updateRequiredBackends() { + window.postMessage( + { + source: 'react-devtools-backend-manager', + payload: { + type: 'react-devtools-required-backends', + versions: Array.from(requiredBackends), + }, + }, + '*', + ); +} diff --git a/packages/react-devtools-extensions/src/background.js b/packages/react-devtools-extensions/src/background.js index fea54f292c0d4..ce5dba6a797d8 100644 --- a/packages/react-devtools-extensions/src/background.js +++ b/packages/react-devtools-extensions/src/background.js @@ -2,7 +2,7 @@ 'use strict'; -import {IS_FIREFOX} from './utils'; +import {IS_FIREFOX, EXTENSION_CONTAINED_VERSIONS} from './utils'; const ports = {}; @@ -179,26 +179,50 @@ chrome.runtime.onMessage.addListener((request, sender) => { if (request.hasDetectedReact) { setIconAndPopup(request.reactBuildType, id); } else { + const devtools = ports[id]?.devtools; switch (request.payload?.type) { case 'fetch-file-with-cache-complete': case 'fetch-file-with-cache-error': // Forward the result of fetch-in-page requests back to the extension. - const devtools = ports[id]?.devtools; - if (devtools) { - devtools.postMessage(request); - } + devtools?.postMessage(request); + break; + // This is sent from the backend manager running on a page + case 'react-devtools-required-backends': + const backendsToDownload = []; + request.payload.versions.forEach(version => { + if (EXTENSION_CONTAINED_VERSIONS.includes(version)) { + if (!IS_FIREFOX) { + // TODO: add equivalent logic for Firefox is in prepareInjection.js + chrome.scripting.executeScript({ + target: {tabId: id}, + files: [`/build/react_devtools_backend_${version}.js`], + world: chrome.scripting.ExecutionWorld.MAIN, + }); + } + } else { + backendsToDownload.push(version); + } + }); + // Request the necessary backends in the extension DevTools UI + // TODO: handle this message in main.js + devtools?.postMessage({ + payload: { + type: 'react-devtools-additional-backends', + versions: backendsToDownload, + }, + }); break; } } } else if (request.payload?.tabId) { const tabId = request.payload?.tabId; // This is sent from the devtools page when it is ready for injecting the backend - if (request.payload.type === 'react-devtools-inject-backend') { + if (request.payload.type === 'react-devtools-inject-backend-manager') { if (!IS_FIREFOX) { // equivalent logic for Firefox is in prepareInjection.js chrome.scripting.executeScript({ target: {tabId}, - files: ['/build/react_devtools_backend.js'], + files: [`/build/backendManager.js`], world: chrome.scripting.ExecutionWorld.MAIN, }); } diff --git a/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js b/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js index 2e979e33daf7f..f936905d185bb 100644 --- a/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js +++ b/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js @@ -90,11 +90,9 @@ window.addEventListener('message', function onMessage({data, source}) { ); } break; - case 'react-devtools-inject-backend': + case 'react-devtools-inject-backend-manager': if (IS_FIREFOX) { - injectScriptSync( - chrome.runtime.getURL('build/react_devtools_backend.js'), - ); + injectScriptSync(chrome.runtime.getURL('build/backendManager.js')); } break; } diff --git a/packages/react-devtools-extensions/src/contentScripts/proxy.js b/packages/react-devtools-extensions/src/contentScripts/proxy.js index 8021fea780527..3bf4bf5cab445 100644 --- a/packages/react-devtools-extensions/src/contentScripts/proxy.js +++ b/packages/react-devtools-extensions/src/contentScripts/proxy.js @@ -5,7 +5,7 @@ let backendDisconnected: boolean = false; let backendInitialized: boolean = false; -function sayHelloToBackend() { +function sayHelloToBackendManager() { window.postMessage( { source: 'react-devtools-content-script', @@ -26,14 +26,19 @@ function handleMessageFromDevtools(message) { } function handleMessageFromPage(event) { - if ( - event.source === window && - event.data && - event.data.source === 'react-devtools-bridge' - ) { - backendInitialized = true; + if (event.source === window && event.data) { + // This is a message from a bridge (initialized by a devtools backend) + if (event.data.source === 'react-devtools-bridge') { + backendInitialized = true; - port.postMessage(event.data.payload); + port.postMessage(event.data.payload); + } + // This is a message from the backend manager + if (event.data.source === 'react-devtools-backend-manager') { + chrome.runtime.sendMessage({ + payload: event.data.payload, + }); + } } } @@ -63,17 +68,17 @@ port.onDisconnect.addListener(handleDisconnect); window.addEventListener('message', handleMessageFromPage); -sayHelloToBackend(); +sayHelloToBackendManager(); // 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 is injected. -// Because of this we need to poll the backend until it has been initialized. +// 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. if (!backendInitialized) { const intervalID = setInterval(() => { if (backendInitialized || backendDisconnected) { clearInterval(intervalID); } else { - sayHelloToBackend(); + sayHelloToBackendManager(); } }, 500); } diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index dd947be35564b..56907fd6ffb8a 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -191,7 +191,7 @@ function createPanelIfReactLoaded() { chrome.runtime.sendMessage({ source: 'react-devtools-main', payload: { - type: 'react-devtools-inject-backend', + type: 'react-devtools-inject-backend-manager', tabId, }, }); @@ -199,7 +199,7 @@ function createPanelIfReactLoaded() { // Firefox does not support executing script in ExecutionWorld.MAIN from content script. // see prepareInjection.js chrome.devtools.inspectedWindow.eval( - `window.postMessage({ source: 'react-devtools-inject-backend' }, '*');`, + `window.postMessage({ source: 'react-devtools-inject-backend-manager' }, '*');`, function (response, evalError) { if (evalError) { console.error(evalError); diff --git a/packages/react-devtools-extensions/src/utils.js b/packages/react-devtools-extensions/src/utils.js index c34c01d21d0a7..9fc9f2502eec1 100644 --- a/packages/react-devtools-extensions/src/utils.js +++ b/packages/react-devtools-extensions/src/utils.js @@ -41,3 +41,6 @@ export function getBrowserTheme(): BrowserTheme { } } } + +export const COMPACT_VERSION_NAME = 'compact'; +export const EXTENSION_CONTAINED_VERSIONS = [COMPACT_VERSION_NAME]; diff --git a/packages/react-devtools-extensions/webpack.backend.js b/packages/react-devtools-extensions/webpack.backend.js index 29d183923cd8e..a1cbb7b6f9bc1 100644 --- a/packages/react-devtools-extensions/webpack.backend.js +++ b/packages/react-devtools-extensions/webpack.backend.js @@ -42,7 +42,7 @@ module.exports = { }, output: { path: __dirname + '/build', - filename: 'react_devtools_backend.js', + filename: 'react_devtools_backend_compact.js', }, node: { // Don't define a polyfill on window.setImmediate diff --git a/packages/react-devtools-extensions/webpack.config.js b/packages/react-devtools-extensions/webpack.config.js index fe5543c04b524..ad2c23caf9fc1 100644 --- a/packages/react-devtools-extensions/webpack.config.js +++ b/packages/react-devtools-extensions/webpack.config.js @@ -51,6 +51,7 @@ module.exports = { devtool: __DEV__ ? 'cheap-module-source-map' : false, entry: { background: './src/background.js', + backendManager: './src/backendManager.js', main: './src/main.js', panel: './src/panel.js', proxy: './src/contentScripts/proxy.js', diff --git a/packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js b/packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js index 665a65f292d80..728f0e691c98b 100644 --- a/packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js +++ b/packages/react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor.js @@ -16,6 +16,7 @@ import type {RendererID} from '../types'; import type {StyleAndLayout} from './types'; export type ResolveNativeStyle = (stylesheetID: any) => ?Object; +export type SetupNativeStyleEditor = typeof setupNativeStyleEditor; export default function setupNativeStyleEditor( bridge: BackendBridge, diff --git a/packages/react-devtools-shared/src/backend/index.js b/packages/react-devtools-shared/src/backend/index.js index 6aa8a2f58a300..d3c194602eae8 100644 --- a/packages/react-devtools-shared/src/backend/index.js +++ b/packages/react-devtools-shared/src/backend/index.js @@ -11,9 +11,17 @@ import Agent from './agent'; import {attach} from './renderer'; import {attach as attachLegacy} from './legacy/renderer'; +import {hasAssignedBackend} from './utils'; import type {DevToolsHook, ReactRenderer, RendererInterface} from './types'; +// this is the backend that is compactible with all older React versions +function isMatchingRender(version: string): boolean { + return !hasAssignedBackend(version); +} + +export type InitBackend = typeof initBackend; + export function initBackend( hook: DevToolsHook, agent: Agent, @@ -56,6 +64,14 @@ export function initBackend( ]; const attachRenderer = (id: number, renderer: ReactRenderer) => { + // skip if already attached + if (renderer.attached) { + return; + } + // only attach if the renderer is compatible with the current version of the backend + if (!isMatchingRender(renderer.reconcilerVersion || renderer.version)) { + return; + } let rendererInterface = hook.rendererInterfaces.get(id); // Inject any not-yet-injected renderers (if we didn't reload-and-profile) @@ -86,6 +102,7 @@ export function initBackend( } else { hook.emit('unsupported-renderer-version', id); } + renderer.attached = true; }; // Connect renderers that have already injected themselves. diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 61eb1876ec441..ab859e71fc707 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -22,9 +22,15 @@ import type { ElementType, Plugins, } from 'react-devtools-shared/src/types'; -import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; +import type { + ResolveNativeStyle, + SetupNativeStyleEditor, +} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor'; +import type {InitBackend} from 'react-devtools-shared/src/backend'; import type {TimelineDataExport} from 'react-devtools-timeline/src/types'; import type {BrowserTheme} from 'react-devtools-shared/src/types'; +import type {BackendBridge} from 'react-devtools-shared/src/bridge'; +import type Agent from './agent'; type BundleType = | 0 // PROD @@ -165,6 +171,8 @@ export type ReactRenderer = { // 18.0+ injectProfilingHooks?: (profilingHooks: DevToolsProfilingHooks) => void, getLaneLabelMap?: () => Map | null, + // set by backend after successful attaching + attached?: boolean, ... }; @@ -464,10 +472,18 @@ export type DevToolsProfilingHooks = { markComponentPassiveEffectUnmountStopped: () => void, }; +export type DevToolsBackend = { + Agent: Class, + Bridge: Class, + initBackend: InitBackend, + setupNativeStyleEditor?: SetupNativeStyleEditor, +}; + export type DevToolsHook = { listeners: {[key: string]: Array, ...}, rendererInterfaces: Map, renderers: Map, + backends: Map, emit: (event: string, data: any) => void, getFiberRoots: (rendererID: RendererID) => Set, diff --git a/packages/react-devtools-shared/src/backend/utils.js b/packages/react-devtools-shared/src/backend/utils.js index da70575295e02..94ef4ef2133e7 100644 --- a/packages/react-devtools-shared/src/backend/utils.js +++ b/packages/react-devtools-shared/src/backend/utils.js @@ -14,6 +14,12 @@ import isArray from 'shared/isArray'; import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Components/types'; +// TODO: update this to the first React version that has a corresponding DevTools backend +const FIRST_DEVTOOLS_BACKEND_LOCKSTEP_VER = '999.9.9'; +export function hasAssignedBackend(version: string): boolean { + return gte(version, FIRST_DEVTOOLS_BACKEND_LOCKSTEP_VER); +} + export function cleanForBridge( data: Object | null, isPathAllowed: (path: Array) => boolean, diff --git a/packages/react-devtools-shared/src/hook.js b/packages/react-devtools-shared/src/hook.js index 0d0d2785ba2be..39af7daa374ce 100644 --- a/packages/react-devtools-shared/src/hook.js +++ b/packages/react-devtools-shared/src/hook.js @@ -15,6 +15,7 @@ import type { ReactRenderer, RendererID, RendererInterface, + DevToolsBackend, } from './backend/types'; declare var window: any; @@ -506,11 +507,14 @@ export function installHook(target: any): DevToolsHook | null { const rendererInterfaces = new Map(); const listeners: {[string]: Array} = {}; const renderers = new Map(); + const backends = new Map(); const hook: DevToolsHook = { rendererInterfaces, listeners, + backends, + // Fast Refresh for web relies on this. renderers, From 54594d2f0348d7c71d5f429d623f04257cc709bc Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 13 Apr 2023 09:25:23 -0400 Subject: [PATCH 2/6] [DevTools] should update backend versions after it's activated --- packages/react-devtools-extensions/src/backendManager.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react-devtools-extensions/src/backendManager.js b/packages/react-devtools-extensions/src/backendManager.js index 0f59e1ca5f963..b5282ee1e6f3b 100644 --- a/packages/react-devtools-extensions/src/backendManager.js +++ b/packages/react-devtools-extensions/src/backendManager.js @@ -71,9 +71,10 @@ function setup(hook: ?DevToolsHook) { }); // listen for backend installations. - hook.sub('devtools-backend-installed', version => - activateBackend(version, hook), - ); + hook.sub('devtools-backend-installed', version => { + activateBackend(version, hook); + updateRequiredBackends(); + }); } const requiredBackends = new Set(); From 6929f4541bac10b076ee5d4e746e720871e581d6 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 13 Apr 2023 09:29:57 -0400 Subject: [PATCH 3/6] unused vars --- packages/react-devtools-extensions/src/backend.js | 6 +----- packages/react-devtools-extensions/src/backendManager.js | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/react-devtools-extensions/src/backend.js b/packages/react-devtools-extensions/src/backend.js index 1e4f92e749288..b79e1916518e4 100644 --- a/packages/react-devtools-extensions/src/backend.js +++ b/packages/react-devtools-extensions/src/backend.js @@ -7,11 +7,7 @@ * @flow */ -import type { - DevToolsBackend, - DevToolsHook, - ReactRenderer, -} from 'react-devtools-shared/src/backend/types'; +import type {DevToolsHook} from 'react-devtools-shared/src/backend/types'; import Agent from 'react-devtools-shared/src/backend/agent'; import Bridge from 'react-devtools-shared/src/bridge'; diff --git a/packages/react-devtools-extensions/src/backendManager.js b/packages/react-devtools-extensions/src/backendManager.js index b5282ee1e6f3b..c0b57bbc4135e 100644 --- a/packages/react-devtools-extensions/src/backendManager.js +++ b/packages/react-devtools-extensions/src/backendManager.js @@ -8,7 +8,6 @@ */ import type { - DevToolsBackend, DevToolsHook, ReactRenderer, } from 'react-devtools-shared/src/backend/types'; From 6debd13341c502f2a2712aad19e4e6cd65820f6e Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 13 Apr 2023 09:43:15 -0400 Subject: [PATCH 4/6] avoid checking for empty version --- packages/react-devtools-extensions/src/backendManager.js | 4 ++-- packages/react-devtools-shared/src/backend/utils.js | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/react-devtools-extensions/src/backendManager.js b/packages/react-devtools-extensions/src/backendManager.js index c0b57bbc4135e..23c6be57bc031 100644 --- a/packages/react-devtools-extensions/src/backendManager.js +++ b/packages/react-devtools-extensions/src/backendManager.js @@ -79,8 +79,8 @@ function setup(hook: ?DevToolsHook) { const requiredBackends = new Set(); function registerRenderer(renderer: ReactRenderer) { - let version = renderer.version; - if (!hasAssignedBackend(renderer.version)) { + let version = renderer.reconcilerVersion || renderer.version; + if (!hasAssignedBackend(version)) { version = COMPACT_VERSION_NAME; } requiredBackends.add(version); diff --git a/packages/react-devtools-shared/src/backend/utils.js b/packages/react-devtools-shared/src/backend/utils.js index 94ef4ef2133e7..bcadf4a7c0ccd 100644 --- a/packages/react-devtools-shared/src/backend/utils.js +++ b/packages/react-devtools-shared/src/backend/utils.js @@ -16,7 +16,10 @@ import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Comp // TODO: update this to the first React version that has a corresponding DevTools backend const FIRST_DEVTOOLS_BACKEND_LOCKSTEP_VER = '999.9.9'; -export function hasAssignedBackend(version: string): boolean { +export function hasAssignedBackend(version?: string): boolean { + if (version == null || version === '') { + return false; + } return gte(version, FIRST_DEVTOOLS_BACKEND_LOCKSTEP_VER); } From 8ee161f53e3dad9141a69d47185238ee3aeea60e Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 13 Apr 2023 15:03:29 -0400 Subject: [PATCH 5/6] handle backend loading in Firefox --- .../react-devtools-extensions/src/background.js | 6 +++--- .../src/contentScripts/prepareInjection.js | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/react-devtools-extensions/src/background.js b/packages/react-devtools-extensions/src/background.js index ce5dba6a797d8..89d87724ddac2 100644 --- a/packages/react-devtools-extensions/src/background.js +++ b/packages/react-devtools-extensions/src/background.js @@ -192,7 +192,7 @@ chrome.runtime.onMessage.addListener((request, sender) => { request.payload.versions.forEach(version => { if (EXTENSION_CONTAINED_VERSIONS.includes(version)) { if (!IS_FIREFOX) { - // TODO: add equivalent logic for Firefox is in prepareInjection.js + // equivalent logic for Firefox is in prepareInjection.js chrome.scripting.executeScript({ target: {tabId: id}, files: [`/build/react_devtools_backend_${version}.js`], @@ -204,7 +204,7 @@ chrome.runtime.onMessage.addListener((request, sender) => { } }); // Request the necessary backends in the extension DevTools UI - // TODO: handle this message in main.js + // TODO: handle this message in main.js to build the UI devtools?.postMessage({ payload: { type: 'react-devtools-additional-backends', @@ -222,7 +222,7 @@ chrome.runtime.onMessage.addListener((request, sender) => { // equivalent logic for Firefox is in prepareInjection.js chrome.scripting.executeScript({ target: {tabId}, - files: [`/build/backendManager.js`], + files: ['/build/backendManager.js'], world: chrome.scripting.ExecutionWorld.MAIN, }); } diff --git a/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js b/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js index f936905d185bb..a62cce3903f46 100644 --- a/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js +++ b/packages/react-devtools-extensions/src/contentScripts/prepareInjection.js @@ -3,7 +3,7 @@ import nullthrows from 'nullthrows'; import {SESSION_STORAGE_RELOAD_AND_PROFILE_KEY} from 'react-devtools-shared/src/constants'; import {sessionStorageGetItem} from 'react-devtools-shared/src/storage'; -import {IS_FIREFOX} from '../utils'; +import {IS_FIREFOX, EXTENSION_CONTAINED_VERSIONS} from '../utils'; // We run scripts on the page via the service worker (backgroud.js) for // Manifest V3 extensions (Chrome & Edge). @@ -95,6 +95,19 @@ window.addEventListener('message', function onMessage({data, source}) { injectScriptSync(chrome.runtime.getURL('build/backendManager.js')); } break; + case 'react-devtools-backend-manager': + if (IS_FIREFOX) { + data.payload?.versions?.forEach(version => { + if (EXTENSION_CONTAINED_VERSIONS.includes(version)) { + injectScriptSync( + chrome.runtime.getURL( + `/build/react_devtools_backend_${version}.js`, + ), + ); + } + }); + } + break; } }); From 90434b5e8f67296f389602b7b759da7956ee7d7d Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Thu, 13 Apr 2023 15:07:19 -0400 Subject: [PATCH 6/6] use wildcard for web resources in manifest.json --- packages/react-devtools-extensions/chrome/manifest.json | 5 +---- packages/react-devtools-extensions/edge/manifest.json | 5 +---- packages/react-devtools-extensions/firefox/manifest.json | 5 +---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/react-devtools-extensions/chrome/manifest.json b/packages/react-devtools-extensions/chrome/manifest.json index d27120df284e9..0c1d2899709f9 100644 --- a/packages/react-devtools-extensions/chrome/manifest.json +++ b/packages/react-devtools-extensions/chrome/manifest.json @@ -29,10 +29,7 @@ "resources": [ "main.html", "panel.html", - "build/react_devtools_backend.js", - "build/proxy.js", - "build/renderer.js", - "build/installHook.js" + "build/*.js" ], "matches": [ "" diff --git a/packages/react-devtools-extensions/edge/manifest.json b/packages/react-devtools-extensions/edge/manifest.json index e0699d40d00ea..faeaf0ee0cedd 100644 --- a/packages/react-devtools-extensions/edge/manifest.json +++ b/packages/react-devtools-extensions/edge/manifest.json @@ -29,10 +29,7 @@ "resources": [ "main.html", "panel.html", - "build/react_devtools_backend.js", - "build/proxy.js", - "build/renderer.js", - "build/installHook.js" + "build/*.js" ], "matches": [ "" diff --git a/packages/react-devtools-extensions/firefox/manifest.json b/packages/react-devtools-extensions/firefox/manifest.json index e0680a617ca3f..dbd797d3caa58 100644 --- a/packages/react-devtools-extensions/firefox/manifest.json +++ b/packages/react-devtools-extensions/firefox/manifest.json @@ -30,10 +30,7 @@ "web_accessible_resources": [ "main.html", "panel.html", - "build/react_devtools_backend.js", - "build/proxy.js", - "build/renderer.js", - "build/installHook.js" + "build/*.js" ], "background": { "scripts": [