diff --git a/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html b/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html index d65a85994482..2834f982da3c 100644 --- a/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html +++ b/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html @@ -295,6 +295,6 @@

Do better web tester page

} - + diff --git a/lighthouse-cli/test/fixtures/static-server.js b/lighthouse-cli/test/fixtures/static-server.js index 7687ce222c42..4328644aa858 100644 --- a/lighthouse-cli/test/fixtures/static-server.js +++ b/lighthouse-cli/test/fixtures/static-server.js @@ -27,11 +27,11 @@ function requestHandler(request, response) { const queryString = requestUrl.search; let absoluteFilePath = path.join(__dirname, filePath); - if (filePath === '/promise_polyfill.js') { + if (filePath === '/zone.js') { // evaluateAsync previously had a bug that LH would fail if a page polyfilled Promise. - // We bring in a third-party Promise polyfill to ensure we don't still fail. - const thirdPartyPath = '../../../lighthouse-core/third_party'; - absoluteFilePath = path.join(__dirname, `${thirdPartyPath}/promise-polyfill/promise.js`); + // We bring in an aggressive Promise polyfill (zone) to ensure we don't still fail. + const zonePath = '../../../node_modules/zone.js'; + absoluteFilePath = path.join(__dirname, `${zonePath}/dist/zone.js`); } fs.exists(absoluteFilePath, fsExistsCallback); diff --git a/lighthouse-core/gather/driver.js b/lighthouse-core/gather/driver.js index d246bc7769e9..155a4b8c81ae 100644 --- a/lighthouse-core/gather/driver.js +++ b/lighthouse-core/gather/driver.js @@ -143,15 +143,19 @@ class Driver { ); this.sendCommand('Runtime.evaluate', { - // We need to wrap the raw expression for several purposes + // We need to explicitly wrap the raw expression for several purposes: // 1. Ensure that the expression will be a native Promise and not a polyfill/non-Promise. - // 2. Ensure that errors captured in the Promise are converted into plain-old JS Objects + // 2. Ensure that errors in the expression are captured by the Promise. + // 3. Ensure that errors captured in the Promise are converted into plain-old JS Objects // so that they can be serialized properly b/c JSON.stringify(new Error('foo')) === '{}' expression: `(function wrapInNativePromise() { const __nativePromise = window.__nativePromise || Promise; - return __nativePromise.resolve() - .then(_ => ${expression}) - .catch(${wrapRuntimeEvalErrorInBrowser.toString()}); + return new __nativePromise(function (resolve) { + return __nativePromise.resolve() + .then(_ => ${expression}) + .catch(${wrapRuntimeEvalErrorInBrowser.toString()}) + .then(resolve); + }); }())`, includeCommandLineAPI: true, awaitPromise: true, diff --git a/lighthouse-core/third_party/promise-polyfill/promise.js b/lighthouse-core/third_party/promise-polyfill/promise.js deleted file mode 100644 index e4c2e85b1a4f..000000000000 --- a/lighthouse-core/third_party/promise-polyfill/promise.js +++ /dev/null @@ -1,257 +0,0 @@ -/* - * @license - * From taylorhakes/promise-polyfill - - * 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. -*/ -'use strict'; - -(function() { - /* eslint-disable */ - // 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; - }; - - window.Promise = Promise; - - /* eslint-enable */ -})(); diff --git a/package.json b/package.json index 812eba155170..d334032fcb41 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "jsdom": "^9.0.0", "mkdirp": "^0.5.1", "mocha": "^2.3.3", - "walk": "^2.3.9" + "walk": "^2.3.9", + "zone.js": "^0.7.3" }, "dependencies": { "axe-core": "2.1.7", @@ -52,13 +53,13 @@ "handlebars": "^4.0.5", "json-stringify-safe": "^5.0.1", "mkdirp": "^0.5.1", + "opn": "^4.0.2", "rimraf": "^2.2.8", "semver": ">=4.3.3", "speedline": "1.0.3", "whatwg-url": "^4.0.0", "ws": "^1.1.1", - "yargs": "3.30.0", - "opn": "^4.0.2" + "yargs": "3.30.0" }, "repository": "googlechrome/lighthouse", "keywords": [ diff --git a/yarn.lock b/yarn.lock index f7029a0c3c1e..fce37dc83a95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2873,3 +2873,7 @@ yargs@~3.10.0: cliui "^2.1.0" decamelize "^1.0.0" window-size "0.1.0" + +zone.js@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.3.tgz#d91432b6584f73c2c9e03ce4bb0870becc45d445"