resource-loaders.js (5483B)
1 const load = { 2 3 cache_bust: path => { 4 let url = new URL(path, location.origin); 5 url.href += (url.href.includes("?")) ? '&' : '?'; 6 // The `Number` type in Javascript, when interpreted as an integer, can only 7 // safely represent [-2^53 + 1, 2^53 - 1] without the loss of precision [1]. 8 // We do not generate a global value and increment from it, as the increment 9 // might not have enough precision to be reflected. 10 // 11 // [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number 12 url.href += "unique=" + Math.random().toString().substring(2); 13 return url.href; 14 }, 15 16 image_with_attrs: async (path, attribute_map) => { 17 return new Promise(resolve => { 18 const img = new Image(); 19 for (const key in attribute_map) 20 img[key] = attribute_map[key]; 21 img.onload = img.onerror = resolve; 22 img.src = load.cache_bust(path); 23 }); 24 }, 25 26 // Returns a promise that settles once the given path has been fetched as an 27 // image resource. 28 image: path => { 29 return load.image_with_attrs(path, undefined); 30 }, 31 32 // Returns a promise that settles once the given path has been fetched as an 33 // image resource. 34 image_cors: path => load.image_with_attrs(path, {crossOrigin: "anonymous"}), 35 36 // Returns a promise that settles once the given path has been fetched as a 37 // font resource. 38 font: path => { 39 const div = document.createElement('div'); 40 div.innerHTML = ` 41 <style> 42 @font-face { 43 font-family: ahem; 44 src: url('${load.cache_bust(path)}'); 45 } 46 </style> 47 <div style="font-family: ahem;">This fetches ahem font.</div> 48 `; 49 document.body.appendChild(div); 50 return document.fonts.ready.then(() => { 51 document.body.removeChild(div); 52 }); 53 }, 54 55 stylesheet_with_attrs: async (path, attribute_map) => { 56 const link = document.createElement("link"); 57 if (attribute_map instanceof Object) { 58 for (const [key, value] of Object.entries(attribute_map)) { 59 link[key] = value; 60 } 61 } 62 link.rel = "stylesheet"; 63 link.type = "text/css"; 64 link.href = load.cache_bust(path); 65 66 const loaded = new Promise(resolve => { 67 link.onload = link.onerror = resolve; 68 }); 69 70 document.head.appendChild(link); 71 await loaded; 72 document.head.removeChild(link); 73 }, 74 75 // Returns a promise that settles once the given path has been fetched as a 76 // stylesheet resource. 77 stylesheet: async path => { 78 return load.stylesheet_with_attrs(path, undefined); 79 }, 80 81 iframe_with_attrs: async (path, attribute_map, validator, skip_wait_for_navigation) => { 82 const frame = document.createElement("iframe"); 83 if (attribute_map instanceof Object) { 84 for (const [key, value] of Object.entries(attribute_map)) { 85 frame[key] = value; 86 } 87 } 88 const loaded = new Promise(resolve => { 89 frame.onload = frame.onerror = resolve; 90 }); 91 frame.src = load.cache_bust(path); 92 document.body.appendChild(frame); 93 if ( !skip_wait_for_navigation ) { 94 await loaded; 95 } 96 if (validator instanceof Function) { 97 validator(frame); 98 } 99 // since we skipped the wait for load animation, we cannot 100 // remove the iframe here since the request could get cancelled 101 if ( !skip_wait_for_navigation ) { 102 document.body.removeChild(frame); 103 } 104 }, 105 106 // Returns a promise that settles once the given path has been fetched as an 107 // iframe. 108 iframe: async (path, validator) => { 109 return load.iframe_with_attrs(path, undefined, validator); 110 }, 111 112 script_with_attrs: async (path, attribute_map) => { 113 const script = document.createElement("script"); 114 if (attribute_map instanceof Object) { 115 for (const [key, value] of Object.entries(attribute_map)) { 116 script[key] = value; 117 } 118 } 119 const loaded = new Promise(resolve => { 120 script.onload = script.onerror = resolve; 121 }); 122 script.src = load.cache_bust(path); 123 document.body.appendChild(script); 124 await loaded; 125 document.body.removeChild(script); 126 }, 127 128 // Returns a promise that settles once the given path has been fetched as a 129 // script. 130 script: async path => { 131 return load.script_with_attrs(path, undefined); 132 }, 133 134 // Returns a promise that settles once the given path has been fetched as an 135 // object. 136 object: async (path, type) => { 137 const object = document.createElement("object"); 138 const object_load_settled = new Promise(resolve => { 139 object.onload = object.onerror = resolve; 140 }); 141 object.data = load.cache_bust(path); 142 if (type) { 143 object.type = type; 144 } 145 document.body.appendChild(object); 146 await await_with_timeout(2000, 147 "Timeout was reached before load or error events fired", 148 object_load_settled, 149 () => { document.body.removeChild(object) } 150 ); 151 }, 152 153 // Returns a promise that settles once the given path has been fetched 154 // through a synchronous XMLHttpRequest. 155 xhr_sync: async (path, headers) => { 156 const xhr = new XMLHttpRequest; 157 xhr.open("GET", path, /* async = */ false); 158 if (headers instanceof Object) { 159 for (const [key, value] of Object.entries(headers)) { 160 xhr.setRequestHeader(key, value); 161 } 162 } 163 xhr.send(); 164 }, 165 166 xhr_async: path => { 167 const xhr = new XMLHttpRequest(); 168 xhr.open("GET", path) 169 xhr.send(); 170 return new Promise(resolve => { 171 xhr.onload = xhr.onerror = resolve; 172 }); 173 } 174 };