diff --git a/lib/zone.ts b/lib/zone.ts index 992410575..b83871187 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -1310,12 +1310,52 @@ const Zone: ZoneType = (function(global: any) { let frameParserStrategy = null; const stackRewrite = 'stackRewrite'; + const assignAll = function(to, from) { + if (!to) { + return to; + } + + if (from) { + let keys = Object.getOwnPropertyNames(from); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + // Avoid bugs when hasOwnProperty is shadowed + if (Object.prototype.hasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + + // copy all properties from prototype + // in Error, property such as name/message is in Error's prototype + // but not enumerable, so we copy those properties through + // Error's prototype + const proto = Object.getPrototypeOf(from); + if (proto) { + let pKeys = Object.getOwnPropertyNames(proto); + for (let i = 0; i < pKeys.length; i++) { + const key = pKeys[i]; + // skip constructor + if (key !== 'constructor') { + to[key] = from[key]; + } + } + } + } + return to; + }; /** * This is ZoneAwareError which processes the stack frame and cleans up extra frames as well as * adds zone information to it. */ function ZoneAwareError() { + // make sure we have a valid this + // if this is undefined(call Error without new) or this is global + // or this is some other objects, we should force to create a + // valid ZoneAwareError by call Object.create() + if (!(this instanceof ZoneAwareError)) { + return ZoneAwareError.apply(Object.create(ZoneAwareError.prototype), arguments); + } // Create an Error. let error: Error = NativeError.apply(this, arguments); @@ -1354,7 +1394,7 @@ const Zone: ZoneType = (function(global: any) { } error.stack = error.zoneAwareStack = frames.join('\n'); } - return error; + return assignAll(this, error); } // Copy the prototype so that instanceof operator works as expected @@ -1394,8 +1434,6 @@ const Zone: ZoneType = (function(global: any) { } }); - // Now we need to populet the `blacklistedStackFrames` as well as find the - // Now we need to populet the `blacklistedStackFrames` as well as find the // run/runGuraded/runTask frames. This is done by creating a detect zone and then threading // the execution through all of the above methods so that we can look at the stack trace and diff --git a/test/common/Error.spec.ts b/test/common/Error.spec.ts index 053265331..6cfd00a95 100644 --- a/test/common/Error.spec.ts +++ b/test/common/Error.spec.ts @@ -11,6 +11,62 @@ describe('ZoneAwareError', () => { // and there is no point in running them. if (!Error['stackRewrite']) return; + it('should keep error prototype chain correctly', () => { + class MyError extends Error {} + const myError = new MyError(); + expect(myError instanceof Error).toBe(true); + expect(myError instanceof MyError).toBe(true); + expect(myError.stack).not.toBe(undefined); + }); + + it('should instanceof error correctly', () => { + let myError = Error('myError'); + expect(myError instanceof Error).toBe(true); + let myError1 = Error.call(undefined, 'myError'); + expect(myError1 instanceof Error).toBe(true); + let myError2 = Error.call(global, 'myError'); + expect(myError2 instanceof Error).toBe(true); + let myError3 = Error.call({}, 'myError'); + expect(myError3 instanceof Error).toBe(true); + let myError4 = Error.call({test: 'test'}, 'myError'); + expect(myError4 instanceof Error).toBe(true); + }); + + it('should return error itself from constructor', () => { + class MyError1 extends Error { + constructor() { + const err: any = super('MyError1'); + this.message = err.message; + } + } + let myError1 = new MyError1(); + expect(myError1.message).toEqual('MyError1'); + expect(myError1.name).toEqual('Error'); + }); + + it('should return error by calling error directly', () => { + let myError = Error('myError'); + expect(myError.message).toEqual('myError'); + let myError1 = Error.call(undefined, 'myError'); + expect(myError1.message).toEqual('myError'); + let myError2 = Error.call(global, 'myError'); + expect(myError2.message).toEqual('myError'); + let myError3 = Error.call({}, 'myError'); + expect(myError3.message).toEqual('myError'); + }); + + it('should have browser specified property', () => { + let myError = new Error('myError'); + if (Object.prototype.hasOwnProperty.call(Error.prototype, 'description')) { + // in IE, error has description property + expect((myError).description).toEqual('myError'); + } + if (Object.prototype.hasOwnProperty.call(Error.prototype, 'fileName')) { + // in firefox, error has fileName property + expect((myError).fileName).toContain('zone'); + } + }); + it('should show zone names in stack frames and remove extra frames', () => { const rootZone = getRootZone(); const innerZone = rootZone.fork({name: 'InnerZone'});