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