Sync DevTools Elements and Components tabs
Dan Abramov committed
Apr 8, 2019 at 20:41 UTC
4a301bd0e0457542994d0064dfa7687dd9191be3
2 files changed
+57
-2
shells/browser/shared/src/main.js
+34
-2
@@ -162,10 +162,43 @@ function createPanelIfReactLoaded() {
162
container._hasInitialHTMLBeenCleared = true;
163
}
164
165
+ function maybeSetReactSelectionFromBrowser() {
166
+ // When the user chooses a different node in the browser Elements tab,
167
+ // copy it over to the hook object so that we can sync the selection.
168
+ chrome.devtools.inspectedWindow.eval(
169
+ // Don't reset selection if it didn't change in Elements tab.
170
+ '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0)' +
171
+ ' ? (window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true)' +
172
+ ' : false',
173
+ (didChangeSelection, error) => {
174
+ if (error) {
175
+ console.error(error);
176
+ } else if (didChangeSelection) {
177
+ bridge.send('syncSelectionFromBrowserTools');
178
+ }
179
+ }
180
+ );
181
+ }
182
+
183
+ function maybeSetBrowserSelectionFromReact() {
184
+ chrome.devtools.inspectedWindow.eval(
185
+ '(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 != null)' +
186
+ ' ? (inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true)' +
187
+ ' : false',
188
+ (didChangeSelection, error) => {
189
+ if (error) {
190
+ console.error(error);
191
+ }
192
+ }
193
+ );
194
+ }
195
+
196
let currentPanel = null;
197
198
chrome.devtools.panels.create('⚛ Components', '', 'panel.html', panel => {
199
panel.onShown.addListener(panel => {
200
+ maybeSetReactSelectionFromBrowser();
201
+
202
if (currentPanel === panel) {
203
return;
204
}
@@ -178,10 +211,9 @@ function createPanelIfReactLoaded() {
211
render('components');
212
panel.injectStyles(cloneStyleTags);
213
}
181
-
182
- // TODO: When the user switches to the panel, check for an Elements tab selection.
214
});
215
panel.onHidden.addListener(() => {
216
+ maybeSetBrowserSelectionFromReact();
217
// TODO: Stop highlighting and stuff.
218
});
219
});
src/backend/agent.js
+23
@@ -87,6 +87,10 @@ export default class Agent extends EventEmitter {
87
bridge.addListener('startProfiling', this.startProfiling);
88
bridge.addListener('stopInspectingDOM', this.stopInspectingDOM);
89
bridge.addListener('stopProfiling', this.stopProfiling);
90
+ bridge.addListener(
91
+ 'syncSelectionFromBrowserTools',
92
+ this.syncSelectionFromBrowserTools
93
+ );
94
bridge.addListener('shutdown', this.shutdown);
95
bridge.addListener('viewElementSource', this.viewElementSource);
96
@@ -292,6 +296,14 @@ export default class Agent extends EventEmitter {
296
if (renderer == null) {
297
console.warn(`Invalid renderer id "${rendererID}" for element "${id}"`);
298
} else {
299
+ // Update the active DOM node on the global hook object.
300
+ // The content script will read this to update window.$0
301
+ // when we switch tabs.
302
+ let node: HTMLElement | null = null;
303
+ node = ((renderer.findNativeByFiberID(id): any): HTMLElement);
304
+ if (node !== null) {
305
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = node;
306
+ }
307
this._bridge.send('selectElement', renderer.selectElement(id));
308
}
309
};
@@ -362,6 +374,17 @@ export default class Agent extends EventEmitter {
374
}
375
}
376
377
+ syncSelectionFromBrowserTools = () => {
378
+ const target = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0;
379
+ if (target == null) {
380
+ return;
381
+ }
382
+ const id = this.getIDForNode(target);
383
+ if (id !== null) {
384
+ this._bridge.send('selectFiber', id);
385
+ }
386
+ };
387
+
388
shutdown = () => {
389
this.emit('shutdown');
390
};