Add support for FormData in mw.Api.upload
authorMark Holmquist <mtraceur@member.fsf.org>
Wed, 24 Jun 2015 19:48:12 +0000 (14:48 -0500)
committerBartosz Dziewoński <matma.rex@gmail.com>
Mon, 13 Jul 2015 17:41:28 +0000 (17:41 +0000)
Bug: T103398
Change-Id: I6185551468cf9799127f57e20e5a4134ca2a2a0f

resources/src/mediawiki.api/mediawiki.api.upload.js

index 86cfff2..b03e52c 100644 (file)
                        .val( val );
        }
 
+       /**
+        * Parse response from an XHR to the server.
+        * @private
+        * @param {Event} e
+        * @return {Object}
+        */
+       function parseXHRResponse( e ) {
+               var response;
+
+               try {
+                       response = $.parseJSON( e.target.responseText );
+               } catch ( error ) {
+                       response = {
+                               error: {
+                                       code: e.target.code,
+                                       info: e.target.responseText
+                               }
+                       };
+               }
+
+               return response;
+       }
+
        /**
         * Process the result of the form submission, returned to an iframe.
         * This is the iframe's onload event.
                return doc;
        }
 
+       function formDataAvailable() {
+               return window.FormData !== undefined &&
+                       window.File !== undefined &&
+                       window.File.prototype.slice !== undefined;
+       }
+
        $.extend( mw.Api.prototype, {
                /**
                 * Upload a file to MediaWiki.
-                * @param {HTMLInputElement} file HTML input type=file element with a file already inside of it.
+                * @param {HTMLInputElement|File} file HTML input type=file element with a file already inside of it, or a File object.
                 * @param {Object} data Other upload options, see action=upload API docs for more
                 * @return {jQuery.Promise}
                 */
                upload: function ( file, data ) {
+                       var iframe, formData;
+
                        if ( !file ) {
                                return $.Deferred().reject( 'No file' );
                        }
 
-                       if ( !file.nodeType || file.nodeType !== file.ELEMENT_NODE ) {
+                       iframe = file.nodeType && file.nodeType === file.ELEMENT_NODE;
+                       formData = formDataAvailable() && file instanceof window.File;
+
+                       if ( !iframe && !formData ) {
                                return $.Deferred().reject( 'Unsupported argument type passed to mw.Api.upload' );
                        }
 
+                       if ( formData ) {
+                               return this.uploadWithFormData( file, data );
+                       }
+
                        return this.uploadWithIframe( file, data );
                },
 
 
                        $form.css( 'display', 'none' )
                                .attr( {
-                                       action: api.defaults.ajax.url,
+                                       action: this.defaults.ajax.url,
                                        method: 'POST',
                                        target: id,
                                        enctype: 'multipart/form-data'
 
                        $( 'body' ).append( $form, $iframe );
 
+                       return deferred.promise();
+               },
+
+               /**
+                * Uploads a file using the FormData API.
+                * @param {File} file
+                * @param {Object} data
+                */
+               uploadWithFormData: function ( file, data ) {
+                       var xhr, tokenPromise,
+                               api = this,
+                               formData = new FormData(),
+                               deferred = $.Deferred(),
+                               filenameFound = false;
+
+                       formData.append( 'action', 'upload' );
+                       formData.append( 'format', 'json' );
+
+                       $.each( data, function ( key, val ) {
+                               if ( key === 'filename' ) {
+                                       filenameFound = true;
+                               }
+
+                               if ( fieldsAllowed[key] === true ) {
+                                       formData.append( key, val );
+                               }
+                       } );
+
+                       if ( !filenameFound ) {
+                               return $.Deferred().reject( 'Filename not included in file data.' );
+                       }
+
+                       formData.append( 'file', file );
+
+                       xhr = new XMLHttpRequest();
+
+                       xhr.upload.addEventListener( 'progress', function ( e ) {
+                               if ( e.lengthComputable ) {
+                                       deferred.notify( e.loaded / e.total );
+                               }
+                       }, false );
+
+                       xhr.addEventListener( 'abort', function ( e ) {
+                               deferred.reject( parseXHRResponse( e ) );
+                       }, false );
+
+                       xhr.addEventListener( 'load', function ( e ) {
+                               deferred.resolve( parseXHRResponse( e ) );
+                       }, false );
+
+                       xhr.addEventListener( 'error', function ( e ) {
+                               deferred.reject( parseXHRResponse( e ) );
+                       }, false );
+
+                       xhr.open( 'POST', this.defaults.ajax.url, true );
+
+                       tokenPromise = this.getEditToken().then( function ( token ) {
+                               formData.append( 'token', token );
+                               xhr.send( formData );
+                       }, function () {
+                               // Mark the edit token as bad, it's been used.
+                               api.badToken( 'edit' );
+                       } );
+
                        return deferred.promise();
                }
        } );