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

Commit 7fbd655

Browse files
JiaLiPassionmhevery
authored andcommitted
fix: readonly property should not be patched (#860)
1 parent deae751 commit 7fbd655

File tree

2 files changed

+184
-1
lines changed

2 files changed

+184
-1
lines changed

lib/common/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export function patchPrototype(prototype: any, fnNames: string[]) {
3333
const name = fnNames[i];
3434
const delegate = prototype[name];
3535
if (delegate) {
36+
const prototypeDesc = Object.getOwnPropertyDescriptor(prototype, name);
37+
if (!isPropertyWritable(prototypeDesc)) {
38+
continue;
39+
}
3640
prototype[name] = ((delegate: Function) => {
3741
const patched: any = function() {
3842
return delegate.apply(this, bindArguments(<any>arguments, source + '.' + name));
@@ -44,6 +48,22 @@ export function patchPrototype(prototype: any, fnNames: string[]) {
4448
}
4549
}
4650

51+
export function isPropertyWritable(propertyDesc: any) {
52+
if (!propertyDesc) {
53+
return true;
54+
}
55+
56+
if (propertyDesc.writable === false) {
57+
return false;
58+
}
59+
60+
if (typeof propertyDesc.get === 'function' && typeof propertyDesc.set === 'undefined') {
61+
return false;
62+
}
63+
64+
return true;
65+
}
66+
4767
export const isWebWorker: boolean =
4868
(typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope);
4969

test/common/util.spec.ts

Lines changed: 164 additions & 1 deletion
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 {patchMethod, patchProperty, zoneSymbol} from '../../lib/common/utils';
9+
import {patchMethod, patchProperty, patchPrototype, zoneSymbol} from '../../lib/common/utils';
1010

1111
describe('utils', function() {
1212

@@ -79,4 +79,167 @@ describe('utils', function() {
7979
expect(!desc.get).toBeTruthy();
8080
});
8181
});
82+
83+
describe('patchPrototype', () => {
84+
it('non configurable property desc should be patched', () => {
85+
'use strict';
86+
const TestFunction: any = function() {};
87+
const log: string[] = [];
88+
Object.defineProperties(TestFunction.prototype, {
89+
'property1': {
90+
value: function Property1(callback: Function) {
91+
Zone.root.run(callback);
92+
},
93+
writable: true,
94+
configurable: true,
95+
enumerable: true
96+
},
97+
'property2': {
98+
value: function Property2(callback: Function) {
99+
Zone.root.run(callback);
100+
},
101+
writable: true,
102+
configurable: false,
103+
enumerable: true
104+
}
105+
});
106+
107+
const zone = Zone.current.fork({name: 'patch'});
108+
109+
zone.run(() => {
110+
const instance = new TestFunction();
111+
instance.property1(() => {
112+
log.push('property1' + Zone.current.name);
113+
});
114+
instance.property2(() => {
115+
log.push('property2' + Zone.current.name);
116+
});
117+
});
118+
expect(log).toEqual(['property1<root>', 'property2<root>']);
119+
log.length = 0;
120+
121+
patchPrototype(TestFunction.prototype, ['property1', 'property2']);
122+
123+
zone.run(() => {
124+
const instance = new TestFunction();
125+
instance.property1(() => {
126+
log.push('property1' + Zone.current.name);
127+
});
128+
instance.property2(() => {
129+
log.push('property2' + Zone.current.name);
130+
});
131+
});
132+
expect(log).toEqual(['property1patch', 'property2patch']);
133+
});
134+
135+
it('non writable property desc should not be patched', () => {
136+
'use strict';
137+
const TestFunction: any = function() {};
138+
const log: string[] = [];
139+
Object.defineProperties(TestFunction.prototype, {
140+
'property1': {
141+
value: function Property1(callback: Function) {
142+
Zone.root.run(callback);
143+
},
144+
writable: true,
145+
configurable: true,
146+
enumerable: true
147+
},
148+
'property2': {
149+
value: function Property2(callback: Function) {
150+
Zone.root.run(callback);
151+
},
152+
writable: false,
153+
configurable: true,
154+
enumerable: true
155+
}
156+
});
157+
158+
const zone = Zone.current.fork({name: 'patch'});
159+
160+
zone.run(() => {
161+
const instance = new TestFunction();
162+
instance.property1(() => {
163+
log.push('property1' + Zone.current.name);
164+
});
165+
instance.property2(() => {
166+
log.push('property2' + Zone.current.name);
167+
});
168+
});
169+
expect(log).toEqual(['property1<root>', 'property2<root>']);
170+
log.length = 0;
171+
172+
patchPrototype(TestFunction.prototype, ['property1', 'property2']);
173+
174+
zone.run(() => {
175+
const instance = new TestFunction();
176+
instance.property1(() => {
177+
log.push('property1' + Zone.current.name);
178+
});
179+
instance.property2(() => {
180+
log.push('property2' + Zone.current.name);
181+
});
182+
});
183+
expect(log).toEqual(['property1patch', 'property2<root>']);
184+
});
185+
186+
it('readonly property desc should not be patched', () => {
187+
'use strict';
188+
const TestFunction: any = function() {};
189+
const log: string[] = [];
190+
Object.defineProperties(TestFunction.prototype, {
191+
'property1': {
192+
get: function() {
193+
if (!this._property1) {
194+
this._property1 = function Property2(callback: Function) {
195+
Zone.root.run(callback);
196+
};
197+
}
198+
return this._property1;
199+
},
200+
set: function(func: Function) {
201+
this._property1 = func;
202+
},
203+
configurable: true,
204+
enumerable: true
205+
},
206+
'property2': {
207+
get: function() {
208+
return function Property2(callback: Function) {
209+
Zone.root.run(callback);
210+
};
211+
},
212+
configurable: true,
213+
enumerable: true
214+
}
215+
});
216+
217+
const zone = Zone.current.fork({name: 'patch'});
218+
219+
zone.run(() => {
220+
const instance = new TestFunction();
221+
instance.property1(() => {
222+
log.push('property1' + Zone.current.name);
223+
});
224+
instance.property2(() => {
225+
log.push('property2' + Zone.current.name);
226+
});
227+
});
228+
expect(log).toEqual(['property1<root>', 'property2<root>']);
229+
log.length = 0;
230+
231+
patchPrototype(TestFunction.prototype, ['property1', 'property2']);
232+
233+
zone.run(() => {
234+
const instance = new TestFunction();
235+
instance.property1(() => {
236+
log.push('property1' + Zone.current.name);
237+
});
238+
instance.property2(() => {
239+
log.push('property2' + Zone.current.name);
240+
});
241+
});
242+
expect(log).toEqual(['property1patch', 'property2<root>']);
243+
});
244+
});
82245
});

0 commit comments

Comments
 (0)