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

Commit 7bd1418

Browse files
JiaLiPassionmhevery
authored andcommitted
fix(xhr): inner onreadystatechange should not triigger Zone callback (#800)
fix angular/angular#17167, zone.js will patch xhr, and add onreadystatechange internally, this onreadystatechange just for invoke the xhr MacroTask, so it should not trigger ZoneSpec's callback. in this commit, use original native addEventListener/removeEventListener to add internal onreadystatechange event listener.
1 parent 857929d commit 7bd1418

File tree

4 files changed

+54
-10
lines changed

4 files changed

+54
-10
lines changed

Diff for: lib/browser/browser.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,11 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
9191
const data = <XHROptions>task.data;
9292
// remove existing event listener
9393
const listener = data.target[XHR_LISTENER];
94+
const oriAddListener = data.target[zoneSymbol('addEventListener')];
95+
const oriRemoveListener = data.target[zoneSymbol('removeEventListener')];
96+
9497
if (listener) {
95-
data.target.removeEventListener('readystatechange', listener);
98+
oriRemoveListener.apply(data.target, ['readystatechange', listener]);
9699
}
97100
const newListener = data.target[XHR_LISTENER] = () => {
98101
if (data.target.readyState === data.target.DONE) {
@@ -104,7 +107,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType, api: _ZonePrivate) => {
104107
}
105108
}
106109
};
107-
data.target.addEventListener('readystatechange', newListener);
110+
oriAddListener.apply(data.target, ['readystatechange', newListener]);
108111

109112
const storedTask: Task = data.target[XHR_TASK];
110113
if (!storedTask) {

Diff for: test/browser/XMLHttpRequest.spec.ts

+33-7
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 {ifEnvSupports} from '../test-util';
9+
import {ifEnvSupports, supportPatchXHROnProperty} from '../test-util';
1010

1111
describe('XMLHttpRequest', function() {
1212
let testZone: Zone;
@@ -26,14 +26,14 @@ describe('XMLHttpRequest', function() {
2626
// The last entry in the log should be the invocation for the current onload,
2727
// which will vary depending on browser environment. The prior entries
2828
// should be the invocation of the send macrotask.
29-
expect(wtfMock.log[wtfMock.log.length - 5])
30-
.toMatch(/\> Zone\:invokeTask.*addEventListener\:readystatechange/);
31-
expect(wtfMock.log[wtfMock.log.length - 4])
32-
.toEqual('> Zone:invokeTask:XMLHttpRequest.send("<root>::ProxyZone::WTF::TestZone")');
3329
expect(wtfMock.log[wtfMock.log.length - 3])
34-
.toEqual('< Zone:invokeTask:XMLHttpRequest.send');
30+
.toEqual('> Zone:invokeTask:XMLHttpRequest.send("<root>::ProxyZone::WTF::TestZone")');
3531
expect(wtfMock.log[wtfMock.log.length - 2])
36-
.toMatch(/\< Zone\:invokeTask.*addEventListener\:readystatechange/);
32+
.toEqual('< Zone:invokeTask:XMLHttpRequest.send');
33+
if (supportPatchXHROnProperty()) {
34+
expect(wtfMock.log[wtfMock.log.length - 1])
35+
.toMatch(/\> Zone\:invokeTask.*addEventListener\:load/);
36+
}
3737
done();
3838
};
3939

@@ -44,6 +44,32 @@ describe('XMLHttpRequest', function() {
4444
}, null, null, 'unit-test');
4545
});
4646

47+
it('should not trigger Zone callback of internal onreadystatechange', function(done) {
48+
const scheduleSpy = jasmine.createSpy('schedule');
49+
const xhrZone = Zone.current.fork({
50+
name: 'xhr',
51+
onScheduleTask: (delegate: ZoneDelegate, currentZone: Zone, targetZone, task: Task) => {
52+
if (task.type === 'eventTask') {
53+
scheduleSpy(task.source);
54+
}
55+
return delegate.scheduleTask(targetZone, task);
56+
}
57+
});
58+
59+
xhrZone.run(() => {
60+
const req = new XMLHttpRequest();
61+
req.onload = function() {
62+
expect(Zone.current.name).toEqual('xhr');
63+
if (supportPatchXHROnProperty()) {
64+
expect(scheduleSpy).toHaveBeenCalled();
65+
}
66+
done();
67+
};
68+
req.open('get', '/', true);
69+
req.send();
70+
});
71+
});
72+
4773
it('should work with onreadystatechange', function(done) {
4874
let req: XMLHttpRequest;
4975

Diff for: test/test-util.ts

+11
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,14 @@ function _runTest(test: any, block: Function, done: Function) {
5757
});
5858
}
5959
}
60+
61+
export function supportPatchXHROnProperty() {
62+
let desc = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, 'onload');
63+
if (!desc && (window as any)['XMLHttpRequestEventTarget']) {
64+
desc = Object.getOwnPropertyDescriptor(global['XMLHttpRequestEventTarget'].prototype, 'onload');
65+
}
66+
if (!desc || !desc.configurable) {
67+
return false;
68+
}
69+
return true;
70+
}

Diff for: test/zone-spec/task-tracking.spec.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
import '../../lib/zone-spec/task-tracking';
1010

11+
import {supportPatchXHROnProperty} from '../test-util';
12+
1113
declare const global: any;
1214

1315
describe('TaskTrackingZone', function() {
@@ -47,7 +49,9 @@ describe('TaskTrackingZone', function() {
4749
setTimeout(() => {
4850
expect(taskTrackingZoneSpec.macroTasks.length).toBe(0);
4951
expect(taskTrackingZoneSpec.microTasks.length).toBe(0);
50-
expect(taskTrackingZoneSpec.eventTasks.length).not.toBe(0);
52+
if (supportPatchXHROnProperty()) {
53+
expect(taskTrackingZoneSpec.eventTasks.length).not.toBe(0);
54+
}
5155
taskTrackingZoneSpec.clearEvents();
5256
expect(taskTrackingZoneSpec.eventTasks.length).toBe(0);
5357
done();

0 commit comments

Comments
 (0)