diff --git a/file-size-limit.json b/file-size-limit.json index 03d3f5b83..6df60d7e1 100644 --- a/file-size-limit.json +++ b/file-size-limit.json @@ -3,7 +3,7 @@ { "path": "dist/zone.min.js", "checkTarget": true, - "limit": 41500 + "limit": 41600 } ] } \ No newline at end of file diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index 221977e7a..bf01626d7 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -96,6 +96,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { const XHR_LISTENER = zoneSymbol('xhrListener'); const XHR_SCHEDULED = zoneSymbol('xhrScheduled'); const XHR_URL = zoneSymbol('xhrURL'); + const XHR_ERROR_BEFORE_SCHEDULED = zoneSymbol('xhrErrorBeforeScheduled'); interface XHROptions extends TaskData { target: any; @@ -126,9 +127,10 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { const SCHEDULED = 'scheduled'; function scheduleTask(task: Task) { - (XMLHttpRequest as any)[XHR_SCHEDULED] = false; const data = task.data; const target = data.target; + target[XHR_SCHEDULED] = false; + target[XHR_ERROR_BEFORE_SCHEDULED] = false; // remove existing event listener const listener = target[XHR_LISTENER]; if (!oriAddListener) { @@ -143,7 +145,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { if (target.readyState === target.DONE) { // sometimes on some browsers XMLHttpRequest will fire onreadystatechange with // readyState=4 multiple times, so we need to check task state here - if (!data.aborted && (XMLHttpRequest as any)[XHR_SCHEDULED] && task.state === SCHEDULED) { + if (!data.aborted && target[XHR_SCHEDULED] && task.state === SCHEDULED) { // check whether the xhr has registered onload listener // if that is the case, the task should invoke after all // onload listeners finish. @@ -167,6 +169,9 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { } else { task.invoke(); } + } else if (!data.aborted && target[XHR_SCHEDULED] === false) { + // error occurs when xhr.send() + target[XHR_ERROR_BEFORE_SCHEDULED] = true; } } }; @@ -177,7 +182,7 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { target[XHR_TASK] = task; } sendNative!.apply(target, data.args); - (XMLHttpRequest as any)[XHR_SCHEDULED] = true; + target[XHR_SCHEDULED] = true; return task; } @@ -215,8 +220,15 @@ Zone.__load_patch('XHR', (global: any, Zone: ZoneType) => { } else { const options: XHROptions = {target: self, url: self[XHR_URL], isPeriodic: false, args: args, aborted: false}; - return scheduleMacroTaskWithCurrentZone( + const task = scheduleMacroTaskWithCurrentZone( XMLHTTPREQUEST_SOURCE, placeholderCallback, options, scheduleTask, clearTask); + if (self && self[XHR_ERROR_BEFORE_SCHEDULED] === true && !options.aborted && + task.state === SCHEDULED) { + // xhr request throw error when send + // we should invoke task instead of leaving a scheduled + // pending macroTask + task.invoke(); + } } }); diff --git a/test/browser/XMLHttpRequest.spec.ts b/test/browser/XMLHttpRequest.spec.ts index a1b59cff0..584f931e3 100644 --- a/test/browser/XMLHttpRequest.spec.ts +++ b/test/browser/XMLHttpRequest.spec.ts @@ -266,6 +266,79 @@ describe('XMLHttpRequest', function() { }); }); + it('should trigger readystatechange if xhr request trigger cors error', (done) => { + const req = new XMLHttpRequest(); + let err: any = null; + try { + req.open('get', 'file:///test', true); + } catch (err) { + // in IE, open will throw Access is denied error + done(); + return; + } + req.addEventListener('readystatechange', function(ev) { + if (req.readyState === 4) { + const xhrScheduled = (req as any)['__zone_symbol__xhrScheduled']; + const task = (req as any)['__zone_symbol__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(); + try { + req.open('get', 'file:///test', true); + } catch (err) { + // in IE, open will throw Access is denied error + done(); + return; + } + zone.run(() => { + let isError = false; + let timerId = null; + try { + timerId = (window as any)['__zone_symbol__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)['__zone_symbol__clearTimeout'](timerId); + done(); + } + }); + }); + it('should not throw error when get XMLHttpRequest.prototype.onreadystatechange the first time', function() { const func = function() {