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