diff --git a/tests/init.js b/tests/init.js index b77d049f..d2a9fe5d 100644 --- a/tests/init.js +++ b/tests/init.js @@ -30,14 +30,19 @@ require.config({ baseUrl: (window.__karma__ ? 'base/' : '') + 'www/js/lib/', paths: { 'jquery': 'jquery-3.2.1.slim', - 'webpHeroBundle': 'webpHeroBundle_0.0.0-dev.27', - 'webpHeroPolyfills': 'webpHeroPolyfills_0.0.0-dev.27' + 'webpHeroBundle': 'webpHeroBundle_0.0.0-dev.27' }, shim: { - 'webpHeroBundle': { - deps: ['webpHeroPolyfills'] - } + 'webpHeroBundle': '' } }); -requirejs(['../../../tests/tests']); \ No newline at end of file +var req = []; // Baseline Require array + +// Add polyfills to the Require array only if needed +if (!('Promise' in self)) req.push('promisePolyfill'); +if (!('from' in Array)) req.push('arrayFromPolyfill'); + +requirejs(req, function () { + requirejs(['../../../tests/tests']); +}); diff --git a/tests/tests.js b/tests/tests.js index a9daea39..110af528 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -109,13 +109,13 @@ define(['jquery', 'zimArchive', 'zimDirEntry', 'util', 'uiUtil', 'utf8'], var testString3 = "le Couvre-chef Est sur le porte-manteaux"; var testString4 = "épée"; var testString5 = '$¥€“«xριστός» †¡Ἀνέστη!”'; - var testString6 = "Καλά Νερά Μαγνησίας žižek"; + var testString6 = "Καλά Νερά Μαγνησία žižek"; assert.equal(util.allCaseFirstLetters(testString1).indexOf("Téléphone") >= 0, true, "The first letter should be uppercase"); assert.equal(util.allCaseFirstLetters(testString2).indexOf("paris") >= 0, true, "The first letter should be lowercase"); assert.equal(util.allCaseFirstLetters(testString3).indexOf("Le Couvre-Chef Est Sur Le Porte-Manteaux") >= 0, true, "The first letter of every word should be uppercase"); assert.equal(util.allCaseFirstLetters(testString4).indexOf("Épée") >= 0, true, "The first letter should be uppercase (with accent)"); assert.equal(util.allCaseFirstLetters(testString5).indexOf('$¥€“«Xριστός» †¡ἀνέστη!”') >= 0, true, "First non-punctuation/non-currency Unicode letter should be uppercase, second (with breath mark) lowercase"); - assert.equal(util.allCaseFirstLetters(testString6, "full").indexOf("ΚΑΛΆ ΝΕΡΆ ΜΑΓΝΗΣΊΑΣ ŽIŽEK") >= 0, true, "All Unicode letters should be uppercase"); + assert.equal(util.allCaseFirstLetters(testString6, "full").indexOf("ΚΑΛΆ ΝΕΡΆ ΜΑΓΝΗΣΊΑ ŽIŽEK") >= 0, true, "All Unicode letters should be uppercase"); }); QUnit.test("check removal of parameters in URL", function(assert) { var testUrl1 = "A/question.html"; diff --git a/www/js/app.js b/www/js/app.js index abb7a13e..85d6d511 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -26,8 +26,8 @@ // This uses require.js to structure javascript: // http://requirejs.org/docs/api.html#define -define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesystemAccess','q'], - function($, zimArchiveLoader, uiUtil, settingsStore, abstractFilesystemAccess, Q) { +define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesystemAccess'], + function($, zimArchiveLoader, uiUtil, settingsStore, abstractFilesystemAccess) { /** * The delay (in milliseconds) between two "keepalive" messages sent to the ServiceWorker (so that it is not stopped @@ -475,7 +475,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys * @returns {Promise} A Promise for an object with cache attributes 'type', 'description', and 'count' */ function getCacheAttributes() { - return Q.Promise(function (resolve, reject) { + return new Promise(function (resolve, reject) { if (contentInjectionMode === 'serviceworker') { // Create a Message Channel var channel = new MessageChannel(); @@ -963,26 +963,24 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys * @returns {Promise} A promise for the requested file (blob) */ function readRemoteArchive(url) { - // DEV: This deferred can't be standardized to a Promise/A+ pattern (using Q) because - // IE11 is unable to scope the callbacks inside the Promise correctly. See [kiwix.js #589] - var deferred = Q.defer(); - var request = new XMLHttpRequest(); - request.open("GET", url); - request.responseType = "blob"; - request.onreadystatechange = function () { - if (request.readyState === XMLHttpRequest.DONE) { - if (request.status >= 200 && request.status < 300 || request.status === 0) { - // Hack to make this look similar to a file - request.response.name = url; - deferred.resolve(request.response); - } else { - deferred.reject("HTTP status " + request.status + " when reading " + url); + return new Promise(function (resolve, reject) { + var request = new XMLHttpRequest(); + request.open("GET", url); + request.responseType = "blob"; + request.onreadystatechange = function () { + if (request.readyState === XMLHttpRequest.DONE) { + if (request.status >= 200 && request.status < 300 || request.status === 0) { + // Hack to make this look similar to a file + request.response.name = url; + resolve(request.response); + } else { + reject("HTTP status " + request.status + " when reading " + url); + } } - } - }; - request.onabort = request.onerror = deferred.reject; - request.send(); - return deferred.promise; + }; + request.onabort = request.onerror = reject; + request.send(); + }); } /** @@ -994,7 +992,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys Array.prototype.slice.call(arguments).forEach(function (arg) { readRequests.push(readRemoteArchive(arg)); }); - return Q.all(readRequests).then(function (arrayOfArchives) { + return Promise.all(readRequests).then(function (arrayOfArchives) { setLocalArchiveFromFileList(arrayOfArchives); }).catch(function (e) { console.error('Unable to load remote archive(s)', e); diff --git a/www/js/init.js b/www/js/init.js index eb2f149a..5cd2c49b 100644 --- a/www/js/init.js +++ b/www/js/init.js @@ -36,7 +36,6 @@ require.config({ 'jquery': 'jquery-3.2.1.slim', 'bootstrap': 'bootstrap.bundle', 'webpHeroBundle': 'webpHeroBundle_0.0.0-dev.27', - 'webpHeroPolyfills': 'webpHeroPolyfills_0.0.0-dev.27', 'fontawesome': 'fontawesome/fontawesome', 'fontawesome-solid': 'fontawesome/solid' }, @@ -47,13 +46,17 @@ require.config({ 'bootstrap': { deps: ['jquery', 'fontawesome', 'fontawesome-solid'] }, - 'webpHeroBundle': { - deps: ['webpHeroPolyfills'] - } + 'webpHeroBundle': '' } }); -requirejs(['bootstrap'], function (bootstrap) { +var req = ['bootstrap']; // Baseline Require array + +// Add polyfills to the Require array only if needed +if (!('Promise' in self)) req.push('promisePolyfill'); +if (!('from' in Array)) req.push('arrayFromPolyfill'); + +requirejs(req, function () { requirejs(['../app']); }); diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js index 088a8d96..b48eb010 100644 --- a/www/js/lib/abstractFilesystemAccess.js +++ b/www/js/lib/abstractFilesystemAccess.js @@ -25,7 +25,7 @@ * along with Kiwix (file LICENSE-GPLv3.txt). If not, see */ 'use strict'; -define(['q', 'jquery'], function(q, jQuery) { +define([], function() { /** * Storage implemented by Firefox OS @@ -50,11 +50,12 @@ define(['q', 'jquery'], function(q, jQuery) { * rejected with an error message. */ StorageFirefoxOS.prototype.get = function(path) { - var deferred = q.defer(); - var request = this._storage.get(path); - request.onsuccess = function() { deferred.resolve(this.result); }; - request.onerror = function() { deferred.reject(this.error.name); }; - return deferred.promise; + var that = this; + return new Promise(function (resolve, reject){ + var request = that._storage.get(path); + request.onsuccess = function() { resolve(this.result); }; + request.onerror = function() { reject(this.error.name); }; + }); }; // We try to match both a standalone ZIM file (.zim) or @@ -67,26 +68,27 @@ define(['q', 'jquery'], function(q, jQuery) { * paths and rejected with an error message. */ StorageFirefoxOS.prototype.scanForArchives = function() { - var deferred = jQuery.Deferred(); - var directories = []; - var cursor = this._storage.enumerate(); - cursor.onerror = function() { - deferred.reject(cursor.error); - }; - cursor.onsuccess = function() { - if (!cursor.result) { - deferred.resolve(directories); - return; - } - var file = cursor.result; + var that = this; + return new Promise(function (resolve, reject){ + var directories = []; + var cursor = that._storage.enumerate(); + cursor.onerror = function () { + reject(cursor.error); + }; + cursor.onsuccess = function () { + if (!cursor.result) { + resolve(directories); + return; + } + var file = cursor.result; - if (regexpZIMFileName.test(file.name)) { - directories.push(file.name); - } + if (regexpZIMFileName.test(file.name)) { + directories.push(file.name); + } - cursor.continue(); - }; - return deferred.promise(); + cursor.continue(); + }; + }); }; /** diff --git a/www/js/lib/arrayFromPolyfill.js b/www/js/lib/arrayFromPolyfill.js new file mode 100644 index 00000000..6e2c11e2 --- /dev/null +++ b/www/js/lib/arrayFromPolyfill.js @@ -0,0 +1,33 @@ +/** + * Simple Array.from polyfill (with Set support) from https://stackoverflow.com/a/62682524/9727685 + */ +(function () { + + function arrayFrom(arr, callbackFn, thisArg) { + //if you need you can uncomment the following line + //if(!arr || typeof arr == 'function')throw new Error('This function requires an array-like object - not null, undefined or a function'); + + var arNew = [], + k = [], // used for convert Set to an Array + i = 0; + + //if you do not need a Set object support then + //you can comment or delete the following if statement + if (window.Set && arr instanceof Set) { + //we use forEach from Set object + arr.forEach(function (v) { + k.push(v) + }); + arr = k; + } + + for (; i < arr.length; i++) + arNew[i] = callbackFn ? + callbackFn.call(thisArg, arr[i], i, arr) : + arr[i]; + + return arNew; + } + //You could also use it without the following line, but it is not recommended because native function is faster. + Array.from = Array.from || arrayFrom; //We set it as polyfill +}()); \ No newline at end of file diff --git a/www/js/lib/filecache.js b/www/js/lib/filecache.js index f3bb87fa..040d3bf2 100644 --- a/www/js/lib/filecache.js +++ b/www/js/lib/filecache.js @@ -23,7 +23,7 @@ */ 'use strict'; -define(['q'], function (Q) { +define([], function () { /** * Set maximum number of cache blocks of BLOCK_SIZE bytes each * Maximum size of cache in bytes = MAX_CACHE_SIZE * BLOCK_SIZE @@ -166,7 +166,7 @@ define(['q'], function (Q) { // misses = 0; // } // Wait for all the blocks to be read either from the cache or from the archive - return Q.all(readRequests).then(function () { + return Promise.all(readRequests).then(function () { var result = new Uint8Array(end - begin); var pos = 0; // Stitch together the data parts in the right order diff --git a/www/js/lib/promisePolyfill.js b/www/js/lib/promisePolyfill.js new file mode 100644 index 00000000..734ff750 --- /dev/null +++ b/www/js/lib/promisePolyfill.js @@ -0,0 +1,878 @@ +/* + Yaku v0.19.3 + (c) 2015 Yad Smood. http://ysmood.org + License MIT +*/ +/* + Yaku v0.17.9 + (c) 2015 Yad Smood. http://ysmood.org + License MIT +*/ + +(function () { + 'use strict'; + + var $undefined + , $null = null + , isBrowser = typeof self === 'object' + , root = self + , nativePromise = root.Promise + , process = root.process + , console = root.console + , isLongStackTrace = true + , Arr = Array + , Err = Error + + , $rejected = 1 + , $resolved = 2 + , $pending = 3 + + , $Symbol = 'Symbol' + , $iterator = 'iterator' + , $species = 'species' + , $speciesKey = $Symbol + '(' + $species + ')' + , $return = 'return' + + , $unhandled = '_uh' + , $promiseTrace = '_pt' + , $settlerTrace = '_st' + + , $invalidThis = 'Invalid this' + , $invalidArgument = 'Invalid argument' + , $fromPrevious = '\nFrom previous ' + , $promiseCircularChain = 'Chaining cycle detected for promise' + , $unhandledRejectionMsg = 'Uncaught (in promise)' + , $rejectionHandled = 'rejectionHandled' + , $unhandledRejection = 'unhandledRejection' + + , $tryCatchFn + , $tryCatchThis + , $tryErr = { e: $null } + , $noop = function () {} + , $cleanStackReg = /^.+\/node_modules\/yaku\/.+\n?/mg + ; + + /** + * This class follows the [Promises/A+](https://promisesaplus.com) and + * [ES6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-promise-objects) spec + * with some extra helpers. + * @param {Function} executor Function object with two arguments resolve, reject. + * The first argument fulfills the promise, the second argument rejects it. + * We can call these functions, once our operation is completed. + */ + var Yaku = function (executor) { + var self = this, + err; + + // "this._s" is the internao state of: pending, resolved or rejected + // "this._v" is the internal value + + if (!isObject(self) || self._s !== $undefined) + throw genTypeError($invalidThis); + + self._s = $pending; + + if (isLongStackTrace) self[$promiseTrace] = genTraceInfo(); + + if (executor !== $noop) { + if (!isFunction(executor)) + throw genTypeError($invalidArgument); + + err = genTryCatcher(executor)( + genSettler(self, $resolved), + genSettler(self, $rejected) + ); + + if (err === $tryErr) + settlePromise(self, $rejected, err.e); + } + }; + + Yaku['default'] = Yaku; + + extend(Yaku.prototype, { + /** + * Appends fulfillment and rejection handlers to the promise, + * and returns a new promise resolving to the return value of the called handler. + * @param {Function} onFulfilled Optional. Called when the Promise is resolved. + * @param {Function} onRejected Optional. Called when the Promise is rejected. + * @return {Yaku} It will return a new Yaku which will resolve or reject after + * @example + * the current Promise. + * ```js + * var Promise = require('yaku'); + * var p = Promise.resolve(10); + * + * p.then((v) => { + * console.log(v); + * }); + * ``` + */ + then: function (onFulfilled, onRejected) { + if (this._s === undefined) throw genTypeError(); + + return addHandler( + this, + newCapablePromise(Yaku.speciesConstructor(this, Yaku)), + onFulfilled, + onRejected + ); + }, + + /** + * The `catch()` method returns a Promise and deals with rejected cases only. + * It behaves the same as calling `Promise.prototype.then(undefined, onRejected)`. + * @param {Function} onRejected A Function called when the Promise is rejected. + * This function has one argument, the rejection reason. + * @return {Yaku} A Promise that deals with rejected cases only. + * @example + * ```js + * var Promise = require('yaku'); + * var p = Promise.reject(new Error("ERR")); + * + * p['catch']((v) => { + * console.log(v); + * }); + * ``` + */ + 'catch': function (onRejected) { + return this.then($undefined, onRejected); + }, + + /** + * Register a callback to be invoked when a promise is settled (either fulfilled or rejected). + * Similar with the try-catch-finally, it's often used for cleanup. + * @param {Function} onFinally A Function called when the Promise is settled. + * It will not receive any argument. + * @return {Yaku} A Promise that will reject if onFinally throws an error or returns a rejected promise. + * Else it will resolve previous promise's final state (either fulfilled or rejected). + * @example + * ```js + * var Promise = require('yaku'); + * var p = Math.random() > 0.5 ? Promise.resolve() : Promise.reject(); + * p.finally(() => { + * console.log('finally'); + * }); + * ``` + */ + 'finally': function (onFinally) { + return this.then(function (val) { + return Yaku.resolve(onFinally()).then(function () { + return val; + }); + }, function (err) { + return Yaku.resolve(onFinally()).then(function () { + throw err; + }); + }); + }, + + // The number of current promises that attach to this Yaku instance. + _c: 0, + + // The parent Yaku. + _p: $null + }); + + /** + * The `Promise.resolve(value)` method returns a Promise object that is resolved with the given value. + * If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable, + * adopting its eventual state; otherwise the returned promise will be fulfilled with the value. + * @param {Any} value Argument to be resolved by this Promise. + * Can also be a Promise or a thenable to resolve. + * @return {Yaku} + * @example + * ```js + * var Promise = require('yaku'); + * var p = Promise.resolve(10); + * ``` + */ + Yaku.resolve = function (val) { + return isYaku(val) ? val : settleWithX(newCapablePromise(this), val); + }; + + /** + * The `Promise.reject(reason)` method returns a Promise object that is rejected with the given reason. + * @param {Any} reason Reason why this Promise rejected. + * @return {Yaku} + * @example + * ```js + * var Promise = require('yaku'); + * var p = Promise.reject(new Error("ERR")); + * ``` + */ + Yaku.reject = function (reason) { + return settlePromise(newCapablePromise(this), $rejected, reason); + }; + + /** + * The `Promise.race(iterable)` method returns a promise that resolves or rejects + * as soon as one of the promises in the iterable resolves or rejects, + * with the value or reason from that promise. + * @param {iterable} iterable An iterable object, such as an Array. + * @return {Yaku} The race function returns a Promise that is settled + * the same way as the first passed promise to settle. + * It resolves or rejects, whichever happens first. + * @example + * ```js + * var Promise = require('yaku'); + * Promise.race([ + * 123, + * Promise.resolve(0) + * ]) + * .then((value) => { + * console.log(value); // => 123 + * }); + * ``` + */ + Yaku.race = function (iterable) { + var self = this + , p = newCapablePromise(self) + + , resolve = function (val) { + settlePromise(p, $resolved, val); + } + + , reject = function (val) { + settlePromise(p, $rejected, val); + } + + , ret = genTryCatcher(each)(iterable, function (v) { + self.resolve(v).then(resolve, reject); + }); + + if (ret === $tryErr) return self.reject(ret.e); + + return p; + }; + + /** + * The `Promise.all(iterable)` method returns a promise that resolves when + * all of the promises in the iterable argument have resolved. + * + * The result is passed as an array of values from all the promises. + * If something passed in the iterable array is not a promise, + * it's converted to one by Promise.resolve. If any of the passed in promises rejects, + * the all Promise immediately rejects with the value of the promise that rejected, + * discarding all the other promises whether or not they have resolved. + * @param {iterable} iterable An iterable object, such as an Array. + * @return {Yaku} + * @example + * ```js + * var Promise = require('yaku'); + * Promise.all([ + * 123, + * Promise.resolve(0) + * ]) + * .then((values) => { + * console.log(values); // => [123, 0] + * }); + * ``` + * @example + * Use with iterable. + * ```js + * var Promise = require('yaku'); + * Promise.all((function * () { + * yield 10; + * yield new Promise(function (r) { setTimeout(r, 1000, "OK") }); + * })()) + * .then((values) => { + * console.log(values); // => [123, 0] + * }); + * ``` + */ + Yaku.all = function (iterable) { + var self = this + , p1 = newCapablePromise(self) + , res = [] + , ret + ; + + function reject (reason) { + settlePromise(p1, $rejected, reason); + } + + ret = genTryCatcher(each)(iterable, function (item, i) { + self.resolve(item).then(function (value) { + res[i] = value; + if (!--ret) settlePromise(p1, $resolved, res); + }, reject); + }); + + if (ret === $tryErr) return self.reject(ret.e); + + if (!ret) settlePromise(p1, $resolved, []); + + return p1; + }; + + /** + * The ES6 Symbol object that Yaku should use, by default it will use the + * global one. + * @type {Object} + * @example + * ```js + * var core = require("core-js/library"); + * var Promise = require("yaku"); + * Promise.Symbol = core.Symbol; + * ``` + */ + Yaku.Symbol = root[$Symbol] || {}; + + // To support browsers that don't support `Object.defineProperty`. + genTryCatcher(function () { + Object.defineProperty(Yaku, getSpecies(), { + get: function () { return this; } + }); + })(); + + /** + * Use this api to custom the species behavior. + * https://tc39.github.io/ecma262/#sec-speciesconstructor + * @param {Any} O The current this object. + * @param {Function} defaultConstructor + */ + Yaku.speciesConstructor = function (O, D) { + var C = O.constructor; + + return C ? (C[getSpecies()] || D) : D; + }; + + /** + * Catch all possibly unhandled rejections. If you want to use specific + * format to display the error stack, overwrite it. + * If it is set, auto `console.error` unhandled rejection will be disabled. + * @param {Any} reason The rejection reason. + * @param {Yaku} p The promise that was rejected. + * @example + * ```js + * var Promise = require('yaku'); + * Promise.unhandledRejection = (reason) => { + * console.error(reason); + * }; + * + * // The console will log an unhandled rejection error message. + * Promise.reject('my reason'); + * + * // The below won't log the unhandled rejection error message. + * Promise.reject('v')["catch"](() => {}); + * ``` + */ + Yaku.unhandledRejection = function (reason, p) { + console && console.error( + $unhandledRejectionMsg, + isLongStackTrace ? p.longStack : genStackInfo(reason, p) + ); + }; + + /** + * Emitted whenever a Promise was rejected and an error handler was + * attached to it (for example with `["catch"]()`) later than after an event loop turn. + * @param {Any} reason The rejection reason. + * @param {Yaku} p The promise that was rejected. + */ + Yaku.rejectionHandled = $noop; + + /** + * It is used to enable the long stack trace. + * Once it is enabled, it can't be reverted. + * While it is very helpful in development and testing environments, + * it is not recommended to use it in production. It will slow down + * application and eat up memory. + * It will add an extra property `longStack` to the Error object. + * @example + * ```js + * var Promise = require('yaku'); + * Promise.enableLongStackTrace(); + * Promise.reject(new Error("err"))["catch"]((err) => { + * console.log(err.longStack); + * }); + * ``` + */ + Yaku.enableLongStackTrace = function () { + isLongStackTrace = true; + }; + + /** + * Only Node has `process.nextTick` function. For browser there are + * so many ways to polyfill it. Yaku won't do it for you, instead you + * can choose what you prefer. For example, this project + * [next-tick](https://github.com/medikoo/next-tick). + * By default, Yaku will use `process.nextTick` on Node, `setTimeout` on browser. + * @type {Function} + * @example + * ```js + * var Promise = require('yaku'); + * Promise.nextTick = require('next-tick'); + * ``` + * @example + * You can even use sync resolution if you really know what you are doing. + * ```js + * var Promise = require('yaku'); + * Promise.nextTick = fn => fn(); + * ``` + */ + Yaku.nextTick = isBrowser ? + function (fn) { + nativePromise ? + new nativePromise(function (resolve) { resolve(); }).then(fn) : + setTimeout(fn); + } : + process.nextTick; + + // ********************** Private ********************** + + Yaku._s = 1; + + /** + * All static variable name will begin with `$`. Such as `$rejected`. + * @private + */ + + // ******************************* Utils ******************************** + + function getSpecies () { + return Yaku[$Symbol][$species] || $speciesKey; + } + + function extend (src, target) { + for (var k in target) { + src[k] = target[k]; + } + } + + function isObject (obj) { + return obj && typeof obj === 'object'; + } + + function isFunction (obj) { + return typeof obj === 'function'; + } + + function isInstanceOf (a, b) { + return a instanceof b; + } + + function isError (obj) { + return isInstanceOf(obj, Err); + } + + function ensureType (obj, fn, msg) { + if (!fn(obj)) throw genTypeError(msg); + } + + /** + * Wrap a function into a try-catch. + * @private + * @return {Any | $tryErr} + */ + function tryCatcher () { + try { + return $tryCatchFn.apply($tryCatchThis, arguments); + } catch (e) { + $tryErr.e = e; + return $tryErr; + } + } + + /** + * Generate a try-catch wrapped function. + * @private + * @param {Function} fn + * @return {Function} + */ + function genTryCatcher (fn, self) { + $tryCatchFn = fn; + $tryCatchThis = self; + return tryCatcher; + } + + /** + * Generate a scheduler. + * @private + * @param {Integer} initQueueSize + * @param {Function} fn `(Yaku, Value) ->` The schedule handler. + * @return {Function} `(Yaku, Value) ->` The scheduler. + */ + function genScheduler (initQueueSize, fn) { + /** + * All async promise will be scheduled in + * here, so that they can be execute on the next tick. + * @private + */ + var fnQueue = Arr(initQueueSize) + , fnQueueLen = 0; + + /** + * Run all queued functions. + * @private + */ + function flush () { + var i = 0; + while (i < fnQueueLen) { + fn(fnQueue[i], fnQueue[i + 1]); + fnQueue[i++] = $undefined; + fnQueue[i++] = $undefined; + } + + fnQueueLen = 0; + if (fnQueue.length > initQueueSize) fnQueue.length = initQueueSize; + } + + return function (v, arg) { + fnQueue[fnQueueLen++] = v; + fnQueue[fnQueueLen++] = arg; + + if (fnQueueLen === 2) Yaku.nextTick(flush); + }; + } + + /** + * Generate a iterator + * @param {Any} obj + * @private + * @return {Object || TypeError} + */ + function each (iterable, fn) { + var len + , i = 0 + , iter + , item + , ret + ; + + if (!iterable) throw genTypeError($invalidArgument); + + var gen = iterable[Yaku[$Symbol][$iterator]]; + if (isFunction(gen)) + iter = gen.call(iterable); + else if (isFunction(iterable.next)) { + iter = iterable; + } + else if (isInstanceOf(iterable, Arr)) { + len = iterable.length; + while (i < len) { + fn(iterable[i], i++); + } + return i; + } else + throw genTypeError($invalidArgument); + + while (!(item = iter.next()).done) { + ret = genTryCatcher(fn)(item.value, i++); + if (ret === $tryErr) { + isFunction(iter[$return]) && iter[$return](); + throw ret.e; + } + } + + return i; + } + + /** + * Generate type error object. + * @private + * @param {String} msg + * @return {TypeError} + */ + function genTypeError (msg) { + return new TypeError(msg); + } + + function genTraceInfo (noTitle) { + return (noTitle ? '' : $fromPrevious) + new Err().stack; + } + + + // *************************** Promise Helpers **************************** + + /** + * Resolve the value returned by onFulfilled or onRejected. + * @private + * @param {Yaku} p1 + * @param {Yaku} p2 + */ + var scheduleHandler = genScheduler(999, function (p1, p2) { + var x, handler; + + // 2.2.2 + // 2.2.3 + handler = p1._s !== $rejected ? p2._onFulfilled : p2._onRejected; + + // 2.2.7.3 + // 2.2.7.4 + if (handler === $undefined) { + settlePromise(p2, p1._s, p1._v); + return; + } + + // 2.2.7.1 + x = genTryCatcher(callHanler)(handler, p1._v); + if (x === $tryErr) { + // 2.2.7.2 + settlePromise(p2, $rejected, x.e); + return; + } + + settleWithX(p2, x); + }); + + var scheduleUnhandledRejection = genScheduler(9, function (p) { + if (!hashOnRejected(p)) { + p[$unhandled] = 1; + emitEvent($unhandledRejection, p); + } + }); + + function emitEvent (name, p) { + var browserEventName = 'on' + name.toLowerCase() + , browserHandler = root[browserEventName]; + + if (process && process.listeners(name).length) + name === $unhandledRejection ? + process.emit(name, p._v, p) : process.emit(name, p); + else if (browserHandler) + browserHandler({ reason: p._v, promise: p }); + else + Yaku[name](p._v, p); + } + + function isYaku (val) { return val && val._s; } + + function newCapablePromise (Constructor) { + if (isYaku(Constructor)) return new Constructor($noop); + + var p, r, j; + p = new Constructor(function (resolve, reject) { + if (p) throw genTypeError(); + + r = resolve; + j = reject; + }); + + ensureType(r, isFunction); + ensureType(j, isFunction); + + return p; + } + + /** + * It will produce a settlePromise function to user. + * Such as the resolve and reject in this `new Yaku (resolve, reject) ->`. + * @private + * @param {Yaku} self + * @param {Integer} state The value is one of `$pending`, `$resolved` or `$rejected`. + * @return {Function} `(value) -> undefined` A resolve or reject function. + */ + function genSettler (self, state) { + var isCalled = false; + return function (value) { + if (isCalled) return; + isCalled = true; + + if (isLongStackTrace) + self[$settlerTrace] = genTraceInfo(true); + + if (state === $resolved) + settleWithX(self, value); + else + settlePromise(self, state, value); + }; + } + + /** + * Link the promise1 to the promise2. + * @private + * @param {Yaku} p1 + * @param {Yaku} p2 + * @param {Function} onFulfilled + * @param {Function} onRejected + */ + function addHandler (p1, p2, onFulfilled, onRejected) { + // 2.2.1 + if (isFunction(onFulfilled)) + p2._onFulfilled = onFulfilled; + if (isFunction(onRejected)) { + if (p1[$unhandled]) emitEvent($rejectionHandled, p1); + + p2._onRejected = onRejected; + } + + if (isLongStackTrace) p2._p = p1; + p1[p1._c++] = p2; + + // 2.2.6 + if (p1._s !== $pending) + scheduleHandler(p1, p2); + + // 2.2.7 + return p2; + } + + // iterate tree + function hashOnRejected (node) { + // A node shouldn't be checked twice. + if (node._umark) + return true; + else + node._umark = true; + + var i = 0 + , len = node._c + , child; + + while (i < len) { + child = node[i++]; + if (child._onRejected || hashOnRejected(child)) return true; + } + } + + function genStackInfo (reason, p) { + var stackInfo = []; + + function push (trace) { + return stackInfo.push(trace.replace(/^\s+|\s+$/g, '')); + } + + if (isLongStackTrace) { + if (p[$settlerTrace]) + push(p[$settlerTrace]); + + // Hope you guys could understand how the back trace works. + // We only have to iterate through the tree from the bottom to root. + (function iter (node) { + if (node && $promiseTrace in node) { + iter(node._next); + push(node[$promiseTrace] + ''); + iter(node._p); + } + })(p); + } + + return (reason && reason.stack ? reason.stack : reason) + + ('\n' + stackInfo.join('\n')).replace($cleanStackReg, ''); + } + + function callHanler (handler, value) { + // 2.2.5 + return handler(value); + } + + /** + * Resolve or reject a promise. + * @private + * @param {Yaku} p + * @param {Integer} state + * @param {Any} value + */ + function settlePromise (p, state, value) { + var i = 0 + , len = p._c; + + // 2.1.2 + // 2.1.3 + if (p._s === $pending) { + // 2.1.1.1 + p._s = state; + p._v = value; + + if (state === $rejected) { + if (isLongStackTrace && isError(value)) { + value.longStack = genStackInfo(value, p); + } + + scheduleUnhandledRejection(p); + } + + // 2.2.4 + while (i < len) { + scheduleHandler(p, p[i++]); + } + } + + return p; + } + + /** + * Resolve or reject promise with value x. The x can also be a thenable. + * @private + * @param {Yaku} p + * @param {Any | Thenable} x A normal value or a thenable. + */ + function settleWithX (p, x) { + // 2.3.1 + if (x === p && x) { + settlePromise(p, $rejected, genTypeError($promiseCircularChain)); + return p; + } + + // 2.3.2 + // 2.3.3 + if (x !== $null && (isFunction(x) || isObject(x))) { + // 2.3.2.1 + var xthen = genTryCatcher(getThen)(x); + + if (xthen === $tryErr) { + // 2.3.3.2 + settlePromise(p, $rejected, xthen.e); + return p; + } + + if (isFunction(xthen)) { + if (isLongStackTrace && isYaku(x)) + p._next = x; + + // Fix https://bugs.chromium.org/p/v8/issues/detail?id=4162 + if (isYaku(x)) + settleXthen(p, x, xthen); + else + Yaku.nextTick(function () { + settleXthen(p, x, xthen); + }); + } else + // 2.3.3.4 + settlePromise(p, $resolved, x); + } else + // 2.3.4 + settlePromise(p, $resolved, x); + + return p; + } + + /** + * Try to get a promise's then method. + * @private + * @param {Thenable} x + * @return {Function} + */ + function getThen (x) { return x.then; } + + /** + * Resolve then with its promise. + * @private + * @param {Yaku} p + * @param {Thenable} x + * @param {Function} xthen + */ + function settleXthen (p, x, xthen) { + // 2.3.3.3 + var err = genTryCatcher(xthen, x)(function (y) { + // 2.3.3.3.3 + // 2.3.3.3.1 + x && (x = $null, settleWithX(p, y)); + }, function (r) { + // 2.3.3.3.3 + // 2.3.3.3.2 + x && (x = $null, settlePromise(p, $rejected, r)); + }); + + // 2.3.3.3.4.1 + if (err === $tryErr && x) { + // 2.3.3.3.4.2 + settlePromise(p, $rejected, err.e); + x = $null; + } + } + + root.Promise = Yaku; +})(); diff --git a/www/js/lib/q.js b/www/js/lib/q.js deleted file mode 100644 index 6e467958..00000000 --- a/www/js/lib/q.js +++ /dev/null @@ -1,2076 +0,0 @@ -// vim:ts=4:sts=4:sw=4: -/*! - * - * Copyright 2009-2017 Kris Kowal under the terms of the MIT - * license found at https://github.com/kriskowal/q/blob/v1/LICENSE - * - * With parts by Tyler Close - * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found - * at http://www.opensource.org/licenses/mit-license.html - * Forked at ref_send.js version: 2009-05-11 - * - * With parts by Mark Miller - * Copyright (C) 2011 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -(function (definition) { - "use strict"; - - // This file will function properly as a