From 457d14bd1b85b0748b0824253753c6fee7ccc8bd Mon Sep 17 00:00:00 2001 From: alicata Date: Fri, 14 Jun 2024 13:58:14 -0700 Subject: [PATCH] Fix onprogress not firing when Content-Length is not available for XMLHttpRequest (#44899) Summary: When an XMLHttpRequest is performed, the `onprogress` event it is not invoked when the `Content-Length` header is missing in the response. This is the case when we are calling an endpoint that responds with `transfer-encoding: chunked` (https://tools.ietf.org/html/rfc9112#section-7.1), preventing the user to keep track of the progress while the server is sending chunks. Despite we will never know the total length of the content (because it will not be known due to the RFC specification, so it will be always `-1`), we will now be able to keep track of the loaded data. Note that in Android, this is the current default behaviour. To address this issue: - I removed the condition where the `downloadProgressBlock` was dispatched only when `response.expectedContentLength` was greater than 0 - I created a new test case for `XMLHttpRequest` in the tester app to download a chunked file ## Changelog: [IOS] [CHANGED] - fire `onprogress` event for `XMLHttpRequest` even when the `Content-Length` header is missing in the response headers Pull Request resolved: https://github.com/facebook/react-native/pull/44899 Test Plan: |before|after| |----------|:-------------:| |https://github.com/facebook/react-native/assets/37150312/6da3518f-eed3-4808-a2f8-abe26e5c7487|https://github.com/facebook/react-native/assets/37150312/ed1da300-dcf7-4874-a941-a2289f1cb777 Reviewed By: cortinico Differential Revision: D58562088 Pulled By: NickGerleman fbshipit-source-id: 23a1cafa49ddcd25fa0db7d04fae845126771425 --- .../Libraries/Network/RCTNetworkTask.mm | 2 +- .../js/examples/XHR/XHRExampleDownload.js | 33 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/react-native/Libraries/Network/RCTNetworkTask.mm b/packages/react-native/Libraries/Network/RCTNetworkTask.mm index 47a2e92d0d5..96a7ded6c9c 100644 --- a/packages/react-native/Libraries/Network/RCTNetworkTask.mm +++ b/packages/react-native/Libraries/Network/RCTNetworkTask.mm @@ -190,7 +190,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init) incrementalDataBlock(data, length, total); }]; } - if (_downloadProgressBlock && total > 0) { + if (_downloadProgressBlock) { RCTURLRequestProgressBlock downloadProgressBlock = _downloadProgressBlock; [self dispatchCallback:^{ downloadProgressBlock(length, total); diff --git a/packages/rn-tester/js/examples/XHR/XHRExampleDownload.js b/packages/rn-tester/js/examples/XHR/XHRExampleDownload.js index e021cb06f1a..70941b73c5f 100644 --- a/packages/rn-tester/js/examples/XHR/XHRExampleDownload.js +++ b/packages/rn-tester/js/examples/XHR/XHRExampleDownload.js @@ -40,6 +40,7 @@ class XHRExampleDownload extends React.Component<{...}, Object> { readystateHandler: false, progressHandler: true, arraybuffer: false, + chunked: false, }; xhr: ?XMLHttpRequest = null; @@ -107,12 +108,19 @@ class XHRExampleDownload extends React.Component<{...}, Object> { Alert.alert('Error', xhr.responseText); } }; - xhr.open( - 'GET', - 'http://aleph.gutenberg.org/cache/epub/100/pg100-images.html.utf8', - ); - // Avoid gzip so we can actually show progress - xhr.setRequestHeader('Accept-Encoding', ''); + if (this.state.chunked) { + xhr.open( + 'GET', + 'https://filesamples.com/samples/ebook/azw3/Around%20the%20World%20in%2028%20Languages.azw3', + ); + } else { + xhr.open( + 'GET', + 'http://aleph.gutenberg.org/cache/epub/100/pg100-images.html.utf8', + ); + // Avoid gzip so we can actually show progress + xhr.setRequestHeader('Accept-Encoding', ''); + } xhr.send(); this.setState({downloading: true}); @@ -133,7 +141,11 @@ class XHRExampleDownload extends React.Component<{...}, Object> { ) : ( - Download 7MB Text File + + {this.state.chunked + ? 'Download 10MB File' + : 'Download 19KB pdf File'} + ); @@ -188,6 +200,13 @@ class XHRExampleDownload extends React.Component<{...}, Object> { onValueChange={arraybuffer => this.setState({arraybuffer})} /> + + transfer-encoding: chunked + this.setState({chunked})} + /> + {button} {readystate} {progress}