PageSourceSearch

https://tienganhykhoa.vn/statics/plugins/upload/js/jquery.fileupload.js

js tienganhykhoa.vn collected 2026-09-24 15:15:02 UTC 64,004 bytes, 1,486 lines download raw bytes

1/*
2 * jQuery File Upload Plugin
3 * https://github.com/blueimp/jQuery-File-Upload
4 *
5 * Copyright 2010, Sebastian Tschan
6 * https://blueimp.net
7 *
8 * Licensed under the MIT license:
9 * https://opensource.org/licenses/MIT
10 */
11
12/* jshint nomen:false */
13/* global define, require, window, document, location, Blob, FormData */
14
15;(function (factory) {
16    'use strict';
17    if (typeof define === 'function' && define.amd) {
18        // Register as an anonymous AMD module:
19        define([
20            'jquery',
21            'jquery-ui/ui/widget'
22        ], factory);
23    } else if (typeof exports === 'object') {
24        // Node/CommonJS:
25        factory(
26            require('jquery'),
27            require('./vendor/jquery.ui.widget')
28        );
29    } else {
30        // Browser globals:
31        factory(window.jQuery);
32    }
33}(function ($) {
34    'use strict';
35
36    // Detect file input support, based on
37    // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
38    $.support.fileInput = !(new RegExp(
39        // Handle devices which give false positives for the feature detection:
40        '(Android (1\\.[0156]|2\\.[01]))' +
41            '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
42            '|(w(eb)?OSBrowser)|(webOS)' +
43            '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
44    ).test(window.navigator.userAgent) ||
45        // Feature detection for all other devices:
46        $('<input type="file"/>').prop('disabled'));
47
48    // The FileReader API is not actually used, but works as feature detection,
49    // as some Safari versions (5?) support XHR file uploads via the FormData API,
50    // but not non-multipart XHR file uploads.
51    // window.XMLHttpRequestUpload is not available on IE10, so we check for
52    // window.ProgressEvent instead to detect XHR2 file upload capability:
53    $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
54    $.support.xhrFormDataFileUpload = !!window.FormData;
55
56    // Detect support for Blob slicing (required for chunked uploads):
57    $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
58        Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
59
60    // Helper function to create drag handlers for dragover/dragenter/dragleave:
61    function getDragHandler(type) {
62        var isDragOver = type === 'dragover';
63        return function (e) {
64            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
65            var dataTransfer = e.dataTransfer;
66            if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
67                    this._trigger(
68                        type,
69                        $.Event(type, {delegatedEvent: e})
70                    ) !== false) {
71                e.preventDefault();
72                if (isDragOver) {
73                    dataTransfer.dropEffect = 'copy';
74                }
75            }
76        };
77    }
78
79    // The fileupload widget listens for change events on file input fields defined
80    // via fileInput setting and paste or drop events of the given dropZone.
81    // In addition to the default jQuery Widget methods, the fileupload widget
82    // exposes the "add" and "send" methods, to add or directly send files using
83    // the fileupload API.
84    // By default, files added via file input selection, paste, drag & drop or
85    // "add" method are uploaded immediately, but it is possible to override
86    // the "add" callback option to queue file uploads.
87    $.widget('blueimp.fileupload', {
88
89        options: {
90            // The drop target element(s), by the default the complete document.
91            // Set to null to disable drag & drop support:
92            dropZone: $(document),
93            // The paste target element(s), by the default undefined.
94            // Set to a DOM node or jQuery object to enable file pasting:
95            pasteZone: undefined,
96            // The file input field(s), that are listened to for change events.
97            // If undefined, it is set to the file input fields inside
98            // of the widget element on plugin initialization.
99            // Set to null to disable the change listener.
100            fileInput: undefined,
101            // By default, the file input field is replaced with a clone after
102            // each input field change event. This is required for iframe transport
103            // queues and allows change events to be fired for the same file
104            // selection, but can be disabled by setting the following option to fal
104se:
105            replaceFileInput: true,
106            // The parameter name for the file form data (the request argument name).
107            // If undefined or empty, the name property of the file input field is
108            // used, or "files[]" if the file input name property is also empty,
109            // can be a string or an array of strings:
110            paramName: undefined,
111            // By default, each file of a selection is uploaded using an individual
112            // request for XHR type uploads. Set to false to upload file
113            // selections in one request each:
114            singleFileUploads: true,
115            // To limit the number of files uploaded with one XHR request,
116            // set the following option to an integer greater than 0:
117            limitMultiFileUploads: undefined,
118            // The following option limits the number of files uploaded with one
119            // XHR request to keep the request size under or equal to the defined
120            // limit in bytes:
121            limitMultiFileUploadSize: undefined,
122            // Multipart file uploads add a number of bytes to each uploaded file,
123            // therefore the following option adds an overhead for each file used
124            // in the limitMultiFileUploadSize configuration:
125            limitMultiFileUploadSizeOverhead: 512,
126            // Set the following option to true to issue all file upload requests
127            // in a sequential order:
128            sequentialUploads: false,
129            // To limit the number of concurrent uploads,
130            // set the following option to an integer greater than 0:
131            limitConcurrentUploads: undefined,
132            // Set the following option to true to force iframe transport uploads:
133            forceIframeTransport: false,
134            // Set the following option to the location of a redirect url on the
135            // origin server, for cross-domain iframe transport uploads:
136            redirect: undefined,
137            // The parameter name for the redirect url, sent as part of the form
138            // data and set to 'redirect' if this option is empty:
139            redirectParamName: undefined,
140            // Set the following option to the location of a postMessage window,
141            // to enable postMessage transport uploads:
142            postMessage: undefined,
143            // By default, XHR file uploads are sent as multipart/form-data.
144            // The iframe transport is always using multipart/form-data.
145            // Set to false to enable non-multipart XHR uploads:
146            multipart: true,
147            // To upload large files in smaller chunks, set the following option
148            // to a preferred maximum chunk size. If set to 0, null or undefined,
149            // or the browser does not support the required Blob API, files will
150            // be uploaded as a whole.
151            maxChunkSize: undefined,
152            // When a non-multipart upload or a chunked multipart upload has been
153            // aborted, this option can be used to resume the upload by setting
154            // it to the size of the already uploaded bytes. This option is most
155            // useful when modifying the options object inside of the "add" or
156            // "send" callbacks, as the options are cloned for each file upload.
157            uploadedBytes: undefined,
158            // By default, failed (abort or error) file uploads are removed from the
159            // global progress calculation. Set the following option to false to
160            // prevent recalculating the global progress data:
161            recalculateProgress: true,
162            // Interval in milliseconds to calculate and trigger progress events:
163            progressInterval: 100,
164            // Interval in milliseconds to calculate progress bitrate:
165            bitrateInterval: 500,
166            // By default, uploads are started automatically when adding files:
167            autoUpload: true,
168
169            // Error and info messages:
170            messages: {
171                uploadedBytes: 'Uploaded bytes exceed file size'
172            },
173
174            // Translation function, gets the message key to be translated
175            // and an object with context specific data as arguments:
176            i18n: function (message, context) {
177                message = this.messages[message] || message.toString();
178                if (context) {
179                    $.each(context, function (key, value) {
180                        message = message.replace('{' + key + '}', value);
181                    });
182                }
183                return message;
184            },
185
186            // Additional form data to be sent along with the file uploads can be set
187            // using this option, which accepts an array of objects with name and
188            // value properties, a function returning such an array, a FormData
189            // object (for XHR file uploads), or a simple object.
190            // The form of the first fileInput is given as parameter to the function:
191            formData: function (form) {
192                return form.serializeArray();
193            },
194
195            // The add callback is invoked as soon as files are added to the fileupload
196            // widget (via file input selection, drag & drop, paste or add API call).
197            // If the singleFileUploads option is enabled, this callback will be
198            // called once for each file in the selection for XHR file uploads, else
199            // once for each file selection.
200            //
201            // The upload starts when the submit method is invoked on the data parameter.
202            // The data object contains a files property holding the added files
203            // and allows you to override plugin options as well as define ajax settings.
204            //
205            // Listeners for this callback can also be bound the following way:
206            // .bind('fileuploadadd', func);
207            //
208            // data.submit() returns a Promise object and allows to attach additional
209            // handlers using jQuery's Deferred callbacks:
210            // data.submit().done(func).fail(func).always(func);
211            add: function (e, data) {
212                if (e.isDefaultPrevented()) {
213                    return false;
214                }
215                if (data.autoUpload || (data.autoUpload !== false &&
216                        $(this).fileupload('option', 'autoUpload'))) {
217                    data.process().done(function () {
218                        data.submit();
219                    });
220                }
221            },
222
223            // Other callbacks:
224
225            // Callback for the submit event of each file upload:
226            // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
227
228            // Callback for the start of each file upload request:
229            // send: function (e, data) {}, // .bind('fileuploadsend', func);
230
231            // Callback for successful uploads:
232            // done: function (e, data) {}, // .bind('fileuploaddone', func);
233
234            // Callback for failed (abort or error) uploads:
235            // fail: function (e, data) {}, // .bind('fileuploadfail', func);
236
237            // Callback for completed (success, abort or error) requests:
238            // always: function (e, data) {}, // .bind('fileuploadalways', func);
239
240            // Callback for upload progress events:
241            // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
242
243            // Callback for global upload progress events:
244            // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
245
246            // Callback for uploads start, equivalent to the global ajaxStart event:
247            // start: function (e) {}, // .bind('fileuploadstart', func);
248
249            // Callback for uploads stop, equivalent to the global ajaxStop event:
250            // stop: function (e) {}, // .bind('fileuploadstop', func);
251
252            // Callback for change events of the fileInput(s):
253            // change: function (e, data) {}, // .bind('fileuploadchange', func);
254
255            // Callback for paste events to the pasteZone(s):
256            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
257
258            // Callback for drop events of the dropZone(s):
259            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
260
261            // Callback for dragover events of the dropZone(s):
262            // dragover: function (e) {}, // .bind('fileuploaddragover', func);
263
264            // Callback for the start of each chunk upload request:
265            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
266
267            // Callback for successful chunk uploads:
268            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
269
270            // Callback for failed (abort or error) chunk uploads:
271            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
272
273            // Callback for completed (success, abort or error) chunk upload requests:
274            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
275
276            // The plugin options are used as settings object for the ajax calls.
277            // The following are jQuery ajax settings required for the file uploads:
278            processData: false,
279            contentType: false,
280            cache: false,
281            timeout: 0
282        },
283
284        // A list of options that require reinitializing event listeners and/or
285        // special initialization code:
286        _specialOptions: [
287            'fileInput',
288            'dropZone',
289            'pasteZone',
290            'multipart',
291            'forceIframeTransport'
292        ],
293
294        _blobSlice: $.support.blobSlice && function () {
295            var slice = this.slice || this.webkitSlice || this.mozSlice;
296            return slice.apply(this, arguments);
297        },
298
299        _BitrateTimer: function () {
300            this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
301            this.loaded = 0;
302            this.bitrate = 0;
303            this.getBitrate = function (now, loaded, interval) {
304                var timeDiff = now - this.timestamp;
305                if (!this.bitrate || !interval || timeDiff > interval) {
306                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
307                    this.loaded = loaded;
308                    this.timestamp = now;
309                }
310                return this.bitrate;
311            };
312        },
313
314        _isXHRUpload: function (options) {
315            return !options.forceIframeTransport &&
316                ((!options.multipart && $.support.xhrFileUpload) ||
317                $.support.xhrFormDataFileUpload);
318        },
319
320        _getFormData: function (options) {
321            var formData;
322            if ($.type(options.formData) === 'function') {
323                return options.formData(options.form);
324            }
325            if ($.isArray(options.formData)) {
326                return options.formData;
327            }
328            if ($.type(options.formData) === 'object') {
329                formData = [];
330                $.each(options.formData, function (name, value) {
331                    formData.push({name: name, value: value});
332                });
333                return formData;
334            }
335            return [];
336        },
337
338        _getTotal: function (files) {
339            var total = 0;
340            $.each(files, function (index, file) {
341                total += file.size || 1;
342            });
343            return total;
344        },
345
346        _initProgressObject: function (obj) {
347            var progress = {
348                loaded: 0,
349                total: 0,
350                bitrate: 0
351            };
352            if (obj._progress) {
353                $.extend(obj._progress, progress);
354            } else {
355                obj._progress = progress;
356            }
357        },
358
359        _initResponseObject: function (obj) {
360            var prop;
361            if (obj._response) {
362                for (prop in obj._response) {
363                    if (obj._response.hasOwnProperty(prop)) {
364                        delete obj._response[prop];
365                    }
366                }
367            } else {
368                obj._response = {};
369            }
370        },
371
372        _onProgress: function (e, data) {
373            if (e.lengthComputable) {
374                var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
375                    loaded;
376                if (data._time && data.progressInterval &&
377                        (now - data._time < data.progressInterval) &&
378                        e.loaded !== e.total) {
379                    return;
380                }
381                data._time = now;
382                loaded = Math.floor(
383                    e.loaded / e.total * (data.chunkSize || data._progress.total)
384                ) + (data.uploadedBytes || 0);
385                // Add the difference from the previously loaded state
386                // to the global loaded counter:
387                this._progress.loaded += (loaded - data._progress.loaded);
388                this._progress.bitrate = this._bitrateTimer.getBitrate(
389                    now,
390                    this._progress.loaded,
391                    data.bitrateInterval
392                );
393                data._progress.loaded = data.loaded = loaded;
394                data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
395                    now,
396                    loaded,
397                    data.bitrateInterval
398                );
399                // Trigger a custom progress event with a total data property set
400                // to the file size(s) of the current upload and a loaded data
401                // property calculated accordingly:
402                this._trigger(
403                    'progress',
404                    $.Event('progress', {delegatedEvent: e}),
405                    data
406                );
407                // Trigger a global progress event for all current file uploads,
408                // including ajax calls queued for sequential file uploads:
409                this._trigger(
410                    'progressall',
411                    $.Event('progressall', {delegatedEvent: e}),
412                    this._progress
413                );
414            }
415        },
416
417        _initProgressListener: function (options) {
418            var that = this,
419                xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
420            // Accesss to the native XHR object is required to add event listeners
421            // for the upload progress event:
422            if (xhr.upload) {
423                $(xhr.upload).bind('progress', function (e) {
424                    var oe = e.originalEvent;
425                    // Make sure the progress event properties get copied over:
426                    e.lengthComputable = oe.lengthComputable;
427                    e.loaded = oe.loaded;
428                    e.total = oe.total;
429                    that._onProgress(e, options);
430                });
431                options.xhr = function () {
432                    return xhr;
433                };
434            }
435        },
436
437        _isInstanceOf: function (type, obj) {
438            // Cross-frame instanceof check
439            return Object.prototype.toString.call(obj) === '[object ' + type + ']';
440        },
441
442        _initXHRData: function (options) {
443            var that = this,
444                formData,
445                file = options.files[0],
446                // Ignore non-multipart setting if not supported:
447                multipart = options.multipart || !$.support.xhrFileUpload,
448                paramName = $.type(options.paramName) === 'array' ?
449                    options.paramName[0] : options.paramName;
450            options.headers = $.extend({}, options.headers);
451            if (options.contentRange) {
452                options.headers['Content-Range'] = options.contentRange;
453            }
454            if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
455                options.headers['Content-Disposition'] = 'attachment; filename="' +
456                    encodeURI(file.uploadName || file.name) + '"';
457            }
458            if (!multipart) {
459                options.contentType = file.type || 'application/octet-stream';
460                options.data = options.blob || file;
461            } else if ($.support.xhrFormDataFileUpload) {
462                if (options.postMessage) {
463                    // window.postMessage does not allow sending FormData
464                    // objects, so we just add the File/Blob objects to
465                    // the formData array and let the postMessage window
466                    // create the FormData object out of this array:
467                    formData = this._getFormData(options);
468                    if (options.blob) {
469                        formData.push({
470                            name: paramName,
471                            value: options.blob
472                        });
473                    } else {
474                        $.each(options.files, function (index, file) {
475                            formData.push({
476                                name: ($.type(options.paramName) === 'array' &&
477                                    options.paramName[index]) || paramName,
478                                value: file
479                            });
480                        });
481                    }
482                } else {
483                    if (that._isInstanceOf('FormData', options.formData)) {
484                        formData = options.formData;
485                    } else {
486                        formData = new FormData();
487                        $.each(this._getFormData(options), function (index, field) {
488                            formData.append(field.name, field.value);
489                        });
490                    }
491                    if (options.blob) {
492                        formData.append(
493                            paramName,
494                            options.blob,
495                            file.uploadName || file.name
496                        );
497                    } else {
498                        $.each(options.files, function (index, file) {
499                            // This check allows the tests to run with
500                            // dummy objects:
501                            if (that._isInstanceOf('File', file) ||
502                                    that._isInstanceOf('Blob', file)) {
503                                formData.append(
504                                    ($.type(options.paramName) === 'array' &&
505                                        options.paramName[index]) || paramName,
506                                    file,
507                                    file.uploadName || file.name
508                                );
509                            }
510                        });
511                    }
512                }
513                options.data = formData;
514            }
515            // Blob reference is not needed anymore, free memory:
516            options.blob = null;
517        },
518
519        _initIframeSettings: function (options) {
520            var targetHost = $('<a></a>').prop('href', options.url).prop('host');
521            // Setting the dataType to iframe enables the iframe transport:
522            options.dataType = 'iframe ' + (options.dataType || '');
523            // The iframe transport accepts a serialized array as form data:
524            options.formData = this._getFormData(options);
525            // Add redirect url to form data on cross-domain uploads:
526            if (options.redirect && targetHost && targetHost !== location.host) {
527                options.formData.push({
528                    name: options.redirectParamName || 'redirect',
529                    value: options.redirect
530                });
531            }
532        },
533
534        _initDataSettings: function (options) {
535            if (this._isXHRUpload(options)) {
536                if (!this._chunkedUpload(options, true)) {
537                    if (!options.data) {
538                        this._initXHRData(options);
539                    }
540                    this._initProgressListener(options);
541                }
542                if (options.postMessage) {
543                    // Setting the dataType to postmessage enables the
544                    // postMessage transport:
545                    options.dataType = 'postmessage ' + (options.dataType || '');
546                }
547            } else {
548                this._initIframeSettings(options);
549            }
550        },
551
552        _getParamName: function (options) {
553            var fileInput = $(options.fileInput),
554                paramName = options.paramName;
555            if (!paramName) {
556                paramName = [];
557                fileInput.each(function () {
558                    var input = $(this),
559                        name = input.prop('name') || 'files[]',
560                        i = (input.prop('files') || [1]).length;
561                    while (i) {
562                        paramName.push(name);
563                        i -= 1;
564                    }
565                });
566                if (!paramName.length) {
567                    paramName = [fileInput.prop('name') || 'files[]'];
568                }
569            } else if (!$.isArray(paramName)) {
570                paramName = [paramName];
571            }
572            return paramName;
573        },
574
575        _initFormSettings: function (options) {
576            // Retrieve missing options from the input field and the
577            // associated form, if available:
578            if (!options.form || !options.form.length) {
579                options.form = $(options.fileInput.prop('form'));
580                // If the given file input doesn't have an associated form,
581                // use the default widget file input's form:
582                if (!options.form.length) {
583                    options.form = $(this.options.fileInput.prop('form'));
584                }
585            }
586            options.paramName = this._getParamName(options);
587            if (!options.url) {
588                options.url = options.form.prop('action') || location.href;
589            }
590            // The HTTP request method must be "POST" or "PUT":
591            options.type = (options.type ||
592                ($.type(options.form.prop('method')) === 'string' &&
593                    options.form.prop('method')) || ''
594                ).toUpperCase();
595            if (options.type !== 'POST' && options.type !== 'PUT' &&
596                    options.type !== 'PATCH') {
597                options.type = 'POST';
598            }
599            if (!options.formAcceptCharset) {
600                options.formAcceptCharset = options.form.attr('accept-charset');
601            }
602        },
603
604        _getAJAXSettings: function (data) {
605            var options = $.extend({}, this.options, data);
606            this._initFormSettings(options);
607            this._initDataSettings(options);
608            return options;
609        },
610
611        // jQuery 1.6 doesn't provide .state(),
612        // while jQuery 1.8+ removed .isRejected() and .isResolved():
613        _getDeferredState: function (deferred) {
614            if (deferred.state) {
615                return deferred.state();
616            }
617            if (deferred.isResolved()) {
618                return 'resolved';
619            }
620            if (deferred.isRejected()) {
621                return 'rejected';
622            }
623            return 'pending';
624        },
625
626        // Maps jqXHR callbacks to the equivalent
627        // methods of the given Promise object:
628        _enhancePromise: function (promise) {
629            promise.success = promise.done;
630            promise.error = promise.fail;
631            promise.complete = promise.always;
632            return promise;
633        },
634
635        // Creates and returns a Promise object enhanced with
636        // the jqXHR methods abort, success, error and complete:
637        _getXHRPromise: function (resolveOrReject, context, args
637) {
638            var dfd = $.Deferred(),
639                promise = dfd.promise();
640            context = context || this.options.context || promise;
641            if (resolveOrReject === true) {
642                dfd.resolveWith(context, args);
643            } else if (resolveOrReject === false) {
644                dfd.rejectWith(context, args);
645            }
646            promise.abort = dfd.promise;
647            return this._enhancePromise(promise);
648        },
649
650        // Adds convenience methods to the data callback argument:
651        _addConvenienceMethods: function (e, data) {
652            var that = this,
653                getPromise = function (args) {
654                    return $.Deferred().resolveWith(that, args).promise();
655                };
656            data.process = function (resolveFunc, rejectFunc) {
657                if (resolveFunc || rejectFunc) {
658                    data._processQueue = this._processQueue =
659                        (this._processQueue || getPromise([this])).then(
660                            function () {
661                                if (data.errorThrown) {
662                                    return $.Deferred()
663                                        .rejectWith(that, [data]).promise();
664                                }
665                                return getPromise(arguments);
666                            }
667                        ).then(resolveFunc, rejectFunc);
668                }
669                return this._processQueue || getPromise([this]);
670            };
671            data.submit = function () {
672                if (this.state() !== 'pending') {
673                    data.jqXHR = this.jqXHR =
674                        (that._trigger(
675                            'submit',
676                            $.Event('submit', {delegatedEvent: e}),
677                            this
678                        ) !== false) && that._onSend(e, this);
679                }
680                return this.jqXHR || that._getXHRPromise();
681            };
682            data.abort = function () {
683                if (this.jqXHR) {
684                    return this.jqXHR.abort();
685                }
686                this.errorThrown = 'abort';
687                that._trigger('fail', null, this);
688                return that._getXHRPromise(false);
689            };
690            data.state = function () {
691                if (this.jqXHR) {
692                    return that._getDeferredState(this.jqXHR);
693                }
694                if (this._processQueue) {
695                    return that._getDeferredState(this._processQueue);
696                }
697            };
698            data.processing = function () {
699                return !this.jqXHR && this._processQueue && that
700                    ._getDeferredState(this._processQueue) === 'pending';
701            };
702            data.progress = function () {
703                return this._progress;
704            };
705            data.response = function () {
706                return this._response;
707            };
708        },
709
710        // Parses the Range header from the server response
711        // and returns the uploaded bytes:
712        _getUploadedBytes: function (jqXHR) {
713            var range = jqXHR.getResponseHeader('Range'),
714                parts = range && range.split('-'),
715                upperBytesPos = parts && parts.length > 1 &&
716                    parseInt(parts[1], 10);
717            return upperBytesPos && upperBytesPos + 1;
718        },
719
720        // Uploads a file in multiple, sequential requests
721        // by splitting the file up in multiple blob chunks.
722        // If the second parameter is true, only tests if the file
723        // should be uploaded in chunks, but does not invoke any
724        // upload requests:
725        _chunkedUpload: function (options, testOnly) {
726            options.uploadedBytes = options.uploadedBytes || 0;
727            var that = this,
728                file = options.files[0],
729                fs = file.size,
730                ub = options.uploadedBytes,
731                mcs = options.maxChunkSize || fs,
732                slice = this._blobSlice,
733                dfd = $.Deferred(),
734                promise = dfd.promise(),
735                jqXHR,
736                upload;
737            if (!(this._isXHRUpload(options) && slice && (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs)) ||
738                    options.data) {
739                return false;
740            }
741            if (testOnly) {
742                return true;
743            }
744            if (ub >= fs) {
745                file.error = options.i18n('uploadedBytes');
746                return this._getXHRPromise(
747                    false,
748                    options.context,
749                    [null, 'error', file.error]
750                );
751            }
752            // The chunk upload method:
753            upload = function () {
754                // Clone the options object for each chunk upload:
755                var o = $.extend({}, options),
756                    currentLoaded = o._progress.loaded;
757                o.blob = slice.call(
758                    file,
759                    ub,
760                    ub + ($.type(mcs) === 'function' ? mcs(o) : mcs),
761                    file.type
762                );
763                // Store the current chunk size, as the blob itself
764                // will be dereferenced after data processing:
765                o.chunkSize = o.blob.size;
766                // Expose the chunk bytes position range:
767                o.contentRange = 'bytes ' + ub + '-' +
768                    (ub + o.chunkSize - 1) + '/' + fs;
769                // Process the upload data (the blob and potential form data):
770                that._initXHRData(o);
771                // Add progress listeners for this chunk upload:
772                that._initProgressListener(o);
773                jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
774                        that._getXHRPromise(false, o.context))
775                    .done(function (result, textStatus, jqXHR) {
776                        ub = that._getUploadedBytes(jqXHR) ||
777                            (ub + o.chunkSize);
778                        // Create a progress event if no final progress event
779                        // with loaded equaling total has been triggered
780                        // for this chunk:
781                        if (currentLoaded + o.chunkSize - o._progress.loaded) {
782                            that._onProgress($.Event('progress', {
783                                lengthComputable: true,
784                                loaded: ub - o.uploadedBytes,
785                                total: ub - o.uploadedBytes
786                            }), o);
787                        }
788                        options.uploadedBytes = o.uploadedBytes = ub;
789                        o.result = result;
790                        o.textStatus = textStatus;
791                        o.jqXHR = jqXHR;
792                        that._trigger('chunkdone', null, o);
793                        that._trigger('chunkalways', null, o);
794                        if (ub < fs) {
795                            // File upload not yet complete,
796                            // continue with the next chunk:
797                            upload();
798                        } else {
799                            dfd.resolveWith(
800                                o.context,
801                                [result, textStatus, jqXHR]
802                            );
803                        }
804                    })
805                    .fail(function (jqXHR, textStatus, errorThrown) {
806                        o.jqXHR = jqXHR;
807                        o.textStatus = textStatus;
808                        o.errorThrown = errorThrown;
809                        that._trigger('chunkfail', null, o);
810                        that._trigger('chunkalways', null, o);
811                        dfd.rejectWith(
812                            o.context,
813                            [jqXHR, textStatus, errorThrown]
814                        );
815                    });
816            };
817            this._enhancePromise(promise);
818            promise.abort = function () {
819                return jqXHR.abort();
820            };
821            upload();
822            return promise;
823        },
824
825        _beforeSend: function (e, data) {
826            if (this._active === 0) {
827                // the start callback is triggered when an upload starts
828                // and no other uploads are currently running,
829                // equivalent to the global ajaxStart event:
830                this._trigger('start');
831                // Set timer for global bitrate progress calculation:
832                this._bitrateTimer = new this._BitrateTimer();
833                // Reset the global progress values:
834                this._progress.loaded = this._progress.total = 0;
835                this._progress.bitrate = 0;
836            }
837            // Make sure the container objects for the .response() and
838            // .progress() methods on the data object are available
839            // and reset to their initial state:
840            this._initResponseObject(data);
841            this._initProgressObject(data);
842            data._progress.loaded = data.loaded = data.uploadedBytes || 0;
843            data._progress.total = data.total = this._getTotal(data.files) || 1;
844            data._progress.bitrate = data.bitrate = 0;
845            this._active += 1;
846            // Initialize the global progress values:
847            this._progress.loaded += data.loaded;
848            this._progress.total += data.total;
849        },
850
851        _onDone: function (result, textStatus, jqXHR, options) {
852            var total = options._progress.total,
853                response = options._response;
854            if (options._progress.loaded < total) {
855                // Create a progress event if no final progress event
856                // with loaded equaling total has been triggered:
857                this._onProgress($.Event('progress', {
858                    lengthComputable: true,
859                    loaded: total,
860                    total: total
861                }), options);
862            }
863            response.result = options.result = result;
864            response.textStatus = options.textStatus = textStatus;
865            response.jqXHR = options.jqXHR = jqXHR;
866            this._trigger('done', null, options);
867        },
868
869        _onFail: function (jqXHR, textStatus, errorThrown, options) {
870            var response = options._response;
871            if (options.recalculateProgress) {
872                // Remove the failed (error or abort) file upload from
873                // the global progress calculation:
874                this._progress.loaded -= options._progress.loaded;
875                this._progress.total -= options._progress.total;
876            }
877            response.jqXHR = options.jqXHR = jqXHR;
878            response.textStatus = options.textStatus = textStatus;
879            response.errorThrown = options.errorThrown = errorThrown;
880            this._trigger('fail', null, options);
881        },
882
883        _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
884            // jqXHRorResult, textStatus and jqXHRorError are added to the
885            // options object via done and fail callbacks
886            this._trigger('always', null, options);
887        },
888
889        _onSend: function (e, data) {
890            if (!data.submit) {
891                this._addConvenienceMethods(e, data);
892            }
893            var that = this,
894                jqXHR,
895                aborted,
896                slot,
897                pipe,
898                options = that._getAJAXSettings(data),
899                send = function () {
900                    that._sending += 1;
901                    // Set timer for bitrate progress calculation:
902                    options._bitrateTimer = new that._BitrateTimer();
903                    jqXHR = jqXHR || (
904                        ((aborted || that._trigger(
905                            'send',
906                            $.Event('send', {delegatedEvent: e}),
907                            options
908                        ) === false) &&
909                        that._getXHRPromise(false, options.context, aborted)) ||
910                        that._chunkedUpload(options) || $.ajax(options)
911                    ).done(function (result, textStatus, jqXHR) {
912                        that._onDone(result, textStatus, jqXHR, options);
913                    }).fail(function (jqXHR, textStatus, errorThrown) {
914                        that._onFail(jqXHR, textStatus, errorThrown, options);
915                    }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
916                        that._onAlways(
917                            jqXHRorResult,
918                            textStatus,
919                            jqXHRorError,
920                            options
921                        );
922                        that._sending -= 1;
923                        that._active -= 1;
924                        if (options.limitConcurrentUploads &&
925                                options.limitConcurrentUploads > that._sending) {
926                            // Start the next queued upload,
927                            // that has not been aborted:
928                            var nextSlot = that._slots.shift();
929                            while (nextSlot) {
930                                if (that._getDeferredState(nextSlot) === 'pending') {
931                                    nextSlot.resolve();
932                                    break;
933                                }
934                                nextSlot = that._slots.shift();
935                            }
936                        }
937                        if (that._active === 0) {
938                            // The stop callback is triggered when all uploads have
939                            // been completed, equivalent to the global ajaxStop event:
940                            that._trigger('stop');
941                        }
942                    });
943                    return jqXHR;
944                };
945            this._beforeSend(e, options);
946            if (this.options.sequentialUploads ||
947                    (this.options.limitConcurrentUploads &&
948                    this.options.limitConcurrentUploads <= this._sending)) {
949                if (this.options.limitConcurrentUploads > 1) {
950                    slot = $.Deferred();
951                    this._slots.push(slot);
952                    pipe = slot.then(send);
953                } else {
954                    this._sequence = this._sequence.then(send, send);
955                    pipe = this._sequence;
956                }
957                // Return the piped Promise object, enhanced with an abort method,
958                // which is delegated to the jqXHR object of the current upload,
959                // and jqXHR callbacks mapped to the equivalent Promise methods:
960                pipe.abort = function () {
961                    aborted = [undefined, 'abort', 'abort'];
962                    if (!jqXHR) {
963                        if (slot) {
964                            slot.rejectWith(options.context, aborted);
965                        }
966                        return send();
967                    }
968                    return jqXHR.abort();
969                };
970                return this._enhancePromise(pipe);
971            }
972            return send();
973        },
974
975        _onAdd: function (e, data) {
976            var that = this,
977                result = true,
978                options = $.extend({}, this.options, data),
979                files = data.files,
980                filesLength = files.length,
981                limit = options.limitMultiFileUploads,
982                limitSize = options.limitMultiFileUploadSize,
983                overhead = options.limitMultiFileUploadSizeOverhead,
984                batchSize = 0,
985                paramName = this._getParamName(options),
986                paramNameSet,
987                paramNameSlice,
988                fileSet,
989                i,
990                j = 0;
991            if (!filesLength) {
992                return false;
993            }
994            if (limitSize && files[0].size === undefined) {
995                limitSize = undefined;
996            }
997            if (!(options.singleFileUploads || limit || limitSize) ||
998                    !this._isXHRUpload(options)) {
999                fileSet = [files];
1000                paramNameSet = [paramName];
1001            } else if (!(options.singleFileUploads || limitSize) && limit) {
1002                fileSet = [];
1003                paramNameSet = [];
1004                for (i = 0; i < filesLength; i += limit) {
1005                    fileSet.push(files.slice(i, i + limit));
1006                    paramNameSlice = paramName.slice(i, i + limit);
1007                    if (!paramNameSlice.length) {
1008                        paramNameSlice = paramName;
1009                    }
1010                    paramNameSet.push(paramNameSlice);
1011                }
1012            } else if (!options.singleFileUploads && limitSize) {
1013                fileSet = [];
1014                paramNameSet = [];
1015                for (i = 0; i < filesLength; i = i + 1) {
1016                    batchSize += files[i].size + overhead;
1017                    if (i + 1 === filesLength ||
1018                            ((batchSize + files[i + 1].size + overhead) > limitSize) ||
1019                            (limit && i + 1 - j >= limit)) {
1020                        fileSet.push(files.slice(j, i + 1));
1021                        paramNameSlice = paramName.slice(j, i + 1);
1022                        if (!paramNameSlice.length) {
1023                            paramNameSlice = paramName;
1024                        }
1025                        paramNameSet.push(paramNameSlice);
1026                        j = i + 1;
1027                        batchSize = 0;
1028                    }
1029                }
1030            } else {
1031                paramNameSet = paramName;
1032            }
1033            data.originalFiles = files;
1034            $.each(fileSet || files, function (index, element) {
1035                var newData = $.extend({}, data);
1036                newData.files = fileSet ? element : [element];
1037                newData.paramName = paramNameSet[index];
1038                that._initResponseObject(newData);
1039                that._initProgressObject(newData);
1040                that._addConvenienceMethods(e, newData);
1041                result = that._trigger(
1042                    'add',
1043                    $.Event('add', {delegatedEvent: e}),
1044                    newData
1045                );
1046                return result;
1047            });
1048            return result;
1049        },
1050
1051        _replaceFileInput: function (data) {
1052            var input = data.fileInput,
1053                inputClone = input.clone(true),
1054                restoreFocus = input.is(document.activeElement);
1055            // Add a reference for the new cloned file input to the data argument:
1056            data.fileInputClone = inputClone;
1057            $('<form></form>').append(inputClone)[0].reset();
1058            // Detaching allows to insert the fileInput on another form
1059            // without loosing the file input value:
1060            input.after(inputClone).detach();
1061            // If the fileInput had focus before it was detached,
1062            // restore focus to the inputClone.
1063            if (restoreFocus) {
1064                inputClone.focus();
1065            }
1066            // Avoid memory leaks with the detached file input:
1067            $.cleanData(input.unbind('remove'));
1068            // Replace the original file input element in the fileInput
1069            // elements set with the clone, which has been copied including
1070            // event handlers:
1071            this.options.fileInput = this.options.fileInput.map(function (i, el) {
1072                if (el === input[0]) {
1073                    return inputClone[0];
1074                }
1075                return el;
1076            });
1077            // If the widget has been initialized on the file input itself,
1078            // override this.element with the file input clone:
1079            if (input[0] === this.element[0]) {
1080                this.element = inputClone;
1081            }
1082        },
1083
1084        _handleFileTreeEntry: function (entry, path) {
1085            var that = this,
1086                dfd = $.Deferred(),
1087                entries = [],
1088                dirReader,
1089                errorHandler = function (e) {
1090                    if (e && !e.entry) {
1091                        e.entry = entry;
1092                    }
1093                    // Since $.when returns immediately if one
1094                    // Deferred is rejected, we use resolve instead.
1095                    // This allows valid files and invalid items
1096                    // to be returned together in one set:
1097                    dfd.resolve([e]);
1098                },
1099                successHandler = function (entries) {
1100                    that._handleFileTreeEntries(
1101                        entries,
1102                        path + entry.name + '/'
1103                    ).done(function (files) {
1104                        dfd.resolve(files);
1105                    }).fail(errorHandler);
1106                },
1107                readEntries = function () {
1108                    dirReader.readEntries(function (results) {
1109                        if (!results.length) {
1110                            successHandler(entries);
1111                        } else {
1112                            entries = entries.concat(results);
1113                            readEntries();
1114                        }
1115                    }, errorHandler);
1116                };
1117            path = path || '';
1118            if (entry.isFile) {
1119                if (entry._file) {
1120                    // Workaround for Chrome bug #149735
1121                    entry._file.relativePath = path;
1122                    dfd.resolve(entry._file);
1123                } else {
1124                    entry.file(function (file) {
1125                        file.relativePath = path;
1126                        dfd.resolve(file);
1127                    }, errorHandler);
1128                }
1129            } else if (entry.isDirectory) {
1130                dirReader = entry.createReader();
1131                readEntries();
1132            } else {
1133                // Return an empy list for file system items
1134                // other than files or directories:
1135                dfd.resolve([]);
1136            }
1137            return dfd.promise();
1138        },
1139
1140        _handleFileTreeEntries: function (entries, path) {
1141            var that = this;
1142            return $.when.apply(
1143                $,
1144                $.map(entries, function (entry) {
1145                    return that._handleFileTreeEntry(entry, path);
1146                })
1147            ).then(function () {
1148                return Array.prototype.concat.apply(
1149                    [],
1150                    arguments
1151                );
1152            });
1153        },
1154
1155        _getDroppedFiles: function (dataTransfer) {
1156            dataTransfer = dataTransfer || {};
1157            var items = dataTransfer.items;
1158            if (items && items.length && (items[0].webkitGetAsEntry ||
1159                    items[0].getAsEntry)) {
1160                return this._handleFileTreeEntries(
1161                    $.map(items, function (item) {
1162                        var entry;
1163                        if (item.webkitGetAsEntry) {
1164                            entry = item.webkitGetAsEntry();
1165                            if (entry) {
1166                                // Workaround for Chrome bug #149735:
1167                                entry._file = item.getAsFile();
1168                            }
1169                            return entry;
1170                        }
1171                        return item.getAsEntry();
1172                    })
1173                );
1174            }
1175            return $.Deferred().resolve(
1176                $.makeArray(dataTransfer.files)
1177            ).promise();
1178        },
1179
1180        _getSingleFileInputFiles: function (fileInput) {
1181            fileInput = $(fileInput);
1182            var entries = fileInput.prop('webkitEntries') ||
1183                    fileInput.prop('entries'),
1184                files,
1185                value;
1186            if (entries && entries.length) {
1187                return this._handleFileTreeEntries(entries);
1188            }
1189            files = $.makeArray(fileInput.prop('files'));
1190            if (!files.length) {
1191                value = fileInput.prop('value');
1192                if (!value) {
1193                    return $.Deferred().resolve([]).promise();
1194                }
1195                // If the files property is not available, the browser does not
1196                // support the File API and we add a pseudo File object with
1197                // the input value as name with path information removed:
1198                files = [{name: value.replace(/^.*\\/, '')}];
1199            } else if (files[0].name === undefined && files[0].fileName) {
1200                // File normalization for Safari 4 and Firefox 3:
1201                $.each(files, function (index, file) {
1202                    file.name = file.fileName;
1203                    file.size = file.fileSize;
1204                });
1205            }
1206            return $.Deferred().resolve(files).promise();
1207        },
1208
1209        _getFileInputFiles: function (fileInput) {
1210            if (!(fileInput instanceof $) || fileInput.length === 1) {
1211                return this._getSingleFileInputFiles(fileInput);
1212            }
1213            return $.when.apply(
1214                $,
1215                $.map(fileInput, this._getSingleFileInputFiles)
1216            ).then(function () {
1217                return Array.prototype.concat.apply(
1218                    [],
1219                    arguments
1220                );
1221            });
1222        },
1223
1224        _onChange: function (e) {
1225            var that = this,
1226                data = {
1227                    fileInput: $(e.target),
1228                    form: $(e.target.form)
1229                };
1230            this._getFileInputFiles(data.fileInput).always(function (files) {
1231                data.files = files;
1232                if (that.options.replaceFileInput) {
1233                    that._replaceFileInput(data);
1234                }
1235                if (that._trigger(
1236                        'change',
1237                        $.Event('change', {delegatedEvent: e}),
1238                        data
1239                    ) !== false) {
1240                    that._onAdd(e, data);
1241                }
1242            });
1243        },
1244
1245        _onPaste: function (e) {
1246            var items = e.originalEvent && e.originalEvent.clipboardData &&
1247                    e.originalEvent.clipboardData.items,
1248                data = {files: []};
1249            if (items && items.length) {
1250                $.each(items, function (index, item) {
1251                    var file = item.getAsFile && item.getAsFile();
1252                    if (file) {
1253                        data.files.push(file);
1254                    }
1255                });
1256                if (this._trigger(
1257                        'paste',
1258                        $.Event('paste', {delegatedEvent: e}),
1259                        data
1260                    ) !== false) {
1261                    this._onAdd(e, data);
1262                }
1263            }
1264        },
1265
1266        _onDrop: function (e) {
1267            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
1268            var that = this,
1269                dataTransfer = e.dataTransfer,
1270                data = {};
1271            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
1272                e.preventDefault();
1273                this._getDroppedFiles(dataTransfer).always(function (files) {
1274                    data.files = files;
1275                    if (that._trigger(
1276                            'drop',
1277                            $.Event('drop', {delegatedEvent: e}),
1278                            data
1279                        ) !== false) {
1280                        that._onAdd(e, data);
1281                    }
1282                });
1283            }
1284        },
1285
1286        _onDragOver: getDragHandler('dragover'),
1287
1288        _onDragEnter: getDragHandler('dragenter'),
1289
1290        _onDragLeave: getDragHandler('dragleave'),
1291
1292        _initEventHandlers: function () {
1293            if (this._isXHRUpload(this.options)) {
1294                this._on(this.options.dropZone, {
1295                    dragover: this._onDragOver,
1296                    drop: this._onDrop,
1297                    // event.preventDefault() on dragenter is required for IE10+:
1298                    dragenter: this._onDragEnter,
1299                    // dragleave is not required, but added for c
1299ompleteness:
1300                    dragleave: this._onDragLeave
1301                });
1302                this._on(this.options.pasteZone, {
1303                    paste: this._onPaste
1304                });
1305            }
1306            if ($.support.fileInput) {
1307                this._on(this.options.fileInput, {
1308                    change: this._onChange
1309                });
1310            }
1311        },
1312
1313        _destroyEventHandlers: function () {
1314            this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
1315            this._off(this.options.pasteZone, 'paste');
1316            this._off(this.options.fileInput, 'change');
1317        },
1318
1319        _destroy: function () {
1320            this._destroyEventHandlers();
1321        },
1322
1323        _setOption: function (key, value) {
1324            var reinit = $.inArray(key, this._specialOptions) !== -1;
1325            if (reinit) {
1326                this._destroyEventHandlers();
1327            }
1328            this._super(key, value);
1329            if (reinit) {
1330                this._initSpecialOptions();
1331                this._initEventHandlers();
1332            }
1333        },
1334
1335        _initSpecialOptions: function () {
1336            var options = this.options;
1337            if (options.fileInput === undefined) {
1338                options.fileInput = this.element.is('input[type="file"]') ?
1339                        this.element : this.element.find('input[type="file"]');
1340            } else if (!(options.fileInput instanceof $)) {
1341                options.fileInput = $(options.fileInput);
1342            }
1343            if (!(options.dropZone instanceof $)) {
1344                options.dropZone = $(options.dropZone);
1345            }
1346            if (!(options.pasteZone instanceof $)) {
1347                options.pasteZone = $(options.pasteZone);
1348            }
1349        },
1350
1351        _getRegExp: function (str) {
1352            var parts = str.split('/'),
1353                modifiers = parts.pop();
1354            parts.shift();
1355            return new RegExp(parts.join('/'), modifiers);
1356        },
1357
1358        _isRegExpOption: function (key, value) {
1359            return key !== 'url' && $.type(value) === 'string' &&
1360                /^\/.*\/[igm]{0,3}$/.test(value);
1361        },
1362
1363        _initDataAttributes: function () {
1364            var that = this,
1365                options = this.options,
1366                data = this.element.data();
1367            // Initialize options set via HTML5 data-attributes:
1368            $.each(
1369                this.element[0].attributes,
1370                function (index, attr) {
1371                    var key = attr.name.toLowerCase(),
1372                        value;
1373                    if (/^data-/.test(key)) {
1374                        // Convert hyphen-ated key to camelCase:
1375                        key = key.slice(5).replace(/-[a-z]/g, function (str) {
1376                            return str.charAt(1).toUpperCase();
1377                        });
1378                        value = data[key];
1379                        if (that._isRegExpOption(key, value)) {
1380                            value = that._getRegExp(value);
1381                        }
1382                        options[key] = value;
1383                    }
1384                }
1385            );
1386        },
1387
1388        _create: function () {
1389            this._initDataAttributes();
1390            this._initSpecialOptions();
1391            this._slots = [];
1392            this._sequence = this._getXHRPromise(true);
1393            this._sending = this._active = 0;
1394            this._initProgressObject(this);
1395            this._initEventHandlers();
1396        },
1397
1398        // This method is exposed to the widget API and allows to query
1399        // the number of active uploads:
1400        active: function () {
1401            return this._active;
1402        },
1403
1404        // This method is exposed to the widget API and allows to query
1405        // the widget upload progress.
1406        // It returns an object with loaded, total and bitrate properties
1407        // for the running uploads:
1408        progress: function () {
1409            return this._progress;
1410        },
1411
1412        // This method is exposed to the widget API and allows adding files
1413        // using the fileupload API. The data parameter accepts an object which
1414        // must have a files property and can contain additional options:
1415        // .fileupload('add', {files: filesList});
1416        add: function (data) {
1417            var that = this;
1418            if (!data || this.options.disabled) {
1419                return;
1420            }
1421            if (data.fileInput && !data.files) {
1422                this._getFileInputFiles(data.fileInput).always(function (files) {
1423                    data.files = files;
1424                    that._onAdd(null, data);
1425                });
1426            } else {
1427                data.files = $.makeArray(data.files);
1428                this._onAdd(null, data);
1429            }
1430        },
1431
1432        // This method is exposed to the widget API and allows sending files
1433        // using the fileupload API. The data parameter accepts an object which
1434        // must have a files or fileInput property and can contain additional options:
1435        // .fileupload('send', {files: filesList});
1436        // The method returns a Promise object for the file upload call.
1437        send: function (data) {
1438            if (data && !this.options.disabled) {
1439                if (data.fileInput && !data.files) {
1440                    var that = this,
1441                        dfd = $.Deferred(),
1442                        promise = dfd.promise(),
1443                        jqXHR,
1444                        aborted;
1445                    promise.abort = function () {
1446                        aborted = true;
1447                        if (jqXHR) {
1448                            return jqXHR.abort();
1449                        }
1450                        dfd.reject(null, 'abort', 'abort');
1451                        return promise;
1452                    };
1453                    this._getFileInputFiles(data.fileInput).always(
1454                        function (files) {
1455                            if (aborted) {
1456                                return;
1457                            }
1458                            if (!files.length) {
1459                                dfd.reject();
1460                                return;
1461                            }
1462                            data.files = files;
1463                            jqXHR = that._onSend(null, data);
1464                            jqXHR.then(
1465                                function (result, textStatus, jqXHR) {
1466                                    dfd.resolve(result, textStatus, jqXHR);
1467                                },
1468                                function (jqXHR, textStatus, errorThrown) {
1469                                    dfd.reject(jqXHR, textStatus, errorThrown);
1470                                }
1471                            );
1472                        }
1473                    );
1474                    return this._enhancePromise(promise);
1475                }
1476                data.files = $.makeArray(data.files);
1477                if (data.files.length) {
1478                    return this._onSend(null, data);
1479                }
1480            }
1481            return this._getXHRPromise(false, data && data.context);
1482        }
1483
1484    });
1485
1486}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.