mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Revert logic for checking for duplicate installations of DevTools (#22638)
* Revert "Only show DevTools warning about unrecognized build in Chrome (#22571)" This reverts commitb72dc8e930. * Revert "Show warning in UI when duplicate installations of DevTools extension are detected (#22563)" This reverts commit930c9e7eeb. * Revert "Prevent errors/crashing when multiple installs of DevTools are present (#22517)" This reverts commit545d4c2de7. * Remove all references to passing extensionId in postMessage * Keep build changes * lint
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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:<browser>: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<boolean[]> {
|
||||
return Promise.all(
|
||||
extensionIds.map(extensionId => checkForInstalledExtension(extensionId)),
|
||||
);
|
||||
}
|
||||
|
||||
function checkForInstalledExtension(extensionId: string): Promise<boolean> {
|
||||
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);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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,
|
||||
},
|
||||
'*',
|
||||
);
|
||||
|
||||
@@ -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}",
|
||||
}, '*');
|
||||
});
|
||||
`;
|
||||
|
||||
812
packages/react-devtools-extensions/src/main.js
vendored
812
packages/react-devtools-extensions/src/main.js
vendored
@@ -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;
|
||||
|
||||
clearInterval(loadCheckInterval);
|
||||
|
||||
let bridge = null;
|
||||
let store = null;
|
||||
|
||||
let profilingData = null;
|
||||
|
||||
let componentsPortalContainer = null;
|
||||
let profilerPortalContainer = null;
|
||||
|
||||
let cloneStyleTags = null;
|
||||
let mostRecentOverrideTab = null;
|
||||
let render = null;
|
||||
let root = null;
|
||||
|
||||
const tabId = chrome.devtools.inspectedWindow.tabId;
|
||||
|
||||
registerDevToolsEventLogger('extension');
|
||||
|
||||
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<any>) {
|
||||
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 (__DEBUG__) {
|
||||
console.log(
|
||||
'[main] createPanelIfReactLoaded: No duplicate installations detected, continuing with initialization.',
|
||||
{currentExtension: EXTENSION_INSTALLATION_TYPE},
|
||||
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',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
panelCreated = 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;
|
||||
|
||||
clearInterval(loadCheckInterval);
|
||||
|
||||
let bridge = null;
|
||||
let store = null;
|
||||
|
||||
let profilingData = null;
|
||||
|
||||
let componentsPortalContainer = null;
|
||||
let profilerPortalContainer = null;
|
||||
|
||||
let cloneStyleTags = null;
|
||||
let mostRecentOverrideTab = null;
|
||||
let render = null;
|
||||
let root = null;
|
||||
let warnIfDuplicateInstallation = false;
|
||||
|
||||
const tabId = chrome.devtools.inspectedWindow.tabId;
|
||||
|
||||
registerDevToolsEventLogger('extension');
|
||||
|
||||
function onDuplicateExtensionMessage(message) {
|
||||
if (message === SHOW_DUPLICATE_EXTENSION_WARNING) {
|
||||
chrome.runtime.onMessage.removeListener(
|
||||
onDuplicateExtensionMessage,
|
||||
);
|
||||
|
||||
if (warnIfDuplicateInstallation === true) {
|
||||
return;
|
||||
// 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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
|
||||
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});
|
||||
|
||||
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<any>) {
|
||||
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;
|
||||
}
|
||||
|
||||
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 || 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});
|
||||
|
||||
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});
|
||||
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(`
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.log(
|
||||
`[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`,
|
||||
);
|
||||
}
|
||||
|
||||
// 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.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);
|
||||
});
|
||||
};
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
initBridgeAndStore();
|
||||
let debugIDCounter = 0;
|
||||
|
||||
function ensureInitialHTMLIsCleared(container) {
|
||||
if (container._hasInitialHTMLBeenCleared) {
|
||||
return;
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEBUG__) {
|
||||
console.log(
|
||||
`[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`,
|
||||
);
|
||||
}
|
||||
|
||||
// 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.devtools.inspectedWindow.eval(`
|
||||
window.postMessage({
|
||||
source: 'react-devtools-extension',
|
||||
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);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 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,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
render();
|
||||
}
|
||||
|
||||
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 ensureInitialHTMLIsCleared(container) {
|
||||
if (container._hasInitialHTMLBeenCleared) {
|
||||
return;
|
||||
}
|
||||
container.innerHTML = '';
|
||||
container._hasInitialHTMLBeenCleared = true;
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setReactSelectionFromBrowser();
|
||||
chrome.devtools.panels.elements.onSelectionChanged.addListener(() => {
|
||||
setReactSelectionFromBrowser();
|
||||
chrome.devtools.panels.elements.onSelectionChanged.addListener(() => {
|
||||
setReactSelectionFromBrowser();
|
||||
});
|
||||
});
|
||||
|
||||
let currentPanel = null;
|
||||
let needsToSyncElementSelection = false;
|
||||
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');
|
||||
}
|
||||
chrome.devtools.panels.create(
|
||||
isChrome ? '⚛️ Components' : 'Components',
|
||||
'',
|
||||
'panel.html',
|
||||
extensionPanel => {
|
||||
extensionPanel.onShown.addListener(panel => {
|
||||
if (needsToSyncElementSelection) {
|
||||
needsToSyncElementSelection = false;
|
||||
bridge.send('syncSelectionFromNativeElementsPanel');
|
||||
}
|
||||
|
||||
if (currentPanel === panel) {
|
||||
return;
|
||||
}
|
||||
if (currentPanel === panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentPanel = panel;
|
||||
componentsPortalContainer = panel.container;
|
||||
currentPanel = panel;
|
||||
componentsPortalContainer = 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.
|
||||
});
|
||||
},
|
||||
);
|
||||
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;
|
||||
}
|
||||
chrome.devtools.panels.create(
|
||||
isChrome ? '⚛️ Profiler' : 'Profiler',
|
||||
'',
|
||||
'panel.html',
|
||||
extensionPanel => {
|
||||
extensionPanel.onShown.addListener(panel => {
|
||||
if (currentPanel === panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentPanel = panel;
|
||||
profilerPortalContainer = panel.container;
|
||||
currentPanel = panel;
|
||||
profilerPortalContainer = panel.container;
|
||||
|
||||
if (profilerPortalContainer != null) {
|
||||
ensureInitialHTMLIsCleared(profilerPortalContainer);
|
||||
render('profiler');
|
||||
panel.injectStyles(cloneStyleTags);
|
||||
logEvent({event_name: 'selected-profiler-tab'});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
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();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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({
|
||||
</ViewElementSourceContext.Provider>
|
||||
</SettingsContextController>
|
||||
<UnsupportedBridgeProtocolDialog />
|
||||
{warnIfDuplicateInstallation && <DuplicateInstallationDialog />}
|
||||
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
|
||||
{warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
|
||||
</ModalDialogContextController>
|
||||
|
||||
@@ -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: <DialogContent />,
|
||||
});
|
||||
}, []);
|
||||
return null;
|
||||
}
|
||||
|
||||
function DialogContent(_: {||}) {
|
||||
return (
|
||||
<Fragment>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
{isInternalFacebookBuild ? (
|
||||
<p>
|
||||
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 <code>about://extensions</code> page
|
||||
in your browser.
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
Please ensure that you have installed and enabled only a single
|
||||
version of React Developer Tools before proceeding. To manage your
|
||||
extensions, visit the <code>about://extensions</code> page in your
|
||||
browser.
|
||||
</p>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user