@samitouri / QOS-React / commits / 7acf00566e

[DevTools] Create extension panels before React detection (#37151)

The main reason why I am doing this is because there is no API in the browser to "unmount" the created panel. If you have React DevTools installed, we should always create a panel, but the contents of the panel should be dynamically populated based on the target. If it is not a React app, we will continue showing the stub message. Previously, we wouldn't mount a panel at all, and historically we've received a few reports of this as a bug.

Ruslan Lesiutin committed Jul 30, 2026 at 21:17 UTC 7acf00566e35c40817342654438a5757c6c41d0e
1 file changed +81 -77
packages/react-devtools-extensions/src/main/index.js
+81 -77
@@ -344,15 +344,7 @@ function ensureInitialHTMLIsCleared(
344 container._hasInitialHTMLBeenCleared = true;
345 }
346
347 -function createComponentsPanel(instance: DevToolsInstance) {
348 - if (componentsPortalContainer) {
349 - // Panel is created and user opened it at least once
350 - ensureInitialHTMLIsCleared(componentsPortalContainer);
351 - instance.render('components');
352 -
353 - return;
354 - }
355 -
347 +function createComponentsPanel() {
348 if (componentsPanel) {
349 // Panel is created, but wasn't opened yet, so no document is present for it
350 return;
@@ -368,13 +360,15 @@ function createComponentsPanel(instance: DevToolsInstance) {
360 createdPanel.onShown.addListener(portal => {
361 componentsPortalContainer = portal.container;
362 const currentInstance = devToolsInstance;
371 - if (componentsPortalContainer != null && currentInstance !== null) {
372 - ensureInitialHTMLIsCleared(componentsPortalContainer);
363 + if (componentsPortalContainer != null) {
364 + if (currentInstance !== null) {
365 + ensureInitialHTMLIsCleared(componentsPortalContainer);
366
374 - currentInstance.render('components');
375 - portal.injectStyles(cloneStyleTags);
367 + currentInstance.render('components');
368
377 - logEvent({event_name: 'selected-components-tab'});
369 + logEvent({event_name: 'selected-components-tab'});
370 + }
371 + portal.injectStyles(cloneStyleTags);
372 }
373 });
374
@@ -388,15 +382,7 @@ function createComponentsPanel(instance: DevToolsInstance) {
382 );
383 }
384
391 -function createElementsInspectPanel(instance: DevToolsInstance) {
392 - if (inspectedElementPortalContainer) {
393 - // Panel is created and user opened it at least once
394 - ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
395 - instance.render();
396 -
397 - return;
398 - }
399 -
385 +function createElementsInspectPanel() {
386 if (inspectedElementPane) {
387 // Panel is created, but wasn't opened yet, so no document is present for it
388 return;
@@ -422,28 +408,22 @@ function createElementsInspectPanel(instance: DevToolsInstance) {
408 createdPane.onShown.addListener(portal => {
409 inspectedElementPortalContainer = portal.container;
410 const currentInstance = devToolsInstance;
425 - if (inspectedElementPortalContainer != null && currentInstance !== null) {
426 - ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
427 - currentInstance.bridge.send('syncSelectionFromBuiltinElementsPanel');
411 + if (inspectedElementPortalContainer != null) {
412 + if (currentInstance !== null) {
413 + ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
414 + currentInstance.bridge.send('syncSelectionFromBuiltinElementsPanel');
415
429 - currentInstance.render();
430 - portal.injectStyles(cloneStyleTags);
416 + currentInstance.render();
417
432 - logEvent({event_name: 'selected-inspected-element-pane'});
418 + logEvent({event_name: 'selected-inspected-element-pane'});
419 + }
420 + portal.injectStyles(cloneStyleTags);
421 }
422 });
423 });
424 }
425
438 -function createProfilerPanel(instance: DevToolsInstance) {
439 - if (profilerPortalContainer) {
440 - // Panel is created and user opened it at least once
441 - ensureInitialHTMLIsCleared(profilerPortalContainer);
442 - instance.render('profiler');
443 -
444 - return;
445 - }
446 -
426 +function createProfilerPanel() {
427 if (profilerPanel) {
428 // Panel is created, but wasn't opened yet, so no document is present for it
429 return;
@@ -459,28 +439,22 @@ function createProfilerPanel(instance: DevToolsInstance) {
439 createdPanel.onShown.addListener(portal => {
440 profilerPortalContainer = portal.container;
441 const currentInstance = devToolsInstance;
462 - if (profilerPortalContainer != null && currentInstance !== null) {
463 - ensureInitialHTMLIsCleared(profilerPortalContainer);
442 + if (profilerPortalContainer != null) {
443 + if (currentInstance !== null) {
444 + ensureInitialHTMLIsCleared(profilerPortalContainer);
445
465 - currentInstance.render('profiler');
466 - portal.injectStyles(cloneStyleTags);
446 + currentInstance.render('profiler');
447
468 - logEvent({event_name: 'selected-profiler-tab'});
448 + logEvent({event_name: 'selected-profiler-tab'});
449 + }
450 + portal.injectStyles(cloneStyleTags);
451 }
452 });
453 },
454 );
455 }
456
475 -function createSourcesEditorPanel(instance: DevToolsInstance) {
476 - if (editorPortalContainer) {
477 - // Panel is created and user opened it at least once
478 - ensureInitialHTMLIsCleared(editorPortalContainer);
479 - instance.render();
480 -
481 - return;
482 - }
483 -
457 +function createSourcesEditorPanel() {
458 if (editorPane) {
459 // Panel is created, but wasn't opened yet, so no document is present for it
460 return;
@@ -501,27 +475,21 @@ function createSourcesEditorPanel(instance: DevToolsInstance) {
475 createdPane.onShown.addListener(portal => {
476 editorPortalContainer = portal.container;
477 const currentInstance = devToolsInstance;
504 - if (editorPortalContainer != null && currentInstance !== null) {
505 - ensureInitialHTMLIsCleared(editorPortalContainer);
478 + if (editorPortalContainer != null) {
479 + if (currentInstance !== null) {
480 + ensureInitialHTMLIsCleared(editorPortalContainer);
481
507 - currentInstance.render();
508 - portal.injectStyles(cloneStyleTags);
482 + currentInstance.render();
483
510 - logEvent({event_name: 'selected-editor-pane'});
484 + logEvent({event_name: 'selected-editor-pane'});
485 + }
486 + portal.injectStyles(cloneStyleTags);
487 }
488 });
489 });
490 }
491
516 -function createSuspensePanel(instance: DevToolsInstance) {
517 - if (suspensePortalContainer) {
518 - // Panel is created and user opened it at least once
519 - ensureInitialHTMLIsCleared(suspensePortalContainer);
520 - instance.render('suspense');
521 -
522 - return;
523 - }
524 -
492 +function createSuspensePanel() {
493 if (suspensePanel) {
494 // Panel is created, but wasn't opened yet, so no document is present for it
495 return;
@@ -537,19 +505,57 @@ function createSuspensePanel(instance: DevToolsInstance) {
505 createdPanel.onShown.addListener(portal => {
506 suspensePortalContainer = portal.container;
507 const currentInstance = devToolsInstance;
540 - if (suspensePortalContainer != null && currentInstance !== null) {
541 - ensureInitialHTMLIsCleared(suspensePortalContainer);
508 + if (suspensePortalContainer != null) {
509 + if (currentInstance !== null) {
510 + ensureInitialHTMLIsCleared(suspensePortalContainer);
511
543 - currentInstance.render('suspense');
544 - portal.injectStyles(cloneStyleTags);
512 + currentInstance.render('suspense');
513
546 - logEvent({event_name: 'selected-suspense-tab'});
514 + logEvent({event_name: 'selected-suspense-tab'});
515 + }
516 + portal.injectStyles(cloneStyleTags);
517 }
518 });
519 },
520 );
521 }
522
523 +function createDevToolsPanels(): void {
524 + createComponentsPanel();
525 + createProfilerPanel();
526 + createSourcesEditorPanel();
527 + createElementsInspectPanel();
528 + createSuspensePanel();
529 +}
530 +
531 +function renderOpenedDevToolsPanels(instance: DevToolsInstance): void {
532 + if (componentsPortalContainer) {
533 + ensureInitialHTMLIsCleared(componentsPortalContainer);
534 + instance.render('components');
535 + }
536 +
537 + if (profilerPortalContainer) {
538 + ensureInitialHTMLIsCleared(profilerPortalContainer);
539 + instance.render('profiler');
540 + }
541 +
542 + if (editorPortalContainer) {
543 + ensureInitialHTMLIsCleared(editorPortalContainer);
544 + instance.render();
545 + }
546 +
547 + if (inspectedElementPortalContainer) {
548 + ensureInitialHTMLIsCleared(inspectedElementPortalContainer);
549 + instance.bridge.send('syncSelectionFromBuiltinElementsPanel');
550 + instance.render();
551 + }
552 +
553 + if (suspensePortalContainer) {
554 + ensureInitialHTMLIsCleared(suspensePortalContainer);
555 + instance.render('suspense');
556 + }
557 +}
558 +
559 function performInTabNavigationCleanup() {
560 // Potentially, if react hasn't loaded yet and user performs in-tab navigation
561 clearReactPollingInstance();
@@ -666,11 +672,7 @@ function mountReactDevTools() {
672 const instance = createDevToolsInstance();
673 devToolsInstance = instance;
674
669 - createComponentsPanel(instance);
670 - createProfilerPanel(instance);
671 - createSourcesEditorPanel(instance);
672 - createElementsInspectPanel(instance);
673 - createSuspensePanel(instance);
675 + renderOpenedDevToolsPanels(instance);
676 }
677
678 let reactPollingInstance = null;
@@ -743,7 +745,7 @@ const debouncedMountReactDevToolsCallback = debounce(
745 500,
746 );
747
746 -// Clean up everything, but start mounting React DevTools panels if user stays at this page
748 +// Clean up everything, but remount React DevTools if user stays at this page
749 function onNavigatedToOtherPage() {
750 performInTabNavigationCleanup();
751 debouncedMountReactDevToolsCallback();
@@ -762,6 +764,8 @@ if (__IS_FIREFOX__) {
764
765 connectExtensionPort();
766
767 +createDevToolsPanels();
768 +
769 mountReactDevToolsWhenReactHasLoaded();
770
771 function onThemeChanged() {