diff --git a/packages/react-devtools-extensions/src/background.js b/packages/react-devtools-extensions/src/background.js index e8ff0f3f16202..9e09513b78fb4 100644 --- a/packages/react-devtools-extensions/src/background.js +++ b/packages/react-devtools-extensions/src/background.js @@ -1,20 +1,11 @@ -// @flow strict-local +/* global chrome */ 'use strict'; -declare var chrome: any; - -const ports: { - [tab: string]: {|devtools: any, 'content-script': any|}, -} = {}; +const ports = {}; const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0; -import { - EXTENSION_INSTALL_CHECK, - SHOW_DUPLICATE_EXTENSION_WARNING, -} from './constants'; - chrome.runtime.onConnect.addListener(function(port) { let tab = null; let name = null; @@ -125,15 +116,6 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { } }); -chrome.runtime.onMessageExternal.addListener( - (request, sender, sendResponse) => { - if (request === EXTENSION_INSTALL_CHECK) { - sendResponse(true); - chrome.runtime.sendMessage(SHOW_DUPLICATE_EXTENSION_WARNING); - } - }, -); - chrome.runtime.onMessage.addListener((request, sender) => { const tab = sender.tab; if (tab) { diff --git a/packages/react-devtools-extensions/src/checkForDuplicateInstallations.js b/packages/react-devtools-extensions/src/checkForDuplicateInstallations.js deleted file mode 100644 index 01db8edb34f74..0000000000000 --- a/packages/react-devtools-extensions/src/checkForDuplicateInstallations.js +++ /dev/null @@ -1,137 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - */ - -declare var chrome: any; - -import { - INTERNAL_EXTENSION_ID, - LOCAL_EXTENSION_ID, - __DEBUG__, -} from 'react-devtools-shared/src/constants'; -import {getBrowserName} from './utils'; -import { - EXTENSION_INSTALL_CHECK, - EXTENSION_INSTALLATION_TYPE, -} from './constants'; - -const IS_CHROME = getBrowserName() === 'Chrome'; - -const UNRECOGNIZED_EXTENSION_ERROR = - 'React Developer Tools: You are running an unrecognized installation of the React Developer Tools extension, which might conflict with other versions of the extension installed in your browser. ' + - 'Please make sure you only have a single version of the extension installed or enabled. ' + - 'If you are developing this extension locally, make sure to build the extension using the `yarn build::local` command.'; - -export function checkForDuplicateInstallations(callback: boolean => void) { - switch (EXTENSION_INSTALLATION_TYPE) { - case 'public': { - // If this is the public extension (e.g. from Chrome Web Store), check if an internal - // or local build of the extension is also installed, and if so, disable this extension. - // TODO show warning if other installations are present. - checkForInstalledExtensions([ - INTERNAL_EXTENSION_ID, - LOCAL_EXTENSION_ID, - ]).then(areExtensionsInstalled => { - if (areExtensionsInstalled.some(isInstalled => isInstalled)) { - callback(true); - } else { - callback(false); - } - }); - break; - } - case 'internal': { - // If this is the internal extension, check if a local build of the extension - // is also installed, and if so, disable this extension. - // If the public version of the extension is also installed, that extension - // will disable itself. - // TODO show warning if other installations are present. - checkForInstalledExtension(LOCAL_EXTENSION_ID).then(isInstalled => { - if (isInstalled) { - callback(true); - } else { - callback(false); - } - }); - break; - } - case 'local': { - if (__DEV__) { - // If this is the local extension (i.e. built locally during development), - // always keep this one enabled. Other installations disable themselves if - // they detect the local build is installed. - callback(false); - break; - } - - // If this extension wasn't built locally during development, we can't reliably - // detect if there are other installations of DevTools present. - // In this case, assume there are no duplicate exensions and show a warning about - // potential conflicts. - console.error(UNRECOGNIZED_EXTENSION_ERROR); - chrome.devtools.inspectedWindow.eval( - `console.error("${UNRECOGNIZED_EXTENSION_ERROR}")`, - ); - callback(false); - break; - } - case 'unknown': { - // TODO: Support duplicate extension detection in other browsers - if (IS_CHROME) { - // If we don't know how this extension was built, we can't reliably detect if there - // are other installations of DevTools present. - // In this case, assume there are no duplicate exensions and show a warning about - // potential conflicts. - console.error(UNRECOGNIZED_EXTENSION_ERROR); - chrome.devtools.inspectedWindow.eval( - `console.error("${UNRECOGNIZED_EXTENSION_ERROR}")`, - ); - } - callback(false); - break; - } - default: { - (EXTENSION_INSTALLATION_TYPE: empty); - } - } -} - -function checkForInstalledExtensions( - extensionIds: string[], -): Promise { - return Promise.all( - extensionIds.map(extensionId => checkForInstalledExtension(extensionId)), - ); -} - -function checkForInstalledExtension(extensionId: string): Promise { - return new Promise(resolve => { - chrome.runtime.sendMessage( - extensionId, - EXTENSION_INSTALL_CHECK, - response => { - if (__DEBUG__) { - console.log( - 'checkForDuplicateInstallations: Duplicate installation check responded with', - { - response, - error: chrome.runtime.lastError?.message, - currentExtension: EXTENSION_INSTALLATION_TYPE, - checkingExtension: extensionId, - }, - ); - } - if (chrome.runtime.lastError != null) { - resolve(false); - } else { - resolve(true); - } - }, - ); - }); -} diff --git a/packages/react-devtools-extensions/src/constants.js b/packages/react-devtools-extensions/src/constants.js deleted file mode 100644 index c17ad4d64acd8..0000000000000 --- a/packages/react-devtools-extensions/src/constants.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - */ - -import { - CHROME_WEBSTORE_EXTENSION_ID, - INTERNAL_EXTENSION_ID, - LOCAL_EXTENSION_ID, -} from 'react-devtools-shared/src/constants'; - -declare var chrome: any; - -export const CURRENT_EXTENSION_ID = chrome.runtime.id; - -export const EXTENSION_INSTALL_CHECK = 'extension-install-check'; -export const SHOW_DUPLICATE_EXTENSION_WARNING = - 'show-duplicate-extension-warning'; - -export const EXTENSION_INSTALLATION_TYPE: - | 'public' - | 'internal' - | 'local' - | 'unknown' = - CURRENT_EXTENSION_ID === CHROME_WEBSTORE_EXTENSION_ID - ? 'public' - : CURRENT_EXTENSION_ID === INTERNAL_EXTENSION_ID - ? 'internal' - : CURRENT_EXTENSION_ID === LOCAL_EXTENSION_ID - ? 'local' - : 'unknown'; diff --git a/packages/react-devtools-extensions/src/contentScript.js b/packages/react-devtools-extensions/src/contentScript.js index 179959f7e01ec..c914c6e7b3dfc 100644 --- a/packages/react-devtools-extensions/src/contentScript.js +++ b/packages/react-devtools-extensions/src/contentScript.js @@ -2,8 +2,6 @@ 'use strict'; -import {CURRENT_EXTENSION_ID} from './constants'; - let backendDisconnected: boolean = false; let backendInitialized: boolean = false; @@ -12,7 +10,6 @@ function sayHelloToBackend() { { source: 'react-devtools-content-script', hello: true, - extensionId: CURRENT_EXTENSION_ID, }, '*', ); @@ -23,7 +20,6 @@ function handleMessageFromDevtools(message) { { source: 'react-devtools-content-script', payload: message, - extensionId: CURRENT_EXTENSION_ID, }, '*', ); @@ -53,7 +49,6 @@ function handleDisconnect() { type: 'event', event: 'shutdown', }, - extensionId: CURRENT_EXTENSION_ID, }, '*', ); diff --git a/packages/react-devtools-extensions/src/injectGlobalHook.js b/packages/react-devtools-extensions/src/injectGlobalHook.js index a5d96966c7e10..79e5a84adba88 100644 --- a/packages/react-devtools-extensions/src/injectGlobalHook.js +++ b/packages/react-devtools-extensions/src/injectGlobalHook.js @@ -2,11 +2,7 @@ import nullthrows from 'nullthrows'; import {installHook} from 'react-devtools-shared/src/hook'; -import { - __DEBUG__, - SESSION_STORAGE_RELOAD_AND_PROFILE_KEY, -} from 'react-devtools-shared/src/constants'; -import {CURRENT_EXTENSION_ID, EXTENSION_INSTALLATION_TYPE} from './constants'; +import {SESSION_STORAGE_RELOAD_AND_PROFILE_KEY} from 'react-devtools-shared/src/constants'; import {sessionStorageGetItem} from 'react-devtools-shared/src/storage'; function injectCode(code) { @@ -31,19 +27,6 @@ window.addEventListener('message', function onMessage({data, source}) { if (source !== window || !data) { return; } - if (data.extensionId != null && data.extensionId !== CURRENT_EXTENSION_ID) { - if (__DEBUG__) { - console.log( - `[injectGlobalHook] Received message '${data.source}' from different extension instance. Skipping message.`, - { - currentExtension: EXTENSION_INSTALLATION_TYPE, - currentExtensionId: CURRENT_EXTENSION_ID, - providedExtensionId: data.extensionId, - }, - ); - } - return; - } switch (data.source) { case 'react-devtools-detector': lastDetectionResult = { @@ -118,7 +101,6 @@ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function({reactBuildType}) window.postMessage({ source: 'react-devtools-detector', reactBuildType, - extensionId: "${CURRENT_EXTENSION_ID}", }, '*'); }); `; diff --git a/packages/react-devtools-extensions/src/main.js b/packages/react-devtools-extensions/src/main.js index f8750292a1a73..6a3836839a4ea 100644 --- a/packages/react-devtools-extensions/src/main.js +++ b/packages/react-devtools-extensions/src/main.js @@ -22,18 +22,11 @@ import { import DevTools from 'react-devtools-shared/src/devtools/views/DevTools'; import {__DEBUG__} from 'react-devtools-shared/src/constants'; import {logEvent} from 'react-devtools-shared/src/Logger'; -import { - CURRENT_EXTENSION_ID, - EXTENSION_INSTALLATION_TYPE, - SHOW_DUPLICATE_EXTENSION_WARNING, -} from './constants'; -import {checkForDuplicateInstallations} from './checkForDuplicateInstallations'; const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY = 'React::DevTools::supportsProfiling'; const isChrome = getBrowserName() === 'Chrome'; -const isEdge = getBrowserName() === 'Edge'; let panelCreated = false; @@ -77,186 +70,135 @@ function createPanelIfReactLoaded() { return; } - checkForDuplicateInstallations(hasDuplicateInstallation => { - if (hasDuplicateInstallation) { - if (__DEBUG__) { - console.log( - '[main] createPanelIfReactLoaded: Duplicate installation detected, skipping initialization of extension.', - {currentExtension: EXTENSION_INSTALLATION_TYPE}, - ); - } - panelCreated = true; - clearInterval(loadCheckInterval); - return; - } + panelCreated = true; - if (__DEBUG__) { - console.log( - '[main] createPanelIfReactLoaded: No duplicate installations detected, continuing with initialization.', - {currentExtension: EXTENSION_INSTALLATION_TYPE}, - ); - } - - panelCreated = true; + clearInterval(loadCheckInterval); - clearInterval(loadCheckInterval); + let bridge = null; + let store = null; - let bridge = null; - let store = null; + let profilingData = null; - let profilingData = null; + let componentsPortalContainer = null; + let profilerPortalContainer = null; - let componentsPortalContainer = null; - let profilerPortalContainer = null; + let cloneStyleTags = null; + let mostRecentOverrideTab = null; + let render = null; + let root = null; - let cloneStyleTags = null; - let mostRecentOverrideTab = null; - let render = null; - let root = null; - let warnIfDuplicateInstallation = false; + const tabId = chrome.devtools.inspectedWindow.tabId; - const tabId = chrome.devtools.inspectedWindow.tabId; + registerDevToolsEventLogger('extension'); - registerDevToolsEventLogger('extension'); - - function onDuplicateExtensionMessage(message) { - if (message === SHOW_DUPLICATE_EXTENSION_WARNING) { - chrome.runtime.onMessage.removeListener( - onDuplicateExtensionMessage, - ); + function initBridgeAndStore() { + const port = chrome.runtime.connect({ + name: String(tabId), + }); + // Looks like `port.onDisconnect` does not trigger on in-tab navigation like new URL or back/forward navigation, + // so it makes no sense to handle it here. + + bridge = new Bridge({ + listen(fn) { + const listener = message => fn(message); + // Store the reference so that we unsubscribe from the same object. + const portOnMessage = port.onMessage; + portOnMessage.addListener(listener); + return () => { + portOnMessage.removeListener(listener); + }; + }, + send(event: string, payload: any, transferable?: Array) { + port.postMessage({event, payload}, transferable); + }, + }); + bridge.addListener('reloadAppForProfiling', () => { + localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true'); + chrome.devtools.inspectedWindow.eval('window.location.reload();'); + }); + bridge.addListener('syncSelectionToNativeElementsPanel', () => { + setBrowserSelectionFromReact(); + }); - if (warnIfDuplicateInstallation === true) { - return; - } - warnIfDuplicateInstallation = true; - const errorMessage = - 'React Developer Tools: We detected that there are multiple versions of React Developer Tools ' + - 'installed and enabled in your browser at the same time, which will cause ' + - 'issues while using the extension. ' + - 'Please ensure that you have installed and enabled only a single ' + - 'version of React Developer Tools before proceeding.'; - console.error(errorMessage); - chrome.devtools.inspectedWindow.eval( - `console.error("${errorMessage}")`, - ); - if (render != null) { - render(); - } - } + // This flag lets us tip the Store off early that we expect to be profiling. + // This avoids flashing a temporary "Profiling not supported" message in the Profiler tab, + // after a user has clicked the "reload and profile" button. + let isProfiling = false; + let supportsProfiling = false; + if ( + localStorageGetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true' + ) { + supportsProfiling = true; + isProfiling = true; + localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY); } - chrome.runtime.onMessage.addListener(onDuplicateExtensionMessage); - - function initBridgeAndStore() { - const port = chrome.runtime.connect({ - name: String(tabId), - }); - // Looks like `port.onDisconnect` does not trigger on in-tab navigation like new URL or back/forward navigation, - // so it makes no sense to handle it here. - - bridge = new Bridge({ - listen(fn) { - const listener = message => fn(message); - // Store the reference so that we unsubscribe from the same object. - const portOnMessage = port.onMessage; - portOnMessage.addListener(listener); - return () => { - portOnMessage.removeListener(listener); - }; - }, - send(event: string, payload: any, transferable?: Array) { - port.postMessage({event, payload}, transferable); - }, - }); - bridge.addListener('reloadAppForProfiling', () => { - localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true'); - chrome.devtools.inspectedWindow.eval('window.location.reload();'); - }); - bridge.addListener('syncSelectionToNativeElementsPanel', () => { - setBrowserSelectionFromReact(); - }); - // This flag lets us tip the Store off early that we expect to be profiling. - // This avoids flashing a temporary "Profiling not supported" message in the Profiler tab, - // after a user has clicked the "reload and profile" button. - let isProfiling = false; - let supportsProfiling = false; - if ( - localStorageGetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true' - ) { - supportsProfiling = true; - isProfiling = true; - localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY); - } + if (store !== null) { + profilingData = store.profilerStore.profilingData; + } - if (store !== null) { - profilingData = store.profilerStore.profilingData; - } + bridge.addListener('extensionBackendInitialized', () => { + // Initialize the renderer's trace-updates setting. + // This handles the case of navigating to a new page after the DevTools have already been shown. + bridge.send( + 'setTraceUpdatesEnabled', + localStorageGetItem(LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY) === + 'true', + ); + }); - bridge.addListener('extensionBackendInitialized', () => { - // Initialize the renderer's trace-updates setting. - // This handles the case of navigating to a new page after the DevTools have already been shown. - bridge.send( - 'setTraceUpdatesEnabled', - localStorageGetItem(LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY) === - 'true', - ); - }); + store = new Store(bridge, { + isProfiling, + supportsReloadAndProfile: isChrome, + supportsProfiling, + // At this time, the scheduling profiler can only parse Chrome performance profiles. + supportsSchedulingProfiler: isChrome, + supportsTraceUpdates: true, + }); + store.profilerStore.profilingData = profilingData; + + // Initialize the backend only once the Store has been initialized. + // Otherwise the Store may miss important initial tree op codes. + chrome.devtools.inspectedWindow.eval( + `window.postMessage({ source: 'react-devtools-inject-backend' }, '*');`, + function(response, evalError) { + if (evalError) { + console.error(evalError); + } + }, + ); - store = new Store(bridge, { - isProfiling, - supportsReloadAndProfile: isChrome || isEdge, - supportsProfiling, - // At this time, the scheduling profiler can only parse Chrome performance profiles. - supportsSchedulingProfiler: isChrome, - supportsTraceUpdates: true, - }); - store.profilerStore.profilingData = profilingData; - - // Initialize the backend only once the Store has been initialized. - // Otherwise the Store may miss important initial tree op codes. - chrome.devtools.inspectedWindow.eval( - `window.postMessage({ - source: 'react-devtools-inject-backend', - extensionId: "${CURRENT_EXTENSION_ID}", - }, '*');`, - function(response, evalError) { - if (evalError) { - console.error(evalError); - } - }, - ); + const viewAttributeSourceFunction = (id, path) => { + const rendererID = store.getRendererIDForElement(id); + if (rendererID != null) { + // Ask the renderer interface to find the specified attribute, + // and store it as a global variable on the window. + bridge.send('viewAttributeSource', {id, path, rendererID}); - const viewAttributeSourceFunction = (id, path) => { - const rendererID = store.getRendererIDForElement(id); - if (rendererID != null) { - // Ask the renderer interface to find the specified attribute, - // and store it as a global variable on the window. - bridge.send('viewAttributeSource', {id, path, rendererID}); - - setTimeout(() => { - // Ask Chrome to display the location of the attribute, - // assuming the renderer found a match. - chrome.devtools.inspectedWindow.eval(` + setTimeout(() => { + // Ask Chrome to display the location of the attribute, + // assuming the renderer found a match. + chrome.devtools.inspectedWindow.eval(` if (window.$attribute != null) { inspect(window.$attribute); } `); - }, 100); - } - }; + }, 100); + } + }; - const viewElementSourceFunction = id => { - const rendererID = store.getRendererIDForElement(id); - if (rendererID != null) { - // Ask the renderer interface to determine the component function, - // and store it as a global variable on the window - bridge.send('viewElementSource', {id, rendererID}); - - setTimeout(() => { - // Ask Chrome to display the location of the component function, - // or a render method if it is a Class (ideally Class instance, not type) - // assuming the renderer found one. - chrome.devtools.inspectedWindow.eval(` + const viewElementSourceFunction = id => { + const rendererID = store.getRendererIDForElement(id); + if (rendererID != null) { + // Ask the renderer interface to determine the component function, + // and store it as a global variable on the window + bridge.send('viewElementSource', {id, rendererID}); + + setTimeout(() => { + // Ask Chrome to display the location of the component function, + // or a render method if it is a Class (ideally Class instance, not type) + // assuming the renderer found one. + chrome.devtools.inspectedWindow.eval(` if (window.$type != null) { if ( window.$type && @@ -271,294 +213,288 @@ function createPanelIfReactLoaded() { } } `); - }, 100); - } - }; + }, 100); + } + }; - let debugIDCounter = 0; + let debugIDCounter = 0; - // For some reason in Firefox, chrome.runtime.sendMessage() from a content script - // never reaches the chrome.runtime.onMessage event listener. - let fetchFileWithCaching = null; - if (isChrome) { - const fetchFromNetworkCache = (url, resolve, reject) => { - // Debug ID allows us to avoid re-logging (potentially long) URL strings below, - // while also still associating (potentially) interleaved logs with the original request. - let debugID = null; + // For some reason in Firefox, chrome.runtime.sendMessage() from a content script + // never reaches the chrome.runtime.onMessage event listener. + let fetchFileWithCaching = null; + if (isChrome) { + const fetchFromNetworkCache = (url, resolve, reject) => { + // Debug ID allows us to avoid re-logging (potentially long) URL strings below, + // while also still associating (potentially) interleaved logs with the original request. + let debugID = null; - if (__DEBUG__) { - debugID = debugIDCounter++; - console.log(`[main] fetchFromNetworkCache(${debugID})`, url); - } + if (__DEBUG__) { + debugID = debugIDCounter++; + console.log(`[main] fetchFromNetworkCache(${debugID})`, url); + } - chrome.devtools.network.getHAR(harLog => { - for (let i = 0; i < harLog.entries.length; i++) { - const entry = harLog.entries[i]; - if (url === entry.request.url) { - if (__DEBUG__) { - console.log( - `[main] fetchFromNetworkCache(${debugID}) Found matching URL in HAR`, - url, - ); - } + chrome.devtools.network.getHAR(harLog => { + for (let i = 0; i < harLog.entries.length; i++) { + const entry = harLog.entries[i]; + if (url === entry.request.url) { + if (__DEBUG__) { + console.log( + `[main] fetchFromNetworkCache(${debugID}) Found matching URL in HAR`, + url, + ); + } - entry.getContent(content => { - if (content) { - if (__DEBUG__) { - console.log( - `[main] fetchFromNetworkCache(${debugID}) Content retrieved`, - ); - } - - resolve(content); - } else { - if (__DEBUG__) { - console.log( - `[main] fetchFromNetworkCache(${debugID}) Invalid content returned by getContent()`, - content, - ); - } - - // Edge case where getContent() returned null; fall back to fetch. - fetchFromPage(url, resolve, reject); + entry.getContent(content => { + if (content) { + if (__DEBUG__) { + console.log( + `[main] fetchFromNetworkCache(${debugID}) Content retrieved`, + ); } - }); - return; - } - } + resolve(content); + } else { + if (__DEBUG__) { + console.log( + `[main] fetchFromNetworkCache(${debugID}) Invalid content returned by getContent()`, + content, + ); + } - if (__DEBUG__) { - console.log( - `[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`, - ); - } + // Edge case where getContent() returned null; fall back to fetch. + fetchFromPage(url, resolve, reject); + } + }); - // No matching URL found; fall back to fetch. - fetchFromPage(url, resolve, reject); - }); - }; + return; + } + } - const fetchFromPage = (url, resolve, reject) => { if (__DEBUG__) { - console.log('[main] fetchFromPage()', url); + console.log( + `[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`, + ); } - function onPortMessage({payload, source}) { - if (source === 'react-devtools-content-script') { - switch (payload?.type) { - case 'fetch-file-with-cache-complete': - chrome.runtime.onMessage.removeListener(onPortMessage); - resolve(payload.value); - break; - case 'fetch-file-with-cache-error': - chrome.runtime.onMessage.removeListener(onPortMessage); - reject(payload.value); - break; - } + // No matching URL found; fall back to fetch. + fetchFromPage(url, resolve, reject); + }); + }; + + const fetchFromPage = (url, resolve, reject) => { + if (__DEBUG__) { + console.log('[main] fetchFromPage()', url); + } + + function onPortMessage({payload, source}) { + if (source === 'react-devtools-content-script') { + switch (payload?.type) { + case 'fetch-file-with-cache-complete': + chrome.runtime.onMessage.removeListener(onPortMessage); + resolve(payload.value); + break; + case 'fetch-file-with-cache-error': + chrome.runtime.onMessage.removeListener(onPortMessage); + reject(payload.value); + break; } } + } - chrome.runtime.onMessage.addListener(onPortMessage); + chrome.runtime.onMessage.addListener(onPortMessage); - chrome.devtools.inspectedWindow.eval(` - window.postMessage({ - source: 'react-devtools-extension', - extensionId: "${CURRENT_EXTENSION_ID}", - payload: { - type: 'fetch-file-with-cache', - url: "${url}", - }, - }, '*'); - `); - }; - - // Fetching files from the extension won't make use of the network cache - // for resources that have already been loaded by the page. - // This helper function allows the extension to request files to be fetched - // by the content script (running in the page) to increase the likelihood of a cache hit. - fetchFileWithCaching = url => { - return new Promise((resolve, reject) => { - // Try fetching from the Network cache first. - // If DevTools was opened after the page started loading, we may have missed some requests. - // So fall back to a fetch() from the page and hope we get a cached response that way. - fetchFromNetworkCache(url, resolve, reject); + chrome.devtools.inspectedWindow.eval(` + window.postMessage({ + source: 'react-devtools-extension', + payload: { + type: 'fetch-file-with-cache', + url: "${url}", + }, }); - }; - } - - // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration. - const hookNamesModuleLoaderFunction = () => - import( - /* webpackChunkName: 'parseHookNames' */ 'react-devtools-shared/src/hooks/parseHookNames' - ); - - root = createRoot(document.createElement('div')); - - render = (overrideTab = mostRecentOverrideTab) => { - mostRecentOverrideTab = overrideTab; - root.render( - createElement(DevTools, { - bridge, - browserTheme: getBrowserTheme(), - componentsPortalContainer, - enabledInspectedElementContextMenu: true, - fetchFileWithCaching, - hookNamesModuleLoaderFunction, - overrideTab, - profilerPortalContainer, - warnIfDuplicateInstallation, - showTabBar: false, - store, - warnIfUnsupportedVersionDetected: true, - viewAttributeSourceFunction, - viewElementSourceFunction, - }), - ); + `); }; - render(); + // Fetching files from the extension won't make use of the network cache + // for resources that have already been loaded by the page. + // This helper function allows the extension to request files to be fetched + // by the content script (running in the page) to increase the likelihood of a cache hit. + fetchFileWithCaching = url => { + return new Promise((resolve, reject) => { + // Try fetching from the Network cache first. + // If DevTools was opened after the page started loading, we may have missed some requests. + // So fall back to a fetch() from the page and hope we get a cached response that way. + fetchFromNetworkCache(url, resolve, reject); + }); + }; } - cloneStyleTags = () => { - const linkTags = []; - // eslint-disable-next-line no-for-of-loops/no-for-of-loops - for (const linkTag of document.getElementsByTagName('link')) { - if (linkTag.rel === 'stylesheet') { - const newLinkTag = document.createElement('link'); - // eslint-disable-next-line no-for-of-loops/no-for-of-loops - for (const attribute of linkTag.attributes) { - newLinkTag.setAttribute( - attribute.nodeName, - attribute.nodeValue, - ); - } - linkTags.push(newLinkTag); - } - } - return linkTags; + // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration. + const hookNamesModuleLoaderFunction = () => + import( + /* webpackChunkName: 'parseHookNames' */ 'react-devtools-shared/src/hooks/parseHookNames' + ); + + root = createRoot(document.createElement('div')); + + render = (overrideTab = mostRecentOverrideTab) => { + mostRecentOverrideTab = overrideTab; + root.render( + createElement(DevTools, { + bridge, + browserTheme: getBrowserTheme(), + componentsPortalContainer, + enabledInspectedElementContextMenu: true, + fetchFileWithCaching, + hookNamesModuleLoaderFunction, + overrideTab, + profilerPortalContainer, + showTabBar: false, + store, + warnIfUnsupportedVersionDetected: true, + viewAttributeSourceFunction, + viewElementSourceFunction, + }), + ); }; - initBridgeAndStore(); + render(); + } - function ensureInitialHTMLIsCleared(container) { - if (container._hasInitialHTMLBeenCleared) { - return; + cloneStyleTags = () => { + const linkTags = []; + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (const linkTag of document.getElementsByTagName('link')) { + if (linkTag.rel === 'stylesheet') { + const newLinkTag = document.createElement('link'); + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (const attribute of linkTag.attributes) { + newLinkTag.setAttribute(attribute.nodeName, attribute.nodeValue); + } + linkTags.push(newLinkTag); } - container.innerHTML = ''; - container._hasInitialHTMLBeenCleared = true; } + return linkTags; + }; - function setBrowserSelectionFromReact() { - // This is currently only called on demand when you press "view DOM". - // In the future, if Chrome adds an inspect() that doesn't switch tabs, - // we could make this happen automatically when you select another component. - chrome.devtools.inspectedWindow.eval( - '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' + - '(inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true) :' + - 'false', - (didSelectionChange, evalError) => { - if (evalError) { - console.error(evalError); - } - }, - ); - } + initBridgeAndStore(); - function setReactSelectionFromBrowser() { - // When the user chooses a different node in the browser Elements tab, - // copy it over to the hook object so that we can sync the selection. - chrome.devtools.inspectedWindow.eval( - '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' + - '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' + - 'false', - (didSelectionChange, evalError) => { - if (evalError) { - console.error(evalError); - } else if (didSelectionChange) { - // Remember to sync the selection next time we show Components tab. - needsToSyncElementSelection = true; - } - }, - ); + function ensureInitialHTMLIsCleared(container) { + if (container._hasInitialHTMLBeenCleared) { + return; } + container.innerHTML = ''; + container._hasInitialHTMLBeenCleared = true; + } - setReactSelectionFromBrowser(); - chrome.devtools.panels.elements.onSelectionChanged.addListener(() => { - setReactSelectionFromBrowser(); - }); + function setBrowserSelectionFromReact() { + // This is currently only called on demand when you press "view DOM". + // In the future, if Chrome adds an inspect() that doesn't switch tabs, + // we could make this happen automatically when you select another component. + chrome.devtools.inspectedWindow.eval( + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' + + '(inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true) :' + + 'false', + (didSelectionChange, evalError) => { + if (evalError) { + console.error(evalError); + } + }, + ); + } - let currentPanel = null; - let needsToSyncElementSelection = false; - - chrome.devtools.panels.create( - isChrome ? '⚛️ Components' : 'Components', - '', - 'panel.html', - extensionPanel => { - extensionPanel.onShown.addListener(panel => { - if (needsToSyncElementSelection) { - needsToSyncElementSelection = false; - bridge.send('syncSelectionFromNativeElementsPanel'); - } + function setReactSelectionFromBrowser() { + // When the user chooses a different node in the browser Elements tab, + // copy it over to the hook object so that we can sync the selection. + chrome.devtools.inspectedWindow.eval( + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' + + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' + + 'false', + (didSelectionChange, evalError) => { + if (evalError) { + console.error(evalError); + } else if (didSelectionChange) { + // Remember to sync the selection next time we show Components tab. + needsToSyncElementSelection = true; + } + }, + ); + } - if (currentPanel === panel) { - return; - } + setReactSelectionFromBrowser(); + chrome.devtools.panels.elements.onSelectionChanged.addListener(() => { + setReactSelectionFromBrowser(); + }); - currentPanel = panel; - componentsPortalContainer = panel.container; + let currentPanel = null; + let needsToSyncElementSelection = false; + + chrome.devtools.panels.create( + isChrome ? '⚛️ Components' : 'Components', + '', + 'panel.html', + extensionPanel => { + extensionPanel.onShown.addListener(panel => { + if (needsToSyncElementSelection) { + needsToSyncElementSelection = false; + bridge.send('syncSelectionFromNativeElementsPanel'); + } - if (componentsPortalContainer != null) { - ensureInitialHTMLIsCleared(componentsPortalContainer); - render('components'); - panel.injectStyles(cloneStyleTags); - logEvent({event_name: 'selected-components-tab'}); - } - }); - extensionPanel.onHidden.addListener(panel => { - // TODO: Stop highlighting and stuff. - }); - }, - ); + if (currentPanel === panel) { + return; + } - chrome.devtools.panels.create( - isChrome ? '⚛️ Profiler' : 'Profiler', - '', - 'panel.html', - extensionPanel => { - extensionPanel.onShown.addListener(panel => { - if (currentPanel === panel) { - return; - } + currentPanel = panel; + componentsPortalContainer = panel.container; - currentPanel = panel; - profilerPortalContainer = panel.container; + if (componentsPortalContainer != null) { + ensureInitialHTMLIsCleared(componentsPortalContainer); + render('components'); + panel.injectStyles(cloneStyleTags); + logEvent({event_name: 'selected-components-tab'}); + } + }); + extensionPanel.onHidden.addListener(panel => { + // TODO: Stop highlighting and stuff. + }); + }, + ); + + chrome.devtools.panels.create( + isChrome ? '⚛️ Profiler' : 'Profiler', + '', + 'panel.html', + extensionPanel => { + extensionPanel.onShown.addListener(panel => { + if (currentPanel === panel) { + return; + } - if (profilerPortalContainer != null) { - ensureInitialHTMLIsCleared(profilerPortalContainer); - render('profiler'); - panel.injectStyles(cloneStyleTags); - logEvent({event_name: 'selected-profiler-tab'}); - } - }); - }, - ); + currentPanel = panel; + profilerPortalContainer = panel.container; + + if (profilerPortalContainer != null) { + ensureInitialHTMLIsCleared(profilerPortalContainer); + render('profiler'); + panel.injectStyles(cloneStyleTags); + logEvent({event_name: 'selected-profiler-tab'}); + } + }); + }, + ); - chrome.devtools.network.onNavigated.removeListener(checkPageForReact); + chrome.devtools.network.onNavigated.removeListener(checkPageForReact); - // Re-initialize DevTools panel when a new page is loaded. - chrome.devtools.network.onNavigated.addListener(function onNavigated() { - // Re-initialize saved filters on navigation, - // since global values stored on window get reset in this case. - syncSavedPreferences(); + // Re-initialize DevTools panel when a new page is loaded. + chrome.devtools.network.onNavigated.addListener(function onNavigated() { + // Re-initialize saved filters on navigation, + // since global values stored on window get reset in this case. + syncSavedPreferences(); - // It's easiest to recreate the DevTools panel (to clean up potential stale state). - // We can revisit this in the future as a small optimization. - flushSync(() => root.unmount()); + // It's easiest to recreate the DevTools panel (to clean up potential stale state). + // We can revisit this in the future as a small optimization. + flushSync(() => root.unmount()); - initBridgeAndStore(); - }); + initBridgeAndStore(); }); }, ); diff --git a/packages/react-devtools-shared/src/devtools/views/DevTools.js b/packages/react-devtools-shared/src/devtools/views/DevTools.js index 8f1b6a22c11bc..781ad9d902f68 100644 --- a/packages/react-devtools-shared/src/devtools/views/DevTools.js +++ b/packages/react-devtools-shared/src/devtools/views/DevTools.js @@ -34,7 +34,6 @@ import {SchedulingProfilerContextController} from 'react-devtools-scheduling-pro import {ModalDialogContextController} from './ModalDialog'; import ReactLogo from './ReactLogo'; import UnsupportedBridgeProtocolDialog from './UnsupportedBridgeProtocolDialog'; -import DuplicateInstallationDialog from './DuplicateInstallationDialog'; import UnsupportedVersionDialog from './UnsupportedVersionDialog'; import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected'; import {useLocalStorage} from './hooks'; @@ -74,7 +73,6 @@ export type Props = {| enabledInspectedElementContextMenu?: boolean, showTabBar?: boolean, store: Store, - warnIfDuplicateInstallation?: boolean, warnIfLegacyBackendDetected?: boolean, warnIfUnsupportedVersionDetected?: boolean, viewAttributeSourceFunction?: ?ViewAttributeSource, @@ -134,7 +132,6 @@ export default function DevTools({ profilerPortalContainer, showTabBar = false, store, - warnIfDuplicateInstallation = false, warnIfLegacyBackendDetected = false, warnIfUnsupportedVersionDetected = false, viewAttributeSourceFunction, @@ -322,7 +319,6 @@ export default function DevTools({ - {warnIfDuplicateInstallation && } {warnIfLegacyBackendDetected && } {warnIfUnsupportedVersionDetected && } diff --git a/packages/react-devtools-shared/src/devtools/views/DuplicateInstallationDialog.js b/packages/react-devtools-shared/src/devtools/views/DuplicateInstallationDialog.js deleted file mode 100644 index 12a32ce4b7f43..0000000000000 --- a/packages/react-devtools-shared/src/devtools/views/DuplicateInstallationDialog.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - */ - -import * as React from 'react'; -import {Fragment, useContext, useEffect} from 'react'; -import {isInternalFacebookBuild} from 'react-devtools-feature-flags'; -import {ModalDialogContext} from './ModalDialog'; - -export default function DuplicateInstallationDialog(_: {||}) { - const {dispatch} = useContext(ModalDialogContext); - - useEffect(() => { - dispatch({ - canBeDismissed: false, - id: 'DuplicateInstallationDialog', - type: 'SHOW', - title: 'Duplicate Installations of DevTools Detected', - content: , - }); - }, []); - return null; -} - -function DialogContent(_: {||}) { - return ( - -

- We detected that there are multiple versions of React Developer Tools - installed and enabled in your browser at the same time, which will cause - issues while using the extension. -

- {isInternalFacebookBuild ? ( -

- Before proceeding, please ensure that the only enabled version of - React Developer Tools is the internal (Chef-installed) version. To - manage your extensions, visit the about://extensions page - in your browser. -

- ) : ( -

- Please ensure that you have installed and enabled only a single - version of React Developer Tools before proceeding. To manage your - extensions, visit the about://extensions page in your - browser. -

- )} -
- ); -}