| 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 = ` |
| <style> |
| @font-face { |
| font-family: ahem; |
| src: url('${load.cache_bust(path)}'); |
| } |
| </style> |
| <div style="font-family: ahem;">This fetches ahem font.</div> |
| `; |
| 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; |
| }); |
| } |
| }; |