fix(http): emit error on XMLHttpRequest abort event (#40767)

Before this change, when Google Chrome cancels a XMLHttpRequest, an Observable of the response
never finishes. This happens, for example, when you put your computer to sleep or just press
Ctrl+S to save the browser page. After this commit, if request is canceled or aborted an
appropriate Observable will be completed with an error.

Fixes #22324

PR Close #40767
This commit is contained in:
Dmitry Drobyshev 2021-02-09 23:20:03 +03:00 committed by Alex Rickabaugh
parent ddff6b63d7
commit 38972653fa
3 changed files with 20 additions and 3 deletions

View File

@ -312,6 +312,7 @@ export class HttpXhrBackend implements HttpBackend {
xhr.addEventListener('load', onLoad);
xhr.addEventListener('error', onError);
xhr.addEventListener('timeout', onError);
xhr.addEventListener('abort', onError);
// Progress events are only enabled if requested.
if (req.reportProgress) {
@ -333,6 +334,7 @@ export class HttpXhrBackend implements HttpBackend {
return () => {
// On a cancellation, remove all registered event listeners.
xhr.removeEventListener('error', onError);
xhr.removeEventListener('abort', onError);
xhr.removeEventListener('load', onLoad);
xhr.removeEventListener('timeout', onError);
if (req.reportProgress) {

View File

@ -55,6 +55,7 @@ export class MockXMLHttpRequest {
listeners: {
error?: (event: ErrorEvent) => void,
timeout?: (event: ErrorEvent) => void,
abort?: () => void,
load?: () => void,
progress?: (event: ProgressEvent) => void,
uploadProgress?: (event: ProgressEvent) => void,
@ -71,12 +72,13 @@ export class MockXMLHttpRequest {
this.body = body;
}
addEventListener(event: 'error'|'timeout'|'load'|'progress'|'uploadProgress', handler: Function):
void {
addEventListener(
event: 'error'|'timeout'|'load'|'progress'|'uploadProgress'|'abort',
handler: Function): void {
this.listeners[event] = handler as any;
}
removeEventListener(event: 'error'|'timeout'|'load'|'progress'|'uploadProgress'): void {
removeEventListener(event: 'error'|'timeout'|'load'|'progress'|'uploadProgress'|'abort'): void {
delete this.listeners[event];
}
@ -137,6 +139,12 @@ export class MockXMLHttpRequest {
}
}
mockAbortEvent(): void {
if (this.listeners.abort) {
this.listeners.abort();
}
}
abort() {
this.mockAborted = true;
}

View File

@ -173,6 +173,13 @@ const XSSI_PREFIX = ')]}\'\n';
factory.mock.abort = abort;
factory.mock.mockFlush(HttpStatusCode.Ok, 'OK', 'Done');
});
it('emits an error when browser cancels a request', done => {
backend.handle(TEST_POST).subscribe(undefined, (err: HttpErrorResponse) => {
expect(err instanceof HttpErrorResponse).toBe(true);
done();
});
factory.mock.mockAbortEvent();
});
describe('progress events', () => {
it('are emitted for download progress', done => {
backend.handle(TEST_POST.clone({reportProgress: true}))