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 6 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
247 changes: 99 additions & 148 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,178 @@ 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 );
// Initialise array for duplicate requests
loading[ url ] = [];

try {

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

switch ( responseType ) {

case 'arraybuffer':
case 'blob':
loading[ url ].push( {
onLoad: onLoad,
onProgress: onProgress,
onError: onError,
} );

const view = new Uint8Array( data.length );
// TODO-DefinitelyMaybe: Confirm if Safari can handle Data URIs through fetch
DefinitelyMaybe marked this conversation as resolved.
Show resolved Hide resolved
// create request
const req = new Request( url, {
headers: new Headers( this.requestHeader ),
credentials: this.withCredentials ? 'include' : 'same-origin',
// TODO-DefinitelyMaybe: An abort controller could be added within a future PR
} );

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

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

}

if ( responseType === 'blob' ) {

response = new Blob( [ view.buffer ], { type: mimeType } );

} else {

response = view.buffer;

}
// start the fetch
fetch( req )
.then( response => {

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

case 'document':

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

break;

case 'json':

response = JSON.parse( data );

break;

default: // 'text' or other

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

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

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

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

if ( onLoad ) onLoad( response );
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;
DefinitelyMaybe marked this conversation as resolved.
Show resolved Hide resolved
let loaded = 0;

scope.manager.itemEnd( url );
// periodically read data into the new stream tracking while download progress
return new ReadableStream( {
start( controller ) {

}, 0 );
readData();

} catch ( error ) {
function readData() {

// Wait for next browser tick like standard XMLHttpRequest event dispatching does
setTimeout( function () {
reader.read()
.then( ( { done, value } ) => {

if ( onError ) onError( error );
if ( done ) {

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

}, 0 );
} else {

}
loaded += value.byteLength;

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

// Initialise array for duplicate requests
const callback = callbacks[ i ];
if ( callback.onProgress ) callback.onProgress( event );

loading[ url ] = [];
}

loading[ url ].push( {

onLoad: onLoad,
onProgress: onProgress,
onError: onError
controller.enqueue( value );
readData();

} );
}

request = new XMLHttpRequest();
} );

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

request.addEventListener( 'load', function ( event ) {
}
} );
DefinitelyMaybe marked this conversation as resolved.
Show resolved Hide resolved

const response = this.response;
} else {

const callbacks = loading[ url ];
const callbacks = loading[ url ];
delete loading[ url ];

delete loading[ url ];
for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

if ( this.status === 200 || this.status === 0 ) {
const callback = callbacks[ i ];
if ( callback.onError ) callback.onError( response.statusText );
DefinitelyMaybe marked this conversation as resolved.
Show resolved Hide resolved

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

if ( this.status === 0 ) console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
this.manager.itemError( url );
this.manager.itemEnd( url );

// 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 );
}

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

const callback = callbacks[ i ];
if ( callback.onLoad ) callback.onLoad( response );
const response = new Response( stream );

}
switch ( this.responseType ) {

scope.manager.itemEnd( url );
case 'arraybuffer':

} else {
return response.arrayBuffer();

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

const callback = callbacks[ i ];
if ( callback.onError ) callback.onError( event );
return response.blob();

}
case 'document':

scope.manager.itemError( url );
scope.manager.itemEnd( url );
return response.text()
.then( text => {

}
const parser = new DOMParser();
return parser.parseFromString( text, this.mimeType );

}, false );
} );

request.addEventListener( 'progress', function ( event ) {
case 'json':

const callbacks = loading[ url ];
return response.json();

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

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 );
this.manager.itemError( url );
this.manager.itemEnd( url );

}, false );

if ( this.responseType !== undefined ) request.responseType = this.responseType;
if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;

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 );
this.manager.itemStart( url );

return request;
return;
DefinitelyMaybe marked this conversation as resolved.
Show resolved Hide resolved
DefinitelyMaybe marked this conversation as resolved.
Show resolved Hide resolved

}

Expand Down