@@ -661,30 +661,47 @@ function fetchXHR(fetch, onsuccess, onerror, onprogress, onreadystatechange) {
661661 return ;
662662 }
663663 var ptrLen = ( fetchAttrLoadToMemory && fetchAttrStreamData ) ? xhr . response ?. byteLength ?? 0 : 0 ;
664- var ptr = 0 ;
665- if ( ptrLen > 0 && fetchAttrLoadToMemory && fetchAttrStreamData ) {
664+
665+ // Specifies the maximum chunk size that a streaming fetch will transfer from
666+ // JS over to WebAssembly side. Used to cap a streaming fetch to avoid
667+ // overallocating WebAssembly memory needlessly.
668+ var FETCH_STREAMING_MAX_CHUNK_SIZE = 8 * 1024 * 1024 ;
669+
670+ for ( var bytePos = 0 ; bytePos < ptrLen || ! ptrLen ; ) {
671+ var sz = Math . min ( ptrLen - bytePos , FETCH_STREAMING_MAX_CHUNK_SIZE ) ;
672+
673+ var ptr = 0 ;
674+ if ( sz > 0 && fetchAttrLoadToMemory && fetchAttrStreamData ) {
675+ // Even though we are doing a streaming fetch (i.e. in small chunks), Safari may call onprogress with a huge
676+ // chunk size. This will be a problem for Wasm applications that intend to use streaming fetch to process
677+ // an input file in small chunks (to avoid blowing up the WebAssembly heap size). Therefore apply a max
678+ // chunk size ceiling to the received chunks, and transfer the data over to WebAssembly using max sized chunks.
679+
666680#if FETCH_DEBUG
667- dbg ( `fetch: allocating ${ ptrLen } bytes in Emscripten heap for xhr data` ) ;
681+ dbg ( `fetch: allocating ${ sz } bytes in Emscripten heap for xhr data` ) ;
668682#endif
669683#if ASSERTIONS
670- assert ( onprogress , 'streaming fetch requires an onprogress handler' ) ;
684+ assert ( onprogress , 'streaming fetch requires an onprogress handler' ) ;
671685#endif
672- // The data pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is
673- // freed when emscripten_fetch_close() is called.
674- ptr = _realloc ( { { { makeGetValue ( 'fetch' , C_STRUCTS . emscripten_fetch_t . data , '*' ) } } } , ptrLen ) ;
675- HEAPU8 . set ( new Uint8Array ( /** @type {Array<number> } */ ( xhr . response ) ) , ptr ) ;
686+ // The data pointer malloc()ed here has the same lifetime as the emscripten_fetch_t structure itself has, and is
687+ // freed when emscripten_fetch_close() is called.
688+ ptr = _realloc ( { { { makeGetValue ( 'fetch' , C_STRUCTS . emscripten_fetch_t . data , '*' ) } } } , sz ) ;
689+ HEAPU8 . set ( new Uint8Array ( /** @type {Array<number> } */ ( xhr . response ) , bytePos , sz ) , ptr ) ;
690+ }
691+ { { { makeSetValue ( 'fetch' , C_STRUCTS . emscripten_fetch_t . data , 'ptr' , '*' ) } } }
692+ writeI53ToI64 ( fetch + { { { C_STRUCTS . emscripten_fetch_t . numBytes } } } , sz ) ;
693+ writeI53ToI64 ( fetch + { { { C_STRUCTS . emscripten_fetch_t . dataOffset } } } , e . loaded - ptrLen + bytePos ) ;
694+ writeI53ToI64 ( fetch + { { { C_STRUCTS . emscripten_fetch_t . totalBytes } } } , e . total ) ;
695+ { { { makeSetValue ( 'fetch' , C_STRUCTS . emscripten_fetch_t . readyState , 'xhr.readyState' , 'i16' ) } } }
696+ var status = xhr . status ;
697+ // If loading files from a source that does not give HTTP status code, assume success if we get data bytes
698+ if ( xhr . readyState >= 3 && xhr . status === 0 && e . loaded > 0 ) status = 200 ;
699+ { { { makeSetValue ( 'fetch' , C_STRUCTS . emscripten_fetch_t . status , 'status' , 'i16' ) } } }
700+ if ( xhr . statusText ) stringToUTF8 ( xhr . statusText , fetch + { { { C_STRUCTS . emscripten_fetch_t . statusText } } } , 64 ) ;
701+ onprogress ( fetch , e ) ;
702+ bytePos += sz ;
703+ if ( ! ptrLen ) break ;
676704 }
677- { { { makeSetValue ( 'fetch' , C_STRUCTS . emscripten_fetch_t . data , 'ptr' , '*' ) } } }
678- writeI53ToI64 ( fetch + { { { C_STRUCTS . emscripten_fetch_t . numBytes } } } , ptrLen ) ;
679- writeI53ToI64 ( fetch + { { { C_STRUCTS . emscripten_fetch_t . dataOffset } } } , e . loaded - ptrLen ) ;
680- writeI53ToI64 ( fetch + { { { C_STRUCTS . emscripten_fetch_t . totalBytes } } } , e . total ) ;
681- { { { makeSetValue ( 'fetch' , C_STRUCTS . emscripten_fetch_t . readyState , 'xhr.readyState' , 'i16' ) } } }
682- var status = xhr . status ;
683- // If loading files from a source that does not give HTTP status code, assume success if we get data bytes
684- if ( xhr . readyState >= 3 && xhr . status === 0 && e . loaded > 0 ) status = 200 ;
685- { { { makeSetValue ( 'fetch' , C_STRUCTS . emscripten_fetch_t . status , 'status' , 'i16' ) } } }
686- if ( xhr . statusText ) stringToUTF8 ( xhr . statusText , fetch + { { { C_STRUCTS . emscripten_fetch_t . statusText } } } , 64 ) ;
687- onprogress ( fetch , e ) ;
688705 } ;
689706 xhr . onreadystatechange = ( e ) => {
690707 // check if xhr was aborted by user and don't try to call back
0 commit comments