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
-}