2019-05-31 15:56:07 +00:00
|
|
|
/**
|
|
|
|
|
* @license
|
2020-05-19 19:08:49 +00:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-05-31 15:56:07 +00:00
|
|
|
*
|
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
2024-09-20 15:23:15 +00:00
|
|
|
* found in the LICENSE file at https://angular.dev/license
|
2019-05-31 15:56:07 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
ifEnvSupports,
|
|
|
|
|
ifEnvSupportsWithDone,
|
|
|
|
|
supportPatchXHROnProperty,
|
|
|
|
|
zoneSymbol,
|
|
|
|
|
} from '../test-util';
|
|
|
|
|
declare const global: any;
|
|
|
|
|
const wtfMock = global.wtfMock;
|
|
|
|
|
|
|
|
|
|
describe('XMLHttpRequest', function () {
|
|
|
|
|
let testZone: Zone;
|
|
|
|
|
|
2020-04-13 23:40:21 +00:00
|
|
|
beforeEach(() => {
|
|
|
|
|
testZone = Zone.current.fork({name: 'test'});
|
|
|
|
|
});
|
2019-05-31 15:56:07 +00:00
|
|
|
|
|
|
|
|
it('should intercept XHRs and treat them as MacroTasks', function (done) {
|
|
|
|
|
let req: XMLHttpRequest;
|
|
|
|
|
let onStable: any;
|
|
|
|
|
const testZoneWithWtf = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({
|
|
|
|
|
name: 'TestZone',
|
|
|
|
|
onHasTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, hasTask: HasTaskState) => {
|
|
|
|
|
if (!hasTask.macroTask) {
|
|
|
|
|
onStable && onStable();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testZoneWithWtf.run(
|
|
|
|
|
() => {
|
|
|
|
|
req = new XMLHttpRequest();
|
|
|
|
|
const logs: string[] = [];
|
2020-04-13 23:40:21 +00:00
|
|
|
req.onload = () => {
|
|
|
|
|
logs.push('onload');
|
|
|
|
|
};
|
2019-05-31 15:56:07 +00:00
|
|
|
onStable = function () {
|
|
|
|
|
expect(wtfMock.log[wtfMock.log.length - 2]).toEqual(
|
|
|
|
|
'> Zone:invokeTask:XMLHttpRequest.send("<root>::ProxyZone::WTF::TestZone")',
|
|
|
|
|
);
|
|
|
|
|
expect(wtfMock.log[wtfMock.log.length - 1]).toEqual(
|
|
|
|
|
'< Zone:invokeTask:XMLHttpRequest.send',
|
|
|
|
|
);
|
|
|
|
|
if (supportPatchXHROnProperty()) {
|
|
|
|
|
expect(wtfMock.log[wtfMock.log.length - 3]).toMatch(
|
|
|
|
|
/\< Zone\:invokeTask.*addEventListener\:load/,
|
|
|
|
|
);
|
|
|
|
|
expect(wtfMock.log[wtfMock.log.length - 4]).toMatch(
|
|
|
|
|
/\> Zone\:invokeTask.*addEventListener\:load/,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// if browser can patch onload
|
|
|
|
|
if ((req as any)[zoneSymbol('loadfalse')]) {
|
|
|
|
|
expect(logs).toEqual(['onload']);
|
|
|
|
|
}
|
2023-05-06 07:04:24 +00:00
|
|
|
onStable = null;
|
2019-05-31 15:56:07 +00:00
|
|
|
done();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
req.send();
|
|
|
|
|
const lastScheduled = wtfMock.log[wtfMock.log.length - 1];
|
|
|
|
|
expect(lastScheduled).toMatch('# Zone:schedule:macroTask:XMLHttpRequest.send');
|
|
|
|
|
},
|
|
|
|
|
null,
|
|
|
|
|
undefined,
|
|
|
|
|
'unit-test',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not trigger Zone callback of internal onreadystatechange', function (done) {
|
|
|
|
|
const scheduleSpy = jasmine.createSpy('schedule');
|
|
|
|
|
const xhrZone = Zone.current.fork({
|
|
|
|
|
name: 'xhr',
|
|
|
|
|
onScheduleTask: (delegate: ZoneDelegate, currentZone: Zone, targetZone, task: Task) => {
|
|
|
|
|
if (task.type === 'eventTask') {
|
|
|
|
|
scheduleSpy(task.source);
|
|
|
|
|
}
|
|
|
|
|
return delegate.scheduleTask(targetZone, task);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
xhrZone.run(() => {
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.onload = function () {
|
|
|
|
|
expect(Zone.current.name).toEqual('xhr');
|
|
|
|
|
if (supportPatchXHROnProperty()) {
|
|
|
|
|
expect(scheduleSpy).toHaveBeenCalled();
|
|
|
|
|
}
|
|
|
|
|
done();
|
|
|
|
|
};
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
req.send();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work with onreadystatechange', function (done) {
|
|
|
|
|
let req: XMLHttpRequest;
|
|
|
|
|
|
|
|
|
|
testZone.run(function () {
|
|
|
|
|
req = new XMLHttpRequest();
|
|
|
|
|
req.onreadystatechange = function () {
|
|
|
|
|
// Make sure that the wrapCallback will only be called once
|
|
|
|
|
req.onreadystatechange = null as any;
|
|
|
|
|
expect(Zone.current).toBe(testZone);
|
|
|
|
|
done();
|
|
|
|
|
};
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
});
|
|
|
|
|
|
2020-04-13 23:40:21 +00:00
|
|
|
req!.send();
|
2019-05-31 15:56:07 +00:00
|
|
|
});
|
|
|
|
|
|
test(zone.js): should invoke XHR task even onload handler throw error. (#41562)
Close #41520.
This case related to the issue #41522.
```
Zone.root
.fork({
name: 'xhr',
onHasTask(delegate, currentZone, zone, taskState) {
console.log('hasMacrotask', taskState.macroTask);
return delegate.hasTask(zone, taskState);
},
})
.run(() => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js');
xhr.addEventListener('load', () => {
throw new Error();
});
xhr.send();
});
```
zone.js invoke all `onload` event handlers before change the XHR task's state from
`scheduled` to `notscheduled`, so if any `onload` listener throw error, the XHR task
wlll be hang to `scheduled`, and leave the macroTask status in the zone wrongly.
This has been fixed in the previous commit, this commit add test to verify the case.
PR Close #41562
2021-04-11 06:58:00 +00:00
|
|
|
it('should run onload listeners before internal readystatechange', function (done) {
|
|
|
|
|
const logs: string[] = [];
|
|
|
|
|
const xhrZone = Zone.current.fork({
|
|
|
|
|
name: 'xhr',
|
|
|
|
|
onInvokeTask: (delegate, curr, target, task, applyThis, applyArgs) => {
|
|
|
|
|
logs.push('invokeTask ' + task.source);
|
|
|
|
|
return delegate.invokeTask(target, task, applyThis, applyArgs);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
xhrZone.run(function () {
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.onload = function () {
|
|
|
|
|
logs.push('onload');
|
|
|
|
|
(window as any)[Zone.__symbol__('setTimeout')](() => {
|
|
|
|
|
expect(logs).toEqual([
|
|
|
|
|
'invokeTask XMLHttpRequest.addEventListener:load',
|
|
|
|
|
'onload',
|
|
|
|
|
'invokeTask XMLHttpRequest.send',
|
|
|
|
|
]);
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
req.send();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-24 18:12:12 +00:00
|
|
|
// TODO: Sort out why the thrown error bubbles up before the window.onerror callback is
|
|
|
|
|
// executed, though confirmed it does get executed and passes the expectations.
|
|
|
|
|
xit('should invoke xhr task even onload listener throw error', function (done) {
|
test(zone.js): should invoke XHR task even onload handler throw error. (#41562)
Close #41520.
This case related to the issue #41522.
```
Zone.root
.fork({
name: 'xhr',
onHasTask(delegate, currentZone, zone, taskState) {
console.log('hasMacrotask', taskState.macroTask);
return delegate.hasTask(zone, taskState);
},
})
.run(() => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js');
xhr.addEventListener('load', () => {
throw new Error();
});
xhr.send();
});
```
zone.js invoke all `onload` event handlers before change the XHR task's state from
`scheduled` to `notscheduled`, so if any `onload` listener throw error, the XHR task
wlll be hang to `scheduled`, and leave the macroTask status in the zone wrongly.
This has been fixed in the previous commit, this commit add test to verify the case.
PR Close #41562
2021-04-11 06:58:00 +00:00
|
|
|
const oriWindowError = window.onerror;
|
2021-04-29 01:05:25 +00:00
|
|
|
const logs: string[] = [];
|
|
|
|
|
window.onerror = function (err: any) {
|
|
|
|
|
logs.push(err);
|
|
|
|
|
};
|
test(zone.js): should invoke XHR task even onload handler throw error. (#41562)
Close #41520.
This case related to the issue #41522.
```
Zone.root
.fork({
name: 'xhr',
onHasTask(delegate, currentZone, zone, taskState) {
console.log('hasMacrotask', taskState.macroTask);
return delegate.hasTask(zone, taskState);
},
})
.run(() => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js');
xhr.addEventListener('load', () => {
throw new Error();
});
xhr.send();
});
```
zone.js invoke all `onload` event handlers before change the XHR task's state from
`scheduled` to `notscheduled`, so if any `onload` listener throw error, the XHR task
wlll be hang to `scheduled`, and leave the macroTask status in the zone wrongly.
This has been fixed in the previous commit, this commit add test to verify the case.
PR Close #41562
2021-04-11 06:58:00 +00:00
|
|
|
try {
|
|
|
|
|
const xhrZone = Zone.current.fork({
|
|
|
|
|
name: 'xhr',
|
|
|
|
|
onInvokeTask: (delegate, curr, target, task, applyThis, applyArgs) => {
|
|
|
|
|
logs.push('invokeTask ' + task.source);
|
|
|
|
|
return delegate.invokeTask(target, task, applyThis, applyArgs);
|
|
|
|
|
},
|
|
|
|
|
onHasTask: (delegate, curr, target, hasTaskState) => {
|
|
|
|
|
if (hasTaskState.change === 'macroTask') {
|
|
|
|
|
logs.push('hasTask ' + hasTaskState.macroTask);
|
|
|
|
|
}
|
|
|
|
|
return delegate.hasTask(target, hasTaskState);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
xhrZone.run(function () {
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.onload = function () {
|
|
|
|
|
logs.push('onload');
|
|
|
|
|
throw new Error('test');
|
|
|
|
|
};
|
|
|
|
|
const unhandledRejection = (e: PromiseRejectionEvent) => {
|
2021-04-29 01:05:25 +00:00
|
|
|
fail('should not be here');
|
test(zone.js): should invoke XHR task even onload handler throw error. (#41562)
Close #41520.
This case related to the issue #41522.
```
Zone.root
.fork({
name: 'xhr',
onHasTask(delegate, currentZone, zone, taskState) {
console.log('hasMacrotask', taskState.macroTask);
return delegate.hasTask(zone, taskState);
},
})
.run(() => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js');
xhr.addEventListener('load', () => {
throw new Error();
});
xhr.send();
});
```
zone.js invoke all `onload` event handlers before change the XHR task's state from
`scheduled` to `notscheduled`, so if any `onload` listener throw error, the XHR task
wlll be hang to `scheduled`, and leave the macroTask status in the zone wrongly.
This has been fixed in the previous commit, this commit add test to verify the case.
PR Close #41562
2021-04-11 06:58:00 +00:00
|
|
|
};
|
|
|
|
|
window.addEventListener('unhandledrejection', unhandledRejection);
|
|
|
|
|
req.addEventListener('load', () => {
|
|
|
|
|
logs.push('onload1');
|
|
|
|
|
(window as any)[Zone.__symbol__('setTimeout')](() => {
|
|
|
|
|
expect(logs).toEqual([
|
|
|
|
|
'hasTask true',
|
|
|
|
|
'invokeTask XMLHttpRequest.addEventListener:load',
|
|
|
|
|
'onload',
|
|
|
|
|
'invokeTask XMLHttpRequest.addEventListener:load',
|
|
|
|
|
'onload1',
|
2021-04-29 01:05:25 +00:00
|
|
|
'invokeTask XMLHttpRequest.send',
|
|
|
|
|
'hasTask false',
|
|
|
|
|
'Uncaught Error: test',
|
test(zone.js): should invoke XHR task even onload handler throw error. (#41562)
Close #41520.
This case related to the issue #41522.
```
Zone.root
.fork({
name: 'xhr',
onHasTask(delegate, currentZone, zone, taskState) {
console.log('hasMacrotask', taskState.macroTask);
return delegate.hasTask(zone, taskState);
},
})
.run(() => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js');
xhr.addEventListener('load', () => {
throw new Error();
});
xhr.send();
});
```
zone.js invoke all `onload` event handlers before change the XHR task's state from
`scheduled` to `notscheduled`, so if any `onload` listener throw error, the XHR task
wlll be hang to `scheduled`, and leave the macroTask status in the zone wrongly.
This has been fixed in the previous commit, this commit add test to verify the case.
PR Close #41562
2021-04-11 06:58:00 +00:00
|
|
|
]);
|
|
|
|
|
window.removeEventListener('unhandledrejection', unhandledRejection);
|
|
|
|
|
window.onerror = oriWindowError;
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
req.send();
|
|
|
|
|
});
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
window.onerror = oriWindowError;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
it('should return null when access ontimeout first time without error', function () {
|
|
|
|
|
let req: XMLHttpRequest = new XMLHttpRequest();
|
|
|
|
|
expect(req.ontimeout).toBe(null);
|
|
|
|
|
});
|
|
|
|
|
|
2020-04-13 23:40:21 +00:00
|
|
|
const supportsOnProgress = function () {
|
|
|
|
|
return 'onprogress' in new XMLHttpRequest();
|
|
|
|
|
};
|
2019-05-31 15:56:07 +00:00
|
|
|
|
|
|
|
|
(<any>supportsOnProgress).message = 'XMLHttpRequest.onprogress';
|
|
|
|
|
|
|
|
|
|
describe(
|
|
|
|
|
'onprogress',
|
|
|
|
|
ifEnvSupports(supportsOnProgress, function () {
|
|
|
|
|
it('should work with onprogress', function (done) {
|
|
|
|
|
let req: XMLHttpRequest;
|
|
|
|
|
testZone.run(function () {
|
|
|
|
|
req = new XMLHttpRequest();
|
|
|
|
|
req.onprogress = function () {
|
|
|
|
|
// Make sure that the wrapCallback will only be called once
|
|
|
|
|
req.onprogress = null as any;
|
|
|
|
|
expect(Zone.current).toBe(testZone);
|
|
|
|
|
done();
|
|
|
|
|
};
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
});
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2020-04-13 23:40:21 +00:00
|
|
|
req!.send();
|
2019-05-31 15:56:07 +00:00
|
|
|
});
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
it('should allow canceling of an XMLHttpRequest', function (done) {
|
|
|
|
|
const spy = jasmine.createSpy('spy');
|
|
|
|
|
let req: XMLHttpRequest;
|
|
|
|
|
let pending = false;
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
const trackingTestZone = Zone.current.fork({
|
|
|
|
|
name: 'tracking test zone',
|
2020-04-13 23:40:21 +00:00
|
|
|
onHasTask: (
|
|
|
|
|
delegate: ZoneDelegate,
|
|
|
|
|
current: Zone,
|
|
|
|
|
target: Zone,
|
|
|
|
|
hasTaskState: HasTaskState,
|
|
|
|
|
) => {
|
|
|
|
|
if (hasTaskState.change == 'macroTask') {
|
|
|
|
|
pending = hasTaskState.macroTask;
|
|
|
|
|
}
|
|
|
|
|
delegate.hasTask(target, hasTaskState);
|
|
|
|
|
},
|
2019-05-31 15:56:07 +00:00
|
|
|
});
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
trackingTestZone.run(function () {
|
|
|
|
|
req = new XMLHttpRequest();
|
|
|
|
|
req.onreadystatechange = function () {
|
|
|
|
|
if (req.readyState === XMLHttpRequest.DONE) {
|
|
|
|
|
if (req.status !== 0) {
|
|
|
|
|
spy();
|
|
|
|
|
}
|
2024-04-19 16:53:49 +00:00
|
|
|
}
|
2019-05-31 15:56:07 +00:00
|
|
|
};
|
|
|
|
|
req.open('get', '/', true);
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
req.send();
|
|
|
|
|
req.abort();
|
|
|
|
|
});
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
setTimeout(function () {
|
|
|
|
|
expect(spy).not.toHaveBeenCalled();
|
|
|
|
|
expect(pending).toEqual(false);
|
|
|
|
|
done();
|
|
|
|
|
}, 0);
|
|
|
|
|
});
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
it('should allow aborting an XMLHttpRequest after its completed', function (done) {
|
|
|
|
|
let req: XMLHttpRequest;
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
testZone.run(function () {
|
|
|
|
|
req = new XMLHttpRequest();
|
|
|
|
|
req.onreadystatechange = function () {
|
|
|
|
|
if (req.readyState === XMLHttpRequest.DONE) {
|
|
|
|
|
if (req.status !== 0) {
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
req.abort();
|
|
|
|
|
done();
|
|
|
|
|
}, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
req.open('get', '/', true);
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
req.send();
|
2024-04-19 16:53:49 +00:00
|
|
|
});
|
2019-05-31 15:56:07 +00:00
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
);
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
it('should preserve other setters', function () {
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
req.send();
|
|
|
|
|
try {
|
|
|
|
|
req.responseType = 'document';
|
|
|
|
|
expect(req.responseType).toBe('document');
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Android browser: using this setter throws, this should be preserved
|
2022-01-07 18:00:40 +00:00
|
|
|
expect((e as Error).message).toBe('INVALID_STATE_ERR: DOM Exception 11');
|
2019-05-31 15:56:07 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work with synchronous XMLHttpRequest', function () {
|
|
|
|
|
const log: HasTaskState[] = [];
|
|
|
|
|
Zone.current
|
|
|
|
|
.fork({
|
|
|
|
|
name: 'sync-xhr-test',
|
|
|
|
|
onHasTask: function (
|
|
|
|
|
delegate: ZoneDelegate,
|
|
|
|
|
current: Zone,
|
|
|
|
|
target: Zone,
|
|
|
|
|
hasTaskState: HasTaskState,
|
|
|
|
|
) {
|
|
|
|
|
log.push(hasTaskState);
|
|
|
|
|
delegate.hasTask(target, hasTaskState);
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.run(() => {
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.open('get', '/', false);
|
|
|
|
|
req.send();
|
|
|
|
|
});
|
|
|
|
|
expect(log).toEqual([]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should preserve static constants', function () {
|
|
|
|
|
expect(XMLHttpRequest.UNSENT).toEqual(0);
|
|
|
|
|
expect(XMLHttpRequest.OPENED).toEqual(1);
|
|
|
|
|
expect(XMLHttpRequest.HEADERS_RECEIVED).toEqual(2);
|
|
|
|
|
expect(XMLHttpRequest.LOADING).toEqual(3);
|
|
|
|
|
expect(XMLHttpRequest.DONE).toEqual(4);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should work properly when send request multiple times on single xmlRequest instance', function (done) {
|
|
|
|
|
testZone.run(function () {
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
req.send();
|
2022-02-09 06:58:20 +00:00
|
|
|
req.onload = function () {
|
2020-04-13 23:40:21 +00:00
|
|
|
req.onload = null as any;
|
2022-02-09 06:58:20 +00:00
|
|
|
req.open('get', '/', true);
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
req.onload = function () {
|
|
|
|
|
done();
|
|
|
|
|
};
|
|
|
|
|
expect(() => {
|
|
|
|
|
req.send();
|
|
|
|
|
}).not.toThrow();
|
2024-04-19 16:53:49 +00:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
it('should keep taskcount correctly when abort was called multiple times before request is done', function (done) {
|
2019-05-31 15:56:07 +00:00
|
|
|
testZone.run(function () {
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
const req = new XMLHttpRequest();
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
req.open('get', '/', true);
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
req.send();
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2022-07-01 20:28:43 +00:00
|
|
|
let count = 0;
|
2019-05-31 15:56:07 +00:00
|
|
|
const listener = function (ev: any) {
|
|
|
|
|
if (req.readyState >= 2) {
|
2022-07-01 20:28:43 +00:00
|
|
|
const isInitial = count++ === 0;
|
2024-04-19 16:53:49 +00:00
|
|
|
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
expect(() => {
|
2022-07-01 20:28:43 +00:00
|
|
|
// this triggers a synchronous dispatch of the state change event.
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
req.abort();
|
2020-04-13 23:40:21 +00:00
|
|
|
}).not.toThrow();
|
2024-04-19 16:53:49 +00:00
|
|
|
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
req.removeEventListener('readystatechange', listener);
|
2024-04-19 16:53:49 +00:00
|
|
|
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
if (isInitial) {
|
|
|
|
|
done();
|
2024-04-19 16:53:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2022-02-09 06:58:20 +00:00
|
|
|
req.addEventListener('readystatechange', listener);
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
});
|
2024-04-19 16:53:49 +00:00
|
|
|
});
|
|
|
|
|
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
it('should close xhr request if error happened when connect', function (done) {
|
|
|
|
|
const logs: boolean[] = [];
|
|
|
|
|
Zone.current
|
2024-04-19 16:53:49 +00:00
|
|
|
.fork({
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
name: 'xhr',
|
2019-05-31 15:56:07 +00:00
|
|
|
onHasTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, taskState: HasTaskState) => {
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
if (taskState.change === 'macroTask') {
|
|
|
|
|
logs.push(taskState.macroTask);
|
2024-04-19 16:53:49 +00:00
|
|
|
}
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
return delegate.hasTask(target, taskState);
|
2024-04-19 16:53:49 +00:00
|
|
|
},
|
|
|
|
|
})
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
.run(function () {
|
2019-05-31 15:56:07 +00:00
|
|
|
const req = new XMLHttpRequest();
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
req.open('get', 'http://notexists.url', true);
|
2019-05-31 15:56:07 +00:00
|
|
|
req.send();
|
test(zone.js): should invoke XHR task even onload handler throw error. (#41562)
Close #41520.
This case related to the issue #41522.
```
Zone.root
.fork({
name: 'xhr',
onHasTask(delegate, currentZone, zone, taskState) {
console.log('hasMacrotask', taskState.macroTask);
return delegate.hasTask(zone, taskState);
},
})
.run(() => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.11.4/zone.min.js');
xhr.addEventListener('load', () => {
throw new Error();
});
xhr.send();
});
```
zone.js invoke all `onload` event handlers before change the XHR task's state from
`scheduled` to `notscheduled`, so if any `onload` listener throw error, the XHR task
wlll be hang to `scheduled`, and leave the macroTask status in the zone wrongly.
This has been fixed in the previous commit, this commit add test to verify the case.
PR Close #41562
2021-04-11 06:58:00 +00:00
|
|
|
req.addEventListener('error', () => {
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
expect(logs).toEqual([true, false]);
|
2024-04-19 16:53:49 +00:00
|
|
|
done();
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
});
|
2024-04-19 16:53:49 +00:00
|
|
|
});
|
fix(zone.js): should invoke xhr send task when no response error occurs (#38836)
Close #38795
in the XMLHttpRequest patch, when get `readystatechange` event, zone.js try to
invoke `load` event listener first, then call `invokeTask` to finish the
`XMLHttpRequest::send` macroTask, but if the request failed because the
server can not be reached, the `load` event listener will not be invoked,
so the `invokeTask` of the `XMLHttpRequest::send` will not be triggered either,
so we will have a non finished macroTask there which will make the Zone
not stable, also memory leak.
So in this PR, if the `XMLHttpRequest.status = 0` when we get the `readystatechange`
event, that means something wents wrong before we reached the server, we need to
invoke the task to finish the macroTask.
PR Close #38836
2020-09-14 06:54:12 +00:00
|
|
|
});
|
|
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
it('should trigger readystatechange if xhr request trigger cors error', (done) => {
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
let err: any = null;
|
2025-08-31 18:57:49 +00:00
|
|
|
req.open('get', 'file:///test', true);
|
|
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
req.addEventListener('readystatechange', function (ev) {
|
|
|
|
|
if (req.readyState === 4) {
|
|
|
|
|
const xhrScheduled = (req as any)[zoneSymbol('xhrScheduled')];
|
|
|
|
|
const task = (req as any)[zoneSymbol('xhrTask')];
|
|
|
|
|
if (xhrScheduled === false) {
|
|
|
|
|
expect(task.state).toEqual('scheduling');
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (err) {
|
|
|
|
|
expect(task.state).toEqual('unknown');
|
|
|
|
|
} else {
|
|
|
|
|
expect(task.state).toEqual('notScheduled');
|
|
|
|
|
}
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
expect(task.state).toEqual('scheduled');
|
|
|
|
|
done();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
try {
|
|
|
|
|
req.send();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
err = error;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should invoke task if xhr request trigger cors error', (done) => {
|
|
|
|
|
const logs: string[] = [];
|
|
|
|
|
const zone = Zone.current.fork({
|
|
|
|
|
name: 'xhr',
|
|
|
|
|
onHasTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, hasTask: HasTaskState) => {
|
|
|
|
|
logs.push(JSON.stringify(hasTask));
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const req = new XMLHttpRequest();
|
2025-08-31 18:57:49 +00:00
|
|
|
|
|
|
|
|
req.open('get', 'file:///test', true);
|
|
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
zone.run(() => {
|
|
|
|
|
let isError = false;
|
|
|
|
|
let timerId = null;
|
|
|
|
|
try {
|
|
|
|
|
timerId = (window as any)[zoneSymbol('setTimeout')](() => {
|
|
|
|
|
expect(logs).toEqual([
|
|
|
|
|
`{"microTask":false,"macroTask":true,"eventTask":false,"change":"macroTask"}`,
|
|
|
|
|
`{"microTask":false,"macroTask":false,"eventTask":false,"change":"macroTask"}`,
|
|
|
|
|
]);
|
|
|
|
|
done();
|
|
|
|
|
}, 500);
|
|
|
|
|
req.send();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
isError = true;
|
|
|
|
|
(window as any)[zoneSymbol('clearTimeout')](timerId);
|
|
|
|
|
done();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not throw error when get XMLHttpRequest.prototype.onreadystatechange the first time', function () {
|
|
|
|
|
const func = function () {
|
|
|
|
|
testZone.run(function () {
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.onreadystatechange;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
expect(func).not.toThrow();
|
|
|
|
|
});
|
2024-04-19 16:53:49 +00:00
|
|
|
|
2019-05-31 15:56:07 +00:00
|
|
|
it('should be in the zone when use XMLHttpRequest.addEventListener', function (done) {
|
|
|
|
|
testZone.run(function () {
|
|
|
|
|
// sometimes this case will cause timeout
|
|
|
|
|
// so we set it longer
|
|
|
|
|
const interval = (<any>jasmine).DEFAULT_TIMEOUT_INTERVAL;
|
|
|
|
|
(<any>jasmine).DEFAULT_TIMEOUT_INTERVAL = 5000;
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
req.addEventListener('readystatechange', function () {
|
|
|
|
|
if (req.readyState === 4) {
|
|
|
|
|
// expect(Zone.current.name).toEqual('test');
|
|
|
|
|
(<any>jasmine).DEFAULT_TIMEOUT_INTERVAL = interval;
|
|
|
|
|
done();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
req.send();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it(
|
|
|
|
|
'should return origin listener when call xhr.onreadystatechange',
|
|
|
|
|
ifEnvSupportsWithDone(supportPatchXHROnProperty, function (done: Function) {
|
|
|
|
|
testZone.run(function () {
|
|
|
|
|
// sometimes this case will cause timeout
|
|
|
|
|
// so we set it longer
|
|
|
|
|
const req = new XMLHttpRequest();
|
|
|
|
|
req.open('get', '/', true);
|
|
|
|
|
const interval = (<any>jasmine).DEFAULT_TIMEOUT_INTERVAL;
|
|
|
|
|
(<any>jasmine).DEFAULT_TIMEOUT_INTERVAL = 5000;
|
|
|
|
|
const listener = (req.onreadystatechange = function () {
|
|
|
|
|
if (req.readyState === 4) {
|
|
|
|
|
(<any>jasmine).DEFAULT_TIMEOUT_INTERVAL = interval;
|
|
|
|
|
done();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
expect(req.onreadystatechange).toBe(listener);
|
2020-04-13 23:40:21 +00:00
|
|
|
req.onreadystatechange = function () {
|
|
|
|
|
return listener.call(this);
|
|
|
|
|
};
|
2019-05-31 15:56:07 +00:00
|
|
|
req.send();
|
|
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
});
|