]> git.proxmox.com Git - mirror_novnc.git/blob - vendor/browser-es-module-loader/src/browser-es-module-loader.js
Only use unhandledrejection fallback when needed
[mirror_novnc.git] / vendor / browser-es-module-loader / src / browser-es-module-loader.js
1 import RegisterLoader from 'es-module-loader/core/register-loader.js';
2 import { InternalModuleNamespace as ModuleNamespace } from 'es-module-loader/core/loader-polyfill.js';
3
4 import { baseURI, global, isBrowser } from 'es-module-loader/core/common.js';
5 import { resolveIfNotPlain } from 'es-module-loader/core/resolve.js';
6
7 var loader;
8
9 // <script type="module"> support
10 var anonSources = {};
11 if (typeof document != 'undefined' && document.getElementsByTagName) {
12 function handleError(err) {
13 // dispatch an error event so that we can display in errors in browsers
14 // that don't yet support unhandledrejection
15 if (window.onunhandledrejection === undefined) {
16 try {
17 var evt = new Event('error');
18 } catch (_eventError) {
19 var evt = document.createEvent('Event');
20 evt.initEvent('error', true, true);
21 }
22 evt.message = err.message;
23 evt.error = err;
24 window.dispatchEvent(evt);
25 }
26
27 // throw so it still shows up in the console
28 throw err;
29 }
30
31 function ready() {
32 document.removeEventListener('DOMContentLoaded', ready, false );
33
34 var anonCnt = 0;
35
36 var scripts = document.getElementsByTagName('script');
37 for (var i = 0; i < scripts.length; i++) {
38 var script = scripts[i];
39 if (script.type == 'module' && !script.loaded) {
40 script.loaded = true;
41 if (script.src) {
42 loader.import(script.src).catch(handleError);
43 }
44 // anonymous modules supported via a custom naming scheme and registry
45 else {
46 var uri = './<anon' + ++anonCnt + '>';
47 if (script.id !== ""){
48 uri = "./" + script.id;
49 }
50
51 var anonName = resolveIfNotPlain(uri, baseURI);
52 anonSources[anonName] = script.innerHTML;
53 loader.import(anonName).catch(handleError);
54 }
55 }
56 }
57 }
58
59 // simple DOM ready
60 if (document.readyState === 'complete')
61 setTimeout(ready);
62 else
63 document.addEventListener('DOMContentLoaded', ready, false);
64 }
65
66 function BrowserESModuleLoader(baseKey) {
67 if (baseKey)
68 this.baseKey = resolveIfNotPlain(baseKey, baseURI) || resolveIfNotPlain('./' + baseKey, baseURI);
69
70 RegisterLoader.call(this);
71
72 var loader = this;
73
74 // ensure System.register is available
75 global.System = global.System || {};
76 if (typeof global.System.register == 'function')
77 var prevRegister = global.System.register;
78 global.System.register = function() {
79 loader.register.apply(loader, arguments);
80 if (prevRegister)
81 prevRegister.apply(this, arguments);
82 };
83 }
84 BrowserESModuleLoader.prototype = Object.create(RegisterLoader.prototype);
85
86 // normalize is never given a relative name like "./x", that part is already handled
87 BrowserESModuleLoader.prototype[RegisterLoader.resolve] = function(key, parent) {
88 var resolved = RegisterLoader.prototype[RegisterLoader.resolve].call(this, key, parent || this.baseKey) || key;
89 if (!resolved)
90 throw new RangeError('ES module loader does not resolve plain module names, resolving "' + key + '" to ' + parent);
91
92 return resolved;
93 };
94
95 function xhrFetch(url, resolve, reject) {
96 var xhr = new XMLHttpRequest();
97 function load(source) {
98 resolve(xhr.responseText);
99 }
100 function error() {
101 reject(new Error('XHR error' + (xhr.status ? ' (' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : '') + ')' : '') + ' loading ' + url));
102 }
103
104 xhr.onreadystatechange = function () {
105 if (xhr.readyState === 4) {
106 // in Chrome on file:/// URLs, status is 0
107 if (xhr.status == 0) {
108 if (xhr.responseText) {
109 load();
110 }
111 else {
112 // when responseText is empty, wait for load or error event
113 // to inform if it is a 404 or empty file
114 xhr.addEventListener('error', error);
115 xhr.addEventListener('load', load);
116 }
117 }
118 else if (xhr.status === 200) {
119 load();
120 }
121 else {
122 error();
123 }
124 }
125 };
126 xhr.open("GET", url, true);
127 xhr.send(null);
128 }
129
130 var WorkerPool = function (script, size) {
131 this._workers = new Array(size);
132 this._ind = 0;
133 this._size = size;
134 this._jobs = 0;
135 this.onmessage = undefined;
136 this._stopTimeout = undefined;
137 for (let i = 0; i < size; i++) {
138 let wrkr = new Worker(script);
139 wrkr._count = 0;
140 wrkr._ind = i;
141 wrkr.onmessage = this._onmessage.bind(this, wrkr);
142 this._workers[i] = wrkr;
143 }
144
145 this._checkJobs();
146 };
147 WorkerPool.prototype = {
148 postMessage: function (msg) {
149 if (this._stopTimeout !== undefined) {
150 clearTimeout(this._stopTimeout);
151 this._stopTimeout = undefined;
152 }
153 let wrkr = this._workers[this._ind % this._size];
154 wrkr._count++;
155 this._jobs++;
156 wrkr.postMessage(msg);
157 this._ind++;
158 },
159
160 _onmessage: function (wrkr, evt) {
161 wrkr._count--;
162 this._jobs--;
163 this.onmessage(evt, wrkr);
164 this._checkJobs();
165 },
166
167 _checkJobs: function () {
168 if (this._jobs === 0 && this._stopTimeout === undefined) {
169 // wait for 2s of inactivity before stopping (that should be enough for local loading)
170 this._stopTimeout = setTimeout(this._stop.bind(this), 2000);
171 }
172 },
173
174 _stop: function () {
175 this._workers.forEach(function(wrkr) {
176 wrkr.terminate();
177 });
178 }
179 };
180
181 var promiseMap = new Map();
182 var babelWorker = new WorkerPool('/vendor/browser-es-module-loader/dist/babel-worker.js', 3);
183 babelWorker.onmessage = function (evt) {
184 var promFuncs = promiseMap.get(evt.data.key);
185 promFuncs.resolve(evt.data);
186 promiseMap.delete(evt.data.key);
187 };
188
189 // instantiate just needs to run System.register
190 // so we fetch the source, convert into the Babel System module format, then evaluate it
191 BrowserESModuleLoader.prototype[RegisterLoader.instantiate] = function(key, processAnonRegister) {
192 var loader = this;
193
194 // load as ES with Babel converting into System.register
195 return new Promise(function(resolve, reject) {
196 // anonymous module
197 if (anonSources[key]) {
198 resolve(anonSources[key])
199 anonSources[key] = undefined;
200 }
201 // otherwise we fetch
202 else {
203 xhrFetch(key, resolve, reject);
204 }
205 })
206 .then(function(source) {
207 // check our cache first
208 const cacheEntryTrans = localStorage.getItem(key+'!transpiled');
209 if (cacheEntryTrans) {
210 const cacheEntryRaw = localStorage.getItem(key+'!raw');
211 // TODO: store a hash instead
212 if (cacheEntryRaw === source) {
213 return Promise.resolve({key: key, code: cacheEntryTrans, source: source});
214 }
215 }
216 return new Promise(function (resolve, reject) {
217 promiseMap.set(key, {resolve: resolve, reject: reject});
218 babelWorker.postMessage({key: key, source: source});
219 });
220 }).then(function (data) {
221 // evaluate without require, exports and module variables
222 // we leave module in for now to allow module.require access
223 if (data.key.slice(-8) !== '#nocache') {
224 localStorage.setItem(key+'!raw', data.source);
225 localStorage.setItem(data.key+'!transpiled', data.code);
226 }
227 (0, eval)(data.code + '\n//# sourceURL=' + data.key + '!transpiled');
228 processAnonRegister();
229 });
230 };
231
232 // create a default loader instance in the browser
233 if (isBrowser)
234 loader = new BrowserESModuleLoader();
235
236 export default BrowserESModuleLoader;