Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Fetch in FileLoader #22510

Merged
merged 9 commits into from
Oct 6, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 89 additions & 152 deletions src/loaders/FileLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,17 @@ class FileLoader extends Loader {

url = this.manager.resolveURL( url );

const scope = this;

const cached = Cache.get( url );

if ( cached !== undefined ) {

scope.manager.itemStart( url );
this.manager.itemStart( url );

setTimeout( function () {
setTimeout( () => {

if ( onLoad ) onLoad( cached );

scope.manager.itemEnd( url );
this.manager.itemEnd( url );

}, 0 );

Expand All @@ -55,225 +53,164 @@ class FileLoader extends Loader {

}

// Check for data: URI
const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
const dataUriRegexResult = url.match( dataUriRegex );
let request;

// Safari can not handle Data URIs through XMLHttpRequest so process manually
if ( dataUriRegexResult ) {

const mimeType = dataUriRegexResult[ 1 ];
const isBase64 = !! dataUriRegexResult[ 2 ];

let data = dataUriRegexResult[ 3 ];
data = decodeURIComponent( data );

if ( isBase64 ) data = atob( data );

try {

let response;
const responseType = ( this.responseType || '' ).toLowerCase();

switch ( responseType ) {

case 'arraybuffer':
case 'blob':

const view = new Uint8Array( data.length );

for ( let i = 0; i < data.length; i ++ ) {

view[ i ] = data.charCodeAt( i );

}

if ( responseType === 'blob' ) {

response = new Blob( [ view.buffer ], { type: mimeType } );
// Initialise array for duplicate requests
loading[ url ] = [];

} else {
loading[ url ].push( {
onLoad: onLoad,
onProgress: onProgress,
onError: onError,
} );

response = view.buffer;
// create request
const req = new Request( url, {
headers: new Headers( this.requestHeader ),
credentials: this.withCredentials ? 'include' : 'same-origin',
// An abort controller could be added within a future PR
} );

}

break;

case 'document':

const parser = new DOMParser();
response = parser.parseFromString( data, mimeType );

break;

case 'json':

response = JSON.parse( data );
// start the fetch
fetch( req )
.then( response => {

break;
if ( response.status === 200 || response.status === 0 ) {

default: // 'text' or other
// Some browsers return HTTP Status 0 when using non-http protocol
// e.g. 'file://' or 'data://'. Handle as success.

response = data;
if ( response.status === 0 ) {

break;
console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );

}
}

// Wait for next browser tick like standard XMLHttpRequest event dispatching does
setTimeout( function () {
const callbacks = loading[ url ];
const reader = response.body.getReader();
const contentLength = response.headers.get( 'Content-Length' );
const total = contentLength ? parseInt( contentLength ) : 0;
const lengthComputable = total !== 0;
let loaded = 0;

if ( onLoad ) onLoad( response );
// periodically read data into the new stream tracking while download progress
return new ReadableStream( {
start( controller ) {

scope.manager.itemEnd( url );
readData();

}, 0 );
function readData() {

} catch ( error ) {
reader.read().then( ( { done, value } ) => {

// Wait for next browser tick like standard XMLHttpRequest event dispatching does
setTimeout( function () {
if ( done ) {

if ( onError ) onError( error );
controller.close();

scope.manager.itemError( url );
scope.manager.itemEnd( url );
} else {

}, 0 );
loaded += value.byteLength;

}
const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

} else {
const callback = callbacks[ i ];
if ( callback.onProgress ) callback.onProgress( event );

// Initialise array for duplicate requests
}

loading[ url ] = [];
controller.enqueue( value );
readData();

loading[ url ].push( {
}

onLoad: onLoad,
onProgress: onProgress,
onError: onError
} );

} );
}

request = new XMLHttpRequest();

request.open( 'GET', url, true );

request.addEventListener( 'load', function ( event ) {

const response = this.response;

const callbacks = loading[ url ];
}

delete loading[ url ];
} );

if ( this.status === 200 || this.status === 0 ) {
} else {

// Some browsers return HTTP Status 0 when using non-http protocol
// e.g. 'file://' or 'data://'. Handle as success.
throw Error( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}` );

if ( this.status === 0 ) console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
}

// Add to cache only on HTTP success, so that we do not cache
// error response bodies as proper responses to requests.
Cache.add( url, response );
} )
.then( stream => {

for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
const response = new Response( stream );

const callback = callbacks[ i ];
if ( callback.onLoad ) callback.onLoad( response );
switch ( this.responseType ) {

}
case 'arraybuffer':

scope.manager.itemEnd( url );
return response.arrayBuffer();

} else {
case 'blob':

for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
return response.blob();

const callback = callbacks[ i ];
if ( callback.onError ) callback.onError( event );
case 'document':

}
return response.text()
.then( text => {

scope.manager.itemError( url );
scope.manager.itemEnd( url );
const parser = new DOMParser();
return parser.parseFromString( text, this.mimeType );

}
} );

}, false );
case 'json':

request.addEventListener( 'progress', function ( event ) {
return response.json();

const callbacks = loading[ url ];
default:

for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

const callback = callbacks[ i ];
if ( callback.onProgress ) callback.onProgress( event );
return response.text();

}

}, false );
} )
.then( data => {

request.addEventListener( 'error', function ( event ) {
// Add to cache only on HTTP success, so that we do not cache
// error response bodies as proper responses to requests.
Cache.add( url, data );

const callbacks = loading[ url ];

delete loading[ url ];

for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

const callback = callbacks[ i ];
if ( callback.onError ) callback.onError( event );
if ( callback.onLoad ) callback.onLoad( data );

}

scope.manager.itemError( url );
scope.manager.itemEnd( url );
this.manager.itemEnd( url );

}, false );
} )
.catch( err => {

request.addEventListener( 'abort', function ( event ) {
// Abort errors and other errors are handled the same

const callbacks = loading[ url ];

delete loading[ url ];

for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

const callback = callbacks[ i ];
if ( callback.onError ) callback.onError( event );
if ( callback.onError ) callback.onError( err );

}

scope.manager.itemError( url );
scope.manager.itemEnd( url );

}, false );

if ( this.responseType !== undefined ) request.responseType = this.responseType;
if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;
this.manager.itemError( url );
this.manager.itemEnd( url );

if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' );

for ( const header in this.requestHeader ) {

request.setRequestHeader( header, this.requestHeader[ header ] );

}

request.send( null );

}

scope.manager.itemStart( url );
} );

return request;
this.manager.itemStart( url );

}

Expand Down