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