const load = { cache_bust: path => { let url = new URL(path, location.origin); url.href += (url.href.includes("?")) ? '&' : '?'; // The `Number` type in Javascript, when interpreted as an integer, can only // safely represent [-2^53 + 1, 2^53 - 1] without the loss of precision [1]. // We do not generate a global value and increment from it, as the increment // might not have enough precision to be reflected. // // [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number url.href += "unique=" + Math.random().toString().substring(2); return url.href; }, image_with_attrs: async (path, attribute_map) => { return new Promise(resolve => { const img = new Image(); if (attribute_map instanceof Object) { for (const [key, value] of Object.entries(attribute_map)) { img[key] = value; } } img.onload = img.onerror = resolve; img.src = load.cache_bust(path); }); }, // Returns a promise that settles once the given path has been fetched as an // image resource. image: path => { return load.image_with_attrs(path, undefined); }, // Returns a promise that settles once the given path has been fetched as a // font resource. font: path => { const div = document.createElement('div'); div.innerHTML = `
This fetches ahem font.
`; document.body.appendChild(div); return document.fonts.ready.then(() => { document.body.removeChild(div); }); }, stylesheet_with_attrs: async (path, attribute_map) => { const link = document.createElement("link"); if (attribute_map instanceof Object) { for (const [key, value] of Object.entries(attribute_map)) { link[key] = value; } } link.rel = "stylesheet"; link.type = "text/css"; link.href = load.cache_bust(path); const loaded = new Promise(resolve => { link.onload = link.onerror = resolve; }); document.head.appendChild(link); await loaded; document.head.removeChild(link); }, // Returns a promise that settles once the given path has been fetched as a // stylesheet resource. stylesheet: async path => { return load.stylesheet_with_attrs(path, undefined); }, iframe_with_attrs: async (path, attribute_map, validator) => { const frame = document.createElement("iframe"); if (attribute_map instanceof Object) { for (const [key, value] of Object.entries(attribute_map)) { frame[key] = value; } } const loaded = new Promise(resolve => { frame.onload = frame.onerror = resolve; }); frame.src = load.cache_bust(path); document.body.appendChild(frame); await loaded; if (validator instanceof Function) { validator(frame); } document.body.removeChild(frame); }, // Returns a promise that settles once the given path has been fetched as an // iframe. iframe: async (path, validator) => { return load.iframe_with_attrs(path, undefined, validator); }, script_with_attrs: async (path, attribute_map) => { const script = document.createElement("script"); if (attribute_map instanceof Object) { for (const [key, value] of Object.entries(attribute_map)) { script[key] = value; } } const loaded = new Promise(resolve => { script.onload = script.onerror = resolve; }); script.src = load.cache_bust(path); document.body.appendChild(script); await loaded; document.body.removeChild(script); }, // Returns a promise that settles once the given path has been fetched as a // script. script: async path => { return load.script_with_attrs(path, undefined); }, // Returns a promise that settles once the given path has been fetched as an // object. object: async (path, type) => { const object = document.createElement("object"); const loaded = new Promise(resolve => { object.onload = object.onerror = resolve; }); object.data = load.cache_bust(path); if (type) { object.type = type; } document.body.appendChild(object); const timeout = new Promise(r => step_timeout(() => { console.log("Timeout was reached before load or error events fired"); r(); }, 2000)); await Promise.race([loaded, timeout]); document.body.removeChild(object); }, // Returns a promise that settles once the given path has been fetched // through a synchronous XMLHttpRequest. xhr_sync: async (path, headers) => { const xhr = new XMLHttpRequest; xhr.open("GET", path, /* async = */ false); if (headers instanceof Object) { for (const [key, value] of Object.entries(headers)) { xhr.setRequestHeader(key, value); } } xhr.send(); }, xhr_async: path => { const xhr = new XMLHttpRequest(); xhr.open("GET", path) xhr.send(); return new Promise(resolve => { xhr.onload = xhr.onerror = resolve; }); } };