/* global chrome */
class CouldNotFindReactOnThePageError extends Error {
constructor() {
super("Could not find React, or it hasn't been loaded yet");
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CouldNotFindReactOnThePageError);
}
this.name = 'CouldNotFindReactOnThePageError';
}
}
export function startReactPolling(
onReactFound,
attemptsThreshold,
onCouldNotFindReactAfterReachingAttemptsThreshold,
) {
let status = 'idle';
function abort() {
status = 'aborted';
}
// This function will call onSuccess only if React was found and polling is not aborted, onError will be called for every other case
function checkIfReactPresentInInspectedWindow(onSuccess, onError) {
chrome.devtools.inspectedWindow.eval(
'window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0',
(pageHasReact, exceptionInfo) => {
if (status === 'aborted') {
onError(
'Polling was aborted, user probably navigated to the other page',
);
return;
}
if (exceptionInfo) {
const {code, description, isError, isException, value} =
exceptionInfo;
if (isException) {
onError(
`Received error while checking if react has loaded: ${value}`,
);
return;
}
if (isError) {
onError(
`Received error with code ${code} while checking if react has loaded: "${description}"`,
);
return;
}
}
if (pageHasReact) {
onSuccess();
return;
}
onError(new CouldNotFindReactOnThePageError());
},
);
}
// Just a Promise wrapper around `checkIfReactPresentInInspectedWindow`
// returns a Promise, which will resolve only if React has been found on the page
function poll(attempt) {
return new Promise((resolve, reject) => {
checkIfReactPresentInInspectedWindow(resolve, reject);
}).catch(error => {
if (error instanceof CouldNotFindReactOnThePageError) {
if (attempt === attemptsThreshold) {
onCouldNotFindReactAfterReachingAttemptsThreshold();
}
// Start next attempt in 0.5s
return new Promise(r => setTimeout(r, 500)).then(() =>
poll(attempt + 1),
);
}
// Propagating every other Error
throw error;
});
}
poll(1)
.then(onReactFound)
.catch(error => {
// Log propagated errors only if polling was not aborted
// Some errors are expected when user performs in-tab navigation and `.eval()` is still being executed
if (status === 'aborted') {
return;
}
console.error(error);
});
return {abort};
}