Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit 0d0ee53

Browse files
JiaLiPassionmhevery
authored andcommitted
fix(toString): fix #666, Zone patched method toString should like before patched (#686)
1 parent e11d9ff commit 0d0ee53

File tree

7 files changed

+97
-2
lines changed

7 files changed

+97
-2
lines changed

Diff for: lib/browser/browser.ts

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {patchTimer} from '../common/timers';
10+
import {patchFuncToString} from '../common/to-string';
1011
import {findEventTask, patchClass, patchEventTargetMethods, patchMethod, patchPrototype, zoneSymbol} from '../common/utils';
1112

1213
import {propertyPatch} from './define-property';
@@ -151,6 +152,9 @@ if (_global['navigator'] && _global['navigator'].geolocation) {
151152
patchPrototype(_global['navigator'].geolocation, ['getCurrentPosition', 'watchPosition']);
152153
}
153154

155+
// patch Func.prototype.toString to let them look like native
156+
patchFuncToString();
157+
154158
// handle unhandled promise rejection
155159
function findPromiseRejectionHandler(evtName: string) {
156160
return function(e: any) {

Diff for: lib/browser/register-element.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {isBrowser, isMix} from '../common/utils';
9+
import {attachOriginToPatched, isBrowser, isMix} from '../common/utils';
1010

1111
import {_redefineProperty} from './define-property';
1212

@@ -39,4 +39,6 @@ export function registerElementPatch(_global: any) {
3939

4040
return _registerElement.apply(document, [name, opts]);
4141
};
42+
43+
attachOriginToPatched((<any>document).registerElement, _registerElement);
4244
}

Diff for: lib/common/to-string.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {zoneSymbol} from './utils';
9+
10+
// override Function.prototype.toString to make zone.js patched function
11+
// look like native function
12+
export function patchFuncToString() {
13+
const originalFunctionToString = Function.prototype.toString;
14+
const g: any =
15+
typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global;
16+
Function.prototype.toString = function() {
17+
if (typeof this === 'function') {
18+
if (this[zoneSymbol('OriginalDelegate')]) {
19+
return originalFunctionToString.apply(this[zoneSymbol('OriginalDelegate')], arguments);
20+
}
21+
if (this === Promise) {
22+
const nativePromise = g[zoneSymbol('Promise')];
23+
if (nativePromise) {
24+
return originalFunctionToString.apply(nativePromise, arguments);
25+
}
26+
}
27+
if (this === Error) {
28+
const nativeError = g[zoneSymbol('Error')];
29+
if (nativeError) {
30+
return originalFunctionToString.apply(nativeError, arguments);
31+
}
32+
}
33+
}
34+
return originalFunctionToString.apply(this, arguments);
35+
};
36+
}

Diff for: lib/common/utils.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ export function patchPrototype(prototype: any, fnNames: string[]) {
3434
const delegate = prototype[name];
3535
if (delegate) {
3636
prototype[name] = ((delegate: Function) => {
37-
return function() {
37+
const patched: any = function() {
3838
return delegate.apply(this, bindArguments(<any>arguments, source + '.' + name));
3939
};
40+
attachOriginToPatched(patched, delegate);
41+
return patched;
4042
})(delegate);
4143
}
4244
}
@@ -401,6 +403,8 @@ const originalInstanceKey = zoneSymbol('originalInstance');
401403
export function patchClass(className: string) {
402404
const OriginalClass = _global[className];
403405
if (!OriginalClass) return;
406+
// keep original class in global
407+
_global[zoneSymbol(className)] = OriginalClass;
404408

405409
_global[className] = function() {
406410
const a = bindArguments(<any>arguments, className);
@@ -425,6 +429,9 @@ export function patchClass(className: string) {
425429
}
426430
};
427431

432+
// attach original delegate to patched function
433+
attachOriginToPatched(_global[className], OriginalClass);
434+
428435
const instance = new OriginalClass(function() {});
429436

430437
let prop;
@@ -441,6 +448,10 @@ export function patchClass(className: string) {
441448
set: function(fn) {
442449
if (typeof fn === 'function') {
443450
this[originalInstanceKey][prop] = Zone.current.wrap(fn, className + '.' + prop);
451+
// keep callback in wrapped function so we can
452+
// use it in Function.prototype.toString to return
453+
// the native one.
454+
attachOriginToPatched(this[originalInstanceKey][prop], fn);
444455
} else {
445456
this[originalInstanceKey][prop] = fn;
446457
}
@@ -488,6 +499,7 @@ export function patchMethod(
488499
if (proto && !(delegate = proto[delegateName])) {
489500
delegate = proto[delegateName] = proto[name];
490501
proto[name] = createNamedFn(name, patchFn(delegate, delegateName, name));
502+
attachOriginToPatched(proto[name], delegate);
491503
}
492504
return delegate;
493505
}
@@ -575,5 +587,9 @@ export function findEventTask(target: any, evtName: string): Task[] {
575587
return result;
576588
}
577589

590+
export function attachOriginToPatched(patched: Function, original: any) {
591+
(patched as any)[zoneSymbol('OriginalDelegate')] = original;
592+
}
593+
578594
(Zone as any)[zoneSymbol('patchEventTargetMethods')] = patchEventTargetMethods;
579595
(Zone as any)[zoneSymbol('patchOnProperties')] = patchOnProperties;

Diff for: lib/node/node.ts

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import './events';
1111
import './fs';
1212

1313
import {patchTimer} from '../common/timers';
14+
import {patchFuncToString} from '../common/to-string';
1415
import {findEventTask, patchMacroTask, patchMicroTask, zoneSymbol} from '../common/utils';
1516

1617
const set = 'set';
@@ -35,6 +36,9 @@ if (shouldPatchGlobalTimers) {
3536
patchProcess();
3637
handleUnhandledPromiseRejection();
3738

39+
// patch Function.prototyp.toString
40+
patchFuncToString();
41+
3842
// Crypto
3943
let crypto: any;
4044
try {

Diff for: test/common/toString.spec.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {zoneSymbol} from '../../lib/common/utils';
10+
import {ifEnvSupports} from '../test-util';
11+
12+
const g: any =
13+
typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global;
14+
describe('global function patch', () => {
15+
describe('isOriginal', () => {
16+
it('setTimeout toString should be the same with non patched setTimeout', () => {
17+
expect(Function.prototype.toString.call(setTimeout))
18+
.toEqual(Function.prototype.toString.call(g[zoneSymbol('setTimeout')]));
19+
});
20+
});
21+
22+
describe('isNative', () => {
23+
it('ZoneAwareError toString should look like native', () => {
24+
expect(Function.prototype.toString.call(Error)).toContain('[native code]');
25+
});
26+
27+
it('EventTarget addEventListener should look like native', ifEnvSupports('HTMLElement', () => {
28+
expect(Function.prototype.toString.call(HTMLElement.prototype.addEventListener))
29+
.toContain('[native code]');
30+
}));
31+
});
32+
});

Diff for: test/common_tests.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import './common/Promise.spec';
1414
import './common/Error.spec';
1515
import './common/setInterval.spec';
1616
import './common/setTimeout.spec';
17+
import './common/toString.spec';
1718
import './zone-spec/long-stack-trace-zone.spec';
1819
import './zone-spec/async-test.spec';
1920
import './zone-spec/sync-test.spec';

0 commit comments

Comments
 (0)