function createPanelIfReactLoaded()

in shells/browser/shared/src/main.js [22:178]


function createPanelIfReactLoaded() {
  if (panelCreated) {
    return;
  }

  chrome.devtools.inspectedWindow.eval(
    'window.__RELAY_DEVTOOLS_HOOK__ && window.__RELAY_DEVTOOLS_HOOK__.environments.size > 0',
    (pageHasRelay, error) => {
      if (!pageHasRelay || panelCreated) {
        return;
      }

      panelCreated = true;

      clearInterval(loadCheckInterval);

      let bridge = null;
      let store = null;

      let cloneStyleTags = null;
      let render = null;
      let root = null;
      let currentPanel = null;

      const tabId = chrome.devtools.inspectedWindow.tabId;
      const version = chrome.runtime.getManifest().version;
      registerDevToolsEventLogger('extension', version);

      function initBridgeAndStore() {
        const port = chrome.runtime.connect({
          name: '' + tabId,
        });
        // Looks like `port.onDisconnect` does not trigger on in-tab navigation like new URL or back/forward navigation,
        // so it makes no sense to handle it here.

        bridge = new Bridge({
          listen(fn) {
            const listener = message => fn(message);
            // Store the reference so that we unsubscribe from the same object.
            const portOnMessage = port.onMessage;
            portOnMessage.addListener(listener);
            return () => {
              portOnMessage.removeListener(listener);
            };
          },
          send(event: string, payload: any, transferable?: Array<any>) {
            port.postMessage({ event, payload }, transferable);
          },
        });

        store = new Store(bridge);

        // Initialize the backend only once the Store has been initialized.
        // Otherwise the Store may miss important initial tree op codes.
        chrome.devtools.inspectedWindow.eval(
          `window.postMessage({ source: 'relay-devtools-inject-backend' }, window.origin);`,
          function(response, evalError) {
            if (evalError) {
              console.error(evalError);
            }
          }
        );

        const viewElementSourceFunction = createViewElementSource(
          bridge,
          store
        );

        render = () => {
          if (root) {
            root.render(
              createElement(DevTools, {
                bridge,
                browserTheme: getBrowserTheme(),
                showTabBar: true,
                store,
                viewElementSourceFunction,
                rootContainer: currentPanel.container,
              })
            );
          }
        };

        render();
      }

      cloneStyleTags = () => {
        const linkTags = [];
        for (const linkTag of document.getElementsByTagName('link')) {
          if (linkTag.rel === 'stylesheet') {
            const newLinkTag = document.createElement('link');
            for (const attribute of linkTag.attributes) {
              newLinkTag.setAttribute(attribute.nodeName, attribute.nodeValue);
            }
            linkTags.push(newLinkTag);
          }
        }
        return linkTags;
      };

      initBridgeAndStore();

      function ensureInitialHTMLIsCleared(container) {
        if (container._hasInitialHTMLBeenCleared) {
          return;
        }
        container.innerHTML = '';
        container._hasInitialHTMLBeenCleared = true;
      }

      chrome.devtools.panels.create('Relay', '', 'panel.html', panel => {
        panel.onShown.addListener(listenPanel => {
          if (currentPanel === listenPanel) {
            return;
          }
          currentPanel = listenPanel;

          if (listenPanel.container != null) {
            listenPanel.injectStyles(cloneStyleTags);
            ensureInitialHTMLIsCleared(listenPanel.container);
            root = createRoot(listenPanel.container);
            render();
          }
        });
        panel.onHidden.addListener(() => {
          // TODO: Stop highlighting and stuff.
        });
      });

      chrome.devtools.network.onNavigated.removeListener(checkPageForReact);

      // Shutdown bridge before a new page is loaded.
      chrome.webNavigation.onBeforeNavigate.addListener(
        function onBeforeNavigate(details) {
          // Ignore navigation events from other tabs (or from within frames).
          if (details.tabId !== tabId || details.frameId !== 0) {
            return;
          }

          // `bridge.shutdown()` will remove all listeners we added, so we don't have to.
          bridge.shutdown();
        }
      );

      // Re-initialize DevTools panel when a new page is loaded.
      chrome.devtools.network.onNavigated.addListener(function onNavigated() {
        // It's easiest to recreate the DevTools panel (to clean up potential stale state).
        // We can revisit this in the future as a small optimization.
        flushSync(() => {
          root.unmount(() => {
            initBridgeAndStore();
          });
        });
      });
    }
  );
}