1. /* global chrome */
    
  2. 
    
  3. class CouldNotFindReactOnThePageError extends Error {
    
  4.   constructor() {
    
  5.     super("Could not find React, or it hasn't been loaded yet");
    
  6. 
    
  7.     // Maintains proper stack trace for where our error was thrown (only available on V8)
    
  8.     if (Error.captureStackTrace) {
    
  9.       Error.captureStackTrace(this, CouldNotFindReactOnThePageError);
    
  10.     }
    
  11. 
    
  12.     this.name = 'CouldNotFindReactOnThePageError';
    
  13.   }
    
  14. }
    
  15. 
    
  16. export function startReactPolling(
    
  17.   onReactFound,
    
  18.   attemptsThreshold,
    
  19.   onCouldNotFindReactAfterReachingAttemptsThreshold,
    
  20. ) {
    
  21.   let status = 'idle';
    
  22. 
    
  23.   function abort() {
    
  24.     status = 'aborted';
    
  25.   }
    
  26. 
    
  27.   // This function will call onSuccess only if React was found and polling is not aborted, onError will be called for every other case
    
  28.   function checkIfReactPresentInInspectedWindow(onSuccess, onError) {
    
  29.     chrome.devtools.inspectedWindow.eval(
    
  30.       'window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0',
    
  31.       (pageHasReact, exceptionInfo) => {
    
  32.         if (status === 'aborted') {
    
  33.           onError(
    
  34.             'Polling was aborted, user probably navigated to the other page',
    
  35.           );
    
  36.           return;
    
  37.         }
    
  38. 
    
  39.         if (exceptionInfo) {
    
  40.           const {code, description, isError, isException, value} =
    
  41.             exceptionInfo;
    
  42. 
    
  43.           if (isException) {
    
  44.             onError(
    
  45.               `Received error while checking if react has loaded: ${value}`,
    
  46.             );
    
  47.             return;
    
  48.           }
    
  49. 
    
  50.           if (isError) {
    
  51.             onError(
    
  52.               `Received error with code ${code} while checking if react has loaded: "${description}"`,
    
  53.             );
    
  54.             return;
    
  55.           }
    
  56.         }
    
  57. 
    
  58.         if (pageHasReact) {
    
  59.           onSuccess();
    
  60.           return;
    
  61.         }
    
  62. 
    
  63.         onError(new CouldNotFindReactOnThePageError());
    
  64.       },
    
  65.     );
    
  66.   }
    
  67. 
    
  68.   // Just a Promise wrapper around `checkIfReactPresentInInspectedWindow`
    
  69.   // returns a Promise, which will resolve only if React has been found on the page
    
  70.   function poll(attempt) {
    
  71.     return new Promise((resolve, reject) => {
    
  72.       checkIfReactPresentInInspectedWindow(resolve, reject);
    
  73.     }).catch(error => {
    
  74.       if (error instanceof CouldNotFindReactOnThePageError) {
    
  75.         if (attempt === attemptsThreshold) {
    
  76.           onCouldNotFindReactAfterReachingAttemptsThreshold();
    
  77.         }
    
  78. 
    
  79.         // Start next attempt in 0.5s
    
  80.         return new Promise(r => setTimeout(r, 500)).then(() =>
    
  81.           poll(attempt + 1),
    
  82.         );
    
  83.       }
    
  84. 
    
  85.       // Propagating every other Error
    
  86.       throw error;
    
  87.     });
    
  88.   }
    
  89. 
    
  90.   poll(1)
    
  91.     .then(onReactFound)
    
  92.     .catch(error => {
    
  93.       // Log propagated errors only if polling was not aborted
    
  94.       // Some errors are expected when user performs in-tab navigation and `.eval()` is still being executed
    
  95.       if (status === 'aborted') {
    
  96.         return;
    
  97.       }
    
  98. 
    
  99.       console.error(error);
    
  100.     });
    
  101. 
    
  102.   return {abort};
    
  103. }