From 29814dc7fec6fe38557d74093132fadd16e93436 Mon Sep 17 00:00:00 2001 From: Mark Andrus Roberts Date: Tue, 25 Oct 2016 13:36:11 -0700 Subject: [PATCH] Make the check for ZoneAwarePromise more stringent `resolvePromise` assumes that if a value is an `instanceof` ZoneAwarePromise then it has the properties "__zone_symbol__state" and "__zone_symbol__value" and it _is_ a true ZoneAwarePromise; however, a user can construct a value that breaks this assumption by inheriting from ZoneAwarePromise without actually having those properties or being a true ZoneAwarePromise (for example, by attempting to subclass Promise). We can fix this by adding checks for "__zone_symbol__state" and "__zone_symbol__value" to `resolvePromise`. --- lib/zone.ts | 5 +++- test/common/Promise.spec.ts | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/zone.ts b/lib/zone.ts index a87864494..bd2bd2ab4 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -1030,7 +1030,10 @@ const Zone: ZoneType = (function(global: any) { function resolvePromise( promise: ZoneAwarePromise, state: boolean, value: any): ZoneAwarePromise { if (promise[symbolState] === UNRESOLVED) { - if (value instanceof ZoneAwarePromise && value[symbolState] !== UNRESOLVED) { + if (value instanceof ZoneAwarePromise && + value.hasOwnProperty(symbolState) && + value.hasOwnProperty(symbolValue) && + value[symbolState] !== UNRESOLVED) { clearRejectedNoCatch(>value); resolvePromise(promise, value[symbolState], value[symbolValue]); } else if (isThenable(value)) { diff --git a/test/common/Promise.spec.ts b/test/common/Promise.spec.ts index 8a9443918..373f45e49 100644 --- a/test/common/Promise.spec.ts +++ b/test/common/Promise.spec.ts @@ -331,6 +331,53 @@ describe( }); }); + describe('Promise subclasses', function() { + function MyPromise(init) { + this._promise = new Promise(init); + } + + MyPromise.prototype.catch = function _catch() { + return this._promise.catch.apply(this._promise, arguments); + }; + + MyPromise.prototype.then = function then() { + return this._promise.then.apply(this._promise, arguments); + }; + + var setPrototypeOf = (Object as any).setPrototypeOf || function(obj, proto) { + obj.__proto__ = proto; + return obj; + } + + setPrototypeOf(MyPromise.prototype, Promise.prototype); + + it('should reject if the Promise subclass rejects', function() { + var myPromise = new MyPromise(function(resolve, reject) { + reject('foo'); + }); + + return Promise.resolve().then(function() { + return myPromise; + }).then(function() { + throw new Error('Unexpected resolution'); + }, function(result) { + expect(result).toBe('foo'); + }); + }); + + it('should resolve if the Promise subclass resolves', function() { + var myPromise = new MyPromise(function(resolve, reject) { + resolve('foo'); + }); + + return Promise.resolve().then(function() { + return myPromise; + }).then(function(result) { + expect(result).toBe('foo'); + }); + }); + }); + describe('fetch', ifEnvSupports('fetch', function() { it('should work for text response', function(done) { testZone.run(function() {