import WriteStream from './write-stream'; import * as utils from './utils'; /** * A function that intentionally does nothing. */ function doNothing() { } /** * Available options and defaults. * * @type {Object} */ const OPTIONS = { /** * Called when an async script has loaded. */ afterAsync: doNothing, /** * Called immediately before removing from the write queue. */ afterDequeue: doNothing, /** * Called sync after a stream's first thread release. */ afterStreamStart: doNothing, /** * Called after writing buffered document.write calls. */ afterWrite: doNothing, /** * Allows disabling the autoFix feature of prescribe */ autoFix: true, /** * Called immediately before adding to the write queue. */ beforeEnqueue: doNothing, /** * Called before writing a token. * * @param {Object} tok The token */ beforeWriteToken: tok => tok, /** * Called before writing buffered document.write calls. * * @param {String} str The string */ beforeWrite: str => str, /** * Called when evaluation is finished. */ done: doNothing, /** * Called when a write results in an error. * * @param {Error} e The error */ error(e) { throw new Error(e.msg); }, /** * Whether to let scripts w/ async attribute set fall out of the queue. */ releaseAsync: false }; let nextId = 0; let queue = []; let active = null; function nextStream() { const args = queue.shift(); if (args) { const options = utils.last(args); options.afterDequeue(); args.stream = runStream(...args); options.afterStreamStart(); } } function runStream(el, html, options) { active = new WriteStream(el, options); // Identify this stream. active.id = nextId++; active.name = options.name || active.id; postscribe.streams[active.name] = active; // Override document.write. const doc = el.ownerDocument; const stash = { close: doc.close, open: doc.open, write: doc.write, writeln: doc.writeln }; function write(str) { str = options.beforeWrite(str); active.write(str); options.afterWrite(str); } Object.assign(doc, { close: doNothing, open: doNothing, write: (...str) => write(str.join('')), writeln: (...str) => write(str.join('') + '\n') }); // Override window.onerror const oldOnError = active.win.onerror || doNothing; // This works together with the try/catch around WriteStream::insertScript // In modern browsers, exceptions in tag scripts go directly to top level active.win.onerror = (msg, url, line) => { options.error({msg: `${msg} - ${url}: ${line}`}); oldOnError.apply(active.win, [msg, url, line]); }; // Write to the stream active.write(html, () => { // restore document.write Object.assign(doc, stash); // restore window.onerror active.win.onerror = oldOnError; options.done(); active = null; nextStream(); }); return active; } export default function postscribe(el, html, options) { if (utils.isFunction(options)) { options = {done: options}; } else if (options === 'clear') { queue = []; active = null; nextId = 0; return; } options = utils.defaults(options, OPTIONS); // id selector if ((/^#/).test(el)) { el = window.document.getElementById(el.substr(1)); } else { el = el.jquery ? el[0] : el; } const args = [el, html, options]; el.postscribe = { cancel: () => { if (args.stream) { args.stream.abort(); } else { args[1] = doNothing; } } }; options.beforeEnqueue(args); queue.push(args); if (!active) { nextStream(); } return el.postscribe; } Object.assign(postscribe, { // Streams by name. streams: {}, // Queue of streams. queue, // Expose internal classes. WriteStream });