diff --git a/client/src/templates/Challenges/utils/frame.js b/client/src/templates/Challenges/utils/frame.js index def4a19df4..48b511e268 100644 --- a/client/src/templates/Challenges/utils/frame.js +++ b/client/src/templates/Challenges/utils/frame.js @@ -9,6 +9,8 @@ const testId = 'fcc-test-frame'; // the project preview frame demos the finished project export const projectPreviewId = 'fcc-project-preview-frame'; +const DOCUMENT_NOT_FOUND_ERROR = 'document not found'; + // base tag here will force relative links // within iframe to point to '' instead of // append to the current challenge url @@ -103,48 +105,60 @@ const buildProxyConsole = proxyLogger => ctx => { }; const initTestFrame = frameReady => ctx => { - waitForFrame(ctx).then(async () => { - const { sources, loadEnzyme } = ctx; - // default for classic challenges - // should not be used for modern - const code = { - contents: sources.index, - editableContents: sources.editableContents - }; - // provide the file name and get the original source - const getUserInput = fileName => toString(sources[fileName]); - await ctx.document.__initTestFrame({ code, getUserInput, loadEnzyme }); - frameReady(); - }); + waitForFrame(ctx) + .then(async () => { + const { sources, loadEnzyme } = ctx; + // default for classic challenges + // should not be used for modern + const code = { + contents: sources.index, + editableContents: sources.editableContents + }; + // provide the file name and get the original source + const getUserInput = fileName => toString(sources[fileName]); + await ctx.document.__initTestFrame({ code, getUserInput, loadEnzyme }); + frameReady(); + }) + .catch(handleDocumentNotFound); return ctx; }; const initMainFrame = (_, proxyLogger) => ctx => { - waitForFrame(ctx).then(() => { - // Overwriting the onerror added by createHeader to catch any errors thrown - // after the frame is ready. It has to be overwritten, as proxyLogger cannot - // be added as part of createHeader. - ctx.window.onerror = function (msg) { - var string = msg.toLowerCase(); - if (string.includes('script error')) { - msg = 'Error, open your browser console to learn more.'; - } - if (proxyLogger) { - proxyLogger(msg); - } - // let the error propagate so it appears in the browser console, otherwise - // an error from a cross origin script just appears as 'Script error.' - return false; - }; - }); + waitForFrame(ctx) + .then(() => { + // Overwriting the onerror added by createHeader to catch any errors thrown + // after the frame is ready. It has to be overwritten, as proxyLogger cannot + // be added as part of createHeader. + ctx.window.onerror = function (msg) { + var string = msg.toLowerCase(); + if (string.includes('script error')) { + msg = 'Error, open your browser console to learn more.'; + } + if (proxyLogger) { + proxyLogger(msg); + } + // let the error propagate so it appears in the browser console, otherwise + // an error from a cross origin script just appears as 'Script error.' + return false; + }; + }) + .catch(handleDocumentNotFound); return ctx; }; +function handleDocumentNotFound(err) { + if (err !== DOCUMENT_NOT_FOUND_ERROR) { + console.log(err); + } +} + const initPreviewFrame = () => ctx => ctx; const waitForFrame = ctx => { - return new Promise(resolve => { - if (ctx.document.readyState === 'loading') { + return new Promise((resolve, reject) => { + if (!ctx.document) { + reject(DOCUMENT_NOT_FOUND_ERROR); + } else if (ctx.document.readyState === 'loading') { ctx.document.addEventListener('DOMContentLoaded', resolve); } else { resolve(); @@ -153,10 +167,13 @@ const waitForFrame = ctx => { }; function writeToFrame(content, frame) { - frame.open(); - frame.write(content); - frame.close(); - return frame; + // it's possible, if the preview is rapidly opened and closed, for the frame + // to be null at this point. + if (frame) { + frame.open(); + frame.write(content); + frame.close(); + } } const writeContentToFrame = ctx => {