From 38972653fa56ba82ca1475130e766e6da5334501 Mon Sep 17 00:00:00 2001 From: Dmitry Drobyshev Date: Tue, 9 Feb 2021 23:20:03 +0300 Subject: [PATCH] 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 --- packages/common/http/src/xhr.ts | 2 ++ packages/common/http/test/xhr_mock.ts | 14 +++++++++++--- packages/common/http/test/xhr_spec.ts | 7 +++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/common/http/src/xhr.ts b/packages/common/http/src/xhr.ts index eeb66d1dc4..47e0e4cb45 100644 --- a/packages/common/http/src/xhr.ts +++ b/packages/common/http/src/xhr.ts @@ -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) { diff --git a/packages/common/http/test/xhr_mock.ts b/packages/common/http/test/xhr_mock.ts index 26766ffa4b..1ac2481933 100644 --- a/packages/common/http/test/xhr_mock.ts +++ b/packages/common/http/test/xhr_mock.ts @@ -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; } diff --git a/packages/common/http/test/xhr_spec.ts b/packages/common/http/test/xhr_spec.ts index b422b20f6b..e8a462a0b1 100644 --- a/packages/common/http/test/xhr_spec.ts +++ b/packages/common/http/test/xhr_spec.ts @@ -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}))