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

Commit 6ec335c

Browse files
committed
fix(xhr): fix #1072, should set scheduled flag to target
1 parent ff3d545 commit 6ec335c

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

Diff for: lib/browser/browser.ts

+16-4
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
9696
const XHR_LISTENER = zoneSymbol('xhrListener');
9797
const XHR_SCHEDULED = zoneSymbol('xhrScheduled');
9898
const XHR_URL = zoneSymbol('xhrURL');
99+
const XHR_ERROR_BEFORE_SCHEDULED = zoneSymbol('xhrErrorBeforeScheduled');
99100

100101
interface XHROptions extends TaskData {
101102
target: any;
@@ -126,9 +127,10 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
126127
const SCHEDULED = 'scheduled';
127128

128129
function scheduleTask(task: Task) {
129-
(XMLHttpRequest as any)[XHR_SCHEDULED] = false;
130130
const data = <XHROptions>task.data;
131131
const target = data.target;
132+
target[XHR_SCHEDULED] = false;
133+
target[XHR_ERROR_BEFORE_SCHEDULED] = false;
132134
// remove existing event listener
133135
const listener = target[XHR_LISTENER];
134136
if (!oriAddListener) {
@@ -143,7 +145,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
143145
if (target.readyState === target.DONE) {
144146
// sometimes on some browsers XMLHttpRequest will fire onreadystatechange with
145147
// readyState=4 multiple times, so we need to check task state here
146-
if (!data.aborted && (XMLHttpRequest as any)[XHR_SCHEDULED] && task.state === SCHEDULED) {
148+
if (!data.aborted && target[XHR_SCHEDULED] && task.state === SCHEDULED) {
147149
// check whether the xhr has registered onload listener
148150
// if that is the case, the task should invoke after all
149151
// onload listeners finish.
@@ -167,6 +169,9 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
167169
} else {
168170
task.invoke();
169171
}
172+
} else if (!data.aborted && target[XHR_SCHEDULED] === false) {
173+
// error occurs when xhr.send()
174+
target[XHR_ERROR_BEFORE_SCHEDULED] = true;
170175
}
171176
}
172177
};
@@ -177,7 +182,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
177182
target[XHR_TASK] = task;
178183
}
179184
sendNative!.apply(target, data.args);
180-
(XMLHttpRequest as any)[XHR_SCHEDULED] = true;
185+
target[XHR_SCHEDULED] = true;
181186
return task;
182187
}
183188

@@ -207,8 +212,15 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => {
207212
} else {
208213
const options: XHROptions =
209214
{target: self, url: self[XHR_URL], isPeriodic: false, args: args, aborted: false};
210-
return scheduleMacroTaskWithCurrentZone(
215+
const task = scheduleMacroTaskWithCurrentZone(
211216
XMLHTTPREQUEST_SOURCE, placeholderCallback, options, scheduleTask, clearTask);
217+
if (self && self[XHR_ERROR_BEFORE_SCHEDULED] === true && !options.aborted &&
218+
task.state === SCHEDULED) {
219+
// xhr request throw error when send
220+
// we should invoke task instead of leaving a scheduled
221+
// pending macroTask
222+
task.invoke();
223+
}
212224
}
213225
});
214226

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

+73
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,79 @@ describe('XMLHttpRequest', function() {
266266
});
267267
});
268268

269+
it('should trigger readystatechange if xhr request trigger cors error', (done) => {
270+
const req = new XMLHttpRequest();
271+
let err: any = null;
272+
try {
273+
req.open('get', 'file:///test', true);
274+
} catch (err) {
275+
// in IE, open will throw Access is denied error
276+
done();
277+
return;
278+
}
279+
req.addEventListener('readystatechange', function(ev) {
280+
if (req.readyState === 4) {
281+
const xhrScheduled = (req as any)['__zone_symbol__xhrScheduled'];
282+
const task = (req as any)['__zone_symbol__xhrTask'];
283+
if (xhrScheduled === false) {
284+
expect(task.state).toEqual('scheduling');
285+
setTimeout(() => {
286+
if (err) {
287+
expect(task.state).toEqual('unknown');
288+
} else {
289+
expect(task.state).toEqual('notScheduled');
290+
}
291+
done();
292+
});
293+
} else {
294+
expect(task.state).toEqual('scheduled');
295+
done();
296+
}
297+
}
298+
});
299+
try {
300+
req.send();
301+
} catch (error) {
302+
err = error;
303+
}
304+
});
305+
306+
it('should invoke task if xhr request trigger cors error', (done) => {
307+
const logs: string[] = [];
308+
const zone = Zone.current.fork({
309+
name: 'xhr',
310+
onHasTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, hasTask: HasTaskState) => {
311+
logs.push(JSON.stringify(hasTask));
312+
}
313+
});
314+
const req = new XMLHttpRequest();
315+
try {
316+
req.open('get', 'file:///test', true);
317+
} catch (err) {
318+
// in IE, open will throw Access is denied error
319+
done();
320+
return;
321+
}
322+
zone.run(() => {
323+
let isError = false;
324+
let timerId = null;
325+
try {
326+
timerId = (window as any)['__zone_symbol__setTimeout'](() => {
327+
expect(logs).toEqual([
328+
`{"microTask":false,"macroTask":true,"eventTask":false,"change":"macroTask"}`,
329+
`{"microTask":false,"macroTask":false,"eventTask":false,"change":"macroTask"}`
330+
]);
331+
done();
332+
}, 500);
333+
req.send();
334+
} catch (error) {
335+
isError = true;
336+
(window as any)['__zone_symbol__clearTimeout'](timerId);
337+
done();
338+
}
339+
});
340+
});
341+
269342
it('should not throw error when get XMLHttpRequest.prototype.onreadystatechange the first time',
270343
function() {
271344
const func = function() {

0 commit comments

Comments
 (0)