@samitouri / QOS-React-2 / commits / 26bc8ff9bf

Revert logic for checking for duplicate installations of DevTools (#22638)

* Revert "Only show DevTools warning about unrecognized build in Chrome (#22571)" This reverts commit b72dc8e9300f5ae997f7f5cfcd79b604cca3df0c. * Revert "Show warning in UI when duplicate installations of DevTools extension are detected (#22563)" This reverts commit 930c9e7eeb4c9721e1b8dee074c2eef4d1eae5dc. * Revert "Prevent errors/crashing when multiple installs of DevTools are present (#22517)" This reverts commit 545d4c2de7934a43b0c5d3ce050d77b4c3113bd3. * Remove all references to passing extensionId in postMessage * Keep build changes * lint

Juan committed Oct 27, 2021 at 16:34 UTC 26bc8ff9bfcdb67e255fea424b56909899ac208b
8 files changed +352 -688
packages/react-devtools-extensions/src/background.js
+2 -20
@@ -1,20 +1,11 @@
1 -// @flow strict-local
1 +/* global chrome */
2
3 'use strict';
4
5 -declare var chrome: any;
6 -
7 -const ports: {
8 - [tab: string]: {|devtools: any, 'content-script': any|},
9 -} = {};
5 +const ports = {};
6
7 const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0;
8
13 -import {
14 - EXTENSION_INSTALL_CHECK,
15 - SHOW_DUPLICATE_EXTENSION_WARNING,
16 -} from './constants';
17 -
9 chrome.runtime.onConnect.addListener(function(port) {
10 let tab = null;
11 let name = null;
@@ -125,15 +116,6 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
116 }
117 });
118
128 -chrome.runtime.onMessageExternal.addListener(
129 - (request, sender, sendResponse) => {
130 - if (request === EXTENSION_INSTALL_CHECK) {
131 - sendResponse(true);
132 - chrome.runtime.sendMessage(SHOW_DUPLICATE_EXTENSION_WARNING);
133 - }
134 - },
135 -);
136 -
119 chrome.runtime.onMessage.addListener((request, sender) => {
120 const tab = sender.tab;
121 if (tab) {
packages/react-devtools-extensions/src/checkForDuplicateInstallations.js deleted
-137
@@ -1,137 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow strict-local
8 - */
9 -
10 -declare var chrome: any;
11 -
12 -import {
13 - INTERNAL_EXTENSION_ID,
14 - LOCAL_EXTENSION_ID,
15 - __DEBUG__,
16 -} from 'react-devtools-shared/src/constants';
17 -import {getBrowserName} from './utils';
18 -import {
19 - EXTENSION_INSTALL_CHECK,
20 - EXTENSION_INSTALLATION_TYPE,
21 -} from './constants';
22 -
23 -const IS_CHROME = getBrowserName() === 'Chrome';
24 -
25 -const UNRECOGNIZED_EXTENSION_ERROR =
26 - '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. ' +
27 - 'Please make sure you only have a single version of the extension installed or enabled. ' +
28 - 'If you are developing this extension locally, make sure to build the extension using the `yarn build:<browser>:local` command.';
29 -
30 -export function checkForDuplicateInstallations(callback: boolean => void) {
31 - switch (EXTENSION_INSTALLATION_TYPE) {
32 - case 'public': {
33 - // If this is the public extension (e.g. from Chrome Web Store), check if an internal
34 - // or local build of the extension is also installed, and if so, disable this extension.
35 - // TODO show warning if other installations are present.
36 - checkForInstalledExtensions([
37 - INTERNAL_EXTENSION_ID,
38 - LOCAL_EXTENSION_ID,
39 - ]).then(areExtensionsInstalled => {
40 - if (areExtensionsInstalled.some(isInstalled => isInstalled)) {
41 - callback(true);
42 - } else {
43 - callback(false);
44 - }
45 - });
46 - break;
47 - }
48 - case 'internal': {
49 - // If this is the internal extension, check if a local build of the extension
50 - // is also installed, and if so, disable this extension.
51 - // If the public version of the extension is also installed, that extension
52 - // will disable itself.
53 - // TODO show warning if other installations are present.
54 - checkForInstalledExtension(LOCAL_EXTENSION_ID).then(isInstalled => {
55 - if (isInstalled) {
56 - callback(true);
57 - } else {
58 - callback(false);
59 - }
60 - });
61 - break;
62 - }
63 - case 'local': {
64 - if (__DEV__) {
65 - // If this is the local extension (i.e. built locally during development),
66 - // always keep this one enabled. Other installations disable themselves if
67 - // they detect the local build is installed.
68 - callback(false);
69 - break;
70 - }
71 -
72 - // If this extension wasn't built locally during development, we can't reliably
73 - // detect if there are other installations of DevTools present.
74 - // In this case, assume there are no duplicate exensions and show a warning about
75 - // potential conflicts.
76 - console.error(UNRECOGNIZED_EXTENSION_ERROR);
77 - chrome.devtools.inspectedWindow.eval(
78 - `console.error("${UNRECOGNIZED_EXTENSION_ERROR}")`,
79 - );
80 - callback(false);
81 - break;
82 - }
83 - case 'unknown': {
84 - // TODO: Support duplicate extension detection in other browsers
85 - if (IS_CHROME) {
86 - // If we don't know how this extension was built, we can't reliably detect if there
87 - // are other installations of DevTools present.
88 - // In this case, assume there are no duplicate exensions and show a warning about
89 - // potential conflicts.
90 - console.error(UNRECOGNIZED_EXTENSION_ERROR);
91 - chrome.devtools.inspectedWindow.eval(
92 - `console.error("${UNRECOGNIZED_EXTENSION_ERROR}")`,
93 - );
94 - }
95 - callback(false);
96 - break;
97 - }
98 - default: {
99 - (EXTENSION_INSTALLATION_TYPE: empty);
100 - }
101 - }
102 -}
103 -
104 -function checkForInstalledExtensions(
105 - extensionIds: string[],
106 -): Promise<boolean[]> {
107 - return Promise.all(
108 - extensionIds.map(extensionId => checkForInstalledExtension(extensionId)),
109 - );
110 -}
111 -
112 -function checkForInstalledExtension(extensionId: string): Promise<boolean> {
113 - return new Promise(resolve => {
114 - chrome.runtime.sendMessage(
115 - extensionId,
116 - EXTENSION_INSTALL_CHECK,
117 - response => {
118 - if (__DEBUG__) {
119 - console.log(
120 - 'checkForDuplicateInstallations: Duplicate installation check responded with',
121 - {
122 - response,
123 - error: chrome.runtime.lastError?.message,
124 - currentExtension: EXTENSION_INSTALLATION_TYPE,
125 - checkingExtension: extensionId,
126 - },
127 - );
128 - }
129 - if (chrome.runtime.lastError != null) {
130 - resolve(false);
131 - } else {
132 - resolve(true);
133 - }
134 - },
135 - );
136 - });
137 -}
packages/react-devtools-extensions/src/constants.js deleted
-35
@@ -1,35 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow strict-local
8 - */
9 -
10 -import {
11 - CHROME_WEBSTORE_EXTENSION_ID,
12 - INTERNAL_EXTENSION_ID,
13 - LOCAL_EXTENSION_ID,
14 -} from 'react-devtools-shared/src/constants';
15 -
16 -declare var chrome: any;
17 -
18 -export const CURRENT_EXTENSION_ID = chrome.runtime.id;
19 -
20 -export const EXTENSION_INSTALL_CHECK = 'extension-install-check';
21 -export const SHOW_DUPLICATE_EXTENSION_WARNING =
22 - 'show-duplicate-extension-warning';
23 -
24 -export const EXTENSION_INSTALLATION_TYPE:
25 - | 'public'
26 - | 'internal'
27 - | 'local'
28 - | 'unknown' =
29 - CURRENT_EXTENSION_ID === CHROME_WEBSTORE_EXTENSION_ID
30 - ? 'public'
31 - : CURRENT_EXTENSION_ID === INTERNAL_EXTENSION_ID
32 - ? 'internal'
33 - : CURRENT_EXTENSION_ID === LOCAL_EXTENSION_ID
34 - ? 'local'
35 - : 'unknown';
packages/react-devtools-extensions/src/contentScript.js
-5
@@ -2,8 +2,6 @@
2
3 'use strict';
4
5 -import {CURRENT_EXTENSION_ID} from './constants';
6 -
5 let backendDisconnected: boolean = false;
6 let backendInitialized: boolean = false;
7
@@ -12,7 +10,6 @@ function sayHelloToBackend() {
10 {
11 source: 'react-devtools-content-script',
12 hello: true,
15 - extensionId: CURRENT_EXTENSION_ID,
13 },
14 '*',
15 );
@@ -23,7 +20,6 @@ function handleMessageFromDevtools(message) {
20 {
21 source: 'react-devtools-content-script',
22 payload: message,
26 - extensionId: CURRENT_EXTENSION_ID,
23 },
24 '*',
25 );
@@ -53,7 +49,6 @@ function handleDisconnect() {
49 type: 'event',
50 event: 'shutdown',
51 },
56 - extensionId: CURRENT_EXTENSION_ID,
52 },
53 '*',
54 );
packages/react-devtools-extensions/src/injectGlobalHook.js
+1 -19
@@ -2,11 +2,7 @@
2
3 import nullthrows from 'nullthrows';
4 import {installHook} from 'react-devtools-shared/src/hook';
5 -import {
6 - __DEBUG__,
7 - SESSION_STORAGE_RELOAD_AND_PROFILE_KEY,
8 -} from 'react-devtools-shared/src/constants';
9 -import {CURRENT_EXTENSION_ID, EXTENSION_INSTALLATION_TYPE} from './constants';
5 +import {SESSION_STORAGE_RELOAD_AND_PROFILE_KEY} from 'react-devtools-shared/src/constants';
6 import {sessionStorageGetItem} from 'react-devtools-shared/src/storage';
7
8 function injectCode(code) {
@@ -31,19 +27,6 @@ window.addEventListener('message', function onMessage({data, source}) {
27 if (source !== window || !data) {
28 return;
29 }
34 - if (data.extensionId != null && data.extensionId !== CURRENT_EXTENSION_ID) {
35 - if (__DEBUG__) {
36 - console.log(
37 - `[injectGlobalHook] Received message '${data.source}' from different extension instance. Skipping message.`,
38 - {
39 - currentExtension: EXTENSION_INSTALLATION_TYPE,
40 - currentExtensionId: CURRENT_EXTENSION_ID,
41 - providedExtensionId: data.extensionId,
42 - },
43 - );
44 - }
45 - return;
46 - }
30 switch (data.source) {
31 case 'react-devtools-detector':
32 lastDetectionResult = {
@@ -118,7 +101,6 @@ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function({reactBuildType})
101 window.postMessage({
102 source: 'react-devtools-detector',
103 reactBuildType,
121 - extensionId: "${CURRENT_EXTENSION_ID}",
104 }, '*');
105 });
106 `;
packages/react-devtools-extensions/src/main.js
+349 -413
@@ -22,18 +22,11 @@ import {
22 import DevTools from 'react-devtools-shared/src/devtools/views/DevTools';
23 import {__DEBUG__} from 'react-devtools-shared/src/constants';
24 import {logEvent} from 'react-devtools-shared/src/Logger';
25 -import {
26 - CURRENT_EXTENSION_ID,
27 - EXTENSION_INSTALLATION_TYPE,
28 - SHOW_DUPLICATE_EXTENSION_WARNING,
29 -} from './constants';
30 -import {checkForDuplicateInstallations} from './checkForDuplicateInstallations';
25
26 const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY =
27 'React::DevTools::supportsProfiling';
28
29 const isChrome = getBrowserName() === 'Chrome';
36 -const isEdge = getBrowserName() === 'Edge';
30
31 let panelCreated = false;
32
@@ -77,186 +70,135 @@ function createPanelIfReactLoaded() {
70 return;
71 }
72
80 - checkForDuplicateInstallations(hasDuplicateInstallation => {
81 - if (hasDuplicateInstallation) {
82 - if (__DEBUG__) {
83 - console.log(
84 - '[main] createPanelIfReactLoaded: Duplicate installation detected, skipping initialization of extension.',
85 - {currentExtension: EXTENSION_INSTALLATION_TYPE},
86 - );
87 - }
88 - panelCreated = true;
89 - clearInterval(loadCheckInterval);
90 - return;
91 - }
73 + panelCreated = true;
74
93 - if (__DEBUG__) {
94 - console.log(
95 - '[main] createPanelIfReactLoaded: No duplicate installations detected, continuing with initialization.',
96 - {currentExtension: EXTENSION_INSTALLATION_TYPE},
97 - );
98 - }
99 -
100 - panelCreated = true;
75 + clearInterval(loadCheckInterval);
76
102 - clearInterval(loadCheckInterval);
77 + let bridge = null;
78 + let store = null;
79
104 - let bridge = null;
105 - let store = null;
80 + let profilingData = null;
81
107 - let profilingData = null;
82 + let componentsPortalContainer = null;
83 + let profilerPortalContainer = null;
84
109 - let componentsPortalContainer = null;
110 - let profilerPortalContainer = null;
85 + let cloneStyleTags = null;
86 + let mostRecentOverrideTab = null;
87 + let render = null;
88 + let root = null;
89
112 - let cloneStyleTags = null;
113 - let mostRecentOverrideTab = null;
114 - let render = null;
115 - let root = null;
116 - let warnIfDuplicateInstallation = false;
90 + const tabId = chrome.devtools.inspectedWindow.tabId;
91
118 - const tabId = chrome.devtools.inspectedWindow.tabId;
92 + registerDevToolsEventLogger('extension');
93
120 - registerDevToolsEventLogger('extension');
121 -
122 - function onDuplicateExtensionMessage(message) {
123 - if (message === SHOW_DUPLICATE_EXTENSION_WARNING) {
124 - chrome.runtime.onMessage.removeListener(
125 - onDuplicateExtensionMessage,
126 - );
94 + function initBridgeAndStore() {
95 + const port = chrome.runtime.connect({
96 + name: String(tabId),
97 + });
98 + // Looks like `port.onDisconnect` does not trigger on in-tab navigation like new URL or back/forward navigation,
99 + // so it makes no sense to handle it here.
100 +
101 + bridge = new Bridge({
102 + listen(fn) {
103 + const listener = message => fn(message);
104 + // Store the reference so that we unsubscribe from the same object.
105 + const portOnMessage = port.onMessage;
106 + portOnMessage.addListener(listener);
107 + return () => {
108 + portOnMessage.removeListener(listener);
109 + };
110 + },
111 + send(event: string, payload: any, transferable?: Array<any>) {
112 + port.postMessage({event, payload}, transferable);
113 + },
114 + });
115 + bridge.addListener('reloadAppForProfiling', () => {
116 + localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true');
117 + chrome.devtools.inspectedWindow.eval('window.location.reload();');
118 + });
119 + bridge.addListener('syncSelectionToNativeElementsPanel', () => {
120 + setBrowserSelectionFromReact();
121 + });
122
128 - if (warnIfDuplicateInstallation === true) {
129 - return;
130 - }
131 - warnIfDuplicateInstallation = true;
132 - const errorMessage =
133 - 'React Developer Tools: We detected that there are multiple versions of React Developer Tools ' +
134 - 'installed and enabled in your browser at the same time, which will cause ' +
135 - 'issues while using the extension. ' +
136 - 'Please ensure that you have installed and enabled only a single ' +
137 - 'version of React Developer Tools before proceeding.';
138 - console.error(errorMessage);
139 - chrome.devtools.inspectedWindow.eval(
140 - `console.error("${errorMessage}")`,
141 - );
142 - if (render != null) {
143 - render();
144 - }
145 - }
123 + // This flag lets us tip the Store off early that we expect to be profiling.
124 + // This avoids flashing a temporary "Profiling not supported" message in the Profiler tab,
125 + // after a user has clicked the "reload and profile" button.
126 + let isProfiling = false;
127 + let supportsProfiling = false;
128 + if (
129 + localStorageGetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true'
130 + ) {
131 + supportsProfiling = true;
132 + isProfiling = true;
133 + localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
134 }
147 - chrome.runtime.onMessage.addListener(onDuplicateExtensionMessage);
148 -
149 - function initBridgeAndStore() {
150 - const port = chrome.runtime.connect({
151 - name: String(tabId),
152 - });
153 - // Looks like `port.onDisconnect` does not trigger on in-tab navigation like new URL or back/forward navigation,
154 - // so it makes no sense to handle it here.
155 -
156 - bridge = new Bridge({
157 - listen(fn) {
158 - const listener = message => fn(message);
159 - // Store the reference so that we unsubscribe from the same object.
160 - const portOnMessage = port.onMessage;
161 - portOnMessage.addListener(listener);
162 - return () => {
163 - portOnMessage.removeListener(listener);
164 - };
165 - },
166 - send(event: string, payload: any, transferable?: Array<any>) {
167 - port.postMessage({event, payload}, transferable);
168 - },
169 - });
170 - bridge.addListener('reloadAppForProfiling', () => {
171 - localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true');
172 - chrome.devtools.inspectedWindow.eval('window.location.reload();');
173 - });
174 - bridge.addListener('syncSelectionToNativeElementsPanel', () => {
175 - setBrowserSelectionFromReact();
176 - });
135
178 - // This flag lets us tip the Store off early that we expect to be profiling.
179 - // This avoids flashing a temporary "Profiling not supported" message in the Profiler tab,
180 - // after a user has clicked the "reload and profile" button.
181 - let isProfiling = false;
182 - let supportsProfiling = false;
183 - if (
184 - localStorageGetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true'
185 - ) {
186 - supportsProfiling = true;
187 - isProfiling = true;
188 - localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
189 - }
136 + if (store !== null) {
137 + profilingData = store.profilerStore.profilingData;
138 + }
139
191 - if (store !== null) {
192 - profilingData = store.profilerStore.profilingData;
193 - }
140 + bridge.addListener('extensionBackendInitialized', () => {
141 + // Initialize the renderer's trace-updates setting.
142 + // This handles the case of navigating to a new page after the DevTools have already been shown.
143 + bridge.send(
144 + 'setTraceUpdatesEnabled',
145 + localStorageGetItem(LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY) ===
146 + 'true',
147 + );
148 + });
149
195 - bridge.addListener('extensionBackendInitialized', () => {
196 - // Initialize the renderer's trace-updates setting.
197 - // This handles the case of navigating to a new page after the DevTools have already been shown.
198 - bridge.send(
199 - 'setTraceUpdatesEnabled',
200 - localStorageGetItem(LOCAL_STORAGE_TRACE_UPDATES_ENABLED_KEY) ===
201 - 'true',
202 - );
203 - });
150 + store = new Store(bridge, {
151 + isProfiling,
152 + supportsReloadAndProfile: isChrome,
153 + supportsProfiling,
154 + // At this time, the scheduling profiler can only parse Chrome performance profiles.
155 + supportsSchedulingProfiler: isChrome,
156 + supportsTraceUpdates: true,
157 + });
158 + store.profilerStore.profilingData = profilingData;
159 +
160 + // Initialize the backend only once the Store has been initialized.
161 + // Otherwise the Store may miss important initial tree op codes.
162 + chrome.devtools.inspectedWindow.eval(
163 + `window.postMessage({ source: 'react-devtools-inject-backend' }, '*');`,
164 + function(response, evalError) {
165 + if (evalError) {
166 + console.error(evalError);
167 + }
168 + },
169 + );
170
205 - store = new Store(bridge, {
206 - isProfiling,
207 - supportsReloadAndProfile: isChrome || isEdge,
208 - supportsProfiling,
209 - // At this time, the scheduling profiler can only parse Chrome performance profiles.
210 - supportsSchedulingProfiler: isChrome,
211 - supportsTraceUpdates: true,
212 - });
213 - store.profilerStore.profilingData = profilingData;
214 -
215 - // Initialize the backend only once the Store has been initialized.
216 - // Otherwise the Store may miss important initial tree op codes.
217 - chrome.devtools.inspectedWindow.eval(
218 - `window.postMessage({
219 - source: 'react-devtools-inject-backend',
220 - extensionId: "${CURRENT_EXTENSION_ID}",
221 - }, '*');`,
222 - function(response, evalError) {
223 - if (evalError) {
224 - console.error(evalError);
225 - }
226 - },
227 - );
171 + const viewAttributeSourceFunction = (id, path) => {
172 + const rendererID = store.getRendererIDForElement(id);
173 + if (rendererID != null) {
174 + // Ask the renderer interface to find the specified attribute,
175 + // and store it as a global variable on the window.
176 + bridge.send('viewAttributeSource', {id, path, rendererID});
177
229 - const viewAttributeSourceFunction = (id, path) => {
230 - const rendererID = store.getRendererIDForElement(id);
231 - if (rendererID != null) {
232 - // Ask the renderer interface to find the specified attribute,
233 - // and store it as a global variable on the window.
234 - bridge.send('viewAttributeSource', {id, path, rendererID});
235 -
236 - setTimeout(() => {
237 - // Ask Chrome to display the location of the attribute,
238 - // assuming the renderer found a match.
239 - chrome.devtools.inspectedWindow.eval(`
178 + setTimeout(() => {
179 + // Ask Chrome to display the location of the attribute,
180 + // assuming the renderer found a match.
181 + chrome.devtools.inspectedWindow.eval(`
182 if (window.$attribute != null) {
183 inspect(window.$attribute);
184 }
185 `);
244 - }, 100);
245 - }
246 - };
186 + }, 100);
187 + }
188 + };
189
248 - const viewElementSourceFunction = id => {
249 - const rendererID = store.getRendererIDForElement(id);
250 - if (rendererID != null) {
251 - // Ask the renderer interface to determine the component function,
252 - // and store it as a global variable on the window
253 - bridge.send('viewElementSource', {id, rendererID});
254 -
255 - setTimeout(() => {
256 - // Ask Chrome to display the location of the component function,
257 - // or a render method if it is a Class (ideally Class instance, not type)
258 - // assuming the renderer found one.
259 - chrome.devtools.inspectedWindow.eval(`
190 + const viewElementSourceFunction = id => {
191 + const rendererID = store.getRendererIDForElement(id);
192 + if (rendererID != null) {
193 + // Ask the renderer interface to determine the component function,
194 + // and store it as a global variable on the window
195 + bridge.send('viewElementSource', {id, rendererID});
196 +
197 + setTimeout(() => {
198 + // Ask Chrome to display the location of the component function,
199 + // or a render method if it is a Class (ideally Class instance, not type)
200 + // assuming the renderer found one.
201 + chrome.devtools.inspectedWindow.eval(`
202 if (window.$type != null) {
203 if (
204 window.$type &&
@@ -271,294 +213,288 @@ function createPanelIfReactLoaded() {
213 }
214 }
215 `);
274 - }, 100);
275 - }
276 - };
216 + }, 100);
217 + }
218 + };
219
278 - let debugIDCounter = 0;
220 + let debugIDCounter = 0;
221
280 - // For some reason in Firefox, chrome.runtime.sendMessage() from a content script
281 - // never reaches the chrome.runtime.onMessage event listener.
282 - let fetchFileWithCaching = null;
283 - if (isChrome) {
284 - const fetchFromNetworkCache = (url, resolve, reject) => {
285 - // Debug ID allows us to avoid re-logging (potentially long) URL strings below,
286 - // while also still associating (potentially) interleaved logs with the original request.
287 - let debugID = null;
222 + // For some reason in Firefox, chrome.runtime.sendMessage() from a content script
223 + // never reaches the chrome.runtime.onMessage event listener.
224 + let fetchFileWithCaching = null;
225 + if (isChrome) {
226 + const fetchFromNetworkCache = (url, resolve, reject) => {
227 + // Debug ID allows us to avoid re-logging (potentially long) URL strings below,
228 + // while also still associating (potentially) interleaved logs with the original request.
229 + let debugID = null;
230
289 - if (__DEBUG__) {
290 - debugID = debugIDCounter++;
291 - console.log(`[main] fetchFromNetworkCache(${debugID})`, url);
292 - }
231 + if (__DEBUG__) {
232 + debugID = debugIDCounter++;
233 + console.log(`[main] fetchFromNetworkCache(${debugID})`, url);
234 + }
235
294 - chrome.devtools.network.getHAR(harLog => {
295 - for (let i = 0; i < harLog.entries.length; i++) {
296 - const entry = harLog.entries[i];
297 - if (url === entry.request.url) {
298 - if (__DEBUG__) {
299 - console.log(
300 - `[main] fetchFromNetworkCache(${debugID}) Found matching URL in HAR`,
301 - url,
302 - );
303 - }
236 + chrome.devtools.network.getHAR(harLog => {
237 + for (let i = 0; i < harLog.entries.length; i++) {
238 + const entry = harLog.entries[i];
239 + if (url === entry.request.url) {
240 + if (__DEBUG__) {
241 + console.log(
242 + `[main] fetchFromNetworkCache(${debugID}) Found matching URL in HAR`,
243 + url,
244 + );
245 + }
246
305 - entry.getContent(content => {
306 - if (content) {
307 - if (__DEBUG__) {
308 - console.log(
309 - `[main] fetchFromNetworkCache(${debugID}) Content retrieved`,
310 - );
311 - }
312 -
313 - resolve(content);
314 - } else {
315 - if (__DEBUG__) {
316 - console.log(
317 - `[main] fetchFromNetworkCache(${debugID}) Invalid content returned by getContent()`,
318 - content,
319 - );
320 - }
321 -
322 - // Edge case where getContent() returned null; fall back to fetch.
323 - fetchFromPage(url, resolve, reject);
247 + entry.getContent(content => {
248 + if (content) {
249 + if (__DEBUG__) {
250 + console.log(
251 + `[main] fetchFromNetworkCache(${debugID}) Content retrieved`,
252 + );
253 }
325 - });
254
327 - return;
328 - }
329 - }
255 + resolve(content);
256 + } else {
257 + if (__DEBUG__) {
258 + console.log(
259 + `[main] fetchFromNetworkCache(${debugID}) Invalid content returned by getContent()`,
260 + content,
261 + );
262 + }
263
331 - if (__DEBUG__) {
332 - console.log(
333 - `[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`,
334 - );
335 - }
264 + // Edge case where getContent() returned null; fall back to fetch.
265 + fetchFromPage(url, resolve, reject);
266 + }
267 + });
268
337 - // No matching URL found; fall back to fetch.
338 - fetchFromPage(url, resolve, reject);
339 - });
340 - };
269 + return;
270 + }
271 + }
272
342 - const fetchFromPage = (url, resolve, reject) => {
273 if (__DEBUG__) {
344 - console.log('[main] fetchFromPage()', url);
274 + console.log(
275 + `[main] fetchFromNetworkCache(${debugID}) No cached request found in getHAR()`,
276 + );
277 }
278
347 - function onPortMessage({payload, source}) {
348 - if (source === 'react-devtools-content-script') {
349 - switch (payload?.type) {
350 - case 'fetch-file-with-cache-complete':
351 - chrome.runtime.onMessage.removeListener(onPortMessage);
352 - resolve(payload.value);
353 - break;
354 - case 'fetch-file-with-cache-error':
355 - chrome.runtime.onMessage.removeListener(onPortMessage);
356 - reject(payload.value);
357 - break;
358 - }
279 + // No matching URL found; fall back to fetch.
280 + fetchFromPage(url, resolve, reject);
281 + });
282 + };
283 +
284 + const fetchFromPage = (url, resolve, reject) => {
285 + if (__DEBUG__) {
286 + console.log('[main] fetchFromPage()', url);
287 + }
288 +
289 + function onPortMessage({payload, source}) {
290 + if (source === 'react-devtools-content-script') {
291 + switch (payload?.type) {
292 + case 'fetch-file-with-cache-complete':
293 + chrome.runtime.onMessage.removeListener(onPortMessage);
294 + resolve(payload.value);
295 + break;
296 + case 'fetch-file-with-cache-error':
297 + chrome.runtime.onMessage.removeListener(onPortMessage);
298 + reject(payload.value);
299 + break;
300 }
301 }
302 + }
303
362 - chrome.runtime.onMessage.addListener(onPortMessage);
304 + chrome.runtime.onMessage.addListener(onPortMessage);
305
364 - chrome.devtools.inspectedWindow.eval(`
365 - window.postMessage({
366 - source: 'react-devtools-extension',
367 - extensionId: "${CURRENT_EXTENSION_ID}",
368 - payload: {
369 - type: 'fetch-file-with-cache',
370 - url: "${url}",
371 - },
372 - }, '*');
373 - `);
374 - };
375 -
376 - // Fetching files from the extension won't make use of the network cache
377 - // for resources that have already been loaded by the page.
378 - // This helper function allows the extension to request files to be fetched
379 - // by the content script (running in the page) to increase the likelihood of a cache hit.
380 - fetchFileWithCaching = url => {
381 - return new Promise((resolve, reject) => {
382 - // Try fetching from the Network cache first.
383 - // If DevTools was opened after the page started loading, we may have missed some requests.
384 - // So fall back to a fetch() from the page and hope we get a cached response that way.
385 - fetchFromNetworkCache(url, resolve, reject);
306 + chrome.devtools.inspectedWindow.eval(`
307 + window.postMessage({
308 + source: 'react-devtools-extension',
309 + payload: {
310 + type: 'fetch-file-with-cache',
311 + url: "${url}",
312 + },
313 });
387 - };
388 - }
389 -
390 - // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration.
391 - const hookNamesModuleLoaderFunction = () =>
392 - import(
393 - /* webpackChunkName: 'parseHookNames' */ 'react-devtools-shared/src/hooks/parseHookNames'
394 - );
395 -
396 - root = createRoot(document.createElement('div'));
397 -
398 - render = (overrideTab = mostRecentOverrideTab) => {
399 - mostRecentOverrideTab = overrideTab;
400 - root.render(
401 - createElement(DevTools, {
402 - bridge,
403 - browserTheme: getBrowserTheme(),
404 - componentsPortalContainer,
405 - enabledInspectedElementContextMenu: true,
406 - fetchFileWithCaching,
407 - hookNamesModuleLoaderFunction,
408 - overrideTab,
409 - profilerPortalContainer,
410 - warnIfDuplicateInstallation,
411 - showTabBar: false,
412 - store,
413 - warnIfUnsupportedVersionDetected: true,
414 - viewAttributeSourceFunction,
415 - viewElementSourceFunction,
416 - }),
417 - );
314 + `);
315 };
316
420 - render();
317 + // Fetching files from the extension won't make use of the network cache
318 + // for resources that have already been loaded by the page.
319 + // This helper function allows the extension to request files to be fetched
320 + // by the content script (running in the page) to increase the likelihood of a cache hit.
321 + fetchFileWithCaching = url => {
322 + return new Promise((resolve, reject) => {
323 + // Try fetching from the Network cache first.
324 + // If DevTools was opened after the page started loading, we may have missed some requests.
325 + // So fall back to a fetch() from the page and hope we get a cached response that way.
326 + fetchFromNetworkCache(url, resolve, reject);
327 + });
328 + };
329 }
330
423 - cloneStyleTags = () => {
424 - const linkTags = [];
425 - // eslint-disable-next-line no-for-of-loops/no-for-of-loops
426 - for (const linkTag of document.getElementsByTagName('link')) {
427 - if (linkTag.rel === 'stylesheet') {
428 - const newLinkTag = document.createElement('link');
429 - // eslint-disable-next-line no-for-of-loops/no-for-of-loops
430 - for (const attribute of linkTag.attributes) {
431 - newLinkTag.setAttribute(
432 - attribute.nodeName,
433 - attribute.nodeValue,
434 - );
435 - }
436 - linkTags.push(newLinkTag);
437 - }
438 - }
439 - return linkTags;
331 + // TODO (Webpack 5) Hopefully we can remove this prop after the Webpack 5 migration.
332 + const hookNamesModuleLoaderFunction = () =>
333 + import(
334 + /* webpackChunkName: 'parseHookNames' */ 'react-devtools-shared/src/hooks/parseHookNames'
335 + );
336 +
337 + root = createRoot(document.createElement('div'));
338 +
339 + render = (overrideTab = mostRecentOverrideTab) => {
340 + mostRecentOverrideTab = overrideTab;
341 + root.render(
342 + createElement(DevTools, {
343 + bridge,
344 + browserTheme: getBrowserTheme(),
345 + componentsPortalContainer,
346 + enabledInspectedElementContextMenu: true,
347 + fetchFileWithCaching,
348 + hookNamesModuleLoaderFunction,
349 + overrideTab,
350 + profilerPortalContainer,
351 + showTabBar: false,
352 + store,
353 + warnIfUnsupportedVersionDetected: true,
354 + viewAttributeSourceFunction,
355 + viewElementSourceFunction,
356 + }),
357 + );
358 };
359
442 - initBridgeAndStore();
360 + render();
361 + }
362
444 - function ensureInitialHTMLIsCleared(container) {
445 - if (container._hasInitialHTMLBeenCleared) {
446 - return;
363 + cloneStyleTags = () => {
364 + const linkTags = [];
365 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
366 + for (const linkTag of document.getElementsByTagName('link')) {
367 + if (linkTag.rel === 'stylesheet') {
368 + const newLinkTag = document.createElement('link');
369 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
370 + for (const attribute of linkTag.attributes) {
371 + newLinkTag.setAttribute(attribute.nodeName, attribute.nodeValue);
372 + }
373 + linkTags.push(newLinkTag);
374 }
448 - container.innerHTML = '';
449 - container._hasInitialHTMLBeenCleared = true;
375 }
376 + return linkTags;
377 + };
378
452 - function setBrowserSelectionFromReact() {
453 - // This is currently only called on demand when you press "view DOM".
454 - // In the future, if Chrome adds an inspect() that doesn't switch tabs,
455 - // we could make this happen automatically when you select another component.
456 - chrome.devtools.inspectedWindow.eval(
457 - '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
458 - '(inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true) :' +
459 - 'false',
460 - (didSelectionChange, evalError) => {
461 - if (evalError) {
462 - console.error(evalError);
463 - }
464 - },
465 - );
466 - }
379 + initBridgeAndStore();
380
468 - function setReactSelectionFromBrowser() {
469 - // When the user chooses a different node in the browser Elements tab,
470 - // copy it over to the hook object so that we can sync the selection.
471 - chrome.devtools.inspectedWindow.eval(
472 - '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
473 - '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' +
474 - 'false',
475 - (didSelectionChange, evalError) => {
476 - if (evalError) {
477 - console.error(evalError);
478 - } else if (didSelectionChange) {
479 - // Remember to sync the selection next time we show Components tab.
480 - needsToSyncElementSelection = true;
481 - }
482 - },
483 - );
381 + function ensureInitialHTMLIsCleared(container) {
382 + if (container._hasInitialHTMLBeenCleared) {
383 + return;
384 }
385 + container.innerHTML = '';
386 + container._hasInitialHTMLBeenCleared = true;
387 + }
388
486 - setReactSelectionFromBrowser();
487 - chrome.devtools.panels.elements.onSelectionChanged.addListener(() => {
488 - setReactSelectionFromBrowser();
489 - });
389 + function setBrowserSelectionFromReact() {
390 + // This is currently only called on demand when you press "view DOM".
391 + // In the future, if Chrome adds an inspect() that doesn't switch tabs,
392 + // we could make this happen automatically when you select another component.
393 + chrome.devtools.inspectedWindow.eval(
394 + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
395 + '(inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true) :' +
396 + 'false',
397 + (didSelectionChange, evalError) => {
398 + if (evalError) {
399 + console.error(evalError);
400 + }
401 + },
402 + );
403 + }
404
491 - let currentPanel = null;
492 - let needsToSyncElementSelection = false;
493 -
494 - chrome.devtools.panels.create(
495 - isChrome ? '⚛️ Components' : 'Components',
496 - '',
497 - 'panel.html',
498 - extensionPanel => {
499 - extensionPanel.onShown.addListener(panel => {
500 - if (needsToSyncElementSelection) {
501 - needsToSyncElementSelection = false;
502 - bridge.send('syncSelectionFromNativeElementsPanel');
503 - }
405 + function setReactSelectionFromBrowser() {
406 + // When the user chooses a different node in the browser Elements tab,
407 + // copy it over to the hook object so that we can sync the selection.
408 + chrome.devtools.inspectedWindow.eval(
409 + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
410 + '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' +
411 + 'false',
412 + (didSelectionChange, evalError) => {
413 + if (evalError) {
414 + console.error(evalError);
415 + } else if (didSelectionChange) {
416 + // Remember to sync the selection next time we show Components tab.
417 + needsToSyncElementSelection = true;
418 + }
419 + },
420 + );
421 + }
422
505 - if (currentPanel === panel) {
506 - return;
507 - }
423 + setReactSelectionFromBrowser();
424 + chrome.devtools.panels.elements.onSelectionChanged.addListener(() => {
425 + setReactSelectionFromBrowser();
426 + });
427
509 - currentPanel = panel;
510 - componentsPortalContainer = panel.container;
428 + let currentPanel = null;
429 + let needsToSyncElementSelection = false;
430 +
431 + chrome.devtools.panels.create(
432 + isChrome ? '⚛️ Components' : 'Components',
433 + '',
434 + 'panel.html',
435 + extensionPanel => {
436 + extensionPanel.onShown.addListener(panel => {
437 + if (needsToSyncElementSelection) {
438 + needsToSyncElementSelection = false;
439 + bridge.send('syncSelectionFromNativeElementsPanel');
440 + }
441
512 - if (componentsPortalContainer != null) {
513 - ensureInitialHTMLIsCleared(componentsPortalContainer);
514 - render('components');
515 - panel.injectStyles(cloneStyleTags);
516 - logEvent({event_name: 'selected-components-tab'});
517 - }
518 - });
519 - extensionPanel.onHidden.addListener(panel => {
520 - // TODO: Stop highlighting and stuff.
521 - });
522 - },
523 - );
442 + if (currentPanel === panel) {
443 + return;
444 + }
445
525 - chrome.devtools.panels.create(
526 - isChrome ? '⚛️ Profiler' : 'Profiler',
527 - '',
528 - 'panel.html',
529 - extensionPanel => {
530 - extensionPanel.onShown.addListener(panel => {
531 - if (currentPanel === panel) {
532 - return;
533 - }
446 + currentPanel = panel;
447 + componentsPortalContainer = panel.container;
448
535 - currentPanel = panel;
536 - profilerPortalContainer = panel.container;
449 + if (componentsPortalContainer != null) {
450 + ensureInitialHTMLIsCleared(componentsPortalContainer);
451 + render('components');
452 + panel.injectStyles(cloneStyleTags);
453 + logEvent({event_name: 'selected-components-tab'});
454 + }
455 + });
456 + extensionPanel.onHidden.addListener(panel => {
457 + // TODO: Stop highlighting and stuff.
458 + });
459 + },
460 + );
461 +
462 + chrome.devtools.panels.create(
463 + isChrome ? '⚛️ Profiler' : 'Profiler',
464 + '',
465 + 'panel.html',
466 + extensionPanel => {
467 + extensionPanel.onShown.addListener(panel => {
468 + if (currentPanel === panel) {
469 + return;
470 + }
471
538 - if (profilerPortalContainer != null) {
539 - ensureInitialHTMLIsCleared(profilerPortalContainer);
540 - render('profiler');
541 - panel.injectStyles(cloneStyleTags);
542 - logEvent({event_name: 'selected-profiler-tab'});
543 - }
544 - });
545 - },
546 - );
472 + currentPanel = panel;
473 + profilerPortalContainer = panel.container;
474 +
475 + if (profilerPortalContainer != null) {
476 + ensureInitialHTMLIsCleared(profilerPortalContainer);
477 + render('profiler');
478 + panel.injectStyles(cloneStyleTags);
479 + logEvent({event_name: 'selected-profiler-tab'});
480 + }
481 + });
482 + },
483 + );
484
548 - chrome.devtools.network.onNavigated.removeListener(checkPageForReact);
485 + chrome.devtools.network.onNavigated.removeListener(checkPageForReact);
486
550 - // Re-initialize DevTools panel when a new page is loaded.
551 - chrome.devtools.network.onNavigated.addListener(function onNavigated() {
552 - // Re-initialize saved filters on navigation,
553 - // since global values stored on window get reset in this case.
554 - syncSavedPreferences();
487 + // Re-initialize DevTools panel when a new page is loaded.
488 + chrome.devtools.network.onNavigated.addListener(function onNavigated() {
489 + // Re-initialize saved filters on navigation,
490 + // since global values stored on window get reset in this case.
491 + syncSavedPreferences();
492
556 - // It's easiest to recreate the DevTools panel (to clean up potential stale state).
557 - // We can revisit this in the future as a small optimization.
558 - flushSync(() => root.unmount());
493 + // It's easiest to recreate the DevTools panel (to clean up potential stale state).
494 + // We can revisit this in the future as a small optimization.
495 + flushSync(() => root.unmount());
496
560 - initBridgeAndStore();
561 - });
497 + initBridgeAndStore();
498 });
499 },
500 );
packages/react-devtools-shared/src/devtools/views/DevTools.js
-4
@@ -34,7 +34,6 @@ import {SchedulingProfilerContextController} from 'react-devtools-scheduling-pro
34 import {ModalDialogContextController} from './ModalDialog';
35 import ReactLogo from './ReactLogo';
36 import UnsupportedBridgeProtocolDialog from './UnsupportedBridgeProtocolDialog';
37 -import DuplicateInstallationDialog from './DuplicateInstallationDialog';
37 import UnsupportedVersionDialog from './UnsupportedVersionDialog';
38 import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
39 import {useLocalStorage} from './hooks';
@@ -74,7 +73,6 @@ export type Props = {|
73 enabledInspectedElementContextMenu?: boolean,
74 showTabBar?: boolean,
75 store: Store,
77 - warnIfDuplicateInstallation?: boolean,
76 warnIfLegacyBackendDetected?: boolean,
77 warnIfUnsupportedVersionDetected?: boolean,
78 viewAttributeSourceFunction?: ?ViewAttributeSource,
@@ -134,7 +132,6 @@ export default function DevTools({
132 profilerPortalContainer,
133 showTabBar = false,
134 store,
137 - warnIfDuplicateInstallation = false,
135 warnIfLegacyBackendDetected = false,
136 warnIfUnsupportedVersionDetected = false,
137 viewAttributeSourceFunction,
@@ -322,7 +319,6 @@ export default function DevTools({
319 </ViewElementSourceContext.Provider>
320 </SettingsContextController>
321 <UnsupportedBridgeProtocolDialog />
325 - {warnIfDuplicateInstallation && <DuplicateInstallationDialog />}
322 {warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
323 {warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
324 </ModalDialogContextController>
packages/react-devtools-shared/src/devtools/views/DuplicateInstallationDialog.js deleted
-55
@@ -1,55 +0,0 @@
1 -/**
2 - * Copyright (c) Facebook, Inc. and its affiliates.
3 - *
4 - * This source code is licensed under the MIT license found in the
5 - * LICENSE file in the root directory of this source tree.
6 - *
7 - * @flow strict-local
8 - */
9 -
10 -import * as React from 'react';
11 -import {Fragment, useContext, useEffect} from 'react';
12 -import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
13 -import {ModalDialogContext} from './ModalDialog';
14 -
15 -export default function DuplicateInstallationDialog(_: {||}) {
16 - const {dispatch} = useContext(ModalDialogContext);
17 -
18 - useEffect(() => {
19 - dispatch({
20 - canBeDismissed: false,
21 - id: 'DuplicateInstallationDialog',
22 - type: 'SHOW',
23 - title: 'Duplicate Installations of DevTools Detected',
24 - content: <DialogContent />,
25 - });
26 - }, []);
27 - return null;
28 -}
29 -
30 -function DialogContent(_: {||}) {
31 - return (
32 - <Fragment>
33 - <p>
34 - We detected that there are multiple versions of React Developer Tools
35 - installed and enabled in your browser at the same time, which will cause
36 - issues while using the extension.
37 - </p>
38 - {isInternalFacebookBuild ? (
39 - <p>
40 - Before proceeding, please ensure that the only enabled version of
41 - React Developer Tools is the internal (Chef-installed) version. To
42 - manage your extensions, visit the <code>about://extensions</code> page
43 - in your browser.
44 - </p>
45 - ) : (
46 - <p>
47 - Please ensure that you have installed and enabled only a single
48 - version of React Developer Tools before proceeding. To manage your
49 - extensions, visit the <code>about://extensions</code> page in your
50 - browser.
51 - </p>
52 - )}
53 - </Fragment>
54 - );
55 -}