1. /* global chrome */
    
  2. 
    
  3. import nullthrows from 'nullthrows';
    
  4. 
    
  5. // We run scripts on the page via the service worker (background/index.js) for
    
  6. // Manifest V3 extensions (Chrome & Edge).
    
  7. // We need to inject this code for Firefox only because it does not support ExecutionWorld.MAIN
    
  8. // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld
    
  9. // In this content script we have access to DOM, but don't have access to the webpage's window,
    
  10. // so we inject this inline script tag into the webpage (allowed in Manifest V2).
    
  11. function injectScriptSync(src) {
    
  12.   let code = '';
    
  13.   const request = new XMLHttpRequest();
    
  14.   request.addEventListener('load', function () {
    
  15.     code = this.responseText;
    
  16.   });
    
  17.   request.open('GET', src, false);
    
  18.   request.send();
    
  19. 
    
  20.   const script = document.createElement('script');
    
  21.   script.textContent = code;
    
  22. 
    
  23.   // This script runs before the <head> element is created,
    
  24.   // so we add the script to <html> instead.
    
  25.   nullthrows(document.documentElement).appendChild(script);
    
  26.   nullthrows(script.parentNode).removeChild(script);
    
  27. }
    
  28. 
    
  29. let lastSentDevToolsHookMessage;
    
  30. 
    
  31. // We want to detect when a renderer attaches, and notify the "background page"
    
  32. // (which is shared between tabs and can highlight the React icon).
    
  33. // Currently we are in "content script" context, so we can't listen to the hook directly
    
  34. // (it will be injected directly into the page).
    
  35. // So instead, the hook will use postMessage() to pass message to us here.
    
  36. // And when this happens, we'll send a message to the "background page".
    
  37. window.addEventListener('message', function onMessage({data, source}) {
    
  38.   if (source !== window || !data) {
    
  39.     return;
    
  40.   }
    
  41. 
    
  42.   // We keep this logic here and not in `proxy.js`, because proxy content script is injected later at `document_end`
    
  43.   if (data.source === 'react-devtools-hook') {
    
  44.     const {source: messageSource, payload} = data;
    
  45.     const message = {source: messageSource, payload};
    
  46. 
    
  47.     lastSentDevToolsHookMessage = message;
    
  48.     chrome.runtime.sendMessage(message);
    
  49.   }
    
  50. });
    
  51. 
    
  52. // NOTE: Firefox WebExtensions content scripts are still alive and not re-injected
    
  53. // while navigating the history to a document that has not been destroyed yet,
    
  54. // replay the last detection result if the content script is active and the
    
  55. // document has been hidden and shown again.
    
  56. window.addEventListener('pageshow', function ({target}) {
    
  57.   if (!lastSentDevToolsHookMessage || target !== window.document) {
    
  58.     return;
    
  59.   }
    
  60. 
    
  61.   chrome.runtime.sendMessage(lastSentDevToolsHookMessage);
    
  62. });
    
  63. 
    
  64. if (__IS_FIREFOX__) {
    
  65.   injectScriptSync(chrome.runtime.getURL('build/renderer.js'));
    
  66. 
    
  67.   // Inject a __REACT_DEVTOOLS_GLOBAL_HOOK__ global for React to interact with.
    
  68.   // Only do this for HTML documents though, to avoid e.g. breaking syntax highlighting for XML docs.
    
  69.   switch (document.contentType) {
    
  70.     case 'text/html':
    
  71.     case 'application/xhtml+xml': {
    
  72.       injectScriptSync(chrome.runtime.getURL('build/installHook.js'));
    
  73.       break;
    
  74.     }
    
  75.   }
    
  76. }