From 152c399513e250aecda206ce38e2cf934d918c6c Mon Sep 17 00:00:00 2001 From: Solly Ross Date: Wed, 1 Mar 2017 11:17:44 -0500 Subject: [PATCH] Vendor in an IE11 polyfill for Promises This commit introduces a polyfill to add support for Promises in IE11. This means IE11 can be tested without first running `utils/as_require.js`. --- LICENSE.txt | 2 + utils/use_require.js | 41 +-- utils/use_require_helpers.js | 8 +- .../dist/browser-es-module-loader.js | 4 +- .../src/browser-es-module-loader.js | 4 +- vendor/promise.js | 255 ++++++++++++++++++ vnc.html | 2 + 7 files changed, 295 insertions(+), 21 deletions(-) create mode 100644 vendor/promise.js diff --git a/LICENSE.txt b/LICENSE.txt index e9a3e15e..a8d24e39 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -43,6 +43,8 @@ licenses (all MPL 2.0 compatible): vendor/browser-es-module-loader: MIT + vendor/promise.js : MIT + Any other files not mentioned above are typically marked with a copyright/license header at the top of the file. The default noVNC license is MPL-2.0. diff --git a/utils/use_require.js b/utils/use_require.js index f0383d09..8522256b 100755 --- a/utils/use_require.js +++ b/utils/use_require.js @@ -14,24 +14,29 @@ program .parse(process.argv); // the various important paths -var main_path = path.resolve(__dirname, '..'); -var core_path = path.resolve(__dirname, '..', 'core'); -var app_path = path.resolve(__dirname, '..', 'app'); -var vendor_path = path.resolve(__dirname, '..', 'vendor'); -var out_dir_base = path.resolve(__dirname, '..', 'build'); -var lib_dir_base = path.resolve(__dirname, '..', 'lib'); +const paths = { + main: path.resolve(__dirname, '..'), + core: path.resolve(__dirname, '..', 'core'), + app: path.resolve(__dirname, '..', 'app'), + vendor: path.resolve(__dirname, '..', 'vendor'), + out_dir_base: path.resolve(__dirname, '..', 'build'), + lib_dir_base: path.resolve(__dirname, '..', 'lib'), +}; const no_copy_files = new Set([ // skip these -- they don't belong in the processed application - path.join(vendor_path, 'sinon.js'), - path.join(vendor_path, 'browser-es-module-loader'), + path.join(paths.vendor, 'sinon.js'), + path.join(paths.vendor, 'browser-es-module-loader'), + path.join(paths.vendor, 'promise.js'), ]); const no_transform_files = new Set([ // don't transform this -- we want it imported as-is to properly catch loading errors - path.join(app_path, 'error-handler.js'), + path.join(paths.app, 'error-handler.js'), ]); +no_copy_files.forEach((file) => no_transform_files.add(file)); + // walkDir *recursively* walks directories trees, // calling the callback for all normal files found. var walkDir = function (base_path, cb, filter) { @@ -55,7 +60,7 @@ var walkDir = function (base_path, cb, filter) { var transform_html = function (new_script) { // write out the modified vnc.html file that works with the bundle var src_html_path = path.resolve(__dirname, '..', 'vnc.html'); - var out_html_path = path.resolve(out_dir_base, 'vnc.html'); + var out_html_path = path.resolve(paths.out_dir_base, 'vnc.html'); fs.readFile(src_html_path, (err, contents_raw) => { if (err) { throw err; } @@ -92,10 +97,10 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) { var in_path; if (with_app_dir) { - var out_path_base = out_dir_base; - in_path = main_path; + var out_path_base = paths.out_dir_base; + in_path = paths.main; } else { - var out_path_base = lib_dir_base; + var out_path_base = paths.lib_dir_base; } fse.ensureDirSync(out_path_base); @@ -144,11 +149,15 @@ var make_lib_files = function (import_format, source_maps, with_app_dir) { }); }; - walkDir(core_path, handleDir.bind(null, true, in_path || core_path), (filename, stats) => !no_copy_files.has(filename)); - walkDir(vendor_path, handleDir.bind(null, true, in_path || main_path), (filename, stats) => !no_copy_files.has(filename)); + if (with_app_dir && helper && helper.noCopyOverride) { + helper.noCopyOverride(paths, no_copy_files); + } + + walkDir(paths.core, handleDir.bind(null, true, in_path || paths.core), (filename, stats) => !no_copy_files.has(filename)); + walkDir(paths.vendor, handleDir.bind(null, true, in_path || paths.main), (filename, stats) => !no_copy_files.has(filename)); if (with_app_dir) { - walkDir(app_path, handleDir.bind(null, false, in_path || app_path), (filename, stats) => !no_copy_files.has(filename)); + walkDir(paths.app, handleDir.bind(null, false, in_path || paths.app), (filename, stats) => !no_copy_files.has(filename)); const out_app_path = path.join(out_path_base, 'app.js'); if (helper && helper.appWriter) { diff --git a/utils/use_require_helpers.js b/utils/use_require_helpers.js index b8ac46e6..990fb4df 100644 --- a/utils/use_require_helpers.js +++ b/utils/use_require_helpers.js @@ -11,6 +11,7 @@ module.exports = { console.log(`Please place RequireJS in ${path.join(base_out_path, 'require.js')}`); return ``; }, + noCopyOverride: () => {}, }, 'commonjs': { optionsOverride: (opts) => { @@ -23,12 +24,17 @@ module.exports = { b.bundle().pipe(fs.createWriteStream(out_path)); return ``; }, + noCopyOverride: () => {}, }, 'systemjs': { appWriter: (base_out_path, out_path) => { fs.writeFile(out_path, 'SystemJS.import("./app/ui.js");', (err) => { if (err) throw err; }); console.log(`Please place SystemJS in ${path.join(base_out_path, 'system-production.js')}`); - return `\n`; + return ` +\n`; + }, + noCopyOverride: (paths, no_copy_files) => { + no_copy_files.delete(path.join(paths.vendor, 'promise.js')); }, }, 'umd': { diff --git a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js index 0d22bab2..8d3a2eea 100644 --- a/vendor/browser-es-module-loader/dist/browser-es-module-loader.js +++ b/vendor/browser-es-module-loader/dist/browser-es-module-loader.js @@ -1343,9 +1343,9 @@ WorkerPool.prototype = { }, _stop: function () { - for (let wrkr of this._workers) { + this._workers.forEach(function(wrkr) { wrkr.terminate(); - } + }); } }; diff --git a/vendor/browser-es-module-loader/src/browser-es-module-loader.js b/vendor/browser-es-module-loader/src/browser-es-module-loader.js index a9d72097..6154e446 100644 --- a/vendor/browser-es-module-loader/src/browser-es-module-loader.js +++ b/vendor/browser-es-module-loader/src/browser-es-module-loader.js @@ -183,9 +183,9 @@ WorkerPool.prototype = { }, _stop: function () { - for (let wrkr of this._workers) { + this._workers.forEach(function(wrkr) { wrkr.terminate(); - } + }); } }; diff --git a/vendor/promise.js b/vendor/promise.js new file mode 100644 index 00000000..62843432 --- /dev/null +++ b/vendor/promise.js @@ -0,0 +1,255 @@ +/* Copyright (c) 2014 Taylor Hakes + * Copyright (c) 2014 Forbes Lindesay + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +(function (root) { + + // Store setTimeout reference so promise-polyfill will be unaffected by + // other code modifying setTimeout (like sinon.useFakeTimers()) + var setTimeoutFunc = setTimeout; + + function noop() {} + + // Polyfill for Function.prototype.bind + function bind(fn, thisArg) { + return function () { + fn.apply(thisArg, arguments); + }; + } + + function Promise(fn) { + if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new'); + if (typeof fn !== 'function') throw new TypeError('not a function'); + this._state = 0; + this._handled = false; + this._value = undefined; + this._deferreds = []; + + doResolve(fn, this); + } + + function handle(self, deferred) { + while (self._state === 3) { + self = self._value; + } + if (self._state === 0) { + self._deferreds.push(deferred); + return; + } + self._handled = true; + Promise._immediateFn(function () { + var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; + if (cb === null) { + (self._state === 1 ? resolve : reject)(deferred.promise, self._value); + return; + } + var ret; + try { + ret = cb(self._value); + } catch (e) { + reject(deferred.promise, e); + return; + } + resolve(deferred.promise, ret); + }); + } + + function resolve(self, newValue) { + try { + // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure + if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.'); + if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { + var then = newValue.then; + if (newValue instanceof Promise) { + self._state = 3; + self._value = newValue; + finale(self); + return; + } else if (typeof then === 'function') { + doResolve(bind(then, newValue), self); + return; + } + } + self._state = 1; + self._value = newValue; + finale(self); + } catch (e) { + reject(self, e); + } + } + + function reject(self, newValue) { + self._state = 2; + self._value = newValue; + finale(self); + } + + function finale(self) { + if (self._state === 2 && self._deferreds.length === 0) { + Promise._immediateFn(function() { + if (!self._handled) { + Promise._unhandledRejectionFn(self._value); + } + }); + } + + for (var i = 0, len = self._deferreds.length; i < len; i++) { + handle(self, self._deferreds[i]); + } + self._deferreds = null; + } + + function Handler(onFulfilled, onRejected, promise) { + this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null; + this.onRejected = typeof onRejected === 'function' ? onRejected : null; + this.promise = promise; + } + + /** + * Take a potentially misbehaving resolver function and make sure + * onFulfilled and onRejected are only called once. + * + * Makes no guarantees about asynchrony. + */ + function doResolve(fn, self) { + var done = false; + try { + fn(function (value) { + if (done) return; + done = true; + resolve(self, value); + }, function (reason) { + if (done) return; + done = true; + reject(self, reason); + }); + } catch (ex) { + if (done) return; + done = true; + reject(self, ex); + } + } + + Promise.prototype['catch'] = function (onRejected) { + return this.then(null, onRejected); + }; + + Promise.prototype.then = function (onFulfilled, onRejected) { + var prom = new (this.constructor)(noop); + + handle(this, new Handler(onFulfilled, onRejected, prom)); + return prom; + }; + + Promise.all = function (arr) { + var args = Array.prototype.slice.call(arr); + + return new Promise(function (resolve, reject) { + if (args.length === 0) return resolve([]); + var remaining = args.length; + + function res(i, val) { + try { + if (val && (typeof val === 'object' || typeof val === 'function')) { + var then = val.then; + if (typeof then === 'function') { + then.call(val, function (val) { + res(i, val); + }, reject); + return; + } + } + args[i] = val; + if (--remaining === 0) { + resolve(args); + } + } catch (ex) { + reject(ex); + } + } + + for (var i = 0; i < args.length; i++) { + res(i, args[i]); + } + }); + }; + + Promise.resolve = function (value) { + if (value && typeof value === 'object' && value.constructor === Promise) { + return value; + } + + return new Promise(function (resolve) { + resolve(value); + }); + }; + + Promise.reject = function (value) { + return new Promise(function (resolve, reject) { + reject(value); + }); + }; + + Promise.race = function (values) { + return new Promise(function (resolve, reject) { + for (var i = 0, len = values.length; i < len; i++) { + values[i].then(resolve, reject); + } + }); + }; + + // Use polyfill for setImmediate for performance gains + Promise._immediateFn = (typeof setImmediate === 'function' && function (fn) { setImmediate(fn); }) || + function (fn) { + setTimeoutFunc(fn, 0); + }; + + Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) { + if (typeof console !== 'undefined' && console) { + console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console + } + }; + + /** + * Set the immediate function to execute callbacks + * @param fn {function} Function to execute + * @deprecated + */ + Promise._setImmediateFn = function _setImmediateFn(fn) { + Promise._immediateFn = fn; + }; + + /** + * Change the function to execute on unhandled rejection + * @param {function} fn Function to execute on unhandled rejection + * @deprecated + */ + Promise._setUnhandledRejectionFn = function _setUnhandledRejectionFn(fn) { + Promise._unhandledRejectionFn = fn; + }; + + if (typeof module !== 'undefined' && module.exports) { + module.exports = Promise; + } else if (!root.Promise) { + root.Promise = Promise; + } + +})(this); diff --git a/vnc.html b/vnc.html index 10cc1430..02fe5c69 100644 --- a/vnc.html +++ b/vnc.html @@ -63,6 +63,8 @@ + +