Administrator
2022-09-14 58d006e05dcf2a20d0ec5367dd03d66a61db6849
提交 | 用户 | 时间
58d006 1 /*! WebUploader 0.1.5 */
A 2
3
4 var jQuery = require('example:widget/ui/jquery/jquery.js')
5
6 return (function( root, factory ) {
7     var modules = {},
8
9         // 内部require, 简单不完全实现。
10         // https://github.com/amdjs/amdjs-api/wiki/require
11         _require = function( deps, callback ) {
12             var args, len, i;
13
14             // 如果deps不是数组,则直接返回指定module
15             if ( typeof deps === 'string' ) {
16                 return getModule( deps );
17             } else {
18                 args = [];
19                 for( len = deps.length, i = 0; i < len; i++ ) {
20                     args.push( getModule( deps[ i ] ) );
21                 }
22
23                 return callback.apply( null, args );
24             }
25         },
26
27         // 内部define,暂时不支持不指定id.
28         _define = function( id, deps, factory ) {
29             if ( arguments.length === 2 ) {
30                 factory = deps;
31                 deps = null;
32             }
33
34             _require( deps || [], function() {
35                 setModule( id, factory, arguments );
36             });
37         },
38
39         // 设置module, 兼容CommonJs写法。
40         setModule = function( id, factory, args ) {
41             var module = {
42                     exports: factory
43                 },
44                 returned;
45
46             if ( typeof factory === 'function' ) {
47                 args.length || (args = [ _require, module.exports, module ]);
48                 returned = factory.apply( null, args );
49                 returned !== undefined && (module.exports = returned);
50             }
51
52             modules[ id ] = module.exports;
53         },
54
55         // 根据id获取module
56         getModule = function( id ) {
57             var module = modules[ id ] || root[ id ];
58
59             if ( !module ) {
60                 throw new Error( '`' + id + '` is undefined' );
61             }
62
63             return module;
64         },
65
66         // 将所有modules,将路径ids装换成对象。
67         exportsTo = function( obj ) {
68             var key, host, parts, part, last, ucFirst;
69
70             // make the first character upper case.
71             ucFirst = function( str ) {
72                 return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));
73             };
74
75             for ( key in modules ) {
76                 host = obj;
77
78                 if ( !modules.hasOwnProperty( key ) ) {
79                     continue;
80                 }
81
82                 parts = key.split('/');
83                 last = ucFirst( parts.pop() );
84
85                 while( (part = ucFirst( parts.shift() )) ) {
86                     host[ part ] = host[ part ] || {};
87                     host = host[ part ];
88                 }
89
90                 host[ last ] = modules[ key ];
91             }
92
93             return obj;
94         },
95
96         makeExport = function( dollar ) {
97             root.__dollar = dollar;
98
99             // exports every module.
100             return exportsTo( factory( root, _define, _require ) );
101         };
102
103     return makeExport( jQuery );
104 })( window, function( window, define, require ) {
105
106
107     /**
108      * @fileOverview jQuery or Zepto
109      */
110     define('dollar-third',[],function() {
111         var $ = window.__dollar || window.jQuery || window.Zepto;
112     
113         if ( !$ ) {
114             throw new Error('jQuery or Zepto not found!');
115         }
116     
117         return $;
118     });
119     /**
120      * @fileOverview Dom 操作相关
121      */
122     define('dollar',[
123         'dollar-third'
124     ], function( _ ) {
125         return _;
126     });
127     /**
128      * @fileOverview 使用jQuery的Promise
129      */
130     define('promise-third',[
131         'dollar'
132     ], function( $ ) {
133         return {
134             Deferred: $.Deferred,
135             when: $.when,
136     
137             isPromise: function( anything ) {
138                 return anything && typeof anything.then === 'function';
139             }
140         };
141     });
142     /**
143      * @fileOverview Promise/A+
144      */
145     define('promise',[
146         'promise-third'
147     ], function( _ ) {
148         return _;
149     });
150     /**
151      * @fileOverview 基础类方法。
152      */
153     
154     /**
155      * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。
156      *
157      * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.
158      * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如:
159      *
160      * * module `base`:WebUploader.Base
161      * * module `file`: WebUploader.File
162      * * module `lib/dnd`: WebUploader.Lib.Dnd
163      * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd
164      *
165      *
166      * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。
167      * @module WebUploader
168      * @title WebUploader API文档
169      */
170     define('base',[
171         'dollar',
172         'promise'
173     ], function( $, promise ) {
174     
175         var noop = function() {},
176             call = Function.call;
177     
178         // http://jsperf.com/uncurrythis
179         // 反科里化
180         function uncurryThis( fn ) {
181             return function() {
182                 return call.apply( fn, arguments );
183             };
184         }
185     
186         function bindFn( fn, context ) {
187             return function() {
188                 return fn.apply( context, arguments );
189             };
190         }
191     
192         function createObject( proto ) {
193             var f;
194     
195             if ( Object.create ) {
196                 return Object.create( proto );
197             } else {
198                 f = function() {};
199                 f.prototype = proto;
200                 return new f();
201             }
202         }
203     
204     
205         /**
206          * 基础类,提供一些简单常用的方法。
207          * @class Base
208          */
209         return {
210     
211             /**
212              * @property {String} version 当前版本号。
213              */
214             version: '0.1.5',
215     
216             /**
217              * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。
218              */
219             $: $,
220     
221             Deferred: promise.Deferred,
222     
223             isPromise: promise.isPromise,
224     
225             when: promise.when,
226     
227             /**
228              * @description  简单的浏览器检查结果。
229              *
230              * * `webkit`  webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。
231              * * `chrome`  chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。
232              * * `ie`  ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+**
233              * * `firefox`  firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。
234              * * `safari`  safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。
235              * * `opera`  opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。
236              *
237              * @property {Object} [browser]
238              */
239             browser: (function( ua ) {
240                 var ret = {},
241                     webkit = ua.match( /WebKit\/([\d.]+)/ ),
242                     chrome = ua.match( /Chrome\/([\d.]+)/ ) ||
243                         ua.match( /CriOS\/([\d.]+)/ ),
244     
245                     ie = ua.match( /MSIE\s([\d\.]+)/ ) ||
246                         ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ),
247                     firefox = ua.match( /Firefox\/([\d.]+)/ ),
248                     safari = ua.match( /Safari\/([\d.]+)/ ),
249                     opera = ua.match( /OPR\/([\d.]+)/ );
250     
251                 webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));
252                 chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));
253                 ie && (ret.ie = parseFloat( ie[ 1 ] ));
254                 firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));
255                 safari && (ret.safari = parseFloat( safari[ 1 ] ));
256                 opera && (ret.opera = parseFloat( opera[ 1 ] ));
257     
258                 return ret;
259             })( navigator.userAgent ),
260     
261             /**
262              * @description  操作系统检查结果。
263              *
264              * * `android`  如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。
265              * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。
266              * @property {Object} [os]
267              */
268             os: (function( ua ) {
269                 var ret = {},
270     
271                     // osx = !!ua.match( /\(Macintosh\; Intel / ),
272                     android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ),
273                     ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ );
274     
275                 // osx && (ret.osx = true);
276                 android && (ret.android = parseFloat( android[ 1 ] ));
277                 ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));
278     
279                 return ret;
280             })( navigator.userAgent ),
281     
282             /**
283              * 实现类与类之间的继承。
284              * @method inherits
285              * @grammar Base.inherits( super ) => child
286              * @grammar Base.inherits( super, protos ) => child
287              * @grammar Base.inherits( super, protos, statics ) => child
288              * @param  {Class} super 父类
289              * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。
290              * @param  {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。
291              * @param  {Object} [statics] 静态属性或方法。
292              * @return {Class} 返回子类。
293              * @example
294              * function Person() {
295              *     console.log( 'Super' );
296              * }
297              * Person.prototype.hello = function() {
298              *     console.log( 'hello' );
299              * };
300              *
301              * var Manager = Base.inherits( Person, {
302              *     world: function() {
303              *         console.log( 'World' );
304              *     }
305              * });
306              *
307              * // 因为没有指定构造器,父类的构造器将会执行。
308              * var instance = new Manager();    // => Super
309              *
310              * // 继承子父类的方法
311              * instance.hello();    // => hello
312              * instance.world();    // => World
313              *
314              * // 子类的__super__属性指向父类
315              * console.log( Manager.__super__ === Person );    // => true
316              */
317             inherits: function( Super, protos, staticProtos ) {
318                 var child;
319     
320                 if ( typeof protos === 'function' ) {
321                     child = protos;
322                     protos = null;
323                 } else if ( protos && protos.hasOwnProperty('constructor') ) {
324                     child = protos.constructor;
325                 } else {
326                     child = function() {
327                         return Super.apply( this, arguments );
328                     };
329                 }
330     
331                 // 复制静态方法
332                 $.extend( true, child, Super, staticProtos || {} );
333     
334                 /* jshint camelcase: false */
335     
336                 // 让子类的__super__属性指向父类。
337                 child.__super__ = Super.prototype;
338     
339                 // 构建原型,添加原型方法或属性。
340                 // 暂时用Object.create实现。
341                 child.prototype = createObject( Super.prototype );
342                 protos && $.extend( true, child.prototype, protos );
343     
344                 return child;
345             },
346     
347             /**
348              * 一个不做任何事情的方法。可以用来赋值给默认的callback.
349              * @method noop
350              */
351             noop: noop,
352     
353             /**
354              * 返回一个新的方法,此方法将已指定的`context`来执行。
355              * @grammar Base.bindFn( fn, context ) => Function
356              * @method bindFn
357              * @example
358              * var doSomething = function() {
359              *         console.log( this.name );
360              *     },
361              *     obj = {
362              *         name: 'Object Name'
363              *     },
364              *     aliasFn = Base.bind( doSomething, obj );
365              *
366              *  aliasFn();    // => Object Name
367              *
368              */
369             bindFn: bindFn,
370     
371             /**
372              * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。
373              * @grammar Base.log( args... ) => undefined
374              * @method log
375              */
376             log: (function() {
377                 if ( window.console ) {
378                     return bindFn( console.log, console );
379                 }
380                 return noop;
381             })(),
382     
383             nextTick: (function() {
384     
385                 return function( cb ) {
386                     setTimeout( cb, 1 );
387                 };
388     
389                 // @bug 当浏览器不在当前窗口时就停了。
390                 // var next = window.requestAnimationFrame ||
391                 //     window.webkitRequestAnimationFrame ||
392                 //     window.mozRequestAnimationFrame ||
393                 //     function( cb ) {
394                 //         window.setTimeout( cb, 1000 / 60 );
395                 //     };
396     
397                 // // fix: Uncaught TypeError: Illegal invocation
398                 // return bindFn( next, window );
399             })(),
400     
401             /**
402              * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。
403              * 将用来将非数组对象转化成数组对象。
404              * @grammar Base.slice( target, start[, end] ) => Array
405              * @method slice
406              * @example
407              * function doSomthing() {
408              *     var args = Base.slice( arguments, 1 );
409              *     console.log( args );
410              * }
411              *
412              * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array ["arg2", "arg3"]
413              */
414             slice: uncurryThis( [].slice ),
415     
416             /**
417              * 生成唯一的ID
418              * @method guid
419              * @grammar Base.guid() => String
420              * @grammar Base.guid( prefx ) => String
421              */
422             guid: (function() {
423                 var counter = 0;
424     
425                 return function( prefix ) {
426                     var guid = (+new Date()).toString( 32 ),
427                         i = 0;
428     
429                     for ( ; i < 5; i++ ) {
430                         guid += Math.floor( Math.random() * 65535 ).toString( 32 );
431                     }
432     
433                     return (prefix || 'wu_') + guid + (counter++).toString( 32 );
434                 };
435             })(),
436     
437             /**
438              * 格式化文件大小, 输出成带单位的字符串
439              * @method formatSize
440              * @grammar Base.formatSize( size ) => String
441              * @grammar Base.formatSize( size, pointLength ) => String
442              * @grammar Base.formatSize( size, pointLength, units ) => String
443              * @param {Number} size 文件大小
444              * @param {Number} [pointLength=2] 精确到的小数点数。
445              * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K.
446              * @example
447              * console.log( Base.formatSize( 100 ) );    // => 100B
448              * console.log( Base.formatSize( 1024 ) );    // => 1.00K
449              * console.log( Base.formatSize( 1024, 0 ) );    // => 1K
450              * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M
451              * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G
452              * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB
453              */
454             formatSize: function( size, pointLength, units ) {
455                 var unit;
456     
457                 units = units || [ 'B', 'K', 'M', 'G', 'TB' ];
458     
459                 while ( (unit = units.shift()) && size > 1024 ) {
460                     size = size / 1024;
461                 }
462     
463                 return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +
464                         unit;
465             }
466         };
467     });
468     /**
469      * 事件处理类,可以独立使用,也可以扩展给对象使用。
470      * @fileOverview Mediator
471      */
472     define('mediator',[
473         'base'
474     ], function( Base ) {
475         var $ = Base.$,
476             slice = [].slice,
477             separator = /\s+/,
478             protos;
479     
480         // 根据条件过滤出事件handlers.
481         function findHandlers( arr, name, callback, context ) {
482             return $.grep( arr, function( handler ) {
483                 return handler &&
484                         (!name || handler.e === name) &&
485                         (!callback || handler.cb === callback ||
486                         handler.cb._cb === callback) &&
487                         (!context || handler.ctx === context);
488             });
489         }
490     
491         function eachEvent( events, callback, iterator ) {
492             // 不支持对象,只支持多个event用空格隔开
493             $.each( (events || '').split( separator ), function( _, key ) {
494                 iterator( key, callback );
495             });
496         }
497     
498         function triggerHanders( events, args ) {
499             var stoped = false,
500                 i = -1,
501                 len = events.length,
502                 handler;
503     
504             while ( ++i < len ) {
505                 handler = events[ i ];
506     
507                 if ( handler.cb.apply( handler.ctx2, args ) === false ) {
508                     stoped = true;
509                     break;
510                 }
511             }
512     
513             return !stoped;
514         }
515     
516         protos = {
517     
518             /**
519              * 绑定事件。
520              *
521              * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如
522              * ```javascript
523              * var obj = {};
524              *
525              * // 使得obj有事件行为
526              * Mediator.installTo( obj );
527              *
528              * obj.on( 'testa', function( arg1, arg2 ) {
529              *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'
530              * });
531              *
532              * obj.trigger( 'testa', 'arg1', 'arg2' );
533              * ```
534              *
535              * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。
536              * 切会影响到`trigger`方法的返回值,为`false`。
537              *
538              * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处,
539              * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。
540              * ```javascript
541              * obj.on( 'all', function( type, arg1, arg2 ) {
542              *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'
543              * });
544              * ```
545              *
546              * @method on
547              * @grammar on( name, callback[, context] ) => self
548              * @param  {String}   name     事件名,支持多个事件用空格隔开
549              * @param  {Function} callback 事件处理器
550              * @param  {Object}   [context]  事件处理器的上下文。
551              * @return {self} 返回自身,方便链式
552              * @chainable
553              * @class Mediator
554              */
555             on: function( name, callback, context ) {
556                 var me = this,
557                     set;
558     
559                 if ( !callback ) {
560                     return this;
561                 }
562     
563                 set = this._events || (this._events = []);
564     
565                 eachEvent( name, callback, function( name, callback ) {
566                     var handler = { e: name };
567     
568                     handler.cb = callback;
569                     handler.ctx = context;
570                     handler.ctx2 = context || me;
571                     handler.id = set.length;
572     
573                     set.push( handler );
574                 });
575     
576                 return this;
577             },
578     
579             /**
580              * 绑定事件,且当handler执行完后,自动解除绑定。
581              * @method once
582              * @grammar once( name, callback[, context] ) => self
583              * @param  {String}   name     事件名
584              * @param  {Function} callback 事件处理器
585              * @param  {Object}   [context]  事件处理器的上下文。
586              * @return {self} 返回自身,方便链式
587              * @chainable
588              */
589             once: function( name, callback, context ) {
590                 var me = this;
591     
592                 if ( !callback ) {
593                     return me;
594                 }
595     
596                 eachEvent( name, callback, function( name, callback ) {
597                     var once = function() {
598                             me.off( name, once );
599                             return callback.apply( context || me, arguments );
600                         };
601     
602                     once._cb = callback;
603                     me.on( name, once, context );
604                 });
605     
606                 return me;
607             },
608     
609             /**
610              * 解除事件绑定
611              * @method off
612              * @grammar off( [name[, callback[, context] ] ] ) => self
613              * @param  {String}   [name]     事件名
614              * @param  {Function} [callback] 事件处理器
615              * @param  {Object}   [context]  事件处理器的上下文。
616              * @return {self} 返回自身,方便链式
617              * @chainable
618              */
619             off: function( name, cb, ctx ) {
620                 var events = this._events;
621     
622                 if ( !events ) {
623                     return this;
624                 }
625     
626                 if ( !name && !cb && !ctx ) {
627                     this._events = [];
628                     return this;
629                 }
630     
631                 eachEvent( name, cb, function( name, cb ) {
632                     $.each( findHandlers( events, name, cb, ctx ), function() {
633                         delete events[ this.id ];
634                     });
635                 });
636     
637                 return this;
638             },
639     
640             /**
641              * 触发事件
642              * @method trigger
643              * @grammar trigger( name[, args...] ) => self
644              * @param  {String}   type     事件名
645              * @param  {*} [...] 任意参数
646              * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true
647              */
648             trigger: function( type ) {
649                 var args, events, allEvents;
650     
651                 if ( !this._events || !type ) {
652                     return this;
653                 }
654     
655                 args = slice.call( arguments, 1 );
656                 events = findHandlers( this._events, type );
657                 allEvents = findHandlers( this._events, 'all' );
658     
659                 return triggerHanders( events, args ) &&
660                         triggerHanders( allEvents, arguments );
661             }
662         };
663     
664         /**
665          * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。
666          * 主要目的是负责模块与模块之间的合作,降低耦合度。
667          *
668          * @class Mediator
669          */
670         return $.extend({
671     
672             /**
673              * 可以通过这个接口,使任何对象具备事件功能。
674              * @method installTo
675              * @param  {Object} obj 需要具备事件行为的对象。
676              * @return {Object} 返回obj.
677              */
678             installTo: function( obj ) {
679                 return $.extend( obj, protos );
680             }
681     
682         }, protos );
683     });
684     /**
685      * @fileOverview Uploader上传类
686      */
687     define('uploader',[
688         'base',
689         'mediator'
690     ], function( Base, Mediator ) {
691     
692         var $ = Base.$;
693     
694         /**
695          * 上传入口类。
696          * @class Uploader
697          * @constructor
698          * @grammar new Uploader( opts ) => Uploader
699          * @example
700          * var uploader = WebUploader.Uploader({
701          *     swf: 'path_of_swf/Uploader.swf',
702          *
703          *     // 开起分片上传。
704          *     chunked: true
705          * });
706          */
707         function Uploader( opts ) {
708             this.options = $.extend( true, {}, Uploader.options, opts );
709             this._init( this.options );
710         }
711     
712         // default Options
713         // widgets中有相应扩展
714         Uploader.options = {};
715         Mediator.installTo( Uploader.prototype );
716     
717         // 批量添加纯命令式方法。
718         $.each({
719             upload: 'start-upload',
720             stop: 'stop-upload',
721             getFile: 'get-file',
722             getFiles: 'get-files',
723             addFile: 'add-file',
724             addFiles: 'add-file',
725             sort: 'sort-files',
726             removeFile: 'remove-file',
727             cancelFile: 'cancel-file',
728             skipFile: 'skip-file',
729             retry: 'retry',
730             isInProgress: 'is-in-progress',
731             makeThumb: 'make-thumb',
732             md5File: 'md5-file',
733             getDimension: 'get-dimension',
734             addButton: 'add-btn',
735             predictRuntimeType: 'predict-runtime-type',
736             refresh: 'refresh',
737             disable: 'disable',
738             enable: 'enable',
739             reset: 'reset'
740         }, function( fn, command ) {
741             Uploader.prototype[ fn ] = function() {
742                 return this.request( command, arguments );
743             };
744         });
745     
746         $.extend( Uploader.prototype, {
747             state: 'pending',
748     
749             _init: function( opts ) {
750                 var me = this;
751     
752                 me.request( 'init', opts, function() {
753                     me.state = 'ready';
754                     me.trigger('ready');
755                 });
756             },
757     
758             /**
759              * 获取或者设置Uploader配置项。
760              * @method option
761              * @grammar option( key ) => *
762              * @grammar option( key, val ) => self
763              * @example
764              *
765              * // 初始状态图片上传前不会压缩
766              * var uploader = new WebUploader.Uploader({
767              *     compress: null;
768              * });
769              *
770              * // 修改后图片上传前,尝试将图片压缩到1600 * 1600
771              * uploader.option( 'compress', {
772              *     width: 1600,
773              *     height: 1600
774              * });
775              */
776             option: function( key, val ) {
777                 var opts = this.options;
778     
779                 // setter
780                 if ( arguments.length > 1 ) {
781     
782                     if ( $.isPlainObject( val ) &&
783                             $.isPlainObject( opts[ key ] ) ) {
784                         $.extend( opts[ key ], val );
785                     } else {
786                         opts[ key ] = val;
787                     }
788     
789                 } else {    // getter
790                     return key ? opts[ key ] : opts;
791                 }
792             },
793     
794             /**
795              * 获取文件统计信息。返回一个包含一下信息的对象。
796              * * `successNum` 上传成功的文件数
797              * * `progressNum` 上传中的文件数
798              * * `cancelNum` 被删除的文件数
799              * * `invalidNum` 无效的文件数
800              * * `uploadFailNum` 上传失败的文件数
801              * * `queueNum` 还在队列中的文件数
802              * * `interruptNum` 被暂停的文件数
803              * @method getStats
804              * @grammar getStats() => Object
805              */
806             getStats: function() {
807                 // return this._mgr.getStats.apply( this._mgr, arguments );
808                 var stats = this.request('get-stats');
809     
810                 return stats ? {
811                     successNum: stats.numOfSuccess,
812                     progressNum: stats.numOfProgress,
813     
814                     // who care?
815                     // queueFailNum: 0,
816                     cancelNum: stats.numOfCancel,
817                     invalidNum: stats.numOfInvalid,
818                     uploadFailNum: stats.numOfUploadFailed,
819                     queueNum: stats.numOfQueue,
820                     interruptNum: stats.numofInterrupt
821                 } : {};
822             },
823     
824             // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
825             trigger: function( type/*, args...*/ ) {
826                 var args = [].slice.call( arguments, 1 ),
827                     opts = this.options,
828                     name = 'on' + type.substring( 0, 1 ).toUpperCase() +
829                         type.substring( 1 );
830     
831                 if (
832                         // 调用通过on方法注册的handler.
833                         Mediator.trigger.apply( this, arguments ) === false ||
834     
835                         // 调用opts.onEvent
836                         $.isFunction( opts[ name ] ) &&
837                         opts[ name ].apply( this, args ) === false ||
838     
839                         // 调用this.onEvent
840                         $.isFunction( this[ name ] ) &&
841                         this[ name ].apply( this, args ) === false ||
842     
843                         // 广播所有uploader的事件。
844                         Mediator.trigger.apply( Mediator,
845                         [ this, type ].concat( args ) ) === false ) {
846     
847                     return false;
848                 }
849     
850                 return true;
851             },
852     
853             /**
854              * 销毁 webuploader 实例
855              * @method destroy
856              * @grammar destroy() => undefined
857              */
858             destroy: function() {
859                 this.request( 'destroy', arguments );
860                 this.off();
861             },
862     
863             // widgets/widget.js将补充此方法的详细文档。
864             request: Base.noop
865         });
866     
867         /**
868          * 创建Uploader实例,等同于new Uploader( opts );
869          * @method create
870          * @class Base
871          * @static
872          * @grammar Base.create( opts ) => Uploader
873          */
874         Base.create = Uploader.create = function( opts ) {
875             return new Uploader( opts );
876         };
877     
878         // 暴露Uploader,可以通过它来扩展业务逻辑。
879         Base.Uploader = Uploader;
880     
881         return Uploader;
882     });
883     /**
884      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
885      */
886     define('runtime/runtime',[
887         'base',
888         'mediator'
889     ], function( Base, Mediator ) {
890     
891         var $ = Base.$,
892             factories = {},
893     
894             // 获取对象的第一个key
895             getFirstKey = function( obj ) {
896                 for ( var key in obj ) {
897                     if ( obj.hasOwnProperty( key ) ) {
898                         return key;
899                     }
900                 }
901                 return null;
902             };
903     
904         // 接口类。
905         function Runtime( options ) {
906             this.options = $.extend({
907                 container: document.body
908             }, options );
909             this.uid = Base.guid('rt_');
910         }
911     
912         $.extend( Runtime.prototype, {
913     
914             getContainer: function() {
915                 var opts = this.options,
916                     parent, container;
917     
918                 if ( this._container ) {
919                     return this._container;
920                 }
921     
922                 parent = $( opts.container || document.body );
923                 container = $( document.createElement('div') );
924     
925                 container.attr( 'id', 'rt_' + this.uid );
926                 container.css({
927                     position: 'absolute',
928                     top: '0px',
929                     left: '0px',
930                     width: '1px',
931                     height: '1px',
932                     overflow: 'hidden'
933                 });
934     
935                 parent.append( container );
936                 parent.addClass('webuploader-container');
937                 this._container = container;
938                 this._parent = parent;
939                 return container;
940             },
941     
942             init: Base.noop,
943             exec: Base.noop,
944     
945             destroy: function() {
946                 this._container && this._container.remove();
947                 this._parent && this._parent.removeClass('webuploader-container');
948                 this.off();
949             }
950         });
951     
952         Runtime.orders = 'html5,flash';
953     
954     
955         /**
956          * 添加Runtime实现。
957          * @param {String} type    类型
958          * @param {Runtime} factory 具体Runtime实现。
959          */
960         Runtime.addRuntime = function( type, factory ) {
961             factories[ type ] = factory;
962         };
963     
964         Runtime.hasRuntime = function( type ) {
965             return !!(type ? factories[ type ] : getFirstKey( factories ));
966         };
967     
968         Runtime.create = function( opts, orders ) {
969             var type, runtime;
970     
971             orders = orders || Runtime.orders;
972             $.each( orders.split( /\s*,\s*/g ), function() {
973                 if ( factories[ this ] ) {
974                     type = this;
975                     return false;
976                 }
977             });
978     
979             type = type || getFirstKey( factories );
980     
981             if ( !type ) {
982                 throw new Error('Runtime Error');
983             }
984     
985             runtime = new factories[ type ]( opts );
986             return runtime;
987         };
988     
989         Mediator.installTo( Runtime.prototype );
990         return Runtime;
991     });
992     
993     /**
994      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
995      */
996     define('runtime/client',[
997         'base',
998         'mediator',
999         'runtime/runtime'
1000     ], function( Base, Mediator, Runtime ) {
1001     
1002         var cache;
1003     
1004         cache = (function() {
1005             var obj = {};
1006     
1007             return {
1008                 add: function( runtime ) {
1009                     obj[ runtime.uid ] = runtime;
1010                 },
1011     
1012                 get: function( ruid, standalone ) {
1013                     var i;
1014     
1015                     if ( ruid ) {
1016                         return obj[ ruid ];
1017                     }
1018     
1019                     for ( i in obj ) {
1020                         // 有些类型不能重用,比如filepicker.
1021                         if ( standalone && obj[ i ].__standalone ) {
1022                             continue;
1023                         }
1024     
1025                         return obj[ i ];
1026                     }
1027     
1028                     return null;
1029                 },
1030     
1031                 remove: function( runtime ) {
1032                     delete obj[ runtime.uid ];
1033                 }
1034             };
1035         })();
1036     
1037         function RuntimeClient( component, standalone ) {
1038             var deferred = Base.Deferred(),
1039                 runtime;
1040     
1041             this.uid = Base.guid('client_');
1042     
1043             // 允许runtime没有初始化之前,注册一些方法在初始化后执行。
1044             this.runtimeReady = function( cb ) {
1045                 return deferred.done( cb );
1046             };
1047     
1048             this.connectRuntime = function( opts, cb ) {
1049     
1050                 // already connected.
1051                 if ( runtime ) {
1052                     throw new Error('already connected!');
1053                 }
1054     
1055                 deferred.done( cb );
1056     
1057                 if ( typeof opts === 'string' && cache.get( opts ) ) {
1058                     runtime = cache.get( opts );
1059                 }
1060     
1061                 // 像filePicker只能独立存在,不能公用。
1062                 runtime = runtime || cache.get( null, standalone );
1063     
1064                 // 需要创建
1065                 if ( !runtime ) {
1066                     runtime = Runtime.create( opts, opts.runtimeOrder );
1067                     runtime.__promise = deferred.promise();
1068                     runtime.once( 'ready', deferred.resolve );
1069                     runtime.init();
1070                     cache.add( runtime );
1071                     runtime.__client = 1;
1072                 } else {
1073                     // 来自cache
1074                     Base.$.extend( runtime.options, opts );
1075                     runtime.__promise.then( deferred.resolve );
1076                     runtime.__client++;
1077                 }
1078     
1079                 standalone && (runtime.__standalone = standalone);
1080                 return runtime;
1081             };
1082     
1083             this.getRuntime = function() {
1084                 return runtime;
1085             };
1086     
1087             this.disconnectRuntime = function() {
1088                 if ( !runtime ) {
1089                     return;
1090                 }
1091     
1092                 runtime.__client--;
1093     
1094                 if ( runtime.__client <= 0 ) {
1095                     cache.remove( runtime );
1096                     delete runtime.__promise;
1097                     runtime.destroy();
1098                 }
1099     
1100                 runtime = null;
1101             };
1102     
1103             this.exec = function() {
1104                 if ( !runtime ) {
1105                     return;
1106                 }
1107     
1108                 var args = Base.slice( arguments );
1109                 component && args.unshift( component );
1110     
1111                 return runtime.exec.apply( this, args );
1112             };
1113     
1114             this.getRuid = function() {
1115                 return runtime && runtime.uid;
1116             };
1117     
1118             this.destroy = (function( destroy ) {
1119                 return function() {
1120                     destroy && destroy.apply( this, arguments );
1121                     this.trigger('destroy');
1122                     this.off();
1123                     this.exec('destroy');
1124                     this.disconnectRuntime();
1125                 };
1126             })( this.destroy );
1127         }
1128     
1129         Mediator.installTo( RuntimeClient.prototype );
1130         return RuntimeClient;
1131     });
1132     /**
1133      * @fileOverview 错误信息
1134      */
1135     define('lib/dnd',[
1136         'base',
1137         'mediator',
1138         'runtime/client'
1139     ], function( Base, Mediator, RuntimeClent ) {
1140     
1141         var $ = Base.$;
1142     
1143         function DragAndDrop( opts ) {
1144             opts = this.options = $.extend({}, DragAndDrop.options, opts );
1145     
1146             opts.container = $( opts.container );
1147     
1148             if ( !opts.container.length ) {
1149                 return;
1150             }
1151     
1152             RuntimeClent.call( this, 'DragAndDrop' );
1153         }
1154     
1155         DragAndDrop.options = {
1156             accept: null,
1157             disableGlobalDnd: false
1158         };
1159     
1160         Base.inherits( RuntimeClent, {
1161             constructor: DragAndDrop,
1162     
1163             init: function() {
1164                 var me = this;
1165     
1166                 me.connectRuntime( me.options, function() {
1167                     me.exec('init');
1168                     me.trigger('ready');
1169                 });
1170             }
1171         });
1172     
1173         Mediator.installTo( DragAndDrop.prototype );
1174     
1175         return DragAndDrop;
1176     });
1177     /**
1178      * @fileOverview 组件基类。
1179      */
1180     define('widgets/widget',[
1181         'base',
1182         'uploader'
1183     ], function( Base, Uploader ) {
1184     
1185         var $ = Base.$,
1186             _init = Uploader.prototype._init,
1187             _destroy = Uploader.prototype.destroy,
1188             IGNORE = {},
1189             widgetClass = [];
1190     
1191         function isArrayLike( obj ) {
1192             if ( !obj ) {
1193                 return false;
1194             }
1195     
1196             var length = obj.length,
1197                 type = $.type( obj );
1198     
1199             if ( obj.nodeType === 1 && length ) {
1200                 return true;
1201             }
1202     
1203             return type === 'array' || type !== 'function' && type !== 'string' &&
1204                     (length === 0 || typeof length === 'number' && length > 0 &&
1205                     (length - 1) in obj);
1206         }
1207     
1208         function Widget( uploader ) {
1209             this.owner = uploader;
1210             this.options = uploader.options;
1211         }
1212     
1213         $.extend( Widget.prototype, {
1214     
1215             init: Base.noop,
1216     
1217             // 类Backbone的事件监听声明,监听uploader实例上的事件
1218             // widget直接无法监听事件,事件只能通过uploader来传递
1219             invoke: function( apiName, args ) {
1220     
1221                 /*
1222                     {
1223                         'make-thumb': 'makeThumb'
1224                     }
1225                  */
1226                 var map = this.responseMap;
1227     
1228                 // 如果无API响应声明则忽略
1229                 if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||
1230                         !$.isFunction( this[ map[ apiName ] ] ) ) {
1231     
1232                     return IGNORE;
1233                 }
1234     
1235                 return this[ map[ apiName ] ].apply( this, args );
1236     
1237             },
1238     
1239             /**
1240              * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。
1241              * @method request
1242              * @grammar request( command, args ) => * | Promise
1243              * @grammar request( command, args, callback ) => Promise
1244              * @for  Uploader
1245              */
1246             request: function() {
1247                 return this.owner.request.apply( this.owner, arguments );
1248             }
1249         });
1250     
1251         // 扩展Uploader.
1252         $.extend( Uploader.prototype, {
1253     
1254             /**
1255              * @property {String | Array} [disableWidgets=undefined]
1256              * @namespace options
1257              * @for Uploader
1258              * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。
1259              */
1260     
1261             // 覆写_init用来初始化widgets
1262             _init: function() {
1263                 var me = this,
1264                     widgets = me._widgets = [],
1265                     deactives = me.options.disableWidgets || '';
1266     
1267                 $.each( widgetClass, function( _, klass ) {
1268                     (!deactives || !~deactives.indexOf( klass._name )) &&
1269                         widgets.push( new klass( me ) );
1270                 });
1271     
1272                 return _init.apply( me, arguments );
1273             },
1274     
1275             request: function( apiName, args, callback ) {
1276                 var i = 0,
1277                     widgets = this._widgets,
1278                     len = widgets && widgets.length,
1279                     rlts = [],
1280                     dfds = [],
1281                     widget, rlt, promise, key;
1282     
1283                 args = isArrayLike( args ) ? args : [ args ];
1284     
1285                 for ( ; i < len; i++ ) {
1286                     widget = widgets[ i ];
1287                     rlt = widget.invoke( apiName, args );
1288     
1289                     if ( rlt !== IGNORE ) {
1290     
1291                         // Deferred对象
1292                         if ( Base.isPromise( rlt ) ) {
1293                             dfds.push( rlt );
1294                         } else {
1295                             rlts.push( rlt );
1296                         }
1297                     }
1298                 }
1299     
1300                 // 如果有callback,则用异步方式。
1301                 if ( callback || dfds.length ) {
1302                     promise = Base.when.apply( Base, dfds );
1303                     key = promise.pipe ? 'pipe' : 'then';
1304     
1305                     // 很重要不能删除。删除了会死循环。
1306                     // 保证执行顺序。让callback总是在下一个 tick 中执行。
1307                     return promise[ key ](function() {
1308                                 var deferred = Base.Deferred(),
1309                                     args = arguments;
1310     
1311                                 if ( args.length === 1 ) {
1312                                     args = args[ 0 ];
1313                                 }
1314     
1315                                 setTimeout(function() {
1316                                     deferred.resolve( args );
1317                                 }, 1 );
1318     
1319                                 return deferred.promise();
1320                             })[ callback ? key : 'done' ]( callback || Base.noop );
1321                 } else {
1322                     return rlts[ 0 ];
1323                 }
1324             },
1325     
1326             destroy: function() {
1327                 _destroy.apply( this, arguments );
1328                 this._widgets = null;
1329             }
1330         });
1331     
1332         /**
1333          * 添加组件
1334          * @grammar Uploader.register(proto);
1335          * @grammar Uploader.register(map, proto);
1336          * @param  {object} responseMap API 名称与函数实现的映射
1337          * @param  {object} proto 组件原型,构造函数通过 constructor 属性定义
1338          * @method Uploader.register
1339          * @for Uploader
1340          * @example
1341          * Uploader.register({
1342          *     'make-thumb': 'makeThumb'
1343          * }, {
1344          *     init: function( options ) {},
1345          *     makeThumb: function() {}
1346          * });
1347          *
1348          * Uploader.register({
1349          *     'make-thumb': function() {
1350          *         
1351          *     }
1352          * });
1353          */
1354         Uploader.register = Widget.register = function( responseMap, widgetProto ) {
1355             var map = { init: 'init', destroy: 'destroy', name: 'anonymous' },
1356                 klass;
1357     
1358             if ( arguments.length === 1 ) {
1359                 widgetProto = responseMap;
1360     
1361                 // 自动生成 map 表。
1362                 $.each(widgetProto, function(key) {
1363                     if ( key[0] === '_' || key === 'name' ) {
1364                         key === 'name' && (map.name = widgetProto.name);
1365                         return;
1366                     }
1367     
1368                     map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key;
1369                 });
1370     
1371             } else {
1372                 map = $.extend( map, responseMap );
1373             }
1374     
1375             widgetProto.responseMap = map;
1376             klass = Base.inherits( Widget, widgetProto );
1377             klass._name = map.name;
1378             widgetClass.push( klass );
1379     
1380             return klass;
1381         };
1382     
1383         /**
1384          * 删除插件,只有在注册时指定了名字的才能被删除。
1385          * @grammar Uploader.unRegister(name);
1386          * @param  {string} name 组件名字
1387          * @method Uploader.unRegister
1388          * @for Uploader
1389          * @example
1390          *
1391          * Uploader.register({
1392          *     name: 'custom',
1393          *     
1394          *     'make-thumb': function() {
1395          *         
1396          *     }
1397          * });
1398          *
1399          * Uploader.unRegister('custom');
1400          */
1401         Uploader.unRegister = Widget.unRegister = function( name ) {
1402             if ( !name || name === 'anonymous' ) {
1403                 return;
1404             }
1405             
1406             // 删除指定的插件。
1407             for ( var i = widgetClass.length; i--; ) {
1408                 if ( widgetClass[i]._name === name ) {
1409                     widgetClass.splice(i, 1)
1410                 }
1411             }
1412         };
1413     
1414         return Widget;
1415     });
1416     /**
1417      * @fileOverview DragAndDrop Widget。
1418      */
1419     define('widgets/filednd',[
1420         'base',
1421         'uploader',
1422         'lib/dnd',
1423         'widgets/widget'
1424     ], function( Base, Uploader, Dnd ) {
1425         var $ = Base.$;
1426     
1427         Uploader.options.dnd = '';
1428     
1429         /**
1430          * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器,如果不指定,则不启动。
1431          * @namespace options
1432          * @for Uploader
1433          */
1434         
1435         /**
1436          * @property {Selector} [disableGlobalDnd=false]  是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。
1437          * @namespace options
1438          * @for Uploader
1439          */
1440     
1441         /**
1442          * @event dndAccept
1443          * @param {DataTransferItemList} items DataTransferItem
1444          * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。
1445          * @for  Uploader
1446          */
1447         return Uploader.register({
1448             name: 'dnd',
1449             
1450             init: function( opts ) {
1451     
1452                 if ( !opts.dnd ||
1453                         this.request('predict-runtime-type') !== 'html5' ) {
1454                     return;
1455                 }
1456     
1457                 var me = this,
1458                     deferred = Base.Deferred(),
1459                     options = $.extend({}, {
1460                         disableGlobalDnd: opts.disableGlobalDnd,
1461                         container: opts.dnd,
1462                         accept: opts.accept
1463                     }),
1464                     dnd;
1465     
1466                 this.dnd = dnd = new Dnd( options );
1467     
1468                 dnd.once( 'ready', deferred.resolve );
1469                 dnd.on( 'drop', function( files ) {
1470                     me.request( 'add-file', [ files ]);
1471                 });
1472     
1473                 // 检测文件是否全部允许添加。
1474                 dnd.on( 'accept', function( items ) {
1475                     return me.owner.trigger( 'dndAccept', items );
1476                 });
1477     
1478                 dnd.init();
1479     
1480                 return deferred.promise();
1481             },
1482     
1483             destroy: function() {
1484                 this.dnd && this.dnd.destroy();
1485             }
1486         });
1487     });
1488     
1489     /**
1490      * @fileOverview 错误信息
1491      */
1492     define('lib/filepaste',[
1493         'base',
1494         'mediator',
1495         'runtime/client'
1496     ], function( Base, Mediator, RuntimeClent ) {
1497     
1498         var $ = Base.$;
1499     
1500         function FilePaste( opts ) {
1501             opts = this.options = $.extend({}, opts );
1502             opts.container = $( opts.container || document.body );
1503             RuntimeClent.call( this, 'FilePaste' );
1504         }
1505     
1506         Base.inherits( RuntimeClent, {
1507             constructor: FilePaste,
1508     
1509             init: function() {
1510                 var me = this;
1511     
1512                 me.connectRuntime( me.options, function() {
1513                     me.exec('init');
1514                     me.trigger('ready');
1515                 });
1516             }
1517         });
1518     
1519         Mediator.installTo( FilePaste.prototype );
1520     
1521         return FilePaste;
1522     });
1523     /**
1524      * @fileOverview 组件基类。
1525      */
1526     define('widgets/filepaste',[
1527         'base',
1528         'uploader',
1529         'lib/filepaste',
1530         'widgets/widget'
1531     ], function( Base, Uploader, FilePaste ) {
1532         var $ = Base.$;
1533     
1534         /**
1535          * @property {Selector} [paste=undefined]  指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.
1536          * @namespace options
1537          * @for Uploader
1538          */
1539         return Uploader.register({
1540             name: 'paste',
1541             
1542             init: function( opts ) {
1543     
1544                 if ( !opts.paste ||
1545                         this.request('predict-runtime-type') !== 'html5' ) {
1546                     return;
1547                 }
1548     
1549                 var me = this,
1550                     deferred = Base.Deferred(),
1551                     options = $.extend({}, {
1552                         container: opts.paste,
1553                         accept: opts.accept
1554                     }),
1555                     paste;
1556     
1557                 this.paste = paste = new FilePaste( options );
1558     
1559                 paste.once( 'ready', deferred.resolve );
1560                 paste.on( 'paste', function( files ) {
1561                     me.owner.request( 'add-file', [ files ]);
1562                 });
1563                 paste.init();
1564     
1565                 return deferred.promise();
1566             },
1567     
1568             destroy: function() {
1569                 this.paste && this.paste.destroy();
1570             }
1571         });
1572     });
1573     /**
1574      * @fileOverview Blob
1575      */
1576     define('lib/blob',[
1577         'base',
1578         'runtime/client'
1579     ], function( Base, RuntimeClient ) {
1580     
1581         function Blob( ruid, source ) {
1582             var me = this;
1583     
1584             me.source = source;
1585             me.ruid = ruid;
1586             this.size = source.size || 0;
1587     
1588             // 如果没有指定 mimetype, 但是知道文件后缀。
1589             if ( !source.type && this.ext &&
1590                     ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) {
1591                 this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext);
1592             } else {
1593                 this.type = source.type || 'application/octet-stream';
1594             }
1595     
1596             RuntimeClient.call( me, 'Blob' );
1597             this.uid = source.uid || this.uid;
1598     
1599             if ( ruid ) {
1600                 me.connectRuntime( ruid );
1601             }
1602         }
1603     
1604         Base.inherits( RuntimeClient, {
1605             constructor: Blob,
1606     
1607             slice: function( start, end ) {
1608                 return this.exec( 'slice', start, end );
1609             },
1610     
1611             getSource: function() {
1612                 return this.source;
1613             }
1614         });
1615     
1616         return Blob;
1617     });
1618     /**
1619      * 为了统一化Flash的File和HTML5的File而存在。
1620      * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。
1621      * @fileOverview File
1622      */
1623     define('lib/file',[
1624         'base',
1625         'lib/blob'
1626     ], function( Base, Blob ) {
1627     
1628         var uid = 1,
1629             rExt = /\.([^.]+)$/;
1630     
1631         function File( ruid, file ) {
1632             var ext;
1633     
1634             this.name = file.name || ('untitled' + uid++);
1635             ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';
1636     
1637             // todo 支持其他类型文件的转换。
1638             // 如果有 mimetype, 但是文件名里面没有找出后缀规律
1639             if ( !ext && file.type ) {
1640                 ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ?
1641                         RegExp.$1.toLowerCase() : '';
1642                 this.name += '.' + ext;
1643             }
1644     
1645             this.ext = ext;
1646             this.lastModifiedDate = file.lastModifiedDate ||
1647                     (new Date()).toLocaleString();
1648     
1649             Blob.apply( this, arguments );
1650         }
1651     
1652         return Base.inherits( Blob, File );
1653     });
1654     
1655     /**
1656      * @fileOverview 错误信息
1657      */
1658     define('lib/filepicker',[
1659         'base',
1660         'runtime/client',
1661         'lib/file'
1662     ], function( Base, RuntimeClent, File ) {
1663     
1664         var $ = Base.$;
1665     
1666         function FilePicker( opts ) {
1667             opts = this.options = $.extend({}, FilePicker.options, opts );
1668     
1669             opts.container = $( opts.id );
1670     
1671             if ( !opts.container.length ) {
1672                 throw new Error('按钮指定错误');
1673             }
1674     
1675             opts.innerHTML = opts.innerHTML || opts.label ||
1676                     opts.container.html() || '';
1677     
1678             opts.button = $( opts.button || document.createElement('div') );
1679             opts.button.html( opts.innerHTML );
1680             opts.container.html( opts.button );
1681     
1682             RuntimeClent.call( this, 'FilePicker', true );
1683         }
1684     
1685         FilePicker.options = {
1686             button: null,
1687             container: null,
1688             label: null,
1689             innerHTML: null,
1690             multiple: true,
1691             accept: null,
1692             name: 'file'
1693         };
1694     
1695         Base.inherits( RuntimeClent, {
1696             constructor: FilePicker,
1697     
1698             init: function() {
1699                 var me = this,
1700                     opts = me.options,
1701                     button = opts.button;
1702     
1703                 button.addClass('webuploader-pick');
1704     
1705                 me.on( 'all', function( type ) {
1706                     var files;
1707     
1708                     switch ( type ) {
1709                         case 'mouseenter':
1710                             button.addClass('webuploader-pick-hover');
1711                             break;
1712     
1713                         case 'mouseleave':
1714                             button.removeClass('webuploader-pick-hover');
1715                             break;
1716     
1717                         case 'change':
1718                             files = me.exec('getFiles');
1719                             me.trigger( 'select', $.map( files, function( file ) {
1720                                 file = new File( me.getRuid(), file );
1721     
1722                                 // 记录来源。
1723                                 file._refer = opts.container;
1724                                 return file;
1725                             }), opts.container );
1726                             break;
1727                     }
1728                 });
1729     
1730                 me.connectRuntime( opts, function() {
1731                     me.refresh();
1732                     me.exec( 'init', opts );
1733                     me.trigger('ready');
1734                 });
1735     
1736                 this._resizeHandler = Base.bindFn( this.refresh, this );
1737                 $( window ).on( 'resize', this._resizeHandler );
1738             },
1739     
1740             refresh: function() {
1741                 var shimContainer = this.getRuntime().getContainer(),
1742                     button = this.options.button,
1743                     width = button.outerWidth ?
1744                             button.outerWidth() : button.width(),
1745     
1746                     height = button.outerHeight ?
1747                             button.outerHeight() : button.height(),
1748     
1749                     pos = button.offset();
1750     
1751                 width && height && shimContainer.css({
1752                     bottom: 'auto',
1753                     right: 'auto',
1754                     width: width + 'px',
1755                     height: height + 'px'
1756                 }).offset( pos );
1757             },
1758     
1759             enable: function() {
1760                 var btn = this.options.button;
1761     
1762                 btn.removeClass('webuploader-pick-disable');
1763                 this.refresh();
1764             },
1765     
1766             disable: function() {
1767                 var btn = this.options.button;
1768     
1769                 this.getRuntime().getContainer().css({
1770                     top: '-99999px'
1771                 });
1772     
1773                 btn.addClass('webuploader-pick-disable');
1774             },
1775     
1776             destroy: function() {
1777                 var btn = this.options.button;
1778                 $( window ).off( 'resize', this._resizeHandler );
1779                 btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' +
1780                     'webuploader-pick');
1781             }
1782         });
1783     
1784         return FilePicker;
1785     });
1786     
1787     /**
1788      * @fileOverview 文件选择相关
1789      */
1790     define('widgets/filepicker',[
1791         'base',
1792         'uploader',
1793         'lib/filepicker',
1794         'widgets/widget'
1795     ], function( Base, Uploader, FilePicker ) {
1796         var $ = Base.$;
1797     
1798         $.extend( Uploader.options, {
1799     
1800             /**
1801              * @property {Selector | Object} [pick=undefined]
1802              * @namespace options
1803              * @for Uploader
1804              * @description 指定选择文件的按钮容器,不指定则不创建按钮。
1805              *
1806              * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。
1807              * * `label` {String} 请采用 `innerHTML` 代替
1808              * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。
1809              * * `multiple` {Boolean} 是否开起同时选择多个文件能力。
1810              */
1811             pick: null,
1812     
1813             /**
1814              * @property {Arroy} [accept=null]
1815              * @namespace options
1816              * @for Uploader
1817              * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。
1818              *
1819              * * `title` {String} 文字描述
1820              * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。
1821              * * `mimeTypes` {String} 多个用逗号分割。
1822              *
1823              * 如:
1824              *
1825              * ```
1826              * {
1827              *     title: 'Images',
1828              *     extensions: 'gif,jpg,jpeg,bmp,png',
1829              *     mimeTypes: 'image/*'
1830              * }
1831              * ```
1832              */
1833             accept: null/*{
1834                 title: 'Images',
1835                 extensions: 'gif,jpg,jpeg,bmp,png',
1836                 mimeTypes: 'image/*'
1837             }*/
1838         });
1839     
1840         return Uploader.register({
1841             name: 'picker',
1842     
1843             init: function( opts ) {
1844                 this.pickers = [];
1845                 return opts.pick && this.addBtn( opts.pick );
1846             },
1847     
1848             refresh: function() {
1849                 $.each( this.pickers, function() {
1850                     this.refresh();
1851                 });
1852             },
1853     
1854             /**
1855              * @method addButton
1856              * @for Uploader
1857              * @grammar addButton( pick ) => Promise
1858              * @description
1859              * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。
1860              * @example
1861              * uploader.addButton({
1862              *     id: '#btnContainer',
1863              *     innerHTML: '选择文件'
1864              * });
1865              */
1866             addBtn: function( pick ) {
1867                 var me = this,
1868                     opts = me.options,
1869                     accept = opts.accept,
1870                     promises = [];
1871     
1872                 if ( !pick ) {
1873                     return;
1874                 }
1875     
1876                 $.isPlainObject( pick ) || (pick = {
1877                     id: pick
1878                 });
1879     
1880                 $( pick.id ).each(function() {
1881                     var options, picker, deferred;
1882     
1883                     deferred = Base.Deferred();
1884     
1885                     options = $.extend({}, pick, {
1886                         accept: $.isPlainObject( accept ) ? [ accept ] : accept,
1887                         swf: opts.swf,
1888                         runtimeOrder: opts.runtimeOrder,
1889                         id: this
1890                     });
1891     
1892                     picker = new FilePicker( options );
1893     
1894                     picker.once( 'ready', deferred.resolve );
1895                     picker.on( 'select', function( files ) {
1896                         me.owner.request( 'add-file', [ files ]);
1897                     });
1898                     picker.init();
1899     
1900                     me.pickers.push( picker );
1901     
1902                     promises.push( deferred.promise() );
1903                 });
1904     
1905                 return Base.when.apply( Base, promises );
1906             },
1907     
1908             disable: function() {
1909                 $.each( this.pickers, function() {
1910                     this.disable();
1911                 });
1912             },
1913     
1914             enable: function() {
1915                 $.each( this.pickers, function() {
1916                     this.enable();
1917                 });
1918             },
1919     
1920             destroy: function() {
1921                 $.each( this.pickers, function() {
1922                     this.destroy();
1923                 });
1924                 this.pickers = null;
1925             }
1926         });
1927     });
1928     /**
1929      * @fileOverview Image
1930      */
1931     define('lib/image',[
1932         'base',
1933         'runtime/client',
1934         'lib/blob'
1935     ], function( Base, RuntimeClient, Blob ) {
1936         var $ = Base.$;
1937     
1938         // 构造器。
1939         function Image( opts ) {
1940             this.options = $.extend({}, Image.options, opts );
1941             RuntimeClient.call( this, 'Image' );
1942     
1943             this.on( 'load', function() {
1944                 this._info = this.exec('info');
1945                 this._meta = this.exec('meta');
1946             });
1947         }
1948     
1949         // 默认选项。
1950         Image.options = {
1951     
1952             // 默认的图片处理质量
1953             quality: 90,
1954     
1955             // 是否裁剪
1956             crop: false,
1957     
1958             // 是否保留头部信息
1959             preserveHeaders: false,
1960     
1961             // 是否允许放大。
1962             allowMagnify: false
1963         };
1964     
1965         // 继承RuntimeClient.
1966         Base.inherits( RuntimeClient, {
1967             constructor: Image,
1968     
1969             info: function( val ) {
1970     
1971                 // setter
1972                 if ( val ) {
1973                     this._info = val;
1974                     return this;
1975                 }
1976     
1977                 // getter
1978                 return this._info;
1979             },
1980     
1981             meta: function( val ) {
1982     
1983                 // setter
1984                 if ( val ) {
1985                     this._meta = val;
1986                     return this;
1987                 }
1988     
1989                 // getter
1990                 return this._meta;
1991             },
1992     
1993             loadFromBlob: function( blob ) {
1994                 var me = this,
1995                     ruid = blob.getRuid();
1996     
1997                 this.connectRuntime( ruid, function() {
1998                     me.exec( 'init', me.options );
1999                     me.exec( 'loadFromBlob', blob );
2000                 });
2001             },
2002     
2003             resize: function() {
2004                 var args = Base.slice( arguments );
2005                 return this.exec.apply( this, [ 'resize' ].concat( args ) );
2006             },
2007     
2008             crop: function() {
2009                 var args = Base.slice( arguments );
2010                 return this.exec.apply( this, [ 'crop' ].concat( args ) );
2011             },
2012     
2013             getAsDataUrl: function( type ) {
2014                 return this.exec( 'getAsDataUrl', type );
2015             },
2016     
2017             getAsBlob: function( type ) {
2018                 var blob = this.exec( 'getAsBlob', type );
2019     
2020                 return new Blob( this.getRuid(), blob );
2021             }
2022         });
2023     
2024         return Image;
2025     });
2026     /**
2027      * @fileOverview 图片操作, 负责预览图片和上传前压缩图片
2028      */
2029     define('widgets/image',[
2030         'base',
2031         'uploader',
2032         'lib/image',
2033         'widgets/widget'
2034     ], function( Base, Uploader, Image ) {
2035     
2036         var $ = Base.$,
2037             throttle;
2038     
2039         // 根据要处理的文件大小来节流,一次不能处理太多,会卡。
2040         throttle = (function( max ) {
2041             var occupied = 0,
2042                 waiting = [],
2043                 tick = function() {
2044                     var item;
2045     
2046                     while ( waiting.length && occupied < max ) {
2047                         item = waiting.shift();
2048                         occupied += item[ 0 ];
2049                         item[ 1 ]();
2050                     }
2051                 };
2052     
2053             return function( emiter, size, cb ) {
2054                 waiting.push([ size, cb ]);
2055                 emiter.once( 'destroy', function() {
2056                     occupied -= size;
2057                     setTimeout( tick, 1 );
2058                 });
2059                 setTimeout( tick, 1 );
2060             };
2061         })( 5 * 1024 * 1024 );
2062     
2063         $.extend( Uploader.options, {
2064     
2065             /**
2066              * @property {Object} [thumb]
2067              * @namespace options
2068              * @for Uploader
2069              * @description 配置生成缩略图的选项。
2070              *
2071              * 默认为:
2072              *
2073              * ```javascript
2074              * {
2075              *     width: 110,
2076              *     height: 110,
2077              *
2078              *     // 图片质量,只有type为`image/jpeg`的时候才有效。
2079              *     quality: 70,
2080              *
2081              *     // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
2082              *     allowMagnify: true,
2083              *
2084              *     // 是否允许裁剪。
2085              *     crop: true,
2086              *
2087              *     // 为空的话则保留原有图片格式。
2088              *     // 否则强制转换成指定的类型。
2089              *     type: 'image/jpeg'
2090              * }
2091              * ```
2092              */
2093             thumb: {
2094                 width: 110,
2095                 height: 110,
2096                 quality: 70,
2097                 allowMagnify: true,
2098                 crop: true,
2099                 preserveHeaders: false,
2100     
2101                 // 为空的话则保留原有图片格式。
2102                 // 否则强制转换成指定的类型。
2103                 // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可
2104                 // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg
2105                 type: 'image/jpeg'
2106             },
2107     
2108             /**
2109              * @property {Object} [compress]
2110              * @namespace options
2111              * @for Uploader
2112              * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。
2113              *
2114              * 默认为:
2115              *
2116              * ```javascript
2117              * {
2118              *     width: 1600,
2119              *     height: 1600,
2120              *
2121              *     // 图片质量,只有type为`image/jpeg`的时候才有效。
2122              *     quality: 90,
2123              *
2124              *     // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
2125              *     allowMagnify: false,
2126              *
2127              *     // 是否允许裁剪。
2128              *     crop: false,
2129              *
2130              *     // 是否保留头部meta信息。
2131              *     preserveHeaders: true,
2132              *
2133              *     // 如果发现压缩后文件大小比原来还大,则使用原来图片
2134              *     // 此属性可能会影响图片自动纠正功能
2135              *     noCompressIfLarger: false,
2136              *
2137              *     // 单位字节,如果图片大小小于此值,不会采用压缩。
2138              *     compressSize: 0
2139              * }
2140              * ```
2141              */
2142             compress: {
2143                 width: 1600,
2144                 height: 1600,
2145                 quality: 90,
2146                 allowMagnify: false,
2147                 crop: false,
2148                 preserveHeaders: true
2149             }
2150         });
2151     
2152         return Uploader.register({
2153     
2154             name: 'image',
2155     
2156     
2157             /**
2158              * 生成缩略图,此过程为异步,所以需要传入`callback`。
2159              * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。
2160              *
2161              * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。
2162              *
2163              * `callback`中可以接收到两个参数。
2164              * * 第一个为error,如果生成缩略图有错误,此error将为真。
2165              * * 第二个为ret, 缩略图的Data URL值。
2166              *
2167              * **注意**
2168              * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。
2169              * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。
2170              *
2171              * @method makeThumb
2172              * @grammar makeThumb( file, callback ) => undefined
2173              * @grammar makeThumb( file, callback, width, height ) => undefined
2174              * @for Uploader
2175              * @example
2176              *
2177              * uploader.on( 'fileQueued', function( file ) {
2178              *     var $li = ...;
2179              *
2180              *     uploader.makeThumb( file, function( error, ret ) {
2181              *         if ( error ) {
2182              *             $li.text('预览错误');
2183              *         } else {
2184              *             $li.append('<img alt="" src="' + ret + '" />');
2185              *         }
2186              *     });
2187              *
2188              * });
2189              */
2190             makeThumb: function( file, cb, width, height ) {
2191                 var opts, image;
2192     
2193                 file = this.request( 'get-file', file );
2194     
2195                 // 只预览图片格式。
2196                 if ( !file.type.match( /^image/ ) ) {
2197                     cb( true );
2198                     return;
2199                 }
2200     
2201                 opts = $.extend({}, this.options.thumb );
2202     
2203                 // 如果传入的是object.
2204                 if ( $.isPlainObject( width ) ) {
2205                     opts = $.extend( opts, width );
2206                     width = null;
2207                 }
2208     
2209                 width = width || opts.width;
2210                 height = height || opts.height;
2211     
2212                 image = new Image( opts );
2213     
2214                 image.once( 'load', function() {
2215                     file._info = file._info || image.info();
2216                     file._meta = file._meta || image.meta();
2217     
2218                     // 如果 width 的值介于 0 - 1
2219                     // 说明设置的是百分比。
2220                     if ( width <= 1 && width > 0 ) {
2221                         width = file._info.width * width;
2222                     }
2223     
2224                     // 同样的规则应用于 height
2225                     if ( height <= 1 && height > 0 ) {
2226                         height = file._info.height * height;
2227                     }
2228     
2229                     image.resize( width, height );
2230                 });
2231     
2232                 // 当 resize 完后
2233                 image.once( 'complete', function() {
2234                     cb( false, image.getAsDataUrl( opts.type ) );
2235                     image.destroy();
2236                 });
2237     
2238                 image.once( 'error', function( reason ) {
2239                     cb( reason || true );
2240                     image.destroy();
2241                 });
2242     
2243                 throttle( image, file.source.size, function() {
2244                     file._info && image.info( file._info );
2245                     file._meta && image.meta( file._meta );
2246                     image.loadFromBlob( file.source );
2247                 });
2248             },
2249     
2250             beforeSendFile: function( file ) {
2251                 var opts = this.options.compress || this.options.resize,
2252                     compressSize = opts && opts.compressSize || 0,
2253                     noCompressIfLarger = opts && opts.noCompressIfLarger || false,
2254                     image, deferred;
2255     
2256                 file = this.request( 'get-file', file );
2257     
2258                 // 只压缩 jpeg 图片格式。
2259                 // gif 可能会丢失针
2260                 // bmp png 基本上尺寸都不大,且压缩比比较小。
2261                 if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||
2262                         file.size < compressSize ||
2263                         file._compressed ) {
2264                     return;
2265                 }
2266     
2267                 opts = $.extend({}, opts );
2268                 deferred = Base.Deferred();
2269     
2270                 image = new Image( opts );
2271     
2272                 deferred.always(function() {
2273                     image.destroy();
2274                     image = null;
2275                 });
2276                 image.once( 'error', deferred.reject );
2277                 image.once( 'load', function() {
2278                     var width = opts.width,
2279                         height = opts.height;
2280     
2281                     file._info = file._info || image.info();
2282                     file._meta = file._meta || image.meta();
2283     
2284                     // 如果 width 的值介于 0 - 1
2285                     // 说明设置的是百分比。
2286                     if ( width <= 1 && width > 0 ) {
2287                         width = file._info.width * width;
2288                     }
2289     
2290                     // 同样的规则应用于 height
2291                     if ( height <= 1 && height > 0 ) {
2292                         height = file._info.height * height;
2293                     }
2294     
2295                     image.resize( width, height );
2296                 });
2297     
2298                 image.once( 'complete', function() {
2299                     var blob, size;
2300     
2301                     // 移动端 UC / qq 浏览器的无图模式下
2302                     // ctx.getImageData 处理大图的时候会报 Exception
2303                     // INDEX_SIZE_ERR: DOM Exception 1
2304                     try {
2305                         blob = image.getAsBlob( opts.type );
2306     
2307                         size = file.size;
2308     
2309                         // 如果压缩后,比原来还大则不用压缩后的。
2310                         if ( !noCompressIfLarger || blob.size < size ) {
2311                             // file.source.destroy && file.source.destroy();
2312                             file.source = blob;
2313                             file.size = blob.size;
2314     
2315                             file.trigger( 'resize', blob.size, size );
2316                         }
2317     
2318                         // 标记,避免重复压缩。
2319                         file._compressed = true;
2320                         deferred.resolve();
2321                     } catch ( e ) {
2322                         // 出错了直接继续,让其上传原始图片
2323                         deferred.resolve();
2324                     }
2325                 });
2326     
2327                 file._info && image.info( file._info );
2328                 file._meta && image.meta( file._meta );
2329     
2330                 image.loadFromBlob( file.source );
2331                 return deferred.promise();
2332             }
2333         });
2334     });
2335     /**
2336      * @fileOverview 文件属性封装
2337      */
2338     define('file',[
2339         'base',
2340         'mediator'
2341     ], function( Base, Mediator ) {
2342     
2343         var $ = Base.$,
2344             idPrefix = 'WU_FILE_',
2345             idSuffix = 0,
2346             rExt = /\.([^.]+)$/,
2347             statusMap = {};
2348     
2349         function gid() {
2350             return idPrefix + idSuffix++;
2351         }
2352     
2353         /**
2354          * 文件类
2355          * @class File
2356          * @constructor 构造函数
2357          * @grammar new File( source ) => File
2358          * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。
2359          */
2360         function WUFile( source ) {
2361     
2362             /**
2363              * 文件名,包括扩展名(后缀)
2364              * @property name
2365              * @type {string}
2366              */
2367             this.name = source.name || 'Untitled';
2368     
2369             /**
2370              * 文件体积(字节)
2371              * @property size
2372              * @type {uint}
2373              * @default 0
2374              */
2375             this.size = source.size || 0;
2376     
2377             /**
2378              * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)
2379              * @property type
2380              * @type {string}
2381              * @default 'application/octet-stream'
2382              */
2383             this.type = source.type || 'application/octet-stream';
2384     
2385             /**
2386              * 文件最后修改日期
2387              * @property lastModifiedDate
2388              * @type {int}
2389              * @default 当前时间戳
2390              */
2391             this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);
2392     
2393             /**
2394              * 文件ID,每个对象具有唯一ID,与文件名无关
2395              * @property id
2396              * @type {string}
2397              */
2398             this.id = gid();
2399     
2400             /**
2401              * 文件扩展名,通过文件名获取,例如test.png的扩展名为png
2402              * @property ext
2403              * @type {string}
2404              */
2405             this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';
2406     
2407     
2408             /**
2409              * 状态文字说明。在不同的status语境下有不同的用途。
2410              * @property statusText
2411              * @type {string}
2412              */
2413             this.statusText = '';
2414     
2415             // 存储文件状态,防止通过属性直接修改
2416             statusMap[ this.id ] = WUFile.Status.INITED;
2417     
2418             this.source = source;
2419             this.loaded = 0;
2420     
2421             this.on( 'error', function( msg ) {
2422                 this.setStatus( WUFile.Status.ERROR, msg );
2423             });
2424         }
2425     
2426         $.extend( WUFile.prototype, {
2427     
2428             /**
2429              * 设置状态,状态变化时会触发`change`事件。
2430              * @method setStatus
2431              * @grammar setStatus( status[, statusText] );
2432              * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)
2433              * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。
2434              */
2435             setStatus: function( status, text ) {
2436     
2437                 var prevStatus = statusMap[ this.id ];
2438     
2439                 typeof text !== 'undefined' && (this.statusText = text);
2440     
2441                 if ( status !== prevStatus ) {
2442                     statusMap[ this.id ] = status;
2443                     /**
2444                      * 文件状态变化
2445                      * @event statuschange
2446                      */
2447                     this.trigger( 'statuschange', status, prevStatus );
2448                 }
2449     
2450             },
2451     
2452             /**
2453              * 获取文件状态
2454              * @return {File.Status}
2455              * @example
2456                      文件状态具体包括以下几种类型:
2457                      {
2458                          // 初始化
2459                         INITED:     0,
2460                         // 已入队列
2461                         QUEUED:     1,
2462                         // 正在上传
2463                         PROGRESS:     2,
2464                         // 上传出错
2465                         ERROR:         3,
2466                         // 上传成功
2467                         COMPLETE:     4,
2468                         // 上传取消
2469                         CANCELLED:     5
2470                     }
2471              */
2472             getStatus: function() {
2473                 return statusMap[ this.id ];
2474             },
2475     
2476             /**
2477              * 获取文件原始信息。
2478              * @return {*}
2479              */
2480             getSource: function() {
2481                 return this.source;
2482             },
2483     
2484             destroy: function() {
2485                 this.off();
2486                 delete statusMap[ this.id ];
2487             }
2488         });
2489     
2490         Mediator.installTo( WUFile.prototype );
2491     
2492         /**
2493          * 文件状态值,具体包括以下几种类型:
2494          * * `inited` 初始状态
2495          * * `queued` 已经进入队列, 等待上传
2496          * * `progress` 上传中
2497          * * `complete` 上传完成。
2498          * * `error` 上传出错,可重试
2499          * * `interrupt` 上传中断,可续传。
2500          * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。
2501          * * `cancelled` 文件被移除。
2502          * @property {Object} Status
2503          * @namespace File
2504          * @class File
2505          * @static
2506          */
2507         WUFile.Status = {
2508             INITED:     'inited',    // 初始状态
2509             QUEUED:     'queued',    // 已经进入队列, 等待上传
2510             PROGRESS:   'progress',    // 上传中
2511             ERROR:      'error',    // 上传出错,可重试
2512             COMPLETE:   'complete',    // 上传完成。
2513             CANCELLED:  'cancelled',    // 上传取消。
2514             INTERRUPT:  'interrupt',    // 上传中断,可续传。
2515             INVALID:    'invalid'    // 文件不合格,不能重试上传。
2516         };
2517     
2518         return WUFile;
2519     });
2520     
2521     /**
2522      * @fileOverview 文件队列
2523      */
2524     define('queue',[
2525         'base',
2526         'mediator',
2527         'file'
2528     ], function( Base, Mediator, WUFile ) {
2529     
2530         var $ = Base.$,
2531             STATUS = WUFile.Status;
2532     
2533         /**
2534          * 文件队列, 用来存储各个状态中的文件。
2535          * @class Queue
2536          * @extends Mediator
2537          */
2538         function Queue() {
2539     
2540             /**
2541              * 统计文件数。
2542              * * `numOfQueue` 队列中的文件数。
2543              * * `numOfSuccess` 上传成功的文件数
2544              * * `numOfCancel` 被取消的文件数
2545              * * `numOfProgress` 正在上传中的文件数
2546              * * `numOfUploadFailed` 上传错误的文件数。
2547              * * `numOfInvalid` 无效的文件数。
2548              * * `numofDeleted` 被移除的文件数。
2549              * @property {Object} stats
2550              */
2551             this.stats = {
2552                 numOfQueue: 0,
2553                 numOfSuccess: 0,
2554                 numOfCancel: 0,
2555                 numOfProgress: 0,
2556                 numOfUploadFailed: 0,
2557                 numOfInvalid: 0,
2558                 numofDeleted: 0,
2559                 numofInterrupt: 0
2560             };
2561     
2562             // 上传队列,仅包括等待上传的文件
2563             this._queue = [];
2564     
2565             // 存储所有文件
2566             this._map = {};
2567         }
2568     
2569         $.extend( Queue.prototype, {
2570     
2571             /**
2572              * 将新文件加入对队列尾部
2573              *
2574              * @method append
2575              * @param  {File} file   文件对象
2576              */
2577             append: function( file ) {
2578                 this._queue.push( file );
2579                 this._fileAdded( file );
2580                 return this;
2581             },
2582     
2583             /**
2584              * 将新文件加入对队列头部
2585              *
2586              * @method prepend
2587              * @param  {File} file   文件对象
2588              */
2589             prepend: function( file ) {
2590                 this._queue.unshift( file );
2591                 this._fileAdded( file );
2592                 return this;
2593             },
2594     
2595             /**
2596              * 获取文件对象
2597              *
2598              * @method getFile
2599              * @param  {String} fileId   文件ID
2600              * @return {File}
2601              */
2602             getFile: function( fileId ) {
2603                 if ( typeof fileId !== 'string' ) {
2604                     return fileId;
2605                 }
2606                 return this._map[ fileId ];
2607             },
2608     
2609             /**
2610              * 从队列中取出一个指定状态的文件。
2611              * @grammar fetch( status ) => File
2612              * @method fetch
2613              * @param {String} status [文件状态值](#WebUploader:File:File.Status)
2614              * @return {File} [File](#WebUploader:File)
2615              */
2616             fetch: function( status ) {
2617                 var len = this._queue.length,
2618                     i, file;
2619     
2620                 status = status || STATUS.QUEUED;
2621     
2622                 for ( i = 0; i < len; i++ ) {
2623                     file = this._queue[ i ];
2624     
2625                     if ( status === file.getStatus() ) {
2626                         return file;
2627                     }
2628                 }
2629     
2630                 return null;
2631             },
2632     
2633             /**
2634              * 对队列进行排序,能够控制文件上传顺序。
2635              * @grammar sort( fn ) => undefined
2636              * @method sort
2637              * @param {Function} fn 排序方法
2638              */
2639             sort: function( fn ) {
2640                 if ( typeof fn === 'function' ) {
2641                     this._queue.sort( fn );
2642                 }
2643             },
2644     
2645             /**
2646              * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。
2647              * @grammar getFiles( [status1[, status2 ...]] ) => Array
2648              * @method getFiles
2649              * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)
2650              */
2651             getFiles: function() {
2652                 var sts = [].slice.call( arguments, 0 ),
2653                     ret = [],
2654                     i = 0,
2655                     len = this._queue.length,
2656                     file;
2657     
2658                 for ( ; i < len; i++ ) {
2659                     file = this._queue[ i ];
2660     
2661                     if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
2662                         continue;
2663                     }
2664     
2665                     ret.push( file );
2666                 }
2667     
2668                 return ret;
2669             },
2670     
2671             /**
2672              * 在队列中删除文件。
2673              * @grammar removeFile( file ) => Array
2674              * @method removeFile
2675              * @param {File} 文件对象。
2676              */
2677             removeFile: function( file ) {
2678                 var me = this,
2679                     existing = this._map[ file.id ];
2680     
2681                 if ( existing ) {
2682                     delete this._map[ file.id ];
2683                     file.destroy();
2684                     this.stats.numofDeleted++;
2685                 }
2686             },
2687     
2688             _fileAdded: function( file ) {
2689                 var me = this,
2690                     existing = this._map[ file.id ];
2691     
2692                 if ( !existing ) {
2693                     this._map[ file.id ] = file;
2694     
2695                     file.on( 'statuschange', function( cur, pre ) {
2696                         me._onFileStatusChange( cur, pre );
2697                     });
2698                 }
2699             },
2700     
2701             _onFileStatusChange: function( curStatus, preStatus ) {
2702                 var stats = this.stats;
2703     
2704                 switch ( preStatus ) {
2705                     case STATUS.PROGRESS:
2706                         stats.numOfProgress--;
2707                         break;
2708     
2709                     case STATUS.QUEUED:
2710                         stats.numOfQueue --;
2711                         break;
2712     
2713                     case STATUS.ERROR:
2714                         stats.numOfUploadFailed--;
2715                         break;
2716     
2717                     case STATUS.INVALID:
2718                         stats.numOfInvalid--;
2719                         break;
2720     
2721                     case STATUS.INTERRUPT:
2722                         stats.numofInterrupt--;
2723                         break;
2724                 }
2725     
2726                 switch ( curStatus ) {
2727                     case STATUS.QUEUED:
2728                         stats.numOfQueue++;
2729                         break;
2730     
2731                     case STATUS.PROGRESS:
2732                         stats.numOfProgress++;
2733                         break;
2734     
2735                     case STATUS.ERROR:
2736                         stats.numOfUploadFailed++;
2737                         break;
2738     
2739                     case STATUS.COMPLETE:
2740                         stats.numOfSuccess++;
2741                         break;
2742     
2743                     case STATUS.CANCELLED:
2744                         stats.numOfCancel++;
2745                         break;
2746     
2747     
2748                     case STATUS.INVALID:
2749                         stats.numOfInvalid++;
2750                         break;
2751     
2752                     case STATUS.INTERRUPT:
2753                         stats.numofInterrupt++;
2754                         break;
2755                 }
2756             }
2757     
2758         });
2759     
2760         Mediator.installTo( Queue.prototype );
2761     
2762         return Queue;
2763     });
2764     /**
2765      * @fileOverview 队列
2766      */
2767     define('widgets/queue',[
2768         'base',
2769         'uploader',
2770         'queue',
2771         'file',
2772         'lib/file',
2773         'runtime/client',
2774         'widgets/widget'
2775     ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {
2776     
2777         var $ = Base.$,
2778             rExt = /\.\w+$/,
2779             Status = WUFile.Status;
2780     
2781         return Uploader.register({
2782             name: 'queue',
2783     
2784             init: function( opts ) {
2785                 var me = this,
2786                     deferred, len, i, item, arr, accept, runtime;
2787     
2788                 if ( $.isPlainObject( opts.accept ) ) {
2789                     opts.accept = [ opts.accept ];
2790                 }
2791     
2792                 // accept中的中生成匹配正则。
2793                 if ( opts.accept ) {
2794                     arr = [];
2795     
2796                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
2797                         item = opts.accept[ i ].extensions;
2798                         item && arr.push( item );
2799                     }
2800     
2801                     if ( arr.length ) {
2802                         accept = '\\.' + arr.join(',')
2803                                 .replace( /,/g, '$|\\.' )
2804                                 .replace( /\*/g, '.*' ) + '$';
2805                     }
2806     
2807                     me.accept = new RegExp( accept, 'i' );
2808                 }
2809     
2810                 me.queue = new Queue();
2811                 me.stats = me.queue.stats;
2812     
2813                 // 如果当前不是html5运行时,那就算了。
2814                 // 不执行后续操作
2815                 if ( this.request('predict-runtime-type') !== 'html5' ) {
2816                     return;
2817                 }
2818     
2819                 // 创建一个 html5 运行时的 placeholder
2820                 // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
2821                 deferred = Base.Deferred();
2822                 this.placeholder = runtime = new RuntimeClient('Placeholder');
2823                 runtime.connectRuntime({
2824                     runtimeOrder: 'html5'
2825                 }, function() {
2826                     me._ruid = runtime.getRuid();
2827                     deferred.resolve();
2828                 });
2829                 return deferred.promise();
2830             },
2831     
2832     
2833             // 为了支持外部直接添加一个原生File对象。
2834             _wrapFile: function( file ) {
2835                 if ( !(file instanceof WUFile) ) {
2836     
2837                     if ( !(file instanceof File) ) {
2838                         if ( !this._ruid ) {
2839                             throw new Error('Can\'t add external files.');
2840                         }
2841                         file = new File( this._ruid, file );
2842                     }
2843     
2844                     file = new WUFile( file );
2845                 }
2846     
2847                 return file;
2848             },
2849     
2850             // 判断文件是否可以被加入队列
2851             acceptFile: function( file ) {
2852                 var invalid = !file || !file.size || this.accept &&
2853     
2854                         // 如果名字中有后缀,才做后缀白名单处理。
2855                         rExt.exec( file.name ) && !this.accept.test( file.name );
2856     
2857                 return !invalid;
2858             },
2859     
2860     
2861             /**
2862              * @event beforeFileQueued
2863              * @param {File} file File对象
2864              * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。
2865              * @for  Uploader
2866              */
2867     
2868             /**
2869              * @event fileQueued
2870              * @param {File} file File对象
2871              * @description 当文件被加入队列以后触发。
2872              * @for  Uploader
2873              */
2874     
2875             _addFile: function( file ) {
2876                 var me = this;
2877     
2878                 file = me._wrapFile( file );
2879     
2880                 // 不过类型判断允许不允许,先派送 `beforeFileQueued`
2881                 if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {
2882                     return;
2883                 }
2884     
2885                 // 类型不匹配,则派送错误事件,并返回。
2886                 if ( !me.acceptFile( file ) ) {
2887                     me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );
2888                     return;
2889                 }
2890     
2891                 me.queue.append( file );
2892                 me.owner.trigger( 'fileQueued', file );
2893                 return file;
2894             },
2895     
2896             getFile: function( fileId ) {
2897                 return this.queue.getFile( fileId );
2898             },
2899     
2900             /**
2901              * @event filesQueued
2902              * @param {File} files 数组,内容为原始File(lib/File)对象。
2903              * @description 当一批文件添加进队列以后触发。
2904              * @for  Uploader
2905              */
2906             
2907             /**
2908              * @property {Boolean} [auto=false]
2909              * @namespace options
2910              * @for Uploader
2911              * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。
2912              * 
2913              */
2914     
2915             /**
2916              * @method addFiles
2917              * @grammar addFiles( file ) => undefined
2918              * @grammar addFiles( [file1, file2 ...] ) => undefined
2919              * @param {Array of File or File} [files] Files 对象 数组
2920              * @description 添加文件到队列
2921              * @for  Uploader
2922              */
2923             addFile: function( files ) {
2924                 var me = this;
2925     
2926                 if ( !files.length ) {
2927                     files = [ files ];
2928                 }
2929     
2930                 files = $.map( files, function( file ) {
2931                     return me._addFile( file );
2932                 });
2933     
2934                 me.owner.trigger( 'filesQueued', files );
2935     
2936                 if ( me.options.auto ) {
2937                     setTimeout(function() {
2938                         me.request('start-upload');
2939                     }, 20 );
2940                 }
2941             },
2942     
2943             getStats: function() {
2944                 return this.stats;
2945             },
2946     
2947             /**
2948              * @event fileDequeued
2949              * @param {File} file File对象
2950              * @description 当文件被移除队列后触发。
2951              * @for  Uploader
2952              */
2953     
2954              /**
2955              * @method removeFile
2956              * @grammar removeFile( file ) => undefined
2957              * @grammar removeFile( id ) => undefined
2958              * @grammar removeFile( file, true ) => undefined
2959              * @grammar removeFile( id, true ) => undefined
2960              * @param {File|id} file File对象或这File对象的id
2961              * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。
2962              * @for  Uploader
2963              * @example
2964              *
2965              * $li.on('click', '.remove-this', function() {
2966              *     uploader.removeFile( file );
2967              * })
2968              */
2969             removeFile: function( file, remove ) {
2970                 var me = this;
2971     
2972                 file = file.id ? file : me.queue.getFile( file );
2973     
2974                 this.request( 'cancel-file', file );
2975     
2976                 if ( remove ) {
2977                     this.queue.removeFile( file );
2978                 }
2979             },
2980     
2981             /**
2982              * @method getFiles
2983              * @grammar getFiles() => Array
2984              * @grammar getFiles( status1, status2, status... ) => Array
2985              * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。
2986              * @for  Uploader
2987              * @example
2988              * console.log( uploader.getFiles() );    // => all files
2989              * console.log( uploader.getFiles('error') )    // => all error files.
2990              */
2991             getFiles: function() {
2992                 return this.queue.getFiles.apply( this.queue, arguments );
2993             },
2994     
2995             fetchFile: function() {
2996                 return this.queue.fetch.apply( this.queue, arguments );
2997             },
2998     
2999             /**
3000              * @method retry
3001              * @grammar retry() => undefined
3002              * @grammar retry( file ) => undefined
3003              * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。
3004              * @for  Uploader
3005              * @example
3006              * function retry() {
3007              *     uploader.retry();
3008              * }
3009              */
3010             retry: function( file, noForceStart ) {
3011                 var me = this,
3012                     files, i, len;
3013     
3014                 if ( file ) {
3015                     file = file.id ? file : me.queue.getFile( file );
3016                     file.setStatus( Status.QUEUED );
3017                     noForceStart || me.request('start-upload');
3018                     return;
3019                 }
3020     
3021                 files = me.queue.getFiles( Status.ERROR );
3022                 i = 0;
3023                 len = files.length;
3024     
3025                 for ( ; i < len; i++ ) {
3026                     file = files[ i ];
3027                     file.setStatus( Status.QUEUED );
3028                 }
3029     
3030                 me.request('start-upload');
3031             },
3032     
3033             /**
3034              * @method sort
3035              * @grammar sort( fn ) => undefined
3036              * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。
3037              * @for  Uploader
3038              */
3039             sortFiles: function() {
3040                 return this.queue.sort.apply( this.queue, arguments );
3041             },
3042     
3043             /**
3044              * @event reset
3045              * @description 当 uploader 被重置的时候触发。
3046              * @for  Uploader
3047              */
3048     
3049             /**
3050              * @method reset
3051              * @grammar reset() => undefined
3052              * @description 重置uploader。目前只重置了队列。
3053              * @for  Uploader
3054              * @example
3055              * uploader.reset();
3056              */
3057             reset: function() {
3058                 this.owner.trigger('reset');
3059                 this.queue = new Queue();
3060                 this.stats = this.queue.stats;
3061             },
3062     
3063             destroy: function() {
3064                 this.reset();
3065                 this.placeholder && this.placeholder.destroy();
3066             }
3067         });
3068     
3069     });
3070     /**
3071      * @fileOverview 添加获取Runtime相关信息的方法。
3072      */
3073     define('widgets/runtime',[
3074         'uploader',
3075         'runtime/runtime',
3076         'widgets/widget'
3077     ], function( Uploader, Runtime ) {
3078     
3079         Uploader.support = function() {
3080             return Runtime.hasRuntime.apply( Runtime, arguments );
3081         };
3082     
3083         /**
3084          * @property {Object} [runtimeOrder=html5,flash]
3085          * @namespace options
3086          * @for Uploader
3087          * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash.
3088          *
3089          * 可以将此值设置成 `flash`,来强制使用 flash 运行时。
3090          */
3091     
3092         return Uploader.register({
3093             name: 'runtime',
3094     
3095             init: function() {
3096                 if ( !this.predictRuntimeType() ) {
3097                     throw Error('Runtime Error');
3098                 }
3099             },
3100     
3101             /**
3102              * 预测Uploader将采用哪个`Runtime`
3103              * @grammar predictRuntimeType() => String
3104              * @method predictRuntimeType
3105              * @for  Uploader
3106              */
3107             predictRuntimeType: function() {
3108                 var orders = this.options.runtimeOrder || Runtime.orders,
3109                     type = this.type,
3110                     i, len;
3111     
3112                 if ( !type ) {
3113                     orders = orders.split( /\s*,\s*/g );
3114     
3115                     for ( i = 0, len = orders.length; i < len; i++ ) {
3116                         if ( Runtime.hasRuntime( orders[ i ] ) ) {
3117                             this.type = type = orders[ i ];
3118                             break;
3119                         }
3120                     }
3121                 }
3122     
3123                 return type;
3124             }
3125         });
3126     });
3127     /**
3128      * @fileOverview Transport
3129      */
3130     define('lib/transport',[
3131         'base',
3132         'runtime/client',
3133         'mediator'
3134     ], function( Base, RuntimeClient, Mediator ) {
3135     
3136         var $ = Base.$;
3137     
3138         function Transport( opts ) {
3139             var me = this;
3140     
3141             opts = me.options = $.extend( true, {}, Transport.options, opts || {} );
3142             RuntimeClient.call( this, 'Transport' );
3143     
3144             this._blob = null;
3145             this._formData = opts.formData || {};
3146             this._headers = opts.headers || {};
3147     
3148             this.on( 'progress', this._timeout );
3149             this.on( 'load error', function() {
3150                 me.trigger( 'progress', 1 );
3151                 clearTimeout( me._timer );
3152             });
3153         }
3154     
3155         Transport.options = {
3156             server: '',
3157             method: 'POST',
3158     
3159             // 跨域时,是否允许携带cookie, 只有html5 runtime才有效
3160             withCredentials: false,
3161             fileVal: 'file',
3162             timeout: 2 * 60 * 1000,    // 2分钟
3163             formData: {},
3164             headers: {},
3165             sendAsBinary: false
3166         };
3167     
3168         $.extend( Transport.prototype, {
3169     
3170             // 添加Blob, 只能添加一次,最后一次有效。
3171             appendBlob: function( key, blob, filename ) {
3172                 var me = this,
3173                     opts = me.options;
3174     
3175                 if ( me.getRuid() ) {
3176                     me.disconnectRuntime();
3177                 }
3178     
3179                 // 连接到blob归属的同一个runtime.
3180                 me.connectRuntime( blob.ruid, function() {
3181                     me.exec('init');
3182                 });
3183     
3184                 me._blob = blob;
3185                 opts.fileVal = key || opts.fileVal;
3186                 opts.filename = filename || opts.filename;
3187             },
3188     
3189             // 添加其他字段
3190             append: function( key, value ) {
3191                 if ( typeof key === 'object' ) {
3192                     $.extend( this._formData, key );
3193                 } else {
3194                     this._formData[ key ] = value;
3195                 }
3196             },
3197     
3198             setRequestHeader: function( key, value ) {
3199                 if ( typeof key === 'object' ) {
3200                     $.extend( this._headers, key );
3201                 } else {
3202                     this._headers[ key ] = value;
3203                 }
3204             },
3205     
3206             send: function( method ) {
3207                 this.exec( 'send', method );
3208                 this._timeout();
3209             },
3210     
3211             abort: function() {
3212                 clearTimeout( this._timer );
3213                 return this.exec('abort');
3214             },
3215     
3216             destroy: function() {
3217                 this.trigger('destroy');
3218                 this.off();
3219                 this.exec('destroy');
3220                 this.disconnectRuntime();
3221             },
3222     
3223             getResponse: function() {
3224                 return this.exec('getResponse');
3225             },
3226     
3227             getResponseAsJson: function() {
3228                 return this.exec('getResponseAsJson');
3229             },
3230     
3231             getStatus: function() {
3232                 return this.exec('getStatus');
3233             },
3234     
3235             _timeout: function() {
3236                 var me = this,
3237                     duration = me.options.timeout;
3238     
3239                 if ( !duration ) {
3240                     return;
3241                 }
3242     
3243                 clearTimeout( me._timer );
3244                 me._timer = setTimeout(function() {
3245                     me.abort();
3246                     me.trigger( 'error', 'timeout' );
3247                 }, duration );
3248             }
3249     
3250         });
3251     
3252         // 让Transport具备事件功能。
3253         Mediator.installTo( Transport.prototype );
3254     
3255         return Transport;
3256     });
3257     /**
3258      * @fileOverview 负责文件上传相关。
3259      */
3260     define('widgets/upload',[
3261         'base',
3262         'uploader',
3263         'file',
3264         'lib/transport',
3265         'widgets/widget'
3266     ], function( Base, Uploader, WUFile, Transport ) {
3267     
3268         var $ = Base.$,
3269             isPromise = Base.isPromise,
3270             Status = WUFile.Status;
3271     
3272         // 添加默认配置项
3273         $.extend( Uploader.options, {
3274     
3275     
3276             /**
3277              * @property {Boolean} [prepareNextFile=false]
3278              * @namespace options
3279              * @for Uploader
3280              * @description 是否允许在文件传输时提前把下一个文件准备好。
3281              * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。
3282              * 如果能提前在当前文件传输期处理,可以节省总体耗时。
3283              */
3284             prepareNextFile: false,
3285     
3286             /**
3287              * @property {Boolean} [chunked=false]
3288              * @namespace options
3289              * @for Uploader
3290              * @description 是否要分片处理大文件上传。
3291              */
3292             chunked: false,
3293     
3294             /**
3295              * @property {Boolean} [chunkSize=5242880]
3296              * @namespace options
3297              * @for Uploader
3298              * @description 如果要分片,分多大一片? 默认大小为5M.
3299              */
3300             chunkSize: 5 * 1024 * 1024,
3301     
3302             /**
3303              * @property {Boolean} [chunkRetry=2]
3304              * @namespace options
3305              * @for Uploader
3306              * @description 如果某个分片由于网络问题出错,允许自动重传多少次?
3307              */
3308             chunkRetry: 2,
3309     
3310             /**
3311              * @property {Boolean} [threads=3]
3312              * @namespace options
3313              * @for Uploader
3314              * @description 上传并发数。允许同时最大上传进程数。
3315              */
3316             threads: 3,
3317     
3318     
3319             /**
3320              * @property {Object} [formData={}]
3321              * @namespace options
3322              * @for Uploader
3323              * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。
3324              */
3325             formData: {}
3326     
3327             /**
3328              * @property {Object} [fileVal='file']
3329              * @namespace options
3330              * @for Uploader
3331              * @description 设置文件上传域的name。
3332              */
3333     
3334             /**
3335              * @property {Object} [method='POST']
3336              * @namespace options
3337              * @for Uploader
3338              * @description 文件上传方式,`POST`或者`GET`。
3339              */
3340     
3341             /**
3342              * @property {Object} [sendAsBinary=false]
3343              * @namespace options
3344              * @for Uploader
3345              * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容,
3346              * 其他参数在$_GET数组中。
3347              */
3348         });
3349     
3350         // 负责将文件切片。
3351         function CuteFile( file, chunkSize ) {
3352             var pending = [],
3353                 blob = file.source,
3354                 total = blob.size,
3355                 chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
3356                 start = 0,
3357                 index = 0,
3358                 len, api;
3359     
3360             api = {
3361                 file: file,
3362     
3363                 has: function() {
3364                     return !!pending.length;
3365                 },
3366     
3367                 shift: function() {
3368                     return pending.shift();
3369                 },
3370     
3371                 unshift: function( block ) {
3372                     pending.unshift( block );
3373                 }
3374             };
3375     
3376             while ( index < chunks ) {
3377                 len = Math.min( chunkSize, total - start );
3378     
3379                 pending.push({
3380                     file: file,
3381                     start: start,
3382                     end: chunkSize ? (start + len) : total,
3383                     total: total,
3384                     chunks: chunks,
3385                     chunk: index++,
3386                     cuted: api
3387                 });
3388                 start += len;
3389             }
3390     
3391             file.blocks = pending.concat();
3392             file.remaning = pending.length;
3393     
3394             return api;
3395         }
3396     
3397         Uploader.register({
3398             name: 'upload',
3399     
3400             init: function() {
3401                 var owner = this.owner,
3402                     me = this;
3403     
3404                 this.runing = false;
3405                 this.progress = false;
3406     
3407                 owner
3408                     .on( 'startUpload', function() {
3409                         me.progress = true;
3410                     })
3411                     .on( 'uploadFinished', function() {
3412                         me.progress = false;
3413                     });
3414     
3415                 // 记录当前正在传的数据,跟threads相关
3416                 this.pool = [];
3417     
3418                 // 缓存分好片的文件。
3419                 this.stack = [];
3420     
3421                 // 缓存即将上传的文件。
3422                 this.pending = [];
3423     
3424                 // 跟踪还有多少分片在上传中但是没有完成上传。
3425                 this.remaning = 0;
3426                 this.__tick = Base.bindFn( this._tick, this );
3427     
3428                 owner.on( 'uploadComplete', function( file ) {
3429     
3430                     // 把其他块取消了。
3431                     file.blocks && $.each( file.blocks, function( _, v ) {
3432                         v.transport && (v.transport.abort(), v.transport.destroy());
3433                         delete v.transport;
3434                     });
3435     
3436                     delete file.blocks;
3437                     delete file.remaning;
3438                 });
3439             },
3440     
3441             reset: function() {
3442                 this.request( 'stop-upload', true );
3443                 this.runing = false;
3444                 this.pool = [];
3445                 this.stack = [];
3446                 this.pending = [];
3447                 this.remaning = 0;
3448                 this._trigged = false;
3449                 this._promise = null;
3450             },
3451     
3452             /**
3453              * @event startUpload
3454              * @description 当开始上传流程时触发。
3455              * @for  Uploader
3456              */
3457     
3458             /**
3459              * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。
3460              *
3461              * 可以指定开始某一个文件。
3462              * @grammar upload() => undefined
3463              * @grammar upload( file | fileId) => undefined
3464              * @method upload
3465              * @for  Uploader
3466              */
3467             startUpload: function(file) {
3468                 var me = this;
3469     
3470                 // 移出invalid的文件
3471                 $.each( me.request( 'get-files', Status.INVALID ), function() {
3472                     me.request( 'remove-file', this );
3473                 });
3474     
3475                 // 如果指定了开始某个文件,则只开始指定文件。
3476                 if ( file ) {
3477                     file = file.id ? file : me.request( 'get-file', file );
3478     
3479                     if (file.getStatus() === Status.INTERRUPT) {
3480                         $.each( me.pool, function( _, v ) {
3481     
3482                             // 之前暂停过。
3483                             if (v.file !== file) {
3484                                 return;
3485                             }
3486     
3487                             v.transport && v.transport.send();
3488                         });
3489     
3490                         file.setStatus( Status.QUEUED );
3491                     } else if (file.getStatus() === Status.PROGRESS) {
3492                         return;
3493                     } else {
3494                         file.setStatus( Status.QUEUED );
3495                     }
3496                 } else {
3497                     $.each( me.request( 'get-files', [ Status.INITED ] ), function() {
3498                         this.setStatus( Status.QUEUED );
3499                     });
3500                 }
3501     
3502                 if ( me.runing ) {
3503                     return;
3504                 }
3505     
3506                 me.runing = true;
3507     
3508                 var files = [];
3509     
3510                 // 如果有暂停的,则续传
3511                 $.each( me.pool, function( _, v ) {
3512                     var file = v.file;
3513     
3514                     if ( file.getStatus() === Status.INTERRUPT ) {
3515                         files.push(file);
3516                         me._trigged = false;
3517                         v.transport && v.transport.send();
3518                     }
3519                 });
3520     
3521                 var file;
3522                 while ( (file = files.shift()) ) {
3523                     file.setStatus( Status.PROGRESS );
3524                 }
3525     
3526                 file || $.each( me.request( 'get-files',
3527                         Status.INTERRUPT ), function() {
3528                     this.setStatus( Status.PROGRESS );
3529                 });
3530     
3531                 me._trigged = false;
3532                 Base.nextTick( me.__tick );
3533                 me.owner.trigger('startUpload');
3534             },
3535     
3536             /**
3537              * @event stopUpload
3538              * @description 当开始上传流程暂停时触发。
3539              * @for  Uploader
3540              */
3541     
3542             /**
3543              * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。
3544              *
3545              * 如果第一个参数是文件,则只暂停指定文件。
3546              * @grammar stop() => undefined
3547              * @grammar stop( true ) => undefined
3548              * @grammar stop( file ) => undefined
3549              * @method stop
3550              * @for  Uploader
3551              */
3552             stopUpload: function( file, interrupt ) {
3553                 var me = this;
3554     
3555                 if (file === true) {
3556                     interrupt = file;
3557                     file = null;
3558                 }
3559     
3560                 if ( me.runing === false ) {
3561                     return;
3562                 }
3563     
3564                 // 如果只是暂停某个文件。
3565                 if ( file ) {
3566                     file = file.id ? file : me.request( 'get-file', file );
3567     
3568                     if ( file.getStatus() !== Status.PROGRESS &&
3569                             file.getStatus() !== Status.QUEUED ) {
3570                         return;
3571                     }
3572     
3573                     file.setStatus( Status.INTERRUPT );
3574                     $.each( me.pool, function( _, v ) {
3575     
3576                         // 只 abort 指定的文件。
3577                         if (v.file !== file) {
3578                             return;
3579                         }
3580     
3581                         v.transport && v.transport.abort();
3582                         me._putback(v);
3583                         me._popBlock(v);
3584                     });
3585     
3586                     return Base.nextTick( me.__tick );
3587                 }
3588     
3589                 me.runing = false;
3590     
3591                 if (this._promise && this._promise.file) {
3592                     this._promise.file.setStatus( Status.INTERRUPT );
3593                 }
3594     
3595                 interrupt && $.each( me.pool, function( _, v ) {
3596                     v.transport && v.transport.abort();
3597                     v.file.setStatus( Status.INTERRUPT );
3598                 });
3599     
3600                 me.owner.trigger('stopUpload');
3601             },
3602     
3603             /**
3604              * @method cancelFile
3605              * @grammar cancelFile( file ) => undefined
3606              * @grammar cancelFile( id ) => undefined
3607              * @param {File|id} file File对象或这File对象的id
3608              * @description 标记文件状态为已取消, 同时将中断文件传输。
3609              * @for  Uploader
3610              * @example
3611              *
3612              * $li.on('click', '.remove-this', function() {
3613              *     uploader.cancelFile( file );
3614              * })
3615              */
3616             cancelFile: function( file ) {
3617                 file = file.id ? file : this.request( 'get-file', file );
3618     
3619                 // 如果正在上传。
3620                 file.blocks && $.each( file.blocks, function( _, v ) {
3621                     var _tr = v.transport;
3622     
3623                     if ( _tr ) {
3624                         _tr.abort();
3625                         _tr.destroy();
3626                         delete v.transport;
3627                     }
3628                 });
3629     
3630                 file.setStatus( Status.CANCELLED );
3631                 this.owner.trigger( 'fileDequeued', file );
3632             },
3633     
3634             /**
3635              * 判断`Uplaode`r是否正在上传中。
3636              * @grammar isInProgress() => Boolean
3637              * @method isInProgress
3638              * @for  Uploader
3639              */
3640             isInProgress: function() {
3641                 return !!this.progress;
3642             },
3643     
3644             _getStats: function() {
3645                 return this.request('get-stats');
3646             },
3647     
3648             /**
3649              * 掉过一个文件上传,直接标记指定文件为已上传状态。
3650              * @grammar skipFile( file ) => undefined
3651              * @method skipFile
3652              * @for  Uploader
3653              */
3654             skipFile: function( file, status ) {
3655                 file = file.id ? file : this.request( 'get-file', file );
3656     
3657                 file.setStatus( status || Status.COMPLETE );
3658                 file.skipped = true;
3659     
3660                 // 如果正在上传。
3661                 file.blocks && $.each( file.blocks, function( _, v ) {
3662                     var _tr = v.transport;
3663     
3664                     if ( _tr ) {
3665                         _tr.abort();
3666                         _tr.destroy();
3667                         delete v.transport;
3668                     }
3669                 });
3670     
3671                 this.owner.trigger( 'uploadSkip', file );
3672             },
3673     
3674             /**
3675              * @event uploadFinished
3676              * @description 当所有文件上传结束时触发。
3677              * @for  Uploader
3678              */
3679             _tick: function() {
3680                 var me = this,
3681                     opts = me.options,
3682                     fn, val;
3683     
3684                 // 上一个promise还没有结束,则等待完成后再执行。
3685                 if ( me._promise ) {
3686                     return me._promise.always( me.__tick );
3687                 }
3688     
3689                 // 还有位置,且还有文件要处理的话。
3690                 if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
3691                     me._trigged = false;
3692     
3693                     fn = function( val ) {
3694                         me._promise = null;
3695     
3696                         // 有可能是reject过来的,所以要检测val的类型。
3697                         val && val.file && me._startSend( val );
3698                         Base.nextTick( me.__tick );
3699                     };
3700     
3701                     me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
3702     
3703                 // 没有要上传的了,且没有正在传输的了。
3704                 } else if ( !me.remaning && !me._getStats().numOfQueue &&
3705                     !me._getStats().numofInterrupt ) {
3706                     me.runing = false;
3707     
3708                     me._trigged || Base.nextTick(function() {
3709                         me.owner.trigger('uploadFinished');
3710                     });
3711                     me._trigged = true;
3712                 }
3713             },
3714     
3715             _putback: function(block) {
3716                 var idx;
3717     
3718                 block.cuted.unshift(block);
3719                 idx = this.stack.indexOf(block.cuted);
3720     
3721                 if (!~idx) {
3722                     this.stack.unshift(block.cuted);
3723                 }
3724             },
3725     
3726             _getStack: function() {
3727                 var i = 0,
3728                     act;
3729     
3730                 while ( (act = this.stack[ i++ ]) ) {
3731                     if ( act.has() && act.file.getStatus() === Status.PROGRESS ) {
3732                         return act;
3733                     } else if (!act.has() ||
3734                             act.file.getStatus() !== Status.PROGRESS &&
3735                             act.file.getStatus() !== Status.INTERRUPT ) {
3736     
3737                         // 把已经处理完了的,或者,状态为非 progress(上传中)、
3738                         // interupt(暂停中) 的移除。
3739                         this.stack.splice( --i, 1 );
3740                     }
3741                 }
3742     
3743                 return null;
3744             },
3745     
3746             _nextBlock: function() {
3747                 var me = this,
3748                     opts = me.options,
3749                     act, next, done, preparing;
3750     
3751                 // 如果当前文件还有没有需要传输的,则直接返回剩下的。
3752                 if ( (act = this._getStack()) ) {
3753     
3754                     // 是否提前准备下一个文件
3755                     if ( opts.prepareNextFile && !me.pending.length ) {
3756                         me._prepareNextFile();
3757                     }
3758     
3759                     return act.shift();
3760     
3761                 // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。
3762                 } else if ( me.runing ) {
3763     
3764                     // 如果缓存中有,则直接在缓存中取,没有则去queue中取。
3765                     if ( !me.pending.length && me._getStats().numOfQueue ) {
3766                         me._prepareNextFile();
3767                     }
3768     
3769                     next = me.pending.shift();
3770                     done = function( file ) {
3771                         if ( !file ) {
3772                             return null;
3773                         }
3774     
3775                         act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
3776                         me.stack.push(act);
3777                         return act.shift();
3778                     };
3779     
3780                     // 文件可能还在prepare中,也有可能已经完全准备好了。
3781                     if ( isPromise( next) ) {
3782                         preparing = next.file;
3783                         next = next[ next.pipe ? 'pipe' : 'then' ]( done );
3784                         next.file = preparing;
3785                         return next;
3786                     }
3787     
3788                     return done( next );
3789                 }
3790             },
3791     
3792     
3793             /**
3794              * @event uploadStart
3795              * @param {File} file File对象
3796              * @description 某个文件开始上传前触发,一个文件只会触发一次。
3797              * @for  Uploader
3798              */
3799             _prepareNextFile: function() {
3800                 var me = this,
3801                     file = me.request('fetch-file'),
3802                     pending = me.pending,
3803                     promise;
3804     
3805                 if ( file ) {
3806                     promise = me.request( 'before-send-file', file, function() {
3807     
3808                         // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued.
3809                         if ( file.getStatus() === Status.PROGRESS ||
3810                             file.getStatus() === Status.INTERRUPT ) {
3811                             return file;
3812                         }
3813     
3814                         return me._finishFile( file );
3815                     });
3816     
3817                     me.owner.trigger( 'uploadStart', file );
3818                     file.setStatus( Status.PROGRESS );
3819     
3820                     promise.file = file;
3821     
3822                     // 如果还在pending中,则替换成文件本身。
3823                     promise.done(function() {
3824                         var idx = $.inArray( promise, pending );
3825     
3826                         ~idx && pending.splice( idx, 1, file );
3827                     });
3828     
3829                     // befeore-send-file的钩子就有错误发生。
3830                     promise.fail(function( reason ) {
3831                         file.setStatus( Status.ERROR, reason );
3832                         me.owner.trigger( 'uploadError', file, reason );
3833                         me.owner.trigger( 'uploadComplete', file );
3834                     });
3835     
3836                     pending.push( promise );
3837                 }
3838             },
3839     
3840             // 让出位置了,可以让其他分片开始上传
3841             _popBlock: function( block ) {
3842                 var idx = $.inArray( block, this.pool );
3843     
3844                 this.pool.splice( idx, 1 );
3845                 block.file.remaning--;
3846                 this.remaning--;
3847             },
3848     
3849             // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。
3850             _startSend: function( block ) {
3851                 var me = this,
3852                     file = block.file,
3853                     promise;
3854     
3855                 // 有可能在 before-send-file 的 promise 期间改变了文件状态。
3856                 // 如:暂停,取消
3857                 // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。
3858                 if ( file.getStatus() !== Status.PROGRESS ) {
3859     
3860                     // 如果是中断,则还需要放回去。
3861                     if (file.getStatus() === Status.INTERRUPT) {
3862                         me._putback(block);
3863                     }
3864     
3865                     return;
3866                 }
3867     
3868                 me.pool.push( block );
3869                 me.remaning++;
3870     
3871                 // 如果没有分片,则直接使用原始的。
3872                 // 不会丢失content-type信息。
3873                 block.blob = block.chunks === 1 ? file.source :
3874                         file.source.slice( block.start, block.end );
3875     
3876                 // hook, 每个分片发送之前可能要做些异步的事情。
3877                 promise = me.request( 'before-send', block, function() {
3878     
3879                     // 有可能文件已经上传出错了,所以不需要再传输了。
3880                     if ( file.getStatus() === Status.PROGRESS ) {
3881                         me._doSend( block );
3882                     } else {
3883                         me._popBlock( block );
3884                         Base.nextTick( me.__tick );
3885                     }
3886                 });
3887     
3888                 // 如果为fail了,则跳过此分片。
3889                 promise.fail(function() {
3890                     if ( file.remaning === 1 ) {
3891                         me._finishFile( file ).always(function() {
3892                             block.percentage = 1;
3893                             me._popBlock( block );
3894                             me.owner.trigger( 'uploadComplete', file );
3895                             Base.nextTick( me.__tick );
3896                         });
3897                     } else {
3898                         block.percentage = 1;
3899                         me.updateFileProgress( file );
3900                         me._popBlock( block );
3901                         Base.nextTick( me.__tick );
3902                     }
3903                 });
3904             },
3905     
3906     
3907             /**
3908              * @event uploadBeforeSend
3909              * @param {Object} object
3910              * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。
3911              * @param {Object} headers 可以扩展此对象来控制上传头部。
3912              * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。
3913              * @for  Uploader
3914              */
3915     
3916             /**
3917              * @event uploadAccept
3918              * @param {Object} object
3919              * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。
3920              * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。
3921              * @for  Uploader
3922              */
3923     
3924             /**
3925              * @event uploadProgress
3926              * @param {File} file File对象
3927              * @param {Number} percentage 上传进度
3928              * @description 上传过程中触发,携带上传进度。
3929              * @for  Uploader
3930              */
3931     
3932     
3933             /**
3934              * @event uploadError
3935              * @param {File} file File对象
3936              * @param {String} reason 出错的code
3937              * @description 当文件上传出错时触发。
3938              * @for  Uploader
3939              */
3940     
3941             /**
3942              * @event uploadSuccess
3943              * @param {File} file File对象
3944              * @param {Object} response 服务端返回的数据
3945              * @description 当文件上传成功时触发。
3946              * @for  Uploader
3947              */
3948     
3949             /**
3950              * @event uploadComplete
3951              * @param {File} [file] File对象
3952              * @description 不管成功或者失败,文件上传完成时触发。
3953              * @for  Uploader
3954              */
3955     
3956             // 做上传操作。
3957             _doSend: function( block ) {
3958                 var me = this,
3959                     owner = me.owner,
3960                     opts = me.options,
3961                     file = block.file,
3962                     tr = new Transport( opts ),
3963                     data = $.extend({}, opts.formData ),
3964                     headers = $.extend({}, opts.headers ),
3965                     requestAccept, ret;
3966     
3967                 block.transport = tr;
3968     
3969                 tr.on( 'destroy', function() {
3970                     delete block.transport;
3971                     me._popBlock( block );
3972                     Base.nextTick( me.__tick );
3973                 });
3974     
3975                 // 广播上传进度。以文件为单位。
3976                 tr.on( 'progress', function( percentage ) {
3977                     block.percentage = percentage;
3978                     me.updateFileProgress( file );
3979                 });
3980     
3981                 // 用来询问,是否返回的结果是有错误的。
3982                 requestAccept = function( reject ) {
3983                     var fn;
3984     
3985                     ret = tr.getResponseAsJson() || {};
3986                     ret._raw = tr.getResponse();
3987                     fn = function( value ) {
3988                         reject = value;
3989                     };
3990     
3991                     // 服务端响应了,不代表成功了,询问是否响应正确。
3992                     if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {
3993                         reject = reject || 'server';
3994                     }
3995     
3996                     return reject;
3997                 };
3998     
3999                 // 尝试重试,然后广播文件上传出错。
4000                 tr.on( 'error', function( type, flag ) {
4001                     block.retried = block.retried || 0;
4002     
4003                     // 自动重试
4004                     if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&
4005                             block.retried < opts.chunkRetry ) {
4006     
4007                         block.retried++;
4008                         tr.send();
4009     
4010                     } else {
4011     
4012                         // http status 500 ~ 600
4013                         if ( !flag && type === 'server' ) {
4014                             type = requestAccept( type );
4015                         }
4016     
4017                         file.setStatus( Status.ERROR, type );
4018                         owner.trigger( 'uploadError', file, type );
4019                         owner.trigger( 'uploadComplete', file );
4020                     }
4021                 });
4022     
4023                 // 上传成功
4024                 tr.on( 'load', function() {
4025                     var reason;
4026     
4027                     // 如果非预期,转向上传出错。
4028                     if ( (reason = requestAccept()) ) {
4029                         tr.trigger( 'error', reason, true );
4030                         return;
4031                     }
4032     
4033                     // 全部上传完成。
4034                     if ( file.remaning === 1 ) {
4035                         me._finishFile( file, ret );
4036                     } else {
4037                         tr.destroy();
4038                     }
4039                 });
4040     
4041                 // 配置默认的上传字段。
4042                 data = $.extend( data, {
4043                     id: file.id,
4044                     name: file.name,
4045                     type: file.type,
4046                     lastModifiedDate: file.lastModifiedDate,
4047                     size: file.size
4048                 });
4049     
4050                 block.chunks > 1 && $.extend( data, {
4051                     chunks: block.chunks,
4052                     chunk: block.chunk
4053                 });
4054     
4055                 // 在发送之间可以添加字段什么的。。。
4056                 // 如果默认的字段不够使用,可以通过监听此事件来扩展
4057                 owner.trigger( 'uploadBeforeSend', block, data, headers );
4058     
4059                 // 开始发送。
4060                 tr.appendBlob( opts.fileVal, block.blob, file.name );
4061                 tr.append( data );
4062                 tr.setRequestHeader( headers );
4063                 tr.send();
4064             },
4065     
4066             // 完成上传。
4067             _finishFile: function( file, ret, hds ) {
4068                 var owner = this.owner;
4069     
4070                 return owner
4071                         .request( 'after-send-file', arguments, function() {
4072                             file.setStatus( Status.COMPLETE );
4073                             owner.trigger( 'uploadSuccess', file, ret, hds );
4074                         })
4075                         .fail(function( reason ) {
4076     
4077                             // 如果外部已经标记为invalid什么的,不再改状态。
4078                             if ( file.getStatus() === Status.PROGRESS ) {
4079                                 file.setStatus( Status.ERROR, reason );
4080                             }
4081     
4082                             owner.trigger( 'uploadError', file, reason );
4083                         })
4084                         .always(function() {
4085                             owner.trigger( 'uploadComplete', file );
4086                         });
4087             },
4088     
4089             updateFileProgress: function(file) {
4090                 var totalPercent = 0,
4091                     uploaded = 0;
4092     
4093                 if (!file.blocks) {
4094                     return;
4095                 }
4096     
4097                 $.each( file.blocks, function( _, v ) {
4098                     uploaded += (v.percentage || 0) * (v.end - v.start);
4099                 });
4100     
4101                 totalPercent = uploaded / file.size;
4102                 this.owner.trigger( 'uploadProgress', file, totalPercent || 0 );
4103             }
4104     
4105         });
4106     });
4107     /**
4108      * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。
4109      */
4110     
4111     define('widgets/validator',[
4112         'base',
4113         'uploader',
4114         'file',
4115         'widgets/widget'
4116     ], function( Base, Uploader, WUFile ) {
4117     
4118         var $ = Base.$,
4119             validators = {},
4120             api;
4121     
4122         /**
4123          * @event error
4124          * @param {String} type 错误类型。
4125          * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。
4126          *
4127          * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。
4128          * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。
4129          * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。
4130          * @for  Uploader
4131          */
4132     
4133         // 暴露给外面的api
4134         api = {
4135     
4136             // 添加验证器
4137             addValidator: function( type, cb ) {
4138                 validators[ type ] = cb;
4139             },
4140     
4141             // 移除验证器
4142             removeValidator: function( type ) {
4143                 delete validators[ type ];
4144             }
4145         };
4146     
4147         // 在Uploader初始化的时候启动Validators的初始化
4148         Uploader.register({
4149             name: 'validator',
4150     
4151             init: function() {
4152                 var me = this;
4153                 Base.nextTick(function() {
4154                     $.each( validators, function() {
4155                         this.call( me.owner );
4156                     });
4157                 });
4158             }
4159         });
4160     
4161         /**
4162          * @property {int} [fileNumLimit=undefined]
4163          * @namespace options
4164          * @for Uploader
4165          * @description 验证文件总数量, 超出则不允许加入队列。
4166          */
4167         api.addValidator( 'fileNumLimit', function() {
4168             var uploader = this,
4169                 opts = uploader.options,
4170                 count = 0,
4171                 max = parseInt( opts.fileNumLimit, 10 ),
4172                 flag = true;
4173     
4174             if ( !max ) {
4175                 return;
4176             }
4177     
4178             uploader.on( 'beforeFileQueued', function( file ) {
4179     
4180                 if ( count >= max && flag ) {
4181                     flag = false;
4182                     this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );
4183                     setTimeout(function() {
4184                         flag = true;
4185                     }, 1 );
4186                 }
4187     
4188                 return count >= max ? false : true;
4189             });
4190     
4191             uploader.on( 'fileQueued', function() {
4192                 count++;
4193             });
4194     
4195             uploader.on( 'fileDequeued', function() {
4196                 count--;
4197             });
4198     
4199             uploader.on( 'reset', function() {
4200                 count = 0;
4201             });
4202         });
4203     
4204     
4205         /**
4206          * @property {int} [fileSizeLimit=undefined]
4207          * @namespace options
4208          * @for Uploader
4209          * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。
4210          */
4211         api.addValidator( 'fileSizeLimit', function() {
4212             var uploader = this,
4213                 opts = uploader.options,
4214                 count = 0,
4215                 max = parseInt( opts.fileSizeLimit, 10 ),
4216                 flag = true;
4217     
4218             if ( !max ) {
4219                 return;
4220             }
4221     
4222             uploader.on( 'beforeFileQueued', function( file ) {
4223                 var invalid = count + file.size > max;
4224     
4225                 if ( invalid && flag ) {
4226                     flag = false;
4227                     this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );
4228                     setTimeout(function() {
4229                         flag = true;
4230                     }, 1 );
4231                 }
4232     
4233                 return invalid ? false : true;
4234             });
4235     
4236             uploader.on( 'fileQueued', function( file ) {
4237                 count += file.size;
4238             });
4239     
4240             uploader.on( 'fileDequeued', function( file ) {
4241                 count -= file.size;
4242             });
4243     
4244             uploader.on( 'reset', function() {
4245                 count = 0;
4246             });
4247         });
4248     
4249         /**
4250          * @property {int} [fileSingleSizeLimit=undefined]
4251          * @namespace options
4252          * @for Uploader
4253          * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。
4254          */
4255         api.addValidator( 'fileSingleSizeLimit', function() {
4256             var uploader = this,
4257                 opts = uploader.options,
4258                 max = opts.fileSingleSizeLimit;
4259     
4260             if ( !max ) {
4261                 return;
4262             }
4263     
4264             uploader.on( 'beforeFileQueued', function( file ) {
4265     
4266                 if ( file.size > max ) {
4267                     file.setStatus( WUFile.Status.INVALID, 'exceed_size' );
4268                     this.trigger( 'error', 'F_EXCEED_SIZE', max, file );
4269                     return false;
4270                 }
4271     
4272             });
4273     
4274         });
4275     
4276         /**
4277          * @property {Boolean} [duplicate=undefined]
4278          * @namespace options
4279          * @for Uploader
4280          * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key.
4281          */
4282         api.addValidator( 'duplicate', function() {
4283             var uploader = this,
4284                 opts = uploader.options,
4285                 mapping = {};
4286     
4287             if ( opts.duplicate ) {
4288                 return;
4289             }
4290     
4291             function hashString( str ) {
4292                 var hash = 0,
4293                     i = 0,
4294                     len = str.length,
4295                     _char;
4296     
4297                 for ( ; i < len; i++ ) {
4298                     _char = str.charCodeAt( i );
4299                     hash = _char + (hash << 6) + (hash << 16) - hash;
4300                 }
4301     
4302                 return hash;
4303             }
4304     
4305             uploader.on( 'beforeFileQueued', function( file ) {
4306                 var hash = file.__hash || (file.__hash = hashString( file.name +
4307                         file.size + file.lastModifiedDate ));
4308     
4309                 // 已经重复了
4310                 if ( mapping[ hash ] ) {
4311                     this.trigger( 'error', 'F_DUPLICATE', file );
4312                     return false;
4313                 }
4314             });
4315     
4316             uploader.on( 'fileQueued', function( file ) {
4317                 var hash = file.__hash;
4318     
4319                 hash && (mapping[ hash ] = true);
4320             });
4321     
4322             uploader.on( 'fileDequeued', function( file ) {
4323                 var hash = file.__hash;
4324     
4325                 hash && (delete mapping[ hash ]);
4326             });
4327     
4328             uploader.on( 'reset', function() {
4329                 mapping = {};
4330             });
4331         });
4332     
4333         return api;
4334     });
4335     
4336     /**
4337      * @fileOverview Md5
4338      */
4339     define('lib/md5',[
4340         'runtime/client',
4341         'mediator'
4342     ], function( RuntimeClient, Mediator ) {
4343     
4344         function Md5() {
4345             RuntimeClient.call( this, 'Md5' );
4346         }
4347     
4348         // 让 Md5 具备事件功能。
4349         Mediator.installTo( Md5.prototype );
4350     
4351         Md5.prototype.loadFromBlob = function( blob ) {
4352             var me = this;
4353     
4354             if ( me.getRuid() ) {
4355                 me.disconnectRuntime();
4356             }
4357     
4358             // 连接到blob归属的同一个runtime.
4359             me.connectRuntime( blob.ruid, function() {
4360                 me.exec('init');
4361                 me.exec( 'loadFromBlob', blob );
4362             });
4363         };
4364     
4365         Md5.prototype.getResult = function() {
4366             return this.exec('getResult');
4367         };
4368     
4369         return Md5;
4370     });
4371     /**
4372      * @fileOverview 图片操作, 负责预览图片和上传前压缩图片
4373      */
4374     define('widgets/md5',[
4375         'base',
4376         'uploader',
4377         'lib/md5',
4378         'lib/blob',
4379         'widgets/widget'
4380     ], function( Base, Uploader, Md5, Blob ) {
4381     
4382         return Uploader.register({
4383             name: 'md5',
4384     
4385     
4386             /**
4387              * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。
4388              *
4389              *
4390              * @method md5File
4391              * @grammar md5File( file[, start[, end]] ) => promise
4392              * @for Uploader
4393              * @example
4394              *
4395              * uploader.on( 'fileQueued', function( file ) {
4396              *     var $li = ...;
4397              *
4398              *     uploader.md5File( file )
4399              *
4400              *         // 及时显示进度
4401              *         .progress(function(percentage) {
4402              *             console.log('Percentage:', percentage);
4403              *         })
4404              *
4405              *         // 完成
4406              *         .then(function(val) {
4407              *             console.log('md5 result:', val);
4408              *         });
4409              *
4410              * });
4411              */
4412             md5File: function( file, start, end ) {
4413                 var md5 = new Md5(),
4414                     deferred = Base.Deferred(),
4415                     blob = (file instanceof Blob) ? file :
4416                         this.request( 'get-file', file ).source;
4417     
4418                 md5.on( 'progress load', function( e ) {
4419                     e = e || {};
4420                     deferred.notify( e.total ? e.loaded / e.total : 1 );
4421                 });
4422     
4423                 md5.on( 'complete', function() {
4424                     deferred.resolve( md5.getResult() );
4425                 });
4426     
4427                 md5.on( 'error', function( reason ) {
4428                     deferred.reject( reason );
4429                 });
4430     
4431                 if ( arguments.length > 1 ) {
4432                     start = start || 0;
4433                     end = end || 0;
4434                     start < 0 && (start = blob.size + start);
4435                     end < 0 && (end = blob.size + end);
4436                     end = Math.min( end, blob.size );
4437                     blob = blob.slice( start, end );
4438                 }
4439     
4440                 md5.loadFromBlob( blob );
4441     
4442                 return deferred.promise();
4443             }
4444         });
4445     });
4446     /**
4447      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
4448      */
4449     define('runtime/compbase',[],function() {
4450     
4451         function CompBase( owner, runtime ) {
4452     
4453             this.owner = owner;
4454             this.options = owner.options;
4455     
4456             this.getRuntime = function() {
4457                 return runtime;
4458             };
4459     
4460             this.getRuid = function() {
4461                 return runtime.uid;
4462             };
4463     
4464             this.trigger = function() {
4465                 return owner.trigger.apply( owner, arguments );
4466             };
4467         }
4468     
4469         return CompBase;
4470     });
4471     /**
4472      * @fileOverview Html5Runtime
4473      */
4474     define('runtime/html5/runtime',[
4475         'base',
4476         'runtime/runtime',
4477         'runtime/compbase'
4478     ], function( Base, Runtime, CompBase ) {
4479     
4480         var type = 'html5',
4481             components = {};
4482     
4483         function Html5Runtime() {
4484             var pool = {},
4485                 me = this,
4486                 destroy = this.destroy;
4487     
4488             Runtime.apply( me, arguments );
4489             me.type = type;
4490     
4491     
4492             // 这个方法的调用者,实际上是RuntimeClient
4493             me.exec = function( comp, fn/*, args...*/) {
4494                 var client = this,
4495                     uid = client.uid,
4496                     args = Base.slice( arguments, 2 ),
4497                     instance;
4498     
4499                 if ( components[ comp ] ) {
4500                     instance = pool[ uid ] = pool[ uid ] ||
4501                             new components[ comp ]( client, me );
4502     
4503                     if ( instance[ fn ] ) {
4504                         return instance[ fn ].apply( instance, args );
4505                     }
4506                 }
4507             };
4508     
4509             me.destroy = function() {
4510                 // @todo 删除池子中的所有实例
4511                 return destroy && destroy.apply( this, arguments );
4512             };
4513         }
4514     
4515         Base.inherits( Runtime, {
4516             constructor: Html5Runtime,
4517     
4518             // 不需要连接其他程序,直接执行callback
4519             init: function() {
4520                 var me = this;
4521                 setTimeout(function() {
4522                     me.trigger('ready');
4523                 }, 1 );
4524             }
4525     
4526         });
4527     
4528         // 注册Components
4529         Html5Runtime.register = function( name, component ) {
4530             var klass = components[ name ] = Base.inherits( CompBase, component );
4531             return klass;
4532         };
4533     
4534         // 注册html5运行时。
4535         // 只有在支持的前提下注册。
4536         if ( window.Blob && window.FileReader && window.DataView ) {
4537             Runtime.addRuntime( type, Html5Runtime );
4538         }
4539     
4540         return Html5Runtime;
4541     });
4542     /**
4543      * @fileOverview Blob Html实现
4544      */
4545     define('runtime/html5/blob',[
4546         'runtime/html5/runtime',
4547         'lib/blob'
4548     ], function( Html5Runtime, Blob ) {
4549     
4550         return Html5Runtime.register( 'Blob', {
4551             slice: function( start, end ) {
4552                 var blob = this.owner.source,
4553                     slice = blob.slice || blob.webkitSlice || blob.mozSlice;
4554     
4555                 blob = slice.call( blob, start, end );
4556     
4557                 return new Blob( this.getRuid(), blob );
4558             }
4559         });
4560     });
4561     /**
4562      * @fileOverview FilePaste
4563      */
4564     define('runtime/html5/dnd',[
4565         'base',
4566         'runtime/html5/runtime',
4567         'lib/file'
4568     ], function( Base, Html5Runtime, File ) {
4569     
4570         var $ = Base.$,
4571             prefix = 'webuploader-dnd-';
4572     
4573         return Html5Runtime.register( 'DragAndDrop', {
4574             init: function() {
4575                 var elem = this.elem = this.options.container;
4576     
4577                 this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );
4578                 this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );
4579                 this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );
4580                 this.dropHandler = Base.bindFn( this._dropHandler, this );
4581                 this.dndOver = false;
4582     
4583                 elem.on( 'dragenter', this.dragEnterHandler );
4584                 elem.on( 'dragover', this.dragOverHandler );
4585                 elem.on( 'dragleave', this.dragLeaveHandler );
4586                 elem.on( 'drop', this.dropHandler );
4587     
4588                 if ( this.options.disableGlobalDnd ) {
4589                     $( document ).on( 'dragover', this.dragOverHandler );
4590                     $( document ).on( 'drop', this.dropHandler );
4591                 }
4592             },
4593     
4594             _dragEnterHandler: function( e ) {
4595                 var me = this,
4596                     denied = me._denied || false,
4597                     items;
4598     
4599                 e = e.originalEvent || e;
4600     
4601                 if ( !me.dndOver ) {
4602                     me.dndOver = true;
4603     
4604                     // 注意只有 chrome 支持。
4605                     items = e.dataTransfer.items;
4606     
4607                     if ( items && items.length ) {
4608                         me._denied = denied = !me.trigger( 'accept', items );
4609                     }
4610     
4611                     me.elem.addClass( prefix + 'over' );
4612                     me.elem[ denied ? 'addClass' :
4613                             'removeClass' ]( prefix + 'denied' );
4614                 }
4615     
4616                 e.dataTransfer.dropEffect = denied ? 'none' : 'copy';
4617     
4618                 return false;
4619             },
4620     
4621             _dragOverHandler: function( e ) {
4622                 // 只处理框内的。
4623                 var parentElem = this.elem.parent().get( 0 );
4624                 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
4625                     return false;
4626                 }
4627     
4628                 clearTimeout( this._leaveTimer );
4629                 this._dragEnterHandler.call( this, e );
4630     
4631                 return false;
4632             },
4633     
4634             _dragLeaveHandler: function() {
4635                 var me = this,
4636                     handler;
4637     
4638                 handler = function() {
4639                     me.dndOver = false;
4640                     me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );
4641                 };
4642     
4643                 clearTimeout( me._leaveTimer );
4644                 me._leaveTimer = setTimeout( handler, 100 );
4645                 return false;
4646             },
4647     
4648             _dropHandler: function( e ) {
4649                 var me = this,
4650                     ruid = me.getRuid(),
4651                     parentElem = me.elem.parent().get( 0 ),
4652                     dataTransfer, data;
4653     
4654                 // 只处理框内的。
4655                 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
4656                     return false;
4657                 }
4658     
4659                 e = e.originalEvent || e;
4660                 dataTransfer = e.dataTransfer;
4661     
4662                 // 如果是页面内拖拽,还不能处理,不阻止事件。
4663                 // 此处 ie11 下会报参数错误,
4664                 try {
4665                     data = dataTransfer.getData('text/html');
4666                 } catch( err ) {
4667                 }
4668     
4669                 if ( data ) {
4670                     return;
4671                 }
4672     
4673                 me._getTansferFiles( dataTransfer, function( results ) {
4674                     me.trigger( 'drop', $.map( results, function( file ) {
4675                         return new File( ruid, file );
4676                     }) );
4677                 });
4678     
4679                 me.dndOver = false;
4680                 me.elem.removeClass( prefix + 'over' );
4681                 return false;
4682             },
4683     
4684             // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。
4685             _getTansferFiles: function( dataTransfer, callback ) {
4686                 var results  = [],
4687                     promises = [],
4688                     items, files, file, item, i, len, canAccessFolder;
4689     
4690                 items = dataTransfer.items;
4691                 files = dataTransfer.files;
4692     
4693                 canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);
4694     
4695                 for ( i = 0, len = files.length; i < len; i++ ) {
4696                     file = files[ i ];
4697                     item = items && items[ i ];
4698     
4699                     if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {
4700     
4701                         promises.push( this._traverseDirectoryTree(
4702                                 item.webkitGetAsEntry(), results ) );
4703                     } else {
4704                         results.push( file );
4705                     }
4706                 }
4707     
4708                 Base.when.apply( Base, promises ).done(function() {
4709     
4710                     if ( !results.length ) {
4711                         return;
4712                     }
4713     
4714                     callback( results );
4715                 });
4716             },
4717     
4718             _traverseDirectoryTree: function( entry, results ) {
4719                 var deferred = Base.Deferred(),
4720                     me = this;
4721     
4722                 if ( entry.isFile ) {
4723                     entry.file(function( file ) {
4724                         results.push( file );
4725                         deferred.resolve();
4726                     });
4727                 } else if ( entry.isDirectory ) {
4728                     entry.createReader().readEntries(function( entries ) {
4729                         var len = entries.length,
4730                             promises = [],
4731                             arr = [],    // 为了保证顺序。
4732                             i;
4733     
4734                         for ( i = 0; i < len; i++ ) {
4735                             promises.push( me._traverseDirectoryTree(
4736                                     entries[ i ], arr ) );
4737                         }
4738     
4739                         Base.when.apply( Base, promises ).then(function() {
4740                             results.push.apply( results, arr );
4741                             deferred.resolve();
4742                         }, deferred.reject );
4743                     });
4744                 }
4745     
4746                 return deferred.promise();
4747             },
4748     
4749             destroy: function() {
4750                 var elem = this.elem;
4751     
4752                 // 还没 init 就调用 destroy
4753                 if (!elem) {
4754                     return;
4755                 }
4756                 
4757                 elem.off( 'dragenter', this.dragEnterHandler );
4758                 elem.off( 'dragover', this.dragOverHandler );
4759                 elem.off( 'dragleave', this.dragLeaveHandler );
4760                 elem.off( 'drop', this.dropHandler );
4761     
4762                 if ( this.options.disableGlobalDnd ) {
4763                     $( document ).off( 'dragover', this.dragOverHandler );
4764                     $( document ).off( 'drop', this.dropHandler );
4765                 }
4766             }
4767         });
4768     });
4769     
4770     /**
4771      * @fileOverview FilePaste
4772      */
4773     define('runtime/html5/filepaste',[
4774         'base',
4775         'runtime/html5/runtime',
4776         'lib/file'
4777     ], function( Base, Html5Runtime, File ) {
4778     
4779         return Html5Runtime.register( 'FilePaste', {
4780             init: function() {
4781                 var opts = this.options,
4782                     elem = this.elem = opts.container,
4783                     accept = '.*',
4784                     arr, i, len, item;
4785     
4786                 // accetp的mimeTypes中生成匹配正则。
4787                 if ( opts.accept ) {
4788                     arr = [];
4789     
4790                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
4791                         item = opts.accept[ i ].mimeTypes;
4792                         item && arr.push( item );
4793                     }
4794     
4795                     if ( arr.length ) {
4796                         accept = arr.join(',');
4797                         accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' );
4798                     }
4799                 }
4800                 this.accept = accept = new RegExp( accept, 'i' );
4801                 this.hander = Base.bindFn( this._pasteHander, this );
4802                 elem.on( 'paste', this.hander );
4803             },
4804     
4805             _pasteHander: function( e ) {
4806                 var allowed = [],
4807                     ruid = this.getRuid(),
4808                     items, item, blob, i, len;
4809     
4810                 e = e.originalEvent || e;
4811                 items = e.clipboardData.items;
4812     
4813                 for ( i = 0, len = items.length; i < len; i++ ) {
4814                     item = items[ i ];
4815     
4816                     if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {
4817                         continue;
4818                     }
4819     
4820                     allowed.push( new File( ruid, blob ) );
4821                 }
4822     
4823                 if ( allowed.length ) {
4824                     // 不阻止非文件粘贴(文字粘贴)的事件冒泡
4825                     e.preventDefault();
4826                     e.stopPropagation();
4827                     this.trigger( 'paste', allowed );
4828                 }
4829             },
4830     
4831             destroy: function() {
4832                 this.elem.off( 'paste', this.hander );
4833             }
4834         });
4835     });
4836     
4837     /**
4838      * @fileOverview FilePicker
4839      */
4840     define('runtime/html5/filepicker',[
4841         'base',
4842         'runtime/html5/runtime'
4843     ], function( Base, Html5Runtime ) {
4844     
4845         var $ = Base.$;
4846     
4847         return Html5Runtime.register( 'FilePicker', {
4848             init: function() {
4849                 var container = this.getRuntime().getContainer(),
4850                     me = this,
4851                     owner = me.owner,
4852                     opts = me.options,
4853                     label = this.label = $( document.createElement('label') ),
4854                     input =  this.input = $( document.createElement('input') ),
4855                     arr, i, len, mouseHandler;
4856     
4857                 input.attr( 'type', 'file' );
4858                 input.attr( 'name', opts.name );
4859                 input.addClass('webuploader-element-invisible');
4860     
4861                 label.on( 'click', function() {
4862                     input.trigger('click');
4863                 });
4864     
4865                 label.css({
4866                     opacity: 0,
4867                     width: '100%',
4868                     height: '100%',
4869                     display: 'block',
4870                     cursor: 'pointer',
4871                     background: '#ffffff'
4872                 });
4873     
4874                 if ( opts.multiple ) {
4875                     input.attr( 'multiple', 'multiple' );
4876                 }
4877     
4878                 // @todo Firefox不支持单独指定后缀
4879                 if ( opts.accept && opts.accept.length > 0 ) {
4880                     arr = [];
4881     
4882                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
4883                         arr.push( opts.accept[ i ].mimeTypes );
4884                     }
4885     
4886                     input.attr( 'accept', arr.join(',') );
4887                 }
4888     
4889                 container.append( input );
4890                 container.append( label );
4891     
4892                 mouseHandler = function( e ) {
4893                     owner.trigger( e.type );
4894                 };
4895     
4896                 input.on( 'change', function( e ) {
4897                     var fn = arguments.callee,
4898                         clone;
4899     
4900                     me.files = e.target.files;
4901     
4902                     // reset input
4903                     clone = this.cloneNode( true );
4904                     clone.value = null;
4905                     this.parentNode.replaceChild( clone, this );
4906     
4907                     input.off();
4908                     input = $( clone ).on( 'change', fn )
4909                             .on( 'mouseenter mouseleave', mouseHandler );
4910     
4911                     owner.trigger('change');
4912                 });
4913     
4914                 label.on( 'mouseenter mouseleave', mouseHandler );
4915     
4916             },
4917     
4918     
4919             getFiles: function() {
4920                 return this.files;
4921             },
4922     
4923             destroy: function() {
4924                 this.input.off();
4925                 this.label.off();
4926             }
4927         });
4928     });
4929     /**
4930      * Terms:
4931      *
4932      * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer
4933      * @fileOverview Image控件
4934      */
4935     define('runtime/html5/util',[
4936         'base'
4937     ], function( Base ) {
4938     
4939         var urlAPI = window.createObjectURL && window ||
4940                 window.URL && URL.revokeObjectURL && URL ||
4941                 window.webkitURL,
4942             createObjectURL = Base.noop,
4943             revokeObjectURL = createObjectURL;
4944     
4945         if ( urlAPI ) {
4946     
4947             // 更安全的方式调用,比如android里面就能把context改成其他的对象。
4948             createObjectURL = function() {
4949                 return urlAPI.createObjectURL.apply( urlAPI, arguments );
4950             };
4951     
4952             revokeObjectURL = function() {
4953                 return urlAPI.revokeObjectURL.apply( urlAPI, arguments );
4954             };
4955         }
4956     
4957         return {
4958             createObjectURL: createObjectURL,
4959             revokeObjectURL: revokeObjectURL,
4960     
4961             dataURL2Blob: function( dataURI ) {
4962                 var byteStr, intArray, ab, i, mimetype, parts;
4963     
4964                 parts = dataURI.split(',');
4965     
4966                 if ( ~parts[ 0 ].indexOf('base64') ) {
4967                     byteStr = atob( parts[ 1 ] );
4968                 } else {
4969                     byteStr = decodeURIComponent( parts[ 1 ] );
4970                 }
4971     
4972                 ab = new ArrayBuffer( byteStr.length );
4973                 intArray = new Uint8Array( ab );
4974     
4975                 for ( i = 0; i < byteStr.length; i++ ) {
4976                     intArray[ i ] = byteStr.charCodeAt( i );
4977                 }
4978     
4979                 mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];
4980     
4981                 return this.arrayBufferToBlob( ab, mimetype );
4982             },
4983     
4984             dataURL2ArrayBuffer: function( dataURI ) {
4985                 var byteStr, intArray, i, parts;
4986     
4987                 parts = dataURI.split(',');
4988     
4989                 if ( ~parts[ 0 ].indexOf('base64') ) {
4990                     byteStr = atob( parts[ 1 ] );
4991                 } else {
4992                     byteStr = decodeURIComponent( parts[ 1 ] );
4993                 }
4994     
4995                 intArray = new Uint8Array( byteStr.length );
4996     
4997                 for ( i = 0; i < byteStr.length; i++ ) {
4998                     intArray[ i ] = byteStr.charCodeAt( i );
4999                 }
5000     
5001                 return intArray.buffer;
5002             },
5003     
5004             arrayBufferToBlob: function( buffer, type ) {
5005                 var builder = window.BlobBuilder || window.WebKitBlobBuilder,
5006                     bb;
5007     
5008                 // android不支持直接new Blob, 只能借助blobbuilder.
5009                 if ( builder ) {
5010                     bb = new builder();
5011                     bb.append( buffer );
5012                     return bb.getBlob( type );
5013                 }
5014     
5015                 return new Blob([ buffer ], type ? { type: type } : {} );
5016             },
5017     
5018             // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.
5019             // 你得到的结果是png.
5020             canvasToDataUrl: function( canvas, type, quality ) {
5021                 return canvas.toDataURL( type, quality / 100 );
5022             },
5023     
5024             // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。
5025             parseMeta: function( blob, callback ) {
5026                 callback( false, {});
5027             },
5028     
5029             // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。
5030             updateImageHead: function( data ) {
5031                 return data;
5032             }
5033         };
5034     });
5035     /**
5036      * Terms:
5037      *
5038      * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer
5039      * @fileOverview Image控件
5040      */
5041     define('runtime/html5/imagemeta',[
5042         'runtime/html5/util'
5043     ], function( Util ) {
5044     
5045         var api;
5046     
5047         api = {
5048             parsers: {
5049                 0xffe1: []
5050             },
5051     
5052             maxMetaDataSize: 262144,
5053     
5054             parse: function( blob, cb ) {
5055                 var me = this,
5056                     fr = new FileReader();
5057     
5058                 fr.onload = function() {
5059                     cb( false, me._parse( this.result ) );
5060                     fr = fr.onload = fr.onerror = null;
5061                 };
5062     
5063                 fr.onerror = function( e ) {
5064                     cb( e.message );
5065                     fr = fr.onload = fr.onerror = null;
5066                 };
5067     
5068                 blob = blob.slice( 0, me.maxMetaDataSize );
5069                 fr.readAsArrayBuffer( blob.getSource() );
5070             },
5071     
5072             _parse: function( buffer, noParse ) {
5073                 if ( buffer.byteLength < 6 ) {
5074                     return;
5075                 }
5076     
5077                 var dataview = new DataView( buffer ),
5078                     offset = 2,
5079                     maxOffset = dataview.byteLength - 4,
5080                     headLength = offset,
5081                     ret = {},
5082                     markerBytes, markerLength, parsers, i;
5083     
5084                 if ( dataview.getUint16( 0 ) === 0xffd8 ) {
5085     
5086                     while ( offset < maxOffset ) {
5087                         markerBytes = dataview.getUint16( offset );
5088     
5089                         if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||
5090                                 markerBytes === 0xfffe ) {
5091     
5092                             markerLength = dataview.getUint16( offset + 2 ) + 2;
5093     
5094                             if ( offset + markerLength > dataview.byteLength ) {
5095                                 break;
5096                             }
5097     
5098                             parsers = api.parsers[ markerBytes ];
5099     
5100                             if ( !noParse && parsers ) {
5101                                 for ( i = 0; i < parsers.length; i += 1 ) {
5102                                     parsers[ i ].call( api, dataview, offset,
5103                                             markerLength, ret );
5104                                 }
5105                             }
5106     
5107                             offset += markerLength;
5108                             headLength = offset;
5109                         } else {
5110                             break;
5111                         }
5112                     }
5113     
5114                     if ( headLength > 6 ) {
5115                         if ( buffer.slice ) {
5116                             ret.imageHead = buffer.slice( 2, headLength );
5117                         } else {
5118                             // Workaround for IE10, which does not yet
5119                             // support ArrayBuffer.slice:
5120                             ret.imageHead = new Uint8Array( buffer )
5121                                     .subarray( 2, headLength );
5122                         }
5123                     }
5124                 }
5125     
5126                 return ret;
5127             },
5128     
5129             updateImageHead: function( buffer, head ) {
5130                 var data = this._parse( buffer, true ),
5131                     buf1, buf2, bodyoffset;
5132     
5133     
5134                 bodyoffset = 2;
5135                 if ( data.imageHead ) {
5136                     bodyoffset = 2 + data.imageHead.byteLength;
5137                 }
5138     
5139                 if ( buffer.slice ) {
5140                     buf2 = buffer.slice( bodyoffset );
5141                 } else {
5142                     buf2 = new Uint8Array( buffer ).subarray( bodyoffset );
5143                 }
5144     
5145                 buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );
5146     
5147                 buf1[ 0 ] = 0xFF;
5148                 buf1[ 1 ] = 0xD8;
5149                 buf1.set( new Uint8Array( head ), 2 );
5150                 buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );
5151     
5152                 return buf1.buffer;
5153             }
5154         };
5155     
5156         Util.parseMeta = function() {
5157             return api.parse.apply( api, arguments );
5158         };
5159     
5160         Util.updateImageHead = function() {
5161             return api.updateImageHead.apply( api, arguments );
5162         };
5163     
5164         return api;
5165     });
5166     /**
5167      * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image
5168      * 暂时项目中只用了orientation.
5169      *
5170      * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.
5171      * @fileOverview EXIF解析
5172      */
5173     
5174     // Sample
5175     // ====================================
5176     // Make : Apple
5177     // Model : iPhone 4S
5178     // Orientation : 1
5179     // XResolution : 72 [72/1]
5180     // YResolution : 72 [72/1]
5181     // ResolutionUnit : 2
5182     // Software : QuickTime 7.7.1
5183     // DateTime : 2013:09:01 22:53:55
5184     // ExifIFDPointer : 190
5185     // ExposureTime : 0.058823529411764705 [1/17]
5186     // FNumber : 2.4 [12/5]
5187     // ExposureProgram : Normal program
5188     // ISOSpeedRatings : 800
5189     // ExifVersion : 0220
5190     // DateTimeOriginal : 2013:09:01 22:52:51
5191     // DateTimeDigitized : 2013:09:01 22:52:51
5192     // ComponentsConfiguration : YCbCr
5193     // ShutterSpeedValue : 4.058893515764426
5194     // ApertureValue : 2.5260688216892597 [4845/1918]
5195     // BrightnessValue : -0.3126686601998395
5196     // MeteringMode : Pattern
5197     // Flash : Flash did not fire, compulsory flash mode
5198     // FocalLength : 4.28 [107/25]
5199     // SubjectArea : [4 values]
5200     // FlashpixVersion : 0100
5201     // ColorSpace : 1
5202     // PixelXDimension : 2448
5203     // PixelYDimension : 3264
5204     // SensingMethod : One-chip color area sensor
5205     // ExposureMode : 0
5206     // WhiteBalance : Auto white balance
5207     // FocalLengthIn35mmFilm : 35
5208     // SceneCaptureType : Standard
5209     define('runtime/html5/imagemeta/exif',[
5210         'base',
5211         'runtime/html5/imagemeta'
5212     ], function( Base, ImageMeta ) {
5213     
5214         var EXIF = {};
5215     
5216         EXIF.ExifMap = function() {
5217             return this;
5218         };
5219     
5220         EXIF.ExifMap.prototype.map = {
5221             'Orientation': 0x0112
5222         };
5223     
5224         EXIF.ExifMap.prototype.get = function( id ) {
5225             return this[ id ] || this[ this.map[ id ] ];
5226         };
5227     
5228         EXIF.exifTagTypes = {
5229             // byte, 8-bit unsigned int:
5230             1: {
5231                 getValue: function( dataView, dataOffset ) {
5232                     return dataView.getUint8( dataOffset );
5233                 },
5234                 size: 1
5235             },
5236     
5237             // ascii, 8-bit byte:
5238             2: {
5239                 getValue: function( dataView, dataOffset ) {
5240                     return String.fromCharCode( dataView.getUint8( dataOffset ) );
5241                 },
5242                 size: 1,
5243                 ascii: true
5244             },
5245     
5246             // short, 16 bit int:
5247             3: {
5248                 getValue: function( dataView, dataOffset, littleEndian ) {
5249                     return dataView.getUint16( dataOffset, littleEndian );
5250                 },
5251                 size: 2
5252             },
5253     
5254             // long, 32 bit int:
5255             4: {
5256                 getValue: function( dataView, dataOffset, littleEndian ) {
5257                     return dataView.getUint32( dataOffset, littleEndian );
5258                 },
5259                 size: 4
5260             },
5261     
5262             // rational = two long values,
5263             // first is numerator, second is denominator:
5264             5: {
5265                 getValue: function( dataView, dataOffset, littleEndian ) {
5266                     return dataView.getUint32( dataOffset, littleEndian ) /
5267                         dataView.getUint32( dataOffset + 4, littleEndian );
5268                 },
5269                 size: 8
5270             },
5271     
5272             // slong, 32 bit signed int:
5273             9: {
5274                 getValue: function( dataView, dataOffset, littleEndian ) {
5275                     return dataView.getInt32( dataOffset, littleEndian );
5276                 },
5277                 size: 4
5278             },
5279     
5280             // srational, two slongs, first is numerator, second is denominator:
5281             10: {
5282                 getValue: function( dataView, dataOffset, littleEndian ) {
5283                     return dataView.getInt32( dataOffset, littleEndian ) /
5284                         dataView.getInt32( dataOffset + 4, littleEndian );
5285                 },
5286                 size: 8
5287             }
5288         };
5289     
5290         // undefined, 8-bit byte, value depending on field:
5291         EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];
5292     
5293         EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,
5294                 littleEndian ) {
5295     
5296             var tagType = EXIF.exifTagTypes[ type ],
5297                 tagSize, dataOffset, values, i, str, c;
5298     
5299             if ( !tagType ) {
5300                 Base.log('Invalid Exif data: Invalid tag type.');
5301                 return;
5302             }
5303     
5304             tagSize = tagType.size * length;
5305     
5306             // Determine if the value is contained in the dataOffset bytes,
5307             // or if the value at the dataOffset is a pointer to the actual data:
5308             dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,
5309                     littleEndian ) : (offset + 8);
5310     
5311             if ( dataOffset + tagSize > dataView.byteLength ) {
5312                 Base.log('Invalid Exif data: Invalid data offset.');
5313                 return;
5314             }
5315     
5316             if ( length === 1 ) {
5317                 return tagType.getValue( dataView, dataOffset, littleEndian );
5318             }
5319     
5320             values = [];
5321     
5322             for ( i = 0; i < length; i += 1 ) {
5323                 values[ i ] = tagType.getValue( dataView,
5324                         dataOffset + i * tagType.size, littleEndian );
5325             }
5326     
5327             if ( tagType.ascii ) {
5328                 str = '';
5329     
5330                 // Concatenate the chars:
5331                 for ( i = 0; i < values.length; i += 1 ) {
5332                     c = values[ i ];
5333     
5334                     // Ignore the terminating NULL byte(s):
5335                     if ( c === '\u0000' ) {
5336                         break;
5337                     }
5338                     str += c;
5339                 }
5340     
5341                 return str;
5342             }
5343             return values;
5344         };
5345     
5346         EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,
5347                 data ) {
5348     
5349             var tag = dataView.getUint16( offset, littleEndian );
5350             data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,
5351                     dataView.getUint16( offset + 2, littleEndian ),    // tag type
5352                     dataView.getUint32( offset + 4, littleEndian ),    // tag length
5353                     littleEndian );
5354         };
5355     
5356         EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,
5357                 littleEndian, data ) {
5358     
5359             var tagsNumber, dirEndOffset, i;
5360     
5361             if ( dirOffset + 6 > dataView.byteLength ) {
5362                 Base.log('Invalid Exif data: Invalid directory offset.');
5363                 return;
5364             }
5365     
5366             tagsNumber = dataView.getUint16( dirOffset, littleEndian );
5367             dirEndOffset = dirOffset + 2 + 12 * tagsNumber;
5368     
5369             if ( dirEndOffset + 4 > dataView.byteLength ) {
5370                 Base.log('Invalid Exif data: Invalid directory size.');
5371                 return;
5372             }
5373     
5374             for ( i = 0; i < tagsNumber; i += 1 ) {
5375                 this.parseExifTag( dataView, tiffOffset,
5376                         dirOffset + 2 + 12 * i,    // tag offset
5377                         littleEndian, data );
5378             }
5379     
5380             // Return the offset to the next directory:
5381             return dataView.getUint32( dirEndOffset, littleEndian );
5382         };
5383     
5384         // EXIF.getExifThumbnail = function(dataView, offset, length) {
5385         //     var hexData,
5386         //         i,
5387         //         b;
5388         //     if (!length || offset + length > dataView.byteLength) {
5389         //         Base.log('Invalid Exif data: Invalid thumbnail data.');
5390         //         return;
5391         //     }
5392         //     hexData = [];
5393         //     for (i = 0; i < length; i += 1) {
5394         //         b = dataView.getUint8(offset + i);
5395         //         hexData.push((b < 16 ? '0' : '') + b.toString(16));
5396         //     }
5397         //     return 'data:image/jpeg,%' + hexData.join('%');
5398         // };
5399     
5400         EXIF.parseExifData = function( dataView, offset, length, data ) {
5401     
5402             var tiffOffset = offset + 10,
5403                 littleEndian, dirOffset;
5404     
5405             // Check for the ASCII code for "Exif" (0x45786966):
5406             if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {
5407                 // No Exif data, might be XMP data instead
5408                 return;
5409             }
5410             if ( tiffOffset + 8 > dataView.byteLength ) {
5411                 Base.log('Invalid Exif data: Invalid segment size.');
5412                 return;
5413             }
5414     
5415             // Check for the two null bytes:
5416             if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {
5417                 Base.log('Invalid Exif data: Missing byte alignment offset.');
5418                 return;
5419             }
5420     
5421             // Check the byte alignment:
5422             switch ( dataView.getUint16( tiffOffset ) ) {
5423                 case 0x4949:
5424                     littleEndian = true;
5425                     break;
5426     
5427                 case 0x4D4D:
5428                     littleEndian = false;
5429                     break;
5430     
5431                 default:
5432                     Base.log('Invalid Exif data: Invalid byte alignment marker.');
5433                     return;
5434             }
5435     
5436             // Check for the TIFF tag marker (0x002A):
5437             if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {
5438                 Base.log('Invalid Exif data: Missing TIFF marker.');
5439                 return;
5440             }
5441     
5442             // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
5443             dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );
5444             // Create the exif object to store the tags:
5445             data.exif = new EXIF.ExifMap();
5446             // Parse the tags of the main image directory and retrieve the
5447             // offset to the next directory, usually the thumbnail directory:
5448             dirOffset = EXIF.parseExifTags( dataView, tiffOffset,
5449                     tiffOffset + dirOffset, littleEndian, data );
5450     
5451             // 尝试读取缩略图
5452             // if ( dirOffset ) {
5453             //     thumbnailData = {exif: {}};
5454             //     dirOffset = EXIF.parseExifTags(
5455             //         dataView,
5456             //         tiffOffset,
5457             //         tiffOffset + dirOffset,
5458             //         littleEndian,
5459             //         thumbnailData
5460             //     );
5461     
5462             //     // Check for JPEG Thumbnail offset:
5463             //     if (thumbnailData.exif[0x0201]) {
5464             //         data.exif.Thumbnail = EXIF.getExifThumbnail(
5465             //             dataView,
5466             //             tiffOffset + thumbnailData.exif[0x0201],
5467             //             thumbnailData.exif[0x0202] // Thumbnail data length
5468             //         );
5469             //     }
5470             // }
5471         };
5472     
5473         ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );
5474         return EXIF;
5475     });
5476     /**
5477      * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug
5478      * android里面toDataUrl('image/jpege')得到的结果却是png.
5479      *
5480      * 所以这里没辙,只能借助这个工具
5481      * @fileOverview jpeg encoder
5482      */
5483     define('runtime/html5/jpegencoder',[], function( require, exports, module ) {
5484     
5485         /*
5486           Copyright (c) 2008, Adobe Systems Incorporated
5487           All rights reserved.
5488     
5489           Redistribution and use in source and binary forms, with or without
5490           modification, are permitted provided that the following conditions are
5491           met:
5492     
5493           * Redistributions of source code must retain the above copyright notice,
5494             this list of conditions and the following disclaimer.
5495     
5496           * Redistributions in binary form must reproduce the above copyright
5497             notice, this list of conditions and the following disclaimer in the
5498             documentation and/or other materials provided with the distribution.
5499     
5500           * Neither the name of Adobe Systems Incorporated nor the names of its
5501             contributors may be used to endorse or promote products derived from
5502             this software without specific prior written permission.
5503     
5504           THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
5505           IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
5506           THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
5507           PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
5508           CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
5509           EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
5510           PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
5511           PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
5512           LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
5513           NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
5514           SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
5515         */
5516         /*
5517         JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009
5518     
5519         Basic GUI blocking jpeg encoder
5520         */
5521     
5522         function JPEGEncoder(quality) {
5523           var self = this;
5524             var fround = Math.round;
5525             var ffloor = Math.floor;
5526             var YTable = new Array(64);
5527             var UVTable = new Array(64);
5528             var fdtbl_Y = new Array(64);
5529             var fdtbl_UV = new Array(64);
5530             var YDC_HT;
5531             var UVDC_HT;
5532             var YAC_HT;
5533             var UVAC_HT;
5534     
5535             var bitcode = new Array(65535);
5536             var category = new Array(65535);
5537             var outputfDCTQuant = new Array(64);
5538             var DU = new Array(64);
5539             var byteout = [];
5540             var bytenew = 0;
5541             var bytepos = 7;
5542     
5543             var YDU = new Array(64);
5544             var UDU = new Array(64);
5545             var VDU = new Array(64);
5546             var clt = new Array(256);
5547             var RGB_YUV_TABLE = new Array(2048);
5548             var currentQuality;
5549     
5550             var ZigZag = [
5551                      0, 1, 5, 6,14,15,27,28,
5552                      2, 4, 7,13,16,26,29,42,
5553                      3, 8,12,17,25,30,41,43,
5554                      9,11,18,24,31,40,44,53,
5555                     10,19,23,32,39,45,52,54,
5556                     20,22,33,38,46,51,55,60,
5557                     21,34,37,47,50,56,59,61,
5558                     35,36,48,49,57,58,62,63
5559                 ];
5560     
5561             var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
5562             var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
5563             var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
5564             var std_ac_luminance_values = [
5565                     0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
5566                     0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
5567                     0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
5568                     0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
5569                     0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
5570                     0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
5571                     0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
5572                     0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
5573                     0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
5574                     0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
5575                     0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
5576                     0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
5577                     0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
5578                     0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
5579                     0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
5580                     0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
5581                     0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
5582                     0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
5583                     0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
5584                     0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
5585                     0xf9,0xfa
5586                 ];
5587     
5588             var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
5589             var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
5590             var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
5591             var std_ac_chrominance_values = [
5592                     0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
5593                     0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
5594                     0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
5595                     0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
5596                     0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
5597                     0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
5598                     0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
5599                     0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
5600                     0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
5601                     0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
5602                     0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
5603                     0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
5604                     0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
5605                     0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
5606                     0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
5607                     0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
5608                     0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
5609                     0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
5610                     0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
5611                     0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
5612                     0xf9,0xfa
5613                 ];
5614     
5615             function initQuantTables(sf){
5616                     var YQT = [
5617                         16, 11, 10, 16, 24, 40, 51, 61,
5618                         12, 12, 14, 19, 26, 58, 60, 55,
5619                         14, 13, 16, 24, 40, 57, 69, 56,
5620                         14, 17, 22, 29, 51, 87, 80, 62,
5621                         18, 22, 37, 56, 68,109,103, 77,
5622                         24, 35, 55, 64, 81,104,113, 92,
5623                         49, 64, 78, 87,103,121,120,101,
5624                         72, 92, 95, 98,112,100,103, 99
5625                     ];
5626     
5627                     for (var i = 0; i < 64; i++) {
5628                         var t = ffloor((YQT[i]*sf+50)/100);
5629                         if (t < 1) {
5630                             t = 1;
5631                         } else if (t > 255) {
5632                             t = 255;
5633                         }
5634                         YTable[ZigZag[i]] = t;
5635                     }
5636                     var UVQT = [
5637                         17, 18, 24, 47, 99, 99, 99, 99,
5638                         18, 21, 26, 66, 99, 99, 99, 99,
5639                         24, 26, 56, 99, 99, 99, 99, 99,
5640                         47, 66, 99, 99, 99, 99, 99, 99,
5641                         99, 99, 99, 99, 99, 99, 99, 99,
5642                         99, 99, 99, 99, 99, 99, 99, 99,
5643                         99, 99, 99, 99, 99, 99, 99, 99,
5644                         99, 99, 99, 99, 99, 99, 99, 99
5645                     ];
5646                     for (var j = 0; j < 64; j++) {
5647                         var u = ffloor((UVQT[j]*sf+50)/100);
5648                         if (u < 1) {
5649                             u = 1;
5650                         } else if (u > 255) {
5651                             u = 255;
5652                         }
5653                         UVTable[ZigZag[j]] = u;
5654                     }
5655                     var aasf = [
5656                         1.0, 1.387039845, 1.306562965, 1.175875602,
5657                         1.0, 0.785694958, 0.541196100, 0.275899379
5658                     ];
5659                     var k = 0;
5660                     for (var row = 0; row < 8; row++)
5661                     {
5662                         for (var col = 0; col < 8; col++)
5663                         {
5664                             fdtbl_Y[k]  = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
5665                             fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
5666                             k++;
5667                         }
5668                     }
5669                 }
5670     
5671                 function computeHuffmanTbl(nrcodes, std_table){
5672                     var codevalue = 0;
5673                     var pos_in_table = 0;
5674                     var HT = new Array();
5675                     for (var k = 1; k <= 16; k++) {
5676                         for (var j = 1; j <= nrcodes[k]; j++) {
5677                             HT[std_table[pos_in_table]] = [];
5678                             HT[std_table[pos_in_table]][0] = codevalue;
5679                             HT[std_table[pos_in_table]][1] = k;
5680                             pos_in_table++;
5681                             codevalue++;
5682                         }
5683                         codevalue*=2;
5684                     }
5685                     return HT;
5686                 }
5687     
5688                 function initHuffmanTbl()
5689                 {
5690                     YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
5691                     UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
5692                     YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
5693                     UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
5694                 }
5695     
5696                 function initCategoryNumber()
5697                 {
5698                     var nrlower = 1;
5699                     var nrupper = 2;
5700                     for (var cat = 1; cat <= 15; cat++) {
5701                         //Positive numbers
5702                         for (var nr = nrlower; nr<nrupper; nr++) {
5703                             category[32767+nr] = cat;
5704                             bitcode[32767+nr] = [];
5705                             bitcode[32767+nr][1] = cat;
5706                             bitcode[32767+nr][0] = nr;
5707                         }
5708                         //Negative numbers
5709                         for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {
5710                             category[32767+nrneg] = cat;
5711                             bitcode[32767+nrneg] = [];
5712                             bitcode[32767+nrneg][1] = cat;
5713                             bitcode[32767+nrneg][0] = nrupper-1+nrneg;
5714                         }
5715                         nrlower <<= 1;
5716                         nrupper <<= 1;
5717                     }
5718                 }
5719     
5720                 function initRGBYUVTable() {
5721                     for(var i = 0; i < 256;i++) {
5722                         RGB_YUV_TABLE[i]            =  19595 * i;
5723                         RGB_YUV_TABLE[(i+ 256)>>0]  =  38470 * i;
5724                         RGB_YUV_TABLE[(i+ 512)>>0]  =   7471 * i + 0x8000;
5725                         RGB_YUV_TABLE[(i+ 768)>>0]  = -11059 * i;
5726                         RGB_YUV_TABLE[(i+1024)>>0]  = -21709 * i;
5727                         RGB_YUV_TABLE[(i+1280)>>0]  =  32768 * i + 0x807FFF;
5728                         RGB_YUV_TABLE[(i+1536)>>0]  = -27439 * i;
5729                         RGB_YUV_TABLE[(i+1792)>>0]  = - 5329 * i;
5730                     }
5731                 }
5732     
5733                 // IO functions
5734                 function writeBits(bs)
5735                 {
5736                     var value = bs[0];
5737                     var posval = bs[1]-1;
5738                     while ( posval >= 0 ) {
5739                         if (value & (1 << posval) ) {
5740                             bytenew |= (1 << bytepos);
5741                         }
5742                         posval--;
5743                         bytepos--;
5744                         if (bytepos < 0) {
5745                             if (bytenew == 0xFF) {
5746                                 writeByte(0xFF);
5747                                 writeByte(0);
5748                             }
5749                             else {
5750                                 writeByte(bytenew);
5751                             }
5752                             bytepos=7;
5753                             bytenew=0;
5754                         }
5755                     }
5756                 }
5757     
5758                 function writeByte(value)
5759                 {
5760                     byteout.push(clt[value]); // write char directly instead of converting later
5761                 }
5762     
5763                 function writeWord(value)
5764                 {
5765                     writeByte((value>>8)&0xFF);
5766                     writeByte((value   )&0xFF);
5767                 }
5768     
5769                 // DCT & quantization core
5770                 function fDCTQuant(data, fdtbl)
5771                 {
5772                     var d0, d1, d2, d3, d4, d5, d6, d7;
5773                     /* Pass 1: process rows. */
5774                     var dataOff=0;
5775                     var i;
5776                     var I8 = 8;
5777                     var I64 = 64;
5778                     for (i=0; i<I8; ++i)
5779                     {
5780                         d0 = data[dataOff];
5781                         d1 = data[dataOff+1];
5782                         d2 = data[dataOff+2];
5783                         d3 = data[dataOff+3];
5784                         d4 = data[dataOff+4];
5785                         d5 = data[dataOff+5];
5786                         d6 = data[dataOff+6];
5787                         d7 = data[dataOff+7];
5788     
5789                         var tmp0 = d0 + d7;
5790                         var tmp7 = d0 - d7;
5791                         var tmp1 = d1 + d6;
5792                         var tmp6 = d1 - d6;
5793                         var tmp2 = d2 + d5;
5794                         var tmp5 = d2 - d5;
5795                         var tmp3 = d3 + d4;
5796                         var tmp4 = d3 - d4;
5797     
5798                         /* Even part */
5799                         var tmp10 = tmp0 + tmp3;    /* phase 2 */
5800                         var tmp13 = tmp0 - tmp3;
5801                         var tmp11 = tmp1 + tmp2;
5802                         var tmp12 = tmp1 - tmp2;
5803     
5804                         data[dataOff] = tmp10 + tmp11; /* phase 3 */
5805                         data[dataOff+4] = tmp10 - tmp11;
5806     
5807                         var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
5808                         data[dataOff+2] = tmp13 + z1; /* phase 5 */
5809                         data[dataOff+6] = tmp13 - z1;
5810     
5811                         /* Odd part */
5812                         tmp10 = tmp4 + tmp5; /* phase 2 */
5813                         tmp11 = tmp5 + tmp6;
5814                         tmp12 = tmp6 + tmp7;
5815     
5816                         /* The rotator is modified from fig 4-8 to avoid extra negations. */
5817                         var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
5818                         var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
5819                         var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
5820                         var z3 = tmp11 * 0.707106781; /* c4 */
5821     
5822                         var z11 = tmp7 + z3;    /* phase 5 */
5823                         var z13 = tmp7 - z3;
5824     
5825                         data[dataOff+5] = z13 + z2; /* phase 6 */
5826                         data[dataOff+3] = z13 - z2;
5827                         data[dataOff+1] = z11 + z4;
5828                         data[dataOff+7] = z11 - z4;
5829     
5830                         dataOff += 8; /* advance pointer to next row */
5831                     }
5832     
5833                     /* Pass 2: process columns. */
5834                     dataOff = 0;
5835                     for (i=0; i<I8; ++i)
5836                     {
5837                         d0 = data[dataOff];
5838                         d1 = data[dataOff + 8];
5839                         d2 = data[dataOff + 16];
5840                         d3 = data[dataOff + 24];
5841                         d4 = data[dataOff + 32];
5842                         d5 = data[dataOff + 40];
5843                         d6 = data[dataOff + 48];
5844                         d7 = data[dataOff + 56];
5845     
5846                         var tmp0p2 = d0 + d7;
5847                         var tmp7p2 = d0 - d7;
5848                         var tmp1p2 = d1 + d6;
5849                         var tmp6p2 = d1 - d6;
5850                         var tmp2p2 = d2 + d5;
5851                         var tmp5p2 = d2 - d5;
5852                         var tmp3p2 = d3 + d4;
5853                         var tmp4p2 = d3 - d4;
5854     
5855                         /* Even part */
5856                         var tmp10p2 = tmp0p2 + tmp3p2;  /* phase 2 */
5857                         var tmp13p2 = tmp0p2 - tmp3p2;
5858                         var tmp11p2 = tmp1p2 + tmp2p2;
5859                         var tmp12p2 = tmp1p2 - tmp2p2;
5860     
5861                         data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */
5862                         data[dataOff+32] = tmp10p2 - tmp11p2;
5863     
5864                         var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
5865                         data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
5866                         data[dataOff+48] = tmp13p2 - z1p2;
5867     
5868                         /* Odd part */
5869                         tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
5870                         tmp11p2 = tmp5p2 + tmp6p2;
5871                         tmp12p2 = tmp6p2 + tmp7p2;
5872     
5873                         /* The rotator is modified from fig 4-8 to avoid extra negations. */
5874                         var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
5875                         var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
5876                         var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
5877                         var z3p2 = tmp11p2 * 0.707106781; /* c4 */
5878     
5879                         var z11p2 = tmp7p2 + z3p2;  /* phase 5 */
5880                         var z13p2 = tmp7p2 - z3p2;
5881     
5882                         data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
5883                         data[dataOff+24] = z13p2 - z2p2;
5884                         data[dataOff+ 8] = z11p2 + z4p2;
5885                         data[dataOff+56] = z11p2 - z4p2;
5886     
5887                         dataOff++; /* advance pointer to next column */
5888                     }
5889     
5890                     // Quantize/descale the coefficients
5891                     var fDCTQuant;
5892                     for (i=0; i<I64; ++i)
5893                     {
5894                         // Apply the quantization and scaling factor & Round to nearest integer
5895                         fDCTQuant = data[i]*fdtbl[i];
5896                         outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);
5897                         //outputfDCTQuant[i] = fround(fDCTQuant);
5898     
5899                     }
5900                     return outputfDCTQuant;
5901                 }
5902     
5903                 function writeAPP0()
5904                 {
5905                     writeWord(0xFFE0); // marker
5906                     writeWord(16); // length
5907                     writeByte(0x4A); // J
5908                     writeByte(0x46); // F
5909                     writeByte(0x49); // I
5910                     writeByte(0x46); // F
5911                     writeByte(0); // = "JFIF",'\0'
5912                     writeByte(1); // versionhi
5913                     writeByte(1); // versionlo
5914                     writeByte(0); // xyunits
5915                     writeWord(1); // xdensity
5916                     writeWord(1); // ydensity
5917                     writeByte(0); // thumbnwidth
5918                     writeByte(0); // thumbnheight
5919                 }
5920     
5921                 function writeSOF0(width, height)
5922                 {
5923                     writeWord(0xFFC0); // marker
5924                     writeWord(17);   // length, truecolor YUV JPG
5925                     writeByte(8);    // precision
5926                     writeWord(height);
5927                     writeWord(width);
5928                     writeByte(3);    // nrofcomponents
5929                     writeByte(1);    // IdY
5930                     writeByte(0x11); // HVY
5931                     writeByte(0);    // QTY
5932                     writeByte(2);    // IdU
5933                     writeByte(0x11); // HVU
5934                     writeByte(1);    // QTU
5935                     writeByte(3);    // IdV
5936                     writeByte(0x11); // HVV
5937                     writeByte(1);    // QTV
5938                 }
5939     
5940                 function writeDQT()
5941                 {
5942                     writeWord(0xFFDB); // marker
5943                     writeWord(132);    // length
5944                     writeByte(0);
5945                     for (var i=0; i<64; i++) {
5946                         writeByte(YTable[i]);
5947                     }
5948                     writeByte(1);
5949                     for (var j=0; j<64; j++) {
5950                         writeByte(UVTable[j]);
5951                     }
5952                 }
5953     
5954                 function writeDHT()
5955                 {
5956                     writeWord(0xFFC4); // marker
5957                     writeWord(0x01A2); // length
5958     
5959                     writeByte(0); // HTYDCinfo
5960                     for (var i=0; i<16; i++) {
5961                         writeByte(std_dc_luminance_nrcodes[i+1]);
5962                     }
5963                     for (var j=0; j<=11; j++) {
5964                         writeByte(std_dc_luminance_values[j]);
5965                     }
5966     
5967                     writeByte(0x10); // HTYACinfo
5968                     for (var k=0; k<16; k++) {
5969                         writeByte(std_ac_luminance_nrcodes[k+1]);
5970                     }
5971                     for (var l=0; l<=161; l++) {
5972                         writeByte(std_ac_luminance_values[l]);
5973                     }
5974     
5975                     writeByte(1); // HTUDCinfo
5976                     for (var m=0; m<16; m++) {
5977                         writeByte(std_dc_chrominance_nrcodes[m+1]);
5978                     }
5979                     for (var n=0; n<=11; n++) {
5980                         writeByte(std_dc_chrominance_values[n]);
5981                     }
5982     
5983                     writeByte(0x11); // HTUACinfo
5984                     for (var o=0; o<16; o++) {
5985                         writeByte(std_ac_chrominance_nrcodes[o+1]);
5986                     }
5987                     for (var p=0; p<=161; p++) {
5988                         writeByte(std_ac_chrominance_values[p]);
5989                     }
5990                 }
5991     
5992                 function writeSOS()
5993                 {
5994                     writeWord(0xFFDA); // marker
5995                     writeWord(12); // length
5996                     writeByte(3); // nrofcomponents
5997                     writeByte(1); // IdY
5998                     writeByte(0); // HTY
5999                     writeByte(2); // IdU
6000                     writeByte(0x11); // HTU
6001                     writeByte(3); // IdV
6002                     writeByte(0x11); // HTV
6003                     writeByte(0); // Ss
6004                     writeByte(0x3f); // Se
6005                     writeByte(0); // Bf
6006                 }
6007     
6008                 function processDU(CDU, fdtbl, DC, HTDC, HTAC){
6009                     var EOB = HTAC[0x00];
6010                     var M16zeroes = HTAC[0xF0];
6011                     var pos;
6012                     var I16 = 16;
6013                     var I63 = 63;
6014                     var I64 = 64;
6015                     var DU_DCT = fDCTQuant(CDU, fdtbl);
6016                     //ZigZag reorder
6017                     for (var j=0;j<I64;++j) {
6018                         DU[ZigZag[j]]=DU_DCT[j];
6019                     }
6020                     var Diff = DU[0] - DC; DC = DU[0];
6021                     //Encode DC
6022                     if (Diff==0) {
6023                         writeBits(HTDC[0]); // Diff might be 0
6024                     } else {
6025                         pos = 32767+Diff;
6026                         writeBits(HTDC[category[pos]]);
6027                         writeBits(bitcode[pos]);
6028                     }
6029                     //Encode ACs
6030                     var end0pos = 63; // was const... which is crazy
6031                     for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};
6032                     //end0pos = first element in reverse order !=0
6033                     if ( end0pos == 0) {
6034                         writeBits(EOB);
6035                         return DC;
6036                     }
6037                     var i = 1;
6038                     var lng;
6039                     while ( i <= end0pos ) {
6040                         var startpos = i;
6041                         for (; (DU[i]==0) && (i<=end0pos); ++i) {}
6042                         var nrzeroes = i-startpos;
6043                         if ( nrzeroes >= I16 ) {
6044                             lng = nrzeroes>>4;
6045                             for (var nrmarker=1; nrmarker <= lng; ++nrmarker)
6046                                 writeBits(M16zeroes);
6047                             nrzeroes = nrzeroes&0xF;
6048                         }
6049                         pos = 32767+DU[i];
6050                         writeBits(HTAC[(nrzeroes<<4)+category[pos]]);
6051                         writeBits(bitcode[pos]);
6052                         i++;
6053                     }
6054                     if ( end0pos != I63 ) {
6055                         writeBits(EOB);
6056                     }
6057                     return DC;
6058                 }
6059     
6060                 function initCharLookupTable(){
6061                     var sfcc = String.fromCharCode;
6062                     for(var i=0; i < 256; i++){ ///// ACHTUNG // 255
6063                         clt[i] = sfcc(i);
6064                     }
6065                 }
6066     
6067                 this.encode = function(image,quality) // image data object
6068                 {
6069                     // var time_start = new Date().getTime();
6070     
6071                     if(quality) setQuality(quality);
6072     
6073                     // Initialize bit writer
6074                     byteout = new Array();
6075                     bytenew=0;
6076                     bytepos=7;
6077     
6078                     // Add JPEG headers
6079                     writeWord(0xFFD8); // SOI
6080                     writeAPP0();
6081                     writeDQT();
6082                     writeSOF0(image.width,image.height);
6083                     writeDHT();
6084                     writeSOS();
6085     
6086     
6087                     // Encode 8x8 macroblocks
6088                     var DCY=0;
6089                     var DCU=0;
6090                     var DCV=0;
6091     
6092                     bytenew=0;
6093                     bytepos=7;
6094     
6095     
6096                     this.encode.displayName = "_encode_";
6097     
6098                     var imageData = image.data;
6099                     var width = image.width;
6100                     var height = image.height;
6101     
6102                     var quadWidth = width*4;
6103                     var tripleWidth = width*3;
6104     
6105                     var x, y = 0;
6106                     var r, g, b;
6107                     var start,p, col,row,pos;
6108                     while(y < height){
6109                         x = 0;
6110                         while(x < quadWidth){
6111                         start = quadWidth * y + x;
6112                         p = start;
6113                         col = -1;
6114                         row = 0;
6115     
6116                         for(pos=0; pos < 64; pos++){
6117                             row = pos >> 3;// /8
6118                             col = ( pos & 7 ) * 4; // %8
6119                             p = start + ( row * quadWidth ) + col;
6120     
6121                             if(y+row >= height){ // padding bottom
6122                                 p-= (quadWidth*(y+1+row-height));
6123                             }
6124     
6125                             if(x+col >= quadWidth){ // padding right
6126                                 p-= ((x+col) - quadWidth +4)
6127                             }
6128     
6129                             r = imageData[ p++ ];
6130                             g = imageData[ p++ ];
6131                             b = imageData[ p++ ];
6132     
6133     
6134                             /* // calculate YUV values dynamically
6135                             YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80
6136                             UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));
6137                             VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));
6138                             */
6139     
6140                             // use lookup table (slightly faster)
6141                             YDU[pos] = ((RGB_YUV_TABLE[r]             + RGB_YUV_TABLE[(g +  256)>>0] + RGB_YUV_TABLE[(b +  512)>>0]) >> 16)-128;
6142                             UDU[pos] = ((RGB_YUV_TABLE[(r +  768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;
6143                             VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;
6144     
6145                         }
6146     
6147                         DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
6148                         DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
6149                         DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
6150                         x+=32;
6151                         }
6152                         y+=8;
6153                     }
6154     
6155     
6156                     ////////////////////////////////////////////////////////////////
6157     
6158                     // Do the bit alignment of the EOI marker
6159                     if ( bytepos >= 0 ) {
6160                         var fillbits = [];
6161                         fillbits[1] = bytepos+1;
6162                         fillbits[0] = (1<<(bytepos+1))-1;
6163                         writeBits(fillbits);
6164                     }
6165     
6166                     writeWord(0xFFD9); //EOI
6167     
6168                     var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));
6169     
6170                     byteout = [];
6171     
6172                     // benchmarking
6173                     // var duration = new Date().getTime() - time_start;
6174                     // console.log('Encoding time: '+ currentQuality + 'ms');
6175                     //
6176     
6177                     return jpegDataUri
6178             }
6179     
6180             function setQuality(quality){
6181                 if (quality <= 0) {
6182                     quality = 1;
6183                 }
6184                 if (quality > 100) {
6185                     quality = 100;
6186                 }
6187     
6188                 if(currentQuality == quality) return // don't recalc if unchanged
6189     
6190                 var sf = 0;
6191                 if (quality < 50) {
6192                     sf = Math.floor(5000 / quality);
6193                 } else {
6194                     sf = Math.floor(200 - quality*2);
6195                 }
6196     
6197                 initQuantTables(sf);
6198                 currentQuality = quality;
6199                 // console.log('Quality set to: '+quality +'%');
6200             }
6201     
6202             function init(){
6203                 // var time_start = new Date().getTime();
6204                 if(!quality) quality = 50;
6205                 // Create tables
6206                 initCharLookupTable()
6207                 initHuffmanTbl();
6208                 initCategoryNumber();
6209                 initRGBYUVTable();
6210     
6211                 setQuality(quality);
6212                 // var duration = new Date().getTime() - time_start;
6213                 // console.log('Initialization '+ duration + 'ms');
6214             }
6215     
6216             init();
6217     
6218         };
6219     
6220         JPEGEncoder.encode = function( data, quality ) {
6221             var encoder = new JPEGEncoder( quality );
6222     
6223             return encoder.encode( data );
6224         }
6225     
6226         return JPEGEncoder;
6227     });
6228     /**
6229      * @fileOverview Fix android canvas.toDataUrl bug.
6230      */
6231     define('runtime/html5/androidpatch',[
6232         'runtime/html5/util',
6233         'runtime/html5/jpegencoder',
6234         'base'
6235     ], function( Util, encoder, Base ) {
6236         var origin = Util.canvasToDataUrl,
6237             supportJpeg;
6238     
6239         Util.canvasToDataUrl = function( canvas, type, quality ) {
6240             var ctx, w, h, fragement, parts;
6241     
6242             // 非android手机直接跳过。
6243             if ( !Base.os.android ) {
6244                 return origin.apply( null, arguments );
6245             }
6246     
6247             // 检测是否canvas支持jpeg导出,根据数据格式来判断。
6248             // JPEG 前两位分别是:255, 216
6249             if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) {
6250                 fragement = origin.apply( null, arguments );
6251     
6252                 parts = fragement.split(',');
6253     
6254                 if ( ~parts[ 0 ].indexOf('base64') ) {
6255                     fragement = atob( parts[ 1 ] );
6256                 } else {
6257                     fragement = decodeURIComponent( parts[ 1 ] );
6258                 }
6259     
6260                 fragement = fragement.substring( 0, 2 );
6261     
6262                 supportJpeg = fragement.charCodeAt( 0 ) === 255 &&
6263                         fragement.charCodeAt( 1 ) === 216;
6264             }
6265     
6266             // 只有在android环境下才修复
6267             if ( type === 'image/jpeg' && !supportJpeg ) {
6268                 w = canvas.width;
6269                 h = canvas.height;
6270                 ctx = canvas.getContext('2d');
6271     
6272                 return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality );
6273             }
6274     
6275             return origin.apply( null, arguments );
6276         };
6277     });
6278     /**
6279      * @fileOverview Image
6280      */
6281     define('runtime/html5/image',[
6282         'base',
6283         'runtime/html5/runtime',
6284         'runtime/html5/util'
6285     ], function( Base, Html5Runtime, Util ) {
6286     
6287         var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';
6288     
6289         return Html5Runtime.register( 'Image', {
6290     
6291             // flag: 标记是否被修改过。
6292             modified: false,
6293     
6294             init: function() {
6295                 var me = this,
6296                     img = new Image();
6297     
6298                 img.onload = function() {
6299     
6300                     me._info = {
6301                         type: me.type,
6302                         width: this.width,
6303                         height: this.height
6304                     };
6305     
6306                     // 读取meta信息。
6307                     if ( !me._metas && 'image/jpeg' === me.type ) {
6308                         Util.parseMeta( me._blob, function( error, ret ) {
6309                             me._metas = ret;
6310                             me.owner.trigger('load');
6311                         });
6312                     } else {
6313                         me.owner.trigger('load');
6314                     }
6315                 };
6316     
6317                 img.onerror = function() {
6318                     me.owner.trigger('error');
6319                 };
6320     
6321                 me._img = img;
6322             },
6323     
6324             loadFromBlob: function( blob ) {
6325                 var me = this,
6326                     img = me._img;
6327     
6328                 me._blob = blob;
6329                 me.type = blob.type;
6330                 img.src = Util.createObjectURL( blob.getSource() );
6331                 me.owner.once( 'load', function() {
6332                     Util.revokeObjectURL( img.src );
6333                 });
6334             },
6335     
6336             resize: function( width, height ) {
6337                 var canvas = this._canvas ||
6338                         (this._canvas = document.createElement('canvas'));
6339     
6340                 this._resize( this._img, canvas, width, height );
6341                 this._blob = null;    // 没用了,可以删掉了。
6342                 this.modified = true;
6343                 this.owner.trigger( 'complete', 'resize' );
6344             },
6345     
6346             crop: function( x, y, w, h, s ) {
6347                 var cvs = this._canvas ||
6348                         (this._canvas = document.createElement('canvas')),
6349                     opts = this.options,
6350                     img = this._img,
6351                     iw = img.naturalWidth,
6352                     ih = img.naturalHeight,
6353                     orientation = this.getOrientation();
6354     
6355                 s = s || 1;
6356     
6357                 // todo 解决 orientation 的问题。
6358                 // values that require 90 degree rotation
6359                 // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {
6360     
6361                 //     switch ( orientation ) {
6362                 //         case 6:
6363                 //             tmp = x;
6364                 //             x = y;
6365                 //             y = iw * s - tmp - w;
6366                 //             console.log(ih * s, tmp, w)
6367                 //             break;
6368                 //     }
6369     
6370                 //     (w ^= h, h ^= w, w ^= h);
6371                 // }
6372     
6373                 cvs.width = w;
6374                 cvs.height = h;
6375     
6376                 opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );
6377                 this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s );
6378     
6379                 this._blob = null;    // 没用了,可以删掉了。
6380                 this.modified = true;
6381                 this.owner.trigger( 'complete', 'crop' );
6382             },
6383     
6384             getAsBlob: function( type ) {
6385                 var blob = this._blob,
6386                     opts = this.options,
6387                     canvas;
6388     
6389                 type = type || this.type;
6390     
6391                 // blob需要重新生成。
6392                 if ( this.modified || this.type !== type ) {
6393                     canvas = this._canvas;
6394     
6395                     if ( type === 'image/jpeg' ) {
6396     
6397                         blob = Util.canvasToDataUrl( canvas, type, opts.quality );
6398     
6399                         if ( opts.preserveHeaders && this._metas &&
6400                                 this._metas.imageHead ) {
6401     
6402                             blob = Util.dataURL2ArrayBuffer( blob );
6403                             blob = Util.updateImageHead( blob,
6404                                     this._metas.imageHead );
6405                             blob = Util.arrayBufferToBlob( blob, type );
6406                             return blob;
6407                         }
6408                     } else {
6409                         blob = Util.canvasToDataUrl( canvas, type );
6410                     }
6411     
6412                     blob = Util.dataURL2Blob( blob );
6413                 }
6414     
6415                 return blob;
6416             },
6417     
6418             getAsDataUrl: function( type ) {
6419                 var opts = this.options;
6420     
6421                 type = type || this.type;
6422     
6423                 if ( type === 'image/jpeg' ) {
6424                     return Util.canvasToDataUrl( this._canvas, type, opts.quality );
6425                 } else {
6426                     return this._canvas.toDataURL( type );
6427                 }
6428             },
6429     
6430             getOrientation: function() {
6431                 return this._metas && this._metas.exif &&
6432                         this._metas.exif.get('Orientation') || 1;
6433             },
6434     
6435             info: function( val ) {
6436     
6437                 // setter
6438                 if ( val ) {
6439                     this._info = val;
6440                     return this;
6441                 }
6442     
6443                 // getter
6444                 return this._info;
6445             },
6446     
6447             meta: function( val ) {
6448     
6449                 // setter
6450                 if ( val ) {
6451                     this._meta = val;
6452                     return this;
6453                 }
6454     
6455                 // getter
6456                 return this._meta;
6457             },
6458     
6459             destroy: function() {
6460                 var canvas = this._canvas;
6461                 this._img.onload = null;
6462     
6463                 if ( canvas ) {
6464                     canvas.getContext('2d')
6465                             .clearRect( 0, 0, canvas.width, canvas.height );
6466                     canvas.width = canvas.height = 0;
6467                     this._canvas = null;
6468                 }
6469     
6470                 // 释放内存。非常重要,否则释放不了image的内存。
6471                 this._img.src = BLANK;
6472                 this._img = this._blob = null;
6473             },
6474     
6475             _resize: function( img, cvs, width, height ) {
6476                 var opts = this.options,
6477                     naturalWidth = img.width,
6478                     naturalHeight = img.height,
6479                     orientation = this.getOrientation(),
6480                     scale, w, h, x, y;
6481     
6482                 // values that require 90 degree rotation
6483                 if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {
6484     
6485                     // 交换width, height的值。
6486                     width ^= height;
6487                     height ^= width;
6488                     width ^= height;
6489                 }
6490     
6491                 scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,
6492                         height / naturalHeight );
6493     
6494                 // 不允许放大。
6495                 opts.allowMagnify || (scale = Math.min( 1, scale ));
6496     
6497                 w = naturalWidth * scale;
6498                 h = naturalHeight * scale;
6499     
6500                 if ( opts.crop ) {
6501                     cvs.width = width;
6502                     cvs.height = height;
6503                 } else {
6504                     cvs.width = w;
6505                     cvs.height = h;
6506                 }
6507     
6508                 x = (cvs.width - w) / 2;
6509                 y = (cvs.height - h) / 2;
6510     
6511                 opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );
6512     
6513                 this._renderImageToCanvas( cvs, img, x, y, w, h );
6514             },
6515     
6516             _rotate2Orientaion: function( canvas, orientation ) {
6517                 var width = canvas.width,
6518                     height = canvas.height,
6519                     ctx = canvas.getContext('2d');
6520     
6521                 switch ( orientation ) {
6522                     case 5:
6523                     case 6:
6524                     case 7:
6525                     case 8:
6526                         canvas.width = height;
6527                         canvas.height = width;
6528                         break;
6529                 }
6530     
6531                 switch ( orientation ) {
6532                     case 2:    // horizontal flip
6533                         ctx.translate( width, 0 );
6534                         ctx.scale( -1, 1 );
6535                         break;
6536     
6537                     case 3:    // 180 rotate left
6538                         ctx.translate( width, height );
6539                         ctx.rotate( Math.PI );
6540                         break;
6541     
6542                     case 4:    // vertical flip
6543                         ctx.translate( 0, height );
6544                         ctx.scale( 1, -1 );
6545                         break;
6546     
6547                     case 5:    // vertical flip + 90 rotate right
6548                         ctx.rotate( 0.5 * Math.PI );
6549                         ctx.scale( 1, -1 );
6550                         break;
6551     
6552                     case 6:    // 90 rotate right
6553                         ctx.rotate( 0.5 * Math.PI );
6554                         ctx.translate( 0, -height );
6555                         break;
6556     
6557                     case 7:    // horizontal flip + 90 rotate right
6558                         ctx.rotate( 0.5 * Math.PI );
6559                         ctx.translate( width, -height );
6560                         ctx.scale( -1, 1 );
6561                         break;
6562     
6563                     case 8:    // 90 rotate left
6564                         ctx.rotate( -0.5 * Math.PI );
6565                         ctx.translate( -width, 0 );
6566                         break;
6567                 }
6568             },
6569     
6570             // https://github.com/stomita/ios-imagefile-megapixel/
6571             // blob/master/src/megapix-image.js
6572             _renderImageToCanvas: (function() {
6573     
6574                 // 如果不是ios, 不需要这么复杂!
6575                 if ( !Base.os.ios ) {
6576                     return function( canvas ) {
6577                         var args = Base.slice( arguments, 1 ),
6578                             ctx = canvas.getContext('2d');
6579     
6580                         ctx.drawImage.apply( ctx, args );
6581                     };
6582                 }
6583     
6584                 /**
6585                  * Detecting vertical squash in loaded image.
6586                  * Fixes a bug which squash image vertically while drawing into
6587                  * canvas for some images.
6588                  */
6589                 function detectVerticalSquash( img, iw, ih ) {
6590                     var canvas = document.createElement('canvas'),
6591                         ctx = canvas.getContext('2d'),
6592                         sy = 0,
6593                         ey = ih,
6594                         py = ih,
6595                         data, alpha, ratio;
6596     
6597     
6598                     canvas.width = 1;
6599                     canvas.height = ih;
6600                     ctx.drawImage( img, 0, 0 );
6601                     data = ctx.getImageData( 0, 0, 1, ih ).data;
6602     
6603                     // search image edge pixel position in case
6604                     // it is squashed vertically.
6605                     while ( py > sy ) {
6606                         alpha = data[ (py - 1) * 4 + 3 ];
6607     
6608                         if ( alpha === 0 ) {
6609                             ey = py;
6610                         } else {
6611                             sy = py;
6612                         }
6613     
6614                         py = (ey + sy) >> 1;
6615                     }
6616     
6617                     ratio = (py / ih);
6618                     return (ratio === 0) ? 1 : ratio;
6619                 }
6620     
6621                 // fix ie7 bug
6622                 // http://stackoverflow.com/questions/11929099/
6623                 // html5-canvas-drawimage-ratio-bug-ios
6624                 if ( Base.os.ios >= 7 ) {
6625                     return function( canvas, img, x, y, w, h ) {
6626                         var iw = img.naturalWidth,
6627                             ih = img.naturalHeight,
6628                             vertSquashRatio = detectVerticalSquash( img, iw, ih );
6629     
6630                         return canvas.getContext('2d').drawImage( img, 0, 0,
6631                                 iw * vertSquashRatio, ih * vertSquashRatio,
6632                                 x, y, w, h );
6633                     };
6634                 }
6635     
6636                 /**
6637                  * Detect subsampling in loaded image.
6638                  * In iOS, larger images than 2M pixels may be
6639                  * subsampled in rendering.
6640                  */
6641                 function detectSubsampling( img ) {
6642                     var iw = img.naturalWidth,
6643                         ih = img.naturalHeight,
6644                         canvas, ctx;
6645     
6646                     // subsampling may happen overmegapixel image
6647                     if ( iw * ih > 1024 * 1024 ) {
6648                         canvas = document.createElement('canvas');
6649                         canvas.width = canvas.height = 1;
6650                         ctx = canvas.getContext('2d');
6651                         ctx.drawImage( img, -iw + 1, 0 );
6652     
6653                         // subsampled image becomes half smaller in rendering size.
6654                         // check alpha channel value to confirm image is covering
6655                         // edge pixel or not. if alpha value is 0
6656                         // image is not covering, hence subsampled.
6657                         return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;
6658                     } else {
6659                         return false;
6660                     }
6661                 }
6662     
6663     
6664                 return function( canvas, img, x, y, width, height ) {
6665                     var iw = img.naturalWidth,
6666                         ih = img.naturalHeight,
6667                         ctx = canvas.getContext('2d'),
6668                         subsampled = detectSubsampling( img ),
6669                         doSquash = this.type === 'image/jpeg',
6670                         d = 1024,
6671                         sy = 0,
6672                         dy = 0,
6673                         tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;
6674     
6675                     if ( subsampled ) {
6676                         iw /= 2;
6677                         ih /= 2;
6678                     }
6679     
6680                     ctx.save();
6681                     tmpCanvas = document.createElement('canvas');
6682                     tmpCanvas.width = tmpCanvas.height = d;
6683     
6684                     tmpCtx = tmpCanvas.getContext('2d');
6685                     vertSquashRatio = doSquash ?
6686                             detectVerticalSquash( img, iw, ih ) : 1;
6687     
6688                     dw = Math.ceil( d * width / iw );
6689                     dh = Math.ceil( d * height / ih / vertSquashRatio );
6690     
6691                     while ( sy < ih ) {
6692                         sx = 0;
6693                         dx = 0;
6694                         while ( sx < iw ) {
6695                             tmpCtx.clearRect( 0, 0, d, d );
6696                             tmpCtx.drawImage( img, -sx, -sy );
6697                             ctx.drawImage( tmpCanvas, 0, 0, d, d,
6698                                     x + dx, y + dy, dw, dh );
6699                             sx += d;
6700                             dx += dw;
6701                         }
6702                         sy += d;
6703                         dy += dh;
6704                     }
6705                     ctx.restore();
6706                     tmpCanvas = tmpCtx = null;
6707                 };
6708             })()
6709         });
6710     });
6711     /**
6712      * @fileOverview Transport
6713      * @todo 支持chunked传输,优势:
6714      * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分,
6715      * 而不需要重头再传一次。另外断点续传也需要用chunked方式。
6716      */
6717     define('runtime/html5/transport',[
6718         'base',
6719         'runtime/html5/runtime'
6720     ], function( Base, Html5Runtime ) {
6721     
6722         var noop = Base.noop,
6723             $ = Base.$;
6724     
6725         return Html5Runtime.register( 'Transport', {
6726             init: function() {
6727                 this._status = 0;
6728                 this._response = null;
6729             },
6730     
6731             send: function() {
6732                 var owner = this.owner,
6733                     opts = this.options,
6734                     xhr = this._initAjax(),
6735                     blob = owner._blob,
6736                     server = opts.server,
6737                     formData, binary, fr;
6738     
6739                 if ( opts.sendAsBinary ) {
6740                     server += (/\?/.test( server ) ? '&' : '?') +
6741                             $.param( owner._formData );
6742     
6743                     binary = blob.getSource();
6744                 } else {
6745                     formData = new FormData();
6746                     $.each( owner._formData, function( k, v ) {
6747                         formData.append( k, v );
6748                     });
6749     
6750                     formData.append( opts.fileVal, blob.getSource(),
6751                             opts.filename || owner._formData.name || '' );
6752                 }
6753     
6754                 if ( opts.withCredentials && 'withCredentials' in xhr ) {
6755                     xhr.open( opts.method, server, true );
6756                     xhr.withCredentials = true;
6757                 } else {
6758                     xhr.open( opts.method, server );
6759                 }
6760     
6761                 this._setRequestHeader( xhr, opts.headers );
6762     
6763                 if ( binary ) {
6764                     // 强制设置成 content-type 为文件流。
6765                     xhr.overrideMimeType &&
6766                             xhr.overrideMimeType('application/octet-stream');
6767     
6768                     // android直接发送blob会导致服务端接收到的是空文件。
6769                     // bug详情。
6770                     // https://code.google.com/p/android/issues/detail?id=39882
6771                     // 所以先用fileReader读取出来再通过arraybuffer的方式发送。
6772                     if ( Base.os.android ) {
6773                         fr = new FileReader();
6774     
6775                         fr.onload = function() {
6776                             xhr.send( this.result );
6777                             fr = fr.onload = null;
6778                         };
6779     
6780                         fr.readAsArrayBuffer( binary );
6781                     } else {
6782                         xhr.send( binary );
6783                     }
6784                 } else {
6785                     xhr.send( formData );
6786                 }
6787             },
6788     
6789             getResponse: function() {
6790                 return this._response;
6791             },
6792     
6793             getResponseAsJson: function() {
6794                 return this._parseJson( this._response );
6795             },
6796     
6797             getStatus: function() {
6798                 return this._status;
6799             },
6800     
6801             abort: function() {
6802                 var xhr = this._xhr;
6803     
6804                 if ( xhr ) {
6805                     xhr.upload.onprogress = noop;
6806                     xhr.onreadystatechange = noop;
6807                     xhr.abort();
6808     
6809                     this._xhr = xhr = null;
6810                 }
6811             },
6812     
6813             destroy: function() {
6814                 this.abort();
6815             },
6816     
6817             _initAjax: function() {
6818                 var me = this,
6819                     xhr = new XMLHttpRequest(),
6820                     opts = this.options;
6821     
6822                 if ( opts.withCredentials && !('withCredentials' in xhr) &&
6823                         typeof XDomainRequest !== 'undefined' ) {
6824                     xhr = new XDomainRequest();
6825                 }
6826     
6827                 xhr.upload.onprogress = function( e ) {
6828                     var percentage = 0;
6829     
6830                     if ( e.lengthComputable ) {
6831                         percentage = e.loaded / e.total;
6832                     }
6833     
6834                     return me.trigger( 'progress', percentage );
6835                 };
6836     
6837                 xhr.onreadystatechange = function() {
6838     
6839                     if ( xhr.readyState !== 4 ) {
6840                         return;
6841                     }
6842     
6843                     xhr.upload.onprogress = noop;
6844                     xhr.onreadystatechange = noop;
6845                     me._xhr = null;
6846                     me._status = xhr.status;
6847     
6848                     if ( xhr.status >= 200 && xhr.status < 300 ) {
6849                         me._response = xhr.responseText;
6850                         return me.trigger('load');
6851                     } else if ( xhr.status >= 500 && xhr.status < 600 ) {
6852                         me._response = xhr.responseText;
6853                         return me.trigger( 'error', 'server' );
6854                     }
6855     
6856     
6857                     return me.trigger( 'error', me._status ? 'http' : 'abort' );
6858                 };
6859     
6860                 me._xhr = xhr;
6861                 return xhr;
6862             },
6863     
6864             _setRequestHeader: function( xhr, headers ) {
6865                 $.each( headers, function( key, val ) {
6866                     xhr.setRequestHeader( key, val );
6867                 });
6868             },
6869     
6870             _parseJson: function( str ) {
6871                 var json;
6872     
6873                 try {
6874                     json = JSON.parse( str );
6875                 } catch ( ex ) {
6876                     json = {};
6877                 }
6878     
6879                 return json;
6880             }
6881         });
6882     });
6883     /**
6884      * @fileOverview  Transport flash实现
6885      */
6886     define('runtime/html5/md5',[
6887         'runtime/html5/runtime'
6888     ], function( FlashRuntime ) {
6889     
6890         /*
6891          * Fastest md5 implementation around (JKM md5)
6892          * Credits: Joseph Myers
6893          *
6894          * @see http://www.myersdaily.org/joseph/javascript/md5-text.html
6895          * @see http://jsperf.com/md5-shootout/7
6896          */
6897     
6898         /* this function is much faster,
6899           so if possible we use it. Some IEs
6900           are the only ones I know of that
6901           need the idiotic second function,
6902           generated by an if clause.  */
6903         var add32 = function (a, b) {
6904             return (a + b) & 0xFFFFFFFF;
6905         },
6906     
6907         cmn = function (q, a, b, x, s, t) {
6908             a = add32(add32(a, q), add32(x, t));
6909             return add32((a << s) | (a >>> (32 - s)), b);
6910         },
6911     
6912         ff = function (a, b, c, d, x, s, t) {
6913             return cmn((b & c) | ((~b) & d), a, b, x, s, t);
6914         },
6915     
6916         gg = function (a, b, c, d, x, s, t) {
6917             return cmn((b & d) | (c & (~d)), a, b, x, s, t);
6918         },
6919     
6920         hh = function (a, b, c, d, x, s, t) {
6921             return cmn(b ^ c ^ d, a, b, x, s, t);
6922         },
6923     
6924         ii = function (a, b, c, d, x, s, t) {
6925             return cmn(c ^ (b | (~d)), a, b, x, s, t);
6926         },
6927     
6928         md5cycle = function (x, k) {
6929             var a = x[0],
6930                 b = x[1],
6931                 c = x[2],
6932                 d = x[3];
6933     
6934             a = ff(a, b, c, d, k[0], 7, -680876936);
6935             d = ff(d, a, b, c, k[1], 12, -389564586);
6936             c = ff(c, d, a, b, k[2], 17, 606105819);
6937             b = ff(b, c, d, a, k[3], 22, -1044525330);
6938             a = ff(a, b, c, d, k[4], 7, -176418897);
6939             d = ff(d, a, b, c, k[5], 12, 1200080426);
6940             c = ff(c, d, a, b, k[6], 17, -1473231341);
6941             b = ff(b, c, d, a, k[7], 22, -45705983);
6942             a = ff(a, b, c, d, k[8], 7, 1770035416);
6943             d = ff(d, a, b, c, k[9], 12, -1958414417);
6944             c = ff(c, d, a, b, k[10], 17, -42063);
6945             b = ff(b, c, d, a, k[11], 22, -1990404162);
6946             a = ff(a, b, c, d, k[12], 7, 1804603682);
6947             d = ff(d, a, b, c, k[13], 12, -40341101);
6948             c = ff(c, d, a, b, k[14], 17, -1502002290);
6949             b = ff(b, c, d, a, k[15], 22, 1236535329);
6950     
6951             a = gg(a, b, c, d, k[1], 5, -165796510);
6952             d = gg(d, a, b, c, k[6], 9, -1069501632);
6953             c = gg(c, d, a, b, k[11], 14, 643717713);
6954             b = gg(b, c, d, a, k[0], 20, -373897302);
6955             a = gg(a, b, c, d, k[5], 5, -701558691);
6956             d = gg(d, a, b, c, k[10], 9, 38016083);
6957             c = gg(c, d, a, b, k[15], 14, -660478335);
6958             b = gg(b, c, d, a, k[4], 20, -405537848);
6959             a = gg(a, b, c, d, k[9], 5, 568446438);
6960             d = gg(d, a, b, c, k[14], 9, -1019803690);
6961             c = gg(c, d, a, b, k[3], 14, -187363961);
6962             b = gg(b, c, d, a, k[8], 20, 1163531501);
6963             a = gg(a, b, c, d, k[13], 5, -1444681467);
6964             d = gg(d, a, b, c, k[2], 9, -51403784);
6965             c = gg(c, d, a, b, k[7], 14, 1735328473);
6966             b = gg(b, c, d, a, k[12], 20, -1926607734);
6967     
6968             a = hh(a, b, c, d, k[5], 4, -378558);
6969             d = hh(d, a, b, c, k[8], 11, -2022574463);
6970             c = hh(c, d, a, b, k[11], 16, 1839030562);
6971             b = hh(b, c, d, a, k[14], 23, -35309556);
6972             a = hh(a, b, c, d, k[1], 4, -1530992060);
6973             d = hh(d, a, b, c, k[4], 11, 1272893353);
6974             c = hh(c, d, a, b, k[7], 16, -155497632);
6975             b = hh(b, c, d, a, k[10], 23, -1094730640);
6976             a = hh(a, b, c, d, k[13], 4, 681279174);
6977             d = hh(d, a, b, c, k[0], 11, -358537222);
6978             c = hh(c, d, a, b, k[3], 16, -722521979);
6979             b = hh(b, c, d, a, k[6], 23, 76029189);
6980             a = hh(a, b, c, d, k[9], 4, -640364487);
6981             d = hh(d, a, b, c, k[12], 11, -421815835);
6982             c = hh(c, d, a, b, k[15], 16, 530742520);
6983             b = hh(b, c, d, a, k[2], 23, -995338651);
6984     
6985             a = ii(a, b, c, d, k[0], 6, -198630844);
6986             d = ii(d, a, b, c, k[7], 10, 1126891415);
6987             c = ii(c, d, a, b, k[14], 15, -1416354905);
6988             b = ii(b, c, d, a, k[5], 21, -57434055);
6989             a = ii(a, b, c, d, k[12], 6, 1700485571);
6990             d = ii(d, a, b, c, k[3], 10, -1894986606);
6991             c = ii(c, d, a, b, k[10], 15, -1051523);
6992             b = ii(b, c, d, a, k[1], 21, -2054922799);
6993             a = ii(a, b, c, d, k[8], 6, 1873313359);
6994             d = ii(d, a, b, c, k[15], 10, -30611744);
6995             c = ii(c, d, a, b, k[6], 15, -1560198380);
6996             b = ii(b, c, d, a, k[13], 21, 1309151649);
6997             a = ii(a, b, c, d, k[4], 6, -145523070);
6998             d = ii(d, a, b, c, k[11], 10, -1120210379);
6999             c = ii(c, d, a, b, k[2], 15, 718787259);
7000             b = ii(b, c, d, a, k[9], 21, -343485551);
7001     
7002             x[0] = add32(a, x[0]);
7003             x[1] = add32(b, x[1]);
7004             x[2] = add32(c, x[2]);
7005             x[3] = add32(d, x[3]);
7006         },
7007     
7008         /* there needs to be support for Unicode here,
7009            * unless we pretend that we can redefine the MD-5
7010            * algorithm for multi-byte characters (perhaps
7011            * by adding every four 16-bit characters and
7012            * shortening the sum to 32 bits). Otherwise
7013            * I suggest performing MD-5 as if every character
7014            * was two bytes--e.g., 0040 0025 = @%--but then
7015            * how will an ordinary MD-5 sum be matched?
7016            * There is no way to standardize text to something
7017            * like UTF-8 before transformation; speed cost is
7018            * utterly prohibitive. The JavaScript standard
7019            * itself needs to look at this: it should start
7020            * providing access to strings as preformed UTF-8
7021            * 8-bit unsigned value arrays.
7022            */
7023         md5blk = function (s) {
7024             var md5blks = [],
7025                 i; /* Andy King said do it this way. */
7026     
7027             for (i = 0; i < 64; i += 4) {
7028                 md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
7029             }
7030             return md5blks;
7031         },
7032     
7033         md5blk_array = function (a) {
7034             var md5blks = [],
7035                 i; /* Andy King said do it this way. */
7036     
7037             for (i = 0; i < 64; i += 4) {
7038                 md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);
7039             }
7040             return md5blks;
7041         },
7042     
7043         md51 = function (s) {
7044             var n = s.length,
7045                 state = [1732584193, -271733879, -1732584194, 271733878],
7046                 i,
7047                 length,
7048                 tail,
7049                 tmp,
7050                 lo,
7051                 hi;
7052     
7053             for (i = 64; i <= n; i += 64) {
7054                 md5cycle(state, md5blk(s.substring(i - 64, i)));
7055             }
7056             s = s.substring(i - 64);
7057             length = s.length;
7058             tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
7059             for (i = 0; i < length; i += 1) {
7060                 tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
7061             }
7062             tail[i >> 2] |= 0x80 << ((i % 4) << 3);
7063             if (i > 55) {
7064                 md5cycle(state, tail);
7065                 for (i = 0; i < 16; i += 1) {
7066                     tail[i] = 0;
7067                 }
7068             }
7069     
7070             // Beware that the final length might not fit in 32 bits so we take care of that
7071             tmp = n * 8;
7072             tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
7073             lo = parseInt(tmp[2], 16);
7074             hi = parseInt(tmp[1], 16) || 0;
7075     
7076             tail[14] = lo;
7077             tail[15] = hi;
7078     
7079             md5cycle(state, tail);
7080             return state;
7081         },
7082     
7083         md51_array = function (a) {
7084             var n = a.length,
7085                 state = [1732584193, -271733879, -1732584194, 271733878],
7086                 i,
7087                 length,
7088                 tail,
7089                 tmp,
7090                 lo,
7091                 hi;
7092     
7093             for (i = 64; i <= n; i += 64) {
7094                 md5cycle(state, md5blk_array(a.subarray(i - 64, i)));
7095             }
7096     
7097             // Not sure if it is a bug, however IE10 will always produce a sub array of length 1
7098             // containing the last element of the parent array if the sub array specified starts
7099             // beyond the length of the parent array - weird.
7100             // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue
7101             a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0);
7102     
7103             length = a.length;
7104             tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
7105             for (i = 0; i < length; i += 1) {
7106                 tail[i >> 2] |= a[i] << ((i % 4) << 3);
7107             }
7108     
7109             tail[i >> 2] |= 0x80 << ((i % 4) << 3);
7110             if (i > 55) {
7111                 md5cycle(state, tail);
7112                 for (i = 0; i < 16; i += 1) {
7113                     tail[i] = 0;
7114                 }
7115             }
7116     
7117             // Beware that the final length might not fit in 32 bits so we take care of that
7118             tmp = n * 8;
7119             tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
7120             lo = parseInt(tmp[2], 16);
7121             hi = parseInt(tmp[1], 16) || 0;
7122     
7123             tail[14] = lo;
7124             tail[15] = hi;
7125     
7126             md5cycle(state, tail);
7127     
7128             return state;
7129         },
7130     
7131         hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'],
7132     
7133         rhex = function (n) {
7134             var s = '',
7135                 j;
7136             for (j = 0; j < 4; j += 1) {
7137                 s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];
7138             }
7139             return s;
7140         },
7141     
7142         hex = function (x) {
7143             var i;
7144             for (i = 0; i < x.length; i += 1) {
7145                 x[i] = rhex(x[i]);
7146             }
7147             return x.join('');
7148         },
7149     
7150         md5 = function (s) {
7151             return hex(md51(s));
7152         },
7153     
7154     
7155     
7156         ////////////////////////////////////////////////////////////////////////////
7157     
7158         /**
7159          * SparkMD5 OOP implementation.
7160          *
7161          * Use this class to perform an incremental md5, otherwise use the
7162          * static methods instead.
7163          */
7164         SparkMD5 = function () {
7165             // call reset to init the instance
7166             this.reset();
7167         };
7168     
7169     
7170         // In some cases the fast add32 function cannot be used..
7171         if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') {
7172             add32 = function (x, y) {
7173                 var lsw = (x & 0xFFFF) + (y & 0xFFFF),
7174                     msw = (x >> 16) + (y >> 16) + (lsw >> 16);
7175                 return (msw << 16) | (lsw & 0xFFFF);
7176             };
7177         }
7178     
7179     
7180         /**
7181          * Appends a string.
7182          * A conversion will be applied if an utf8 string is detected.
7183          *
7184          * @param {String} str The string to be appended
7185          *
7186          * @return {SparkMD5} The instance itself
7187          */
7188         SparkMD5.prototype.append = function (str) {
7189             // converts the string to utf8 bytes if necessary
7190             if (/[\u0080-\uFFFF]/.test(str)) {
7191                 str = unescape(encodeURIComponent(str));
7192             }
7193     
7194             // then append as binary
7195             this.appendBinary(str);
7196     
7197             return this;
7198         };
7199     
7200         /**
7201          * Appends a binary string.
7202          *
7203          * @param {String} contents The binary string to be appended
7204          *
7205          * @return {SparkMD5} The instance itself
7206          */
7207         SparkMD5.prototype.appendBinary = function (contents) {
7208             this._buff += contents;
7209             this._length += contents.length;
7210     
7211             var length = this._buff.length,
7212                 i;
7213     
7214             for (i = 64; i <= length; i += 64) {
7215                 md5cycle(this._state, md5blk(this._buff.substring(i - 64, i)));
7216             }
7217     
7218             this._buff = this._buff.substr(i - 64);
7219     
7220             return this;
7221         };
7222     
7223         /**
7224          * Finishes the incremental computation, reseting the internal state and
7225          * returning the result.
7226          * Use the raw parameter to obtain the raw result instead of the hex one.
7227          *
7228          * @param {Boolean} raw True to get the raw result, false to get the hex result
7229          *
7230          * @return {String|Array} The result
7231          */
7232         SparkMD5.prototype.end = function (raw) {
7233             var buff = this._buff,
7234                 length = buff.length,
7235                 i,
7236                 tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
7237                 ret;
7238     
7239             for (i = 0; i < length; i += 1) {
7240                 tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3);
7241             }
7242     
7243             this._finish(tail, length);
7244             ret = !!raw ? this._state : hex(this._state);
7245     
7246             this.reset();
7247     
7248             return ret;
7249         };
7250     
7251         /**
7252          * Finish the final calculation based on the tail.
7253          *
7254          * @param {Array}  tail   The tail (will be modified)
7255          * @param {Number} length The length of the remaining buffer
7256          */
7257         SparkMD5.prototype._finish = function (tail, length) {
7258             var i = length,
7259                 tmp,
7260                 lo,
7261                 hi;
7262     
7263             tail[i >> 2] |= 0x80 << ((i % 4) << 3);
7264             if (i > 55) {
7265                 md5cycle(this._state, tail);
7266                 for (i = 0; i < 16; i += 1) {
7267                     tail[i] = 0;
7268                 }
7269             }
7270     
7271             // Do the final computation based on the tail and length
7272             // Beware that the final length may not fit in 32 bits so we take care of that
7273             tmp = this._length * 8;
7274             tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
7275             lo = parseInt(tmp[2], 16);
7276             hi = parseInt(tmp[1], 16) || 0;
7277     
7278             tail[14] = lo;
7279             tail[15] = hi;
7280             md5cycle(this._state, tail);
7281         };
7282     
7283         /**
7284          * Resets the internal state of the computation.
7285          *
7286          * @return {SparkMD5} The instance itself
7287          */
7288         SparkMD5.prototype.reset = function () {
7289             this._buff = "";
7290             this._length = 0;
7291             this._state = [1732584193, -271733879, -1732584194, 271733878];
7292     
7293             return this;
7294         };
7295     
7296         /**
7297          * Releases memory used by the incremental buffer and other aditional
7298          * resources. If you plan to use the instance again, use reset instead.
7299          */
7300         SparkMD5.prototype.destroy = function () {
7301             delete this._state;
7302             delete this._buff;
7303             delete this._length;
7304         };
7305     
7306     
7307         /**
7308          * Performs the md5 hash on a string.
7309          * A conversion will be applied if utf8 string is detected.
7310          *
7311          * @param {String}  str The string
7312          * @param {Boolean} raw True to get the raw result, false to get the hex result
7313          *
7314          * @return {String|Array} The result
7315          */
7316         SparkMD5.hash = function (str, raw) {
7317             // converts the string to utf8 bytes if necessary
7318             if (/[\u0080-\uFFFF]/.test(str)) {
7319                 str = unescape(encodeURIComponent(str));
7320             }
7321     
7322             var hash = md51(str);
7323     
7324             return !!raw ? hash : hex(hash);
7325         };
7326     
7327         /**
7328          * Performs the md5 hash on a binary string.
7329          *
7330          * @param {String}  content The binary string
7331          * @param {Boolean} raw     True to get the raw result, false to get the hex result
7332          *
7333          * @return {String|Array} The result
7334          */
7335         SparkMD5.hashBinary = function (content, raw) {
7336             var hash = md51(content);
7337     
7338             return !!raw ? hash : hex(hash);
7339         };
7340     
7341         /**
7342          * SparkMD5 OOP implementation for array buffers.
7343          *
7344          * Use this class to perform an incremental md5 ONLY for array buffers.
7345          */
7346         SparkMD5.ArrayBuffer = function () {
7347             // call reset to init the instance
7348             this.reset();
7349         };
7350     
7351         ////////////////////////////////////////////////////////////////////////////
7352     
7353         /**
7354          * Appends an array buffer.
7355          *
7356          * @param {ArrayBuffer} arr The array to be appended
7357          *
7358          * @return {SparkMD5.ArrayBuffer} The instance itself
7359          */
7360         SparkMD5.ArrayBuffer.prototype.append = function (arr) {
7361             // TODO: we could avoid the concatenation here but the algorithm would be more complex
7362             //       if you find yourself needing extra performance, please make a PR.
7363             var buff = this._concatArrayBuffer(this._buff, arr),
7364                 length = buff.length,
7365                 i;
7366     
7367             this._length += arr.byteLength;
7368     
7369             for (i = 64; i <= length; i += 64) {
7370                 md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i)));
7371             }
7372     
7373             // Avoids IE10 weirdness (documented above)
7374             this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0);
7375     
7376             return this;
7377         };
7378     
7379         /**
7380          * Finishes the incremental computation, reseting the internal state and
7381          * returning the result.
7382          * Use the raw parameter to obtain the raw result instead of the hex one.
7383          *
7384          * @param {Boolean} raw True to get the raw result, false to get the hex result
7385          *
7386          * @return {String|Array} The result
7387          */
7388         SparkMD5.ArrayBuffer.prototype.end = function (raw) {
7389             var buff = this._buff,
7390                 length = buff.length,
7391                 tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
7392                 i,
7393                 ret;
7394     
7395             for (i = 0; i < length; i += 1) {
7396                 tail[i >> 2] |= buff[i] << ((i % 4) << 3);
7397             }
7398     
7399             this._finish(tail, length);
7400             ret = !!raw ? this._state : hex(this._state);
7401     
7402             this.reset();
7403     
7404             return ret;
7405         };
7406     
7407         SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;
7408     
7409         /**
7410          * Resets the internal state of the computation.
7411          *
7412          * @return {SparkMD5.ArrayBuffer} The instance itself
7413          */
7414         SparkMD5.ArrayBuffer.prototype.reset = function () {
7415             this._buff = new Uint8Array(0);
7416             this._length = 0;
7417             this._state = [1732584193, -271733879, -1732584194, 271733878];
7418     
7419             return this;
7420         };
7421     
7422         /**
7423          * Releases memory used by the incremental buffer and other aditional
7424          * resources. If you plan to use the instance again, use reset instead.
7425          */
7426         SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;
7427     
7428         /**
7429          * Concats two array buffers, returning a new one.
7430          *
7431          * @param  {ArrayBuffer} first  The first array buffer
7432          * @param  {ArrayBuffer} second The second array buffer
7433          *
7434          * @return {ArrayBuffer} The new array buffer
7435          */
7436         SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) {
7437             var firstLength = first.length,
7438                 result = new Uint8Array(firstLength + second.byteLength);
7439     
7440             result.set(first);
7441             result.set(new Uint8Array(second), firstLength);
7442     
7443             return result;
7444         };
7445     
7446         /**
7447          * Performs the md5 hash on an array buffer.
7448          *
7449          * @param {ArrayBuffer} arr The array buffer
7450          * @param {Boolean}     raw True to get the raw result, false to get the hex result
7451          *
7452          * @return {String|Array} The result
7453          */
7454         SparkMD5.ArrayBuffer.hash = function (arr, raw) {
7455             var hash = md51_array(new Uint8Array(arr));
7456     
7457             return !!raw ? hash : hex(hash);
7458         };
7459         
7460         return FlashRuntime.register( 'Md5', {
7461             init: function() {
7462                 // do nothing.
7463             },
7464     
7465             loadFromBlob: function( file ) {
7466                 var blob = file.getSource(),
7467                     chunkSize = 2 * 1024 * 1024,
7468                     chunks = Math.ceil( blob.size / chunkSize ),
7469                     chunk = 0,
7470                     owner = this.owner,
7471                     spark = new SparkMD5.ArrayBuffer(),
7472                     me = this,
7473                     blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice,
7474                     loadNext, fr;
7475     
7476                 fr = new FileReader();
7477     
7478                 loadNext = function() {
7479                     var start, end;
7480     
7481                     start = chunk * chunkSize;
7482                     end = Math.min( start + chunkSize, blob.size );
7483     
7484                     fr.onload = function( e ) {
7485                         spark.append( e.target.result );
7486                         owner.trigger( 'progress', {
7487                             total: file.size,
7488                             loaded: end
7489                         });
7490                     };
7491     
7492                     fr.onloadend = function() {
7493                         fr.onloadend = fr.onload = null;
7494     
7495                         if ( ++chunk < chunks ) {
7496                             setTimeout( loadNext, 1 );
7497                         } else {
7498                             setTimeout(function(){
7499                                 owner.trigger('load');
7500                                 me.result = spark.end();
7501                                 loadNext = file = blob = spark = null;
7502                                 owner.trigger('complete');
7503                             }, 50 );
7504                         }
7505                     };
7506     
7507                     fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) );
7508                 };
7509     
7510                 loadNext();
7511             },
7512     
7513             getResult: function() {
7514                 return this.result;
7515             }
7516         });
7517     });
7518     /**
7519      * @fileOverview FlashRuntime
7520      */
7521     define('runtime/flash/runtime',[
7522         'base',
7523         'runtime/runtime',
7524         'runtime/compbase'
7525     ], function( Base, Runtime, CompBase ) {
7526     
7527         var $ = Base.$,
7528             type = 'flash',
7529             components = {};
7530     
7531     
7532         function getFlashVersion() {
7533             var version;
7534     
7535             try {
7536                 version = navigator.plugins[ 'Shockwave Flash' ];
7537                 version = version.description;
7538             } catch ( ex ) {
7539                 try {
7540                     version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
7541                             .GetVariable('$version');
7542                 } catch ( ex2 ) {
7543                     version = '0.0';
7544                 }
7545             }
7546             version = version.match( /\d+/g );
7547             return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );
7548         }
7549     
7550         function FlashRuntime() {
7551             var pool = {},
7552                 clients = {},
7553                 destroy = this.destroy,
7554                 me = this,
7555                 jsreciver = Base.guid('webuploader_');
7556     
7557             Runtime.apply( me, arguments );
7558             me.type = type;
7559     
7560     
7561             // 这个方法的调用者,实际上是RuntimeClient
7562             me.exec = function( comp, fn/*, args...*/ ) {
7563                 var client = this,
7564                     uid = client.uid,
7565                     args = Base.slice( arguments, 2 ),
7566                     instance;
7567     
7568                 clients[ uid ] = client;
7569     
7570                 if ( components[ comp ] ) {
7571                     if ( !pool[ uid ] ) {
7572                         pool[ uid ] = new components[ comp ]( client, me );
7573                     }
7574     
7575                     instance = pool[ uid ];
7576     
7577                     if ( instance[ fn ] ) {
7578                         return instance[ fn ].apply( instance, args );
7579                     }
7580                 }
7581     
7582                 return me.flashExec.apply( client, arguments );
7583             };
7584     
7585             function handler( evt, obj ) {
7586                 var type = evt.type || evt,
7587                     parts, uid;
7588     
7589                 parts = type.split('::');
7590                 uid = parts[ 0 ];
7591                 type = parts[ 1 ];
7592     
7593                 // console.log.apply( console, arguments );
7594     
7595                 if ( type === 'Ready' && uid === me.uid ) {
7596                     me.trigger('ready');
7597                 } else if ( clients[ uid ] ) {
7598                     clients[ uid ].trigger( type.toLowerCase(), evt, obj );
7599                 }
7600     
7601                 // Base.log( evt, obj );
7602             }
7603     
7604             // flash的接受器。
7605             window[ jsreciver ] = function() {
7606                 var args = arguments;
7607     
7608                 // 为了能捕获得到。
7609                 setTimeout(function() {
7610                     handler.apply( null, args );
7611                 }, 1 );
7612             };
7613     
7614             this.jsreciver = jsreciver;
7615     
7616             this.destroy = function() {
7617                 // @todo 删除池子中的所有实例
7618                 return destroy && destroy.apply( this, arguments );
7619             };
7620     
7621             this.flashExec = function( comp, fn ) {
7622                 var flash = me.getFlash(),
7623                     args = Base.slice( arguments, 2 );
7624     
7625                 return flash.exec( this.uid, comp, fn, args );
7626             };
7627     
7628             // @todo
7629         }
7630     
7631         Base.inherits( Runtime, {
7632             constructor: FlashRuntime,
7633     
7634             init: function() {
7635                 var container = this.getContainer(),
7636                     opts = this.options,
7637                     html;
7638     
7639                 // if not the minimal height, shims are not initialized
7640                 // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
7641                 container.css({
7642                     position: 'absolute',
7643                     top: '-8px',
7644                     left: '-8px',
7645                     width: '9px',
7646                     height: '9px',
7647                     overflow: 'hidden'
7648                 });
7649     
7650                 // insert flash object
7651                 html = '<object id="' + this.uid + '" type="application/' +
7652                         'x-shockwave-flash" data="' +  opts.swf + '" ';
7653     
7654                 if ( Base.browser.ie ) {
7655                     html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
7656                 }
7657     
7658                 html += 'width="100%" height="100%" style="outline:0">'  +
7659                     '<param name="movie" value="' + opts.swf + '" />' +
7660                     '<param name="flashvars" value="uid=' + this.uid +
7661                     '&jsreciver=' + this.jsreciver + '" />' +
7662                     '<param name="wmode" value="transparent" />' +
7663                     '<param name="allowscriptaccess" value="always" />' +
7664                 '</object>';
7665     
7666                 container.html( html );
7667             },
7668     
7669             getFlash: function() {
7670                 if ( this._flash ) {
7671                     return this._flash;
7672                 }
7673     
7674                 this._flash = $( '#' + this.uid ).get( 0 );
7675                 return this._flash;
7676             }
7677     
7678         });
7679     
7680         FlashRuntime.register = function( name, component ) {
7681             component = components[ name ] = Base.inherits( CompBase, $.extend({
7682     
7683                 // @todo fix this later
7684                 flashExec: function() {
7685                     var owner = this.owner,
7686                         runtime = this.getRuntime();
7687     
7688                     return runtime.flashExec.apply( owner, arguments );
7689                 }
7690             }, component ) );
7691     
7692             return component;
7693         };
7694     
7695         if ( getFlashVersion() >= 11.4 ) {
7696             Runtime.addRuntime( type, FlashRuntime );
7697         }
7698     
7699         return FlashRuntime;
7700     });
7701     /**
7702      * @fileOverview FilePicker
7703      */
7704     define('runtime/flash/filepicker',[
7705         'base',
7706         'runtime/flash/runtime'
7707     ], function( Base, FlashRuntime ) {
7708         var $ = Base.$;
7709     
7710         return FlashRuntime.register( 'FilePicker', {
7711             init: function( opts ) {
7712                 var copy = $.extend({}, opts ),
7713                     len, i;
7714     
7715                 // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.
7716                 len = copy.accept && copy.accept.length;
7717                 for (  i = 0; i < len; i++ ) {
7718                     if ( !copy.accept[ i ].title ) {
7719                         copy.accept[ i ].title = 'Files';
7720                     }
7721                 }
7722     
7723                 delete copy.button;
7724                 delete copy.id;
7725                 delete copy.container;
7726     
7727                 this.flashExec( 'FilePicker', 'init', copy );
7728             },
7729     
7730             destroy: function() {
7731                 this.flashExec( 'FilePicker', 'destroy' );
7732             }
7733         });
7734     });
7735     /**
7736      * @fileOverview 图片压缩
7737      */
7738     define('runtime/flash/image',[
7739         'runtime/flash/runtime'
7740     ], function( FlashRuntime ) {
7741     
7742         return FlashRuntime.register( 'Image', {
7743             // init: function( options ) {
7744             //     var owner = this.owner;
7745     
7746             //     this.flashExec( 'Image', 'init', options );
7747             //     owner.on( 'load', function() {
7748             //         debugger;
7749             //     });
7750             // },
7751     
7752             loadFromBlob: function( blob ) {
7753                 var owner = this.owner;
7754     
7755                 owner.info() && this.flashExec( 'Image', 'info', owner.info() );
7756                 owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() );
7757     
7758                 this.flashExec( 'Image', 'loadFromBlob', blob.uid );
7759             }
7760         });
7761     });
7762     /**
7763      * @fileOverview  Transport flash实现
7764      */
7765     define('runtime/flash/transport',[
7766         'base',
7767         'runtime/flash/runtime',
7768         'runtime/client'
7769     ], function( Base, FlashRuntime, RuntimeClient ) {
7770         var $ = Base.$;
7771     
7772         return FlashRuntime.register( 'Transport', {
7773             init: function() {
7774                 this._status = 0;
7775                 this._response = null;
7776                 this._responseJson = null;
7777             },
7778     
7779             send: function() {
7780                 var owner = this.owner,
7781                     opts = this.options,
7782                     xhr = this._initAjax(),
7783                     blob = owner._blob,
7784                     server = opts.server,
7785                     binary;
7786     
7787                 xhr.connectRuntime( blob.ruid );
7788     
7789                 if ( opts.sendAsBinary ) {
7790                     server += (/\?/.test( server ) ? '&' : '?') +
7791                             $.param( owner._formData );
7792     
7793                     binary = blob.uid;
7794                 } else {
7795                     $.each( owner._formData, function( k, v ) {
7796                         xhr.exec( 'append', k, v );
7797                     });
7798     
7799                     xhr.exec( 'appendBlob', opts.fileVal, blob.uid,
7800                             opts.filename || owner._formData.name || '' );
7801                 }
7802     
7803                 this._setRequestHeader( xhr, opts.headers );
7804                 xhr.exec( 'send', {
7805                     method: opts.method,
7806                     url: server,
7807                     forceURLStream: opts.forceURLStream,
7808                     mimeType: 'application/octet-stream'
7809                 }, binary );
7810             },
7811     
7812             getStatus: function() {
7813                 return this._status;
7814             },
7815     
7816             getResponse: function() {
7817                 return this._response || '';
7818             },
7819     
7820             getResponseAsJson: function() {
7821                 return this._responseJson;
7822             },
7823     
7824             abort: function() {
7825                 var xhr = this._xhr;
7826     
7827                 if ( xhr ) {
7828                     xhr.exec('abort');
7829                     xhr.destroy();
7830                     this._xhr = xhr = null;
7831                 }
7832             },
7833     
7834             destroy: function() {
7835                 this.abort();
7836             },
7837     
7838             _initAjax: function() {
7839                 var me = this,
7840                     xhr = new RuntimeClient('XMLHttpRequest');
7841     
7842                 xhr.on( 'uploadprogress progress', function( e ) {
7843                     var percent = e.loaded / e.total;
7844                     percent = Math.min( 1, Math.max( 0, percent ) );
7845                     return me.trigger( 'progress', percent );
7846                 });
7847     
7848                 xhr.on( 'load', function() {
7849                     var status = xhr.exec('getStatus'),
7850                         readBody = false,
7851                         err = '',
7852                         p;
7853     
7854                     xhr.off();
7855                     me._xhr = null;
7856     
7857                     if ( status >= 200 && status < 300 ) {
7858                         readBody = true;
7859                     } else if ( status >= 500 && status < 600 ) {
7860                         readBody = true;
7861                         err = 'server';
7862                     } else {
7863                         err = 'http';
7864                     }
7865     
7866                     if ( readBody ) {
7867                         me._response = xhr.exec('getResponse');
7868                         me._response = decodeURIComponent( me._response );
7869     
7870                         // flash 处理可能存在 bug, 没辙只能靠 js 了
7871                         // try {
7872                         //     me._responseJson = xhr.exec('getResponseAsJson');
7873                         // } catch ( error ) {
7874                             
7875                         p = window.JSON && window.JSON.parse || function( s ) {
7876                             try {
7877                                 return new Function('return ' + s).call();
7878                             } catch ( err ) {
7879                                 return {};
7880                             }
7881                         };
7882                         me._responseJson  = me._response ? p(me._response) : {};
7883                             
7884                         // }
7885                     }
7886                     
7887                     xhr.destroy();
7888                     xhr = null;
7889     
7890                     return err ? me.trigger( 'error', err ) : me.trigger('load');
7891                 });
7892     
7893                 xhr.on( 'error', function() {
7894                     xhr.off();
7895                     me._xhr = null;
7896                     me.trigger( 'error', 'http' );
7897                 });
7898     
7899                 me._xhr = xhr;
7900                 return xhr;
7901             },
7902     
7903             _setRequestHeader: function( xhr, headers ) {
7904                 $.each( headers, function( key, val ) {
7905                     xhr.exec( 'setRequestHeader', key, val );
7906                 });
7907             }
7908         });
7909     });
7910     /**
7911      * @fileOverview Blob Html实现
7912      */
7913     define('runtime/flash/blob',[
7914         'runtime/flash/runtime',
7915         'lib/blob'
7916     ], function( FlashRuntime, Blob ) {
7917     
7918         return FlashRuntime.register( 'Blob', {
7919             slice: function( start, end ) {
7920                 var blob = this.flashExec( 'Blob', 'slice', start, end );
7921     
7922                 return new Blob( blob.uid, blob );
7923             }
7924         });
7925     });
7926     /**
7927      * @fileOverview  Md5 flash实现
7928      */
7929     define('runtime/flash/md5',[
7930         'runtime/flash/runtime'
7931     ], function( FlashRuntime ) {
7932         
7933         return FlashRuntime.register( 'Md5', {
7934             init: function() {
7935                 // do nothing.
7936             },
7937     
7938             loadFromBlob: function( blob ) {
7939                 return this.flashExec( 'Md5', 'loadFromBlob', blob.uid );
7940             }
7941         });
7942     });
7943     /**
7944      * @fileOverview 完全版本。
7945      */
7946     define('preset/all',[
7947         'base',
7948     
7949         // widgets
7950         'widgets/filednd',
7951         'widgets/filepaste',
7952         'widgets/filepicker',
7953         'widgets/image',
7954         'widgets/queue',
7955         'widgets/runtime',
7956         'widgets/upload',
7957         'widgets/validator',
7958         'widgets/md5',
7959     
7960         // runtimes
7961         // html5
7962         'runtime/html5/blob',
7963         'runtime/html5/dnd',
7964         'runtime/html5/filepaste',
7965         'runtime/html5/filepicker',
7966         'runtime/html5/imagemeta/exif',
7967         'runtime/html5/androidpatch',
7968         'runtime/html5/image',
7969         'runtime/html5/transport',
7970         'runtime/html5/md5',
7971     
7972         // flash
7973         'runtime/flash/filepicker',
7974         'runtime/flash/image',
7975         'runtime/flash/transport',
7976         'runtime/flash/blob',
7977         'runtime/flash/md5'
7978     ], function( Base ) {
7979         return Base;
7980     });
7981     /**
7982      * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。
7983      *
7984      * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。
7985      *
7986      * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。
7987      *
7988      * 如:
7989      * WebUploader.create({
7990      *     ...
7991      *
7992      *     disableWidgets: 'log',
7993      *
7994      *     ...
7995      * })
7996      */
7997     define('widgets/log',[
7998         'base',
7999         'uploader',
8000         'widgets/widget'
8001     ], function( Base, Uploader ) {
8002         var $ = Base.$,
8003             logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??',
8004             product = (location.hostname || location.host || 'protected').toLowerCase(),
8005     
8006             // 只针对 baidu 内部产品用户做统计功能。
8007             enable = product && /baidu/i.exec(product),
8008             base;
8009     
8010         if (!enable) {
8011             return;
8012         }
8013     
8014         base = {
8015             dv: 3,
8016             master: 'webuploader',
8017             online: /test/.exec(product) ? 0 : 1,
8018             module: '',
8019             product: product,
8020             type: 0
8021         };
8022     
8023         function send(data) {
8024             var obj = $.extend({}, base, data),
8025                 url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )),
8026                 image = new Image();
8027     
8028             image.src = url;
8029         }
8030     
8031         return Uploader.register({
8032             name: 'log',
8033     
8034             init: function() {
8035                 var owner = this.owner,
8036                     count = 0,
8037                     size = 0;
8038     
8039                 owner
8040                     .on('error', function(code) {
8041                         send({
8042                             type: 2,
8043                             c_error_code: code
8044                         });
8045                     })
8046                     .on('uploadError', function(file, reason) {
8047                         send({
8048                             type: 2,
8049                             c_error_code: 'UPLOAD_ERROR',
8050                             c_reason: '' + reason
8051                         });
8052                     })
8053                     .on('uploadComplete', function(file) {
8054                         count++;
8055                         size += file.size;
8056                     }).
8057                     on('uploadFinished', function() {
8058                         send({
8059                             c_count: count,
8060                             c_size: size
8061                         });
8062                         count = size = 0;
8063                     });
8064     
8065                 send({
8066                     c_usage: 1
8067                 });
8068             }
8069         });
8070     });
8071     /**
8072      * @fileOverview Uploader上传类
8073      */
8074     define('webuploader',[
8075         'preset/all',
8076         'widgets/log'
8077     ], function( preset ) {
8078         return preset;
8079     });
8080
8081     var _require = require;
8082     return _require('webuploader');
8083 });