Administrator
2023-04-21 195945efc5db921a4c9eb8cf9421c172273293f5
提交 | 用户 | 时间
58d006 1 /*! WebUploader 0.1.5 */
A 2
3
4 /**
5  * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。
6  *
7  * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。
8  */
9 (function( root, factory ) {
10     var modules = {},
11
12         // 内部require, 简单不完全实现。
13         // https://github.com/amdjs/amdjs-api/wiki/require
14         _require = function( deps, callback ) {
15             var args, len, i;
16
17             // 如果deps不是数组,则直接返回指定module
18             if ( typeof deps === 'string' ) {
19                 return getModule( deps );
20             } else {
21                 args = [];
22                 for( len = deps.length, i = 0; i < len; i++ ) {
23                     args.push( getModule( deps[ i ] ) );
24                 }
25
26                 return callback.apply( null, args );
27             }
28         },
29
30         // 内部define,暂时不支持不指定id.
31         _define = function( id, deps, factory ) {
32             if ( arguments.length === 2 ) {
33                 factory = deps;
34                 deps = null;
35             }
36
37             _require( deps || [], function() {
38                 setModule( id, factory, arguments );
39             });
40         },
41
42         // 设置module, 兼容CommonJs写法。
43         setModule = function( id, factory, args ) {
44             var module = {
45                     exports: factory
46                 },
47                 returned;
48
49             if ( typeof factory === 'function' ) {
50                 args.length || (args = [ _require, module.exports, module ]);
51                 returned = factory.apply( null, args );
52                 returned !== undefined && (module.exports = returned);
53             }
54
55             modules[ id ] = module.exports;
56         },
57
58         // 根据id获取module
59         getModule = function( id ) {
60             var module = modules[ id ] || root[ id ];
61
62             if ( !module ) {
63                 throw new Error( '`' + id + '` is undefined' );
64             }
65
66             return module;
67         },
68
69         // 将所有modules,将路径ids装换成对象。
70         exportsTo = function( obj ) {
71             var key, host, parts, part, last, ucFirst;
72
73             // make the first character upper case.
74             ucFirst = function( str ) {
75                 return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));
76             };
77
78             for ( key in modules ) {
79                 host = obj;
80
81                 if ( !modules.hasOwnProperty( key ) ) {
82                     continue;
83                 }
84
85                 parts = key.split('/');
86                 last = ucFirst( parts.pop() );
87
88                 while( (part = ucFirst( parts.shift() )) ) {
89                     host[ part ] = host[ part ] || {};
90                     host = host[ part ];
91                 }
92
93                 host[ last ] = modules[ key ];
94             }
95
96             return obj;
97         },
98
99         makeExport = function( dollar ) {
100             root.__dollar = dollar;
101
102             // exports every module.
103             return exportsTo( factory( root, _define, _require ) );
104         },
105
106         origin;
107
108     if ( typeof module === 'object' && typeof module.exports === 'object' ) {
109
110         // For CommonJS and CommonJS-like environments where a proper window is present,
111         module.exports = makeExport();
112     } else if ( typeof define === 'function' && define.amd ) {
113
114         // Allow using this built library as an AMD module
115         // in another project. That other project will only
116         // see this AMD call, not the internal modules in
117         // the closure below.
118         define([ 'jquery' ], makeExport );
119     } else {
120
121         // Browser globals case. Just assign the
122         // result to a property on the global.
123         origin = root.WebUploader;
124         root.WebUploader = makeExport();
125         root.WebUploader.noConflict = function() {
126             root.WebUploader = origin;
127         };
128     }
129 })( window, function( window, define, require ) {
130
131
132     /**
133      * @fileOverview jQuery or Zepto
134      */
135     define('dollar-third',[],function() {
136         var $ = window.__dollar || window.jQuery || window.Zepto;
137     
138         if ( !$ ) {
139             throw new Error('jQuery or Zepto not found!');
140         }
141     
142         return $;
143     });
144     /**
145      * @fileOverview Dom 操作相关
146      */
147     define('dollar',[
148         'dollar-third'
149     ], function( _ ) {
150         return _;
151     });
152     /**
153      * @fileOverview 使用jQuery的Promise
154      */
155     define('promise-third',[
156         'dollar'
157     ], function( $ ) {
158         return {
159             Deferred: $.Deferred,
160             when: $.when,
161     
162             isPromise: function( anything ) {
163                 return anything && typeof anything.then === 'function';
164             }
165         };
166     });
167     /**
168      * @fileOverview Promise/A+
169      */
170     define('promise',[
171         'promise-third'
172     ], function( _ ) {
173         return _;
174     });
175     /**
176      * @fileOverview 基础类方法。
177      */
178     
179     /**
180      * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。
181      *
182      * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.
183      * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如:
184      *
185      * * module `base`:WebUploader.Base
186      * * module `file`: WebUploader.File
187      * * module `lib/dnd`: WebUploader.Lib.Dnd
188      * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd
189      *
190      *
191      * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。
192      * @module WebUploader
193      * @title WebUploader API文档
194      */
195     define('base',[
196         'dollar',
197         'promise'
198     ], function( $, promise ) {
199     
200         var noop = function() {},
201             call = Function.call;
202     
203         // http://jsperf.com/uncurrythis
204         // 反科里化
205         function uncurryThis( fn ) {
206             return function() {
207                 return call.apply( fn, arguments );
208             };
209         }
210     
211         function bindFn( fn, context ) {
212             return function() {
213                 return fn.apply( context, arguments );
214             };
215         }
216     
217         function createObject( proto ) {
218             var f;
219     
220             if ( Object.create ) {
221                 return Object.create( proto );
222             } else {
223                 f = function() {};
224                 f.prototype = proto;
225                 return new f();
226             }
227         }
228     
229     
230         /**
231          * 基础类,提供一些简单常用的方法。
232          * @class Base
233          */
234         return {
235     
236             /**
237              * @property {String} version 当前版本号。
238              */
239             version: '0.1.5',
240     
241             /**
242              * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。
243              */
244             $: $,
245     
246             Deferred: promise.Deferred,
247     
248             isPromise: promise.isPromise,
249     
250             when: promise.when,
251     
252             /**
253              * @description  简单的浏览器检查结果。
254              *
255              * * `webkit`  webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。
256              * * `chrome`  chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。
257              * * `ie`  ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+**
258              * * `firefox`  firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。
259              * * `safari`  safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。
260              * * `opera`  opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。
261              *
262              * @property {Object} [browser]
263              */
264             browser: (function( ua ) {
265                 var ret = {},
266                     webkit = ua.match( /WebKit\/([\d.]+)/ ),
267                     chrome = ua.match( /Chrome\/([\d.]+)/ ) ||
268                         ua.match( /CriOS\/([\d.]+)/ ),
269     
270                     ie = ua.match( /MSIE\s([\d\.]+)/ ) ||
271                         ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ),
272                     firefox = ua.match( /Firefox\/([\d.]+)/ ),
273                     safari = ua.match( /Safari\/([\d.]+)/ ),
274                     opera = ua.match( /OPR\/([\d.]+)/ );
275     
276                 webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));
277                 chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));
278                 ie && (ret.ie = parseFloat( ie[ 1 ] ));
279                 firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));
280                 safari && (ret.safari = parseFloat( safari[ 1 ] ));
281                 opera && (ret.opera = parseFloat( opera[ 1 ] ));
282     
283                 return ret;
284             })( navigator.userAgent ),
285     
286             /**
287              * @description  操作系统检查结果。
288              *
289              * * `android`  如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。
290              * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。
291              * @property {Object} [os]
292              */
293             os: (function( ua ) {
294                 var ret = {},
295     
296                     // osx = !!ua.match( /\(Macintosh\; Intel / ),
297                     android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ),
298                     ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ );
299     
300                 // osx && (ret.osx = true);
301                 android && (ret.android = parseFloat( android[ 1 ] ));
302                 ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));
303     
304                 return ret;
305             })( navigator.userAgent ),
306     
307             /**
308              * 实现类与类之间的继承。
309              * @method inherits
310              * @grammar Base.inherits( super ) => child
311              * @grammar Base.inherits( super, protos ) => child
312              * @grammar Base.inherits( super, protos, statics ) => child
313              * @param  {Class} super 父类
314              * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。
315              * @param  {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。
316              * @param  {Object} [statics] 静态属性或方法。
317              * @return {Class} 返回子类。
318              * @example
319              * function Person() {
320              *     console.log( 'Super' );
321              * }
322              * Person.prototype.hello = function() {
323              *     console.log( 'hello' );
324              * };
325              *
326              * var Manager = Base.inherits( Person, {
327              *     world: function() {
328              *         console.log( 'World' );
329              *     }
330              * });
331              *
332              * // 因为没有指定构造器,父类的构造器将会执行。
333              * var instance = new Manager();    // => Super
334              *
335              * // 继承子父类的方法
336              * instance.hello();    // => hello
337              * instance.world();    // => World
338              *
339              * // 子类的__super__属性指向父类
340              * console.log( Manager.__super__ === Person );    // => true
341              */
342             inherits: function( Super, protos, staticProtos ) {
343                 var child;
344     
345                 if ( typeof protos === 'function' ) {
346                     child = protos;
347                     protos = null;
348                 } else if ( protos && protos.hasOwnProperty('constructor') ) {
349                     child = protos.constructor;
350                 } else {
351                     child = function() {
352                         return Super.apply( this, arguments );
353                     };
354                 }
355     
356                 // 复制静态方法
357                 $.extend( true, child, Super, staticProtos || {} );
358     
359                 /* jshint camelcase: false */
360     
361                 // 让子类的__super__属性指向父类。
362                 child.__super__ = Super.prototype;
363     
364                 // 构建原型,添加原型方法或属性。
365                 // 暂时用Object.create实现。
366                 child.prototype = createObject( Super.prototype );
367                 protos && $.extend( true, child.prototype, protos );
368     
369                 return child;
370             },
371     
372             /**
373              * 一个不做任何事情的方法。可以用来赋值给默认的callback.
374              * @method noop
375              */
376             noop: noop,
377     
378             /**
379              * 返回一个新的方法,此方法将已指定的`context`来执行。
380              * @grammar Base.bindFn( fn, context ) => Function
381              * @method bindFn
382              * @example
383              * var doSomething = function() {
384              *         console.log( this.name );
385              *     },
386              *     obj = {
387              *         name: 'Object Name'
388              *     },
389              *     aliasFn = Base.bind( doSomething, obj );
390              *
391              *  aliasFn();    // => Object Name
392              *
393              */
394             bindFn: bindFn,
395     
396             /**
397              * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。
398              * @grammar Base.log( args... ) => undefined
399              * @method log
400              */
401             log: (function() {
402                 if ( window.console ) {
403                     return bindFn( console.log, console );
404                 }
405                 return noop;
406             })(),
407     
408             nextTick: (function() {
409     
410                 return function( cb ) {
411                     setTimeout( cb, 1 );
412                 };
413     
414                 // @bug 当浏览器不在当前窗口时就停了。
415                 // var next = window.requestAnimationFrame ||
416                 //     window.webkitRequestAnimationFrame ||
417                 //     window.mozRequestAnimationFrame ||
418                 //     function( cb ) {
419                 //         window.setTimeout( cb, 1000 / 60 );
420                 //     };
421     
422                 // // fix: Uncaught TypeError: Illegal invocation
423                 // return bindFn( next, window );
424             })(),
425     
426             /**
427              * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。
428              * 将用来将非数组对象转化成数组对象。
429              * @grammar Base.slice( target, start[, end] ) => Array
430              * @method slice
431              * @example
432              * function doSomthing() {
433              *     var args = Base.slice( arguments, 1 );
434              *     console.log( args );
435              * }
436              *
437              * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array ["arg2", "arg3"]
438              */
439             slice: uncurryThis( [].slice ),
440     
441             /**
442              * 生成唯一的ID
443              * @method guid
444              * @grammar Base.guid() => String
445              * @grammar Base.guid( prefx ) => String
446              */
447             guid: (function() {
448                 var counter = 0;
449     
450                 return function( prefix ) {
451                     var guid = (+new Date()).toString( 32 ),
452                         i = 0;
453     
454                     for ( ; i < 5; i++ ) {
455                         guid += Math.floor( Math.random() * 65535 ).toString( 32 );
456                     }
457     
458                     return (prefix || 'wu_') + guid + (counter++).toString( 32 );
459                 };
460             })(),
461     
462             /**
463              * 格式化文件大小, 输出成带单位的字符串
464              * @method formatSize
465              * @grammar Base.formatSize( size ) => String
466              * @grammar Base.formatSize( size, pointLength ) => String
467              * @grammar Base.formatSize( size, pointLength, units ) => String
468              * @param {Number} size 文件大小
469              * @param {Number} [pointLength=2] 精确到的小数点数。
470              * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K.
471              * @example
472              * console.log( Base.formatSize( 100 ) );    // => 100B
473              * console.log( Base.formatSize( 1024 ) );    // => 1.00K
474              * console.log( Base.formatSize( 1024, 0 ) );    // => 1K
475              * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M
476              * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G
477              * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB
478              */
479             formatSize: function( size, pointLength, units ) {
480                 var unit;
481     
482                 units = units || [ 'B', 'K', 'M', 'G', 'TB' ];
483     
484                 while ( (unit = units.shift()) && size > 1024 ) {
485                     size = size / 1024;
486                 }
487     
488                 return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +
489                         unit;
490             }
491         };
492     });
493     /**
494      * 事件处理类,可以独立使用,也可以扩展给对象使用。
495      * @fileOverview Mediator
496      */
497     define('mediator',[
498         'base'
499     ], function( Base ) {
500         var $ = Base.$,
501             slice = [].slice,
502             separator = /\s+/,
503             protos;
504     
505         // 根据条件过滤出事件handlers.
506         function findHandlers( arr, name, callback, context ) {
507             return $.grep( arr, function( handler ) {
508                 return handler &&
509                         (!name || handler.e === name) &&
510                         (!callback || handler.cb === callback ||
511                         handler.cb._cb === callback) &&
512                         (!context || handler.ctx === context);
513             });
514         }
515     
516         function eachEvent( events, callback, iterator ) {
517             // 不支持对象,只支持多个event用空格隔开
518             $.each( (events || '').split( separator ), function( _, key ) {
519                 iterator( key, callback );
520             });
521         }
522     
523         function triggerHanders( events, args ) {
524             var stoped = false,
525                 i = -1,
526                 len = events.length,
527                 handler;
528     
529             while ( ++i < len ) {
530                 handler = events[ i ];
531     
532                 if ( handler.cb.apply( handler.ctx2, args ) === false ) {
533                     stoped = true;
534                     break;
535                 }
536             }
537     
538             return !stoped;
539         }
540     
541         protos = {
542     
543             /**
544              * 绑定事件。
545              *
546              * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如
547              * ```javascript
548              * var obj = {};
549              *
550              * // 使得obj有事件行为
551              * Mediator.installTo( obj );
552              *
553              * obj.on( 'testa', function( arg1, arg2 ) {
554              *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'
555              * });
556              *
557              * obj.trigger( 'testa', 'arg1', 'arg2' );
558              * ```
559              *
560              * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。
561              * 切会影响到`trigger`方法的返回值,为`false`。
562              *
563              * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处,
564              * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。
565              * ```javascript
566              * obj.on( 'all', function( type, arg1, arg2 ) {
567              *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'
568              * });
569              * ```
570              *
571              * @method on
572              * @grammar on( name, callback[, context] ) => self
573              * @param  {String}   name     事件名,支持多个事件用空格隔开
574              * @param  {Function} callback 事件处理器
575              * @param  {Object}   [context]  事件处理器的上下文。
576              * @return {self} 返回自身,方便链式
577              * @chainable
578              * @class Mediator
579              */
580             on: function( name, callback, context ) {
581                 var me = this,
582                     set;
583     
584                 if ( !callback ) {
585                     return this;
586                 }
587     
588                 set = this._events || (this._events = []);
589     
590                 eachEvent( name, callback, function( name, callback ) {
591                     var handler = { e: name };
592     
593                     handler.cb = callback;
594                     handler.ctx = context;
595                     handler.ctx2 = context || me;
596                     handler.id = set.length;
597     
598                     set.push( handler );
599                 });
600     
601                 return this;
602             },
603     
604             /**
605              * 绑定事件,且当handler执行完后,自动解除绑定。
606              * @method once
607              * @grammar once( name, callback[, context] ) => self
608              * @param  {String}   name     事件名
609              * @param  {Function} callback 事件处理器
610              * @param  {Object}   [context]  事件处理器的上下文。
611              * @return {self} 返回自身,方便链式
612              * @chainable
613              */
614             once: function( name, callback, context ) {
615                 var me = this;
616     
617                 if ( !callback ) {
618                     return me;
619                 }
620     
621                 eachEvent( name, callback, function( name, callback ) {
622                     var once = function() {
623                             me.off( name, once );
624                             return callback.apply( context || me, arguments );
625                         };
626     
627                     once._cb = callback;
628                     me.on( name, once, context );
629                 });
630     
631                 return me;
632             },
633     
634             /**
635              * 解除事件绑定
636              * @method off
637              * @grammar off( [name[, callback[, context] ] ] ) => self
638              * @param  {String}   [name]     事件名
639              * @param  {Function} [callback] 事件处理器
640              * @param  {Object}   [context]  事件处理器的上下文。
641              * @return {self} 返回自身,方便链式
642              * @chainable
643              */
644             off: function( name, cb, ctx ) {
645                 var events = this._events;
646     
647                 if ( !events ) {
648                     return this;
649                 }
650     
651                 if ( !name && !cb && !ctx ) {
652                     this._events = [];
653                     return this;
654                 }
655     
656                 eachEvent( name, cb, function( name, cb ) {
657                     $.each( findHandlers( events, name, cb, ctx ), function() {
658                         delete events[ this.id ];
659                     });
660                 });
661     
662                 return this;
663             },
664     
665             /**
666              * 触发事件
667              * @method trigger
668              * @grammar trigger( name[, args...] ) => self
669              * @param  {String}   type     事件名
670              * @param  {*} [...] 任意参数
671              * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true
672              */
673             trigger: function( type ) {
674                 var args, events, allEvents;
675     
676                 if ( !this._events || !type ) {
677                     return this;
678                 }
679     
680                 args = slice.call( arguments, 1 );
681                 events = findHandlers( this._events, type );
682                 allEvents = findHandlers( this._events, 'all' );
683     
684                 return triggerHanders( events, args ) &&
685                         triggerHanders( allEvents, arguments );
686             }
687         };
688     
689         /**
690          * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。
691          * 主要目的是负责模块与模块之间的合作,降低耦合度。
692          *
693          * @class Mediator
694          */
695         return $.extend({
696     
697             /**
698              * 可以通过这个接口,使任何对象具备事件功能。
699              * @method installTo
700              * @param  {Object} obj 需要具备事件行为的对象。
701              * @return {Object} 返回obj.
702              */
703             installTo: function( obj ) {
704                 return $.extend( obj, protos );
705             }
706     
707         }, protos );
708     });
709     /**
710      * @fileOverview Uploader上传类
711      */
712     define('uploader',[
713         'base',
714         'mediator'
715     ], function( Base, Mediator ) {
716     
717         var $ = Base.$;
718     
719         /**
720          * 上传入口类。
721          * @class Uploader
722          * @constructor
723          * @grammar new Uploader( opts ) => Uploader
724          * @example
725          * var uploader = WebUploader.Uploader({
726          *     swf: 'path_of_swf/Uploader.swf',
727          *
728          *     // 开起分片上传。
729          *     chunked: true
730          * });
731          */
732         function Uploader( opts ) {
733             this.options = $.extend( true, {}, Uploader.options, opts );
734             this._init( this.options );
735         }
736     
737         // default Options
738         // widgets中有相应扩展
739         Uploader.options = {};
740         Mediator.installTo( Uploader.prototype );
741     
742         // 批量添加纯命令式方法。
743         $.each({
744             upload: 'start-upload',
745             stop: 'stop-upload',
746             getFile: 'get-file',
747             getFiles: 'get-files',
748             addFile: 'add-file',
749             addFiles: 'add-file',
750             sort: 'sort-files',
751             removeFile: 'remove-file',
752             cancelFile: 'cancel-file',
753             skipFile: 'skip-file',
754             retry: 'retry',
755             isInProgress: 'is-in-progress',
756             makeThumb: 'make-thumb',
757             md5File: 'md5-file',
758             getDimension: 'get-dimension',
759             addButton: 'add-btn',
760             predictRuntimeType: 'predict-runtime-type',
761             refresh: 'refresh',
762             disable: 'disable',
763             enable: 'enable',
764             reset: 'reset'
765         }, function( fn, command ) {
766             Uploader.prototype[ fn ] = function() {
767                 return this.request( command, arguments );
768             };
769         });
770     
771         $.extend( Uploader.prototype, {
772             state: 'pending',
773     
774             _init: function( opts ) {
775                 var me = this;
776     
777                 me.request( 'init', opts, function() {
778                     me.state = 'ready';
779                     me.trigger('ready');
780                 });
781             },
782     
783             /**
784              * 获取或者设置Uploader配置项。
785              * @method option
786              * @grammar option( key ) => *
787              * @grammar option( key, val ) => self
788              * @example
789              *
790              * // 初始状态图片上传前不会压缩
791              * var uploader = new WebUploader.Uploader({
792              *     compress: null;
793              * });
794              *
795              * // 修改后图片上传前,尝试将图片压缩到1600 * 1600
796              * uploader.option( 'compress', {
797              *     width: 1600,
798              *     height: 1600
799              * });
800              */
801             option: function( key, val ) {
802                 var opts = this.options;
803     
804                 // setter
805                 if ( arguments.length > 1 ) {
806     
807                     if ( $.isPlainObject( val ) &&
808                             $.isPlainObject( opts[ key ] ) ) {
809                         $.extend( opts[ key ], val );
810                     } else {
811                         opts[ key ] = val;
812                     }
813     
814                 } else {    // getter
815                     return key ? opts[ key ] : opts;
816                 }
817             },
818     
819             /**
820              * 获取文件统计信息。返回一个包含一下信息的对象。
821              * * `successNum` 上传成功的文件数
822              * * `progressNum` 上传中的文件数
823              * * `cancelNum` 被删除的文件数
824              * * `invalidNum` 无效的文件数
825              * * `uploadFailNum` 上传失败的文件数
826              * * `queueNum` 还在队列中的文件数
827              * * `interruptNum` 被暂停的文件数
828              * @method getStats
829              * @grammar getStats() => Object
830              */
831             getStats: function() {
832                 // return this._mgr.getStats.apply( this._mgr, arguments );
833                 var stats = this.request('get-stats');
834     
835                 return stats ? {
836                     successNum: stats.numOfSuccess,
837                     progressNum: stats.numOfProgress,
838     
839                     // who care?
840                     // queueFailNum: 0,
841                     cancelNum: stats.numOfCancel,
842                     invalidNum: stats.numOfInvalid,
843                     uploadFailNum: stats.numOfUploadFailed,
844                     queueNum: stats.numOfQueue,
845                     interruptNum: stats.numofInterrupt
846                 } : {};
847             },
848     
849             // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
850             trigger: function( type/*, args...*/ ) {
851                 var args = [].slice.call( arguments, 1 ),
852                     opts = this.options,
853                     name = 'on' + type.substring( 0, 1 ).toUpperCase() +
854                         type.substring( 1 );
855     
856                 if (
857                         // 调用通过on方法注册的handler.
858                         Mediator.trigger.apply( this, arguments ) === false ||
859     
860                         // 调用opts.onEvent
861                         $.isFunction( opts[ name ] ) &&
862                         opts[ name ].apply( this, args ) === false ||
863     
864                         // 调用this.onEvent
865                         $.isFunction( this[ name ] ) &&
866                         this[ name ].apply( this, args ) === false ||
867     
868                         // 广播所有uploader的事件。
869                         Mediator.trigger.apply( Mediator,
870                         [ this, type ].concat( args ) ) === false ) {
871     
872                     return false;
873                 }
874     
875                 return true;
876             },
877     
878             /**
879              * 销毁 webuploader 实例
880              * @method destroy
881              * @grammar destroy() => undefined
882              */
883             destroy: function() {
884                 this.request( 'destroy', arguments );
885                 this.off();
886             },
887     
888             // widgets/widget.js将补充此方法的详细文档。
889             request: Base.noop
890         });
891     
892         /**
893          * 创建Uploader实例,等同于new Uploader( opts );
894          * @method create
895          * @class Base
896          * @static
897          * @grammar Base.create( opts ) => Uploader
898          */
899         Base.create = Uploader.create = function( opts ) {
900             return new Uploader( opts );
901         };
902     
903         // 暴露Uploader,可以通过它来扩展业务逻辑。
904         Base.Uploader = Uploader;
905     
906         return Uploader;
907     });
908     /**
909      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
910      */
911     define('runtime/runtime',[
912         'base',
913         'mediator'
914     ], function( Base, Mediator ) {
915     
916         var $ = Base.$,
917             factories = {},
918     
919             // 获取对象的第一个key
920             getFirstKey = function( obj ) {
921                 for ( var key in obj ) {
922                     if ( obj.hasOwnProperty( key ) ) {
923                         return key;
924                     }
925                 }
926                 return null;
927             };
928     
929         // 接口类。
930         function Runtime( options ) {
931             this.options = $.extend({
932                 container: document.body
933             }, options );
934             this.uid = Base.guid('rt_');
935         }
936     
937         $.extend( Runtime.prototype, {
938     
939             getContainer: function() {
940                 var opts = this.options,
941                     parent, container;
942     
943                 if ( this._container ) {
944                     return this._container;
945                 }
946     
947                 parent = $( opts.container || document.body );
948                 container = $( document.createElement('div') );
949     
950                 container.attr( 'id', 'rt_' + this.uid );
951                 container.css({
952                     position: 'absolute',
953                     top: '0px',
954                     left: '0px',
955                     width: '1px',
956                     height: '1px',
957                     overflow: 'hidden'
958                 });
959     
960                 parent.append( container );
961                 parent.addClass('webuploader-container');
962                 this._container = container;
963                 this._parent = parent;
964                 return container;
965             },
966     
967             init: Base.noop,
968             exec: Base.noop,
969     
970             destroy: function() {
971                 this._container && this._container.remove();
972                 this._parent && this._parent.removeClass('webuploader-container');
973                 this.off();
974             }
975         });
976     
977         Runtime.orders = 'html5,flash';
978     
979     
980         /**
981          * 添加Runtime实现。
982          * @param {String} type    类型
983          * @param {Runtime} factory 具体Runtime实现。
984          */
985         Runtime.addRuntime = function( type, factory ) {
986             factories[ type ] = factory;
987         };
988     
989         Runtime.hasRuntime = function( type ) {
990             return !!(type ? factories[ type ] : getFirstKey( factories ));
991         };
992     
993         Runtime.create = function( opts, orders ) {
994             var type, runtime;
995     
996             orders = orders || Runtime.orders;
997             $.each( orders.split( /\s*,\s*/g ), function() {
998                 if ( factories[ this ] ) {
999                     type = this;
1000                     return false;
1001                 }
1002             });
1003     
1004             type = type || getFirstKey( factories );
1005     
1006             if ( !type ) {
1007                 throw new Error('Runtime Error');
1008             }
1009     
1010             runtime = new factories[ type ]( opts );
1011             return runtime;
1012         };
1013     
1014         Mediator.installTo( Runtime.prototype );
1015         return Runtime;
1016     });
1017     
1018     /**
1019      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
1020      */
1021     define('runtime/client',[
1022         'base',
1023         'mediator',
1024         'runtime/runtime'
1025     ], function( Base, Mediator, Runtime ) {
1026     
1027         var cache;
1028     
1029         cache = (function() {
1030             var obj = {};
1031     
1032             return {
1033                 add: function( runtime ) {
1034                     obj[ runtime.uid ] = runtime;
1035                 },
1036     
1037                 get: function( ruid, standalone ) {
1038                     var i;
1039     
1040                     if ( ruid ) {
1041                         return obj[ ruid ];
1042                     }
1043     
1044                     for ( i in obj ) {
1045                         // 有些类型不能重用,比如filepicker.
1046                         if ( standalone && obj[ i ].__standalone ) {
1047                             continue;
1048                         }
1049     
1050                         return obj[ i ];
1051                     }
1052     
1053                     return null;
1054                 },
1055     
1056                 remove: function( runtime ) {
1057                     delete obj[ runtime.uid ];
1058                 }
1059             };
1060         })();
1061     
1062         function RuntimeClient( component, standalone ) {
1063             var deferred = Base.Deferred(),
1064                 runtime;
1065     
1066             this.uid = Base.guid('client_');
1067     
1068             // 允许runtime没有初始化之前,注册一些方法在初始化后执行。
1069             this.runtimeReady = function( cb ) {
1070                 return deferred.done( cb );
1071             };
1072     
1073             this.connectRuntime = function( opts, cb ) {
1074     
1075                 // already connected.
1076                 if ( runtime ) {
1077                     throw new Error('already connected!');
1078                 }
1079     
1080                 deferred.done( cb );
1081     
1082                 if ( typeof opts === 'string' && cache.get( opts ) ) {
1083                     runtime = cache.get( opts );
1084                 }
1085     
1086                 // 像filePicker只能独立存在,不能公用。
1087                 runtime = runtime || cache.get( null, standalone );
1088     
1089                 // 需要创建
1090                 if ( !runtime ) {
1091                     runtime = Runtime.create( opts, opts.runtimeOrder );
1092                     runtime.__promise = deferred.promise();
1093                     runtime.once( 'ready', deferred.resolve );
1094                     runtime.init();
1095                     cache.add( runtime );
1096                     runtime.__client = 1;
1097                 } else {
1098                     // 来自cache
1099                     Base.$.extend( runtime.options, opts );
1100                     runtime.__promise.then( deferred.resolve );
1101                     runtime.__client++;
1102                 }
1103     
1104                 standalone && (runtime.__standalone = standalone);
1105                 return runtime;
1106             };
1107     
1108             this.getRuntime = function() {
1109                 return runtime;
1110             };
1111     
1112             this.disconnectRuntime = function() {
1113                 if ( !runtime ) {
1114                     return;
1115                 }
1116     
1117                 runtime.__client--;
1118     
1119                 if ( runtime.__client <= 0 ) {
1120                     cache.remove( runtime );
1121                     delete runtime.__promise;
1122                     runtime.destroy();
1123                 }
1124     
1125                 runtime = null;
1126             };
1127     
1128             this.exec = function() {
1129                 if ( !runtime ) {
1130                     return;
1131                 }
1132     
1133                 var args = Base.slice( arguments );
1134                 component && args.unshift( component );
1135     
1136                 return runtime.exec.apply( this, args );
1137             };
1138     
1139             this.getRuid = function() {
1140                 return runtime && runtime.uid;
1141             };
1142     
1143             this.destroy = (function( destroy ) {
1144                 return function() {
1145                     destroy && destroy.apply( this, arguments );
1146                     this.trigger('destroy');
1147                     this.off();
1148                     this.exec('destroy');
1149                     this.disconnectRuntime();
1150                 };
1151             })( this.destroy );
1152         }
1153     
1154         Mediator.installTo( RuntimeClient.prototype );
1155         return RuntimeClient;
1156     });
1157     /**
1158      * @fileOverview 错误信息
1159      */
1160     define('lib/dnd',[
1161         'base',
1162         'mediator',
1163         'runtime/client'
1164     ], function( Base, Mediator, RuntimeClent ) {
1165     
1166         var $ = Base.$;
1167     
1168         function DragAndDrop( opts ) {
1169             opts = this.options = $.extend({}, DragAndDrop.options, opts );
1170     
1171             opts.container = $( opts.container );
1172     
1173             if ( !opts.container.length ) {
1174                 return;
1175             }
1176     
1177             RuntimeClent.call( this, 'DragAndDrop' );
1178         }
1179     
1180         DragAndDrop.options = {
1181             accept: null,
1182             disableGlobalDnd: false
1183         };
1184     
1185         Base.inherits( RuntimeClent, {
1186             constructor: DragAndDrop,
1187     
1188             init: function() {
1189                 var me = this;
1190     
1191                 me.connectRuntime( me.options, function() {
1192                     me.exec('init');
1193                     me.trigger('ready');
1194                 });
1195             }
1196         });
1197     
1198         Mediator.installTo( DragAndDrop.prototype );
1199     
1200         return DragAndDrop;
1201     });
1202     /**
1203      * @fileOverview 组件基类。
1204      */
1205     define('widgets/widget',[
1206         'base',
1207         'uploader'
1208     ], function( Base, Uploader ) {
1209     
1210         var $ = Base.$,
1211             _init = Uploader.prototype._init,
1212             _destroy = Uploader.prototype.destroy,
1213             IGNORE = {},
1214             widgetClass = [];
1215     
1216         function isArrayLike( obj ) {
1217             if ( !obj ) {
1218                 return false;
1219             }
1220     
1221             var length = obj.length,
1222                 type = $.type( obj );
1223     
1224             if ( obj.nodeType === 1 && length ) {
1225                 return true;
1226             }
1227     
1228             return type === 'array' || type !== 'function' && type !== 'string' &&
1229                     (length === 0 || typeof length === 'number' && length > 0 &&
1230                     (length - 1) in obj);
1231         }
1232     
1233         function Widget( uploader ) {
1234             this.owner = uploader;
1235             this.options = uploader.options;
1236         }
1237     
1238         $.extend( Widget.prototype, {
1239     
1240             init: Base.noop,
1241     
1242             // 类Backbone的事件监听声明,监听uploader实例上的事件
1243             // widget直接无法监听事件,事件只能通过uploader来传递
1244             invoke: function( apiName, args ) {
1245     
1246                 /*
1247                     {
1248                         'make-thumb': 'makeThumb'
1249                     }
1250                  */
1251                 var map = this.responseMap;
1252     
1253                 // 如果无API响应声明则忽略
1254                 if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||
1255                         !$.isFunction( this[ map[ apiName ] ] ) ) {
1256     
1257                     return IGNORE;
1258                 }
1259     
1260                 return this[ map[ apiName ] ].apply( this, args );
1261     
1262             },
1263     
1264             /**
1265              * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。
1266              * @method request
1267              * @grammar request( command, args ) => * | Promise
1268              * @grammar request( command, args, callback ) => Promise
1269              * @for  Uploader
1270              */
1271             request: function() {
1272                 return this.owner.request.apply( this.owner, arguments );
1273             }
1274         });
1275     
1276         // 扩展Uploader.
1277         $.extend( Uploader.prototype, {
1278     
1279             /**
1280              * @property {String | Array} [disableWidgets=undefined]
1281              * @namespace options
1282              * @for Uploader
1283              * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。
1284              */
1285     
1286             // 覆写_init用来初始化widgets
1287             _init: function() {
1288                 var me = this,
1289                     widgets = me._widgets = [],
1290                     deactives = me.options.disableWidgets || '';
1291     
1292                 $.each( widgetClass, function( _, klass ) {
1293                     (!deactives || !~deactives.indexOf( klass._name )) &&
1294                         widgets.push( new klass( me ) );
1295                 });
1296     
1297                 return _init.apply( me, arguments );
1298             },
1299     
1300             request: function( apiName, args, callback ) {
1301                 var i = 0,
1302                     widgets = this._widgets,
1303                     len = widgets && widgets.length,
1304                     rlts = [],
1305                     dfds = [],
1306                     widget, rlt, promise, key;
1307     
1308                 args = isArrayLike( args ) ? args : [ args ];
1309     
1310                 for ( ; i < len; i++ ) {
1311                     widget = widgets[ i ];
1312                     rlt = widget.invoke( apiName, args );
1313     
1314                     if ( rlt !== IGNORE ) {
1315     
1316                         // Deferred对象
1317                         if ( Base.isPromise( rlt ) ) {
1318                             dfds.push( rlt );
1319                         } else {
1320                             rlts.push( rlt );
1321                         }
1322                     }
1323                 }
1324     
1325                 // 如果有callback,则用异步方式。
1326                 if ( callback || dfds.length ) {
1327                     promise = Base.when.apply( Base, dfds );
1328                     key = promise.pipe ? 'pipe' : 'then';
1329     
1330                     // 很重要不能删除。删除了会死循环。
1331                     // 保证执行顺序。让callback总是在下一个 tick 中执行。
1332                     return promise[ key ](function() {
1333                                 var deferred = Base.Deferred(),
1334                                     args = arguments;
1335     
1336                                 if ( args.length === 1 ) {
1337                                     args = args[ 0 ];
1338                                 }
1339     
1340                                 setTimeout(function() {
1341                                     deferred.resolve( args );
1342                                 }, 1 );
1343     
1344                                 return deferred.promise();
1345                             })[ callback ? key : 'done' ]( callback || Base.noop );
1346                 } else {
1347                     return rlts[ 0 ];
1348                 }
1349             },
1350     
1351             destroy: function() {
1352                 _destroy.apply( this, arguments );
1353                 this._widgets = null;
1354             }
1355         });
1356     
1357         /**
1358          * 添加组件
1359          * @grammar Uploader.register(proto);
1360          * @grammar Uploader.register(map, proto);
1361          * @param  {object} responseMap API 名称与函数实现的映射
1362          * @param  {object} proto 组件原型,构造函数通过 constructor 属性定义
1363          * @method Uploader.register
1364          * @for Uploader
1365          * @example
1366          * Uploader.register({
1367          *     'make-thumb': 'makeThumb'
1368          * }, {
1369          *     init: function( options ) {},
1370          *     makeThumb: function() {}
1371          * });
1372          *
1373          * Uploader.register({
1374          *     'make-thumb': function() {
1375          *         
1376          *     }
1377          * });
1378          */
1379         Uploader.register = Widget.register = function( responseMap, widgetProto ) {
1380             var map = { init: 'init', destroy: 'destroy', name: 'anonymous' },
1381                 klass;
1382     
1383             if ( arguments.length === 1 ) {
1384                 widgetProto = responseMap;
1385     
1386                 // 自动生成 map 表。
1387                 $.each(widgetProto, function(key) {
1388                     if ( key[0] === '_' || key === 'name' ) {
1389                         key === 'name' && (map.name = widgetProto.name);
1390                         return;
1391                     }
1392     
1393                     map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key;
1394                 });
1395     
1396             } else {
1397                 map = $.extend( map, responseMap );
1398             }
1399     
1400             widgetProto.responseMap = map;
1401             klass = Base.inherits( Widget, widgetProto );
1402             klass._name = map.name;
1403             widgetClass.push( klass );
1404     
1405             return klass;
1406         };
1407     
1408         /**
1409          * 删除插件,只有在注册时指定了名字的才能被删除。
1410          * @grammar Uploader.unRegister(name);
1411          * @param  {string} name 组件名字
1412          * @method Uploader.unRegister
1413          * @for Uploader
1414          * @example
1415          *
1416          * Uploader.register({
1417          *     name: 'custom',
1418          *     
1419          *     'make-thumb': function() {
1420          *         
1421          *     }
1422          * });
1423          *
1424          * Uploader.unRegister('custom');
1425          */
1426         Uploader.unRegister = Widget.unRegister = function( name ) {
1427             if ( !name || name === 'anonymous' ) {
1428                 return;
1429             }
1430             
1431             // 删除指定的插件。
1432             for ( var i = widgetClass.length; i--; ) {
1433                 if ( widgetClass[i]._name === name ) {
1434                     widgetClass.splice(i, 1)
1435                 }
1436             }
1437         };
1438     
1439         return Widget;
1440     });
1441     /**
1442      * @fileOverview DragAndDrop Widget。
1443      */
1444     define('widgets/filednd',[
1445         'base',
1446         'uploader',
1447         'lib/dnd',
1448         'widgets/widget'
1449     ], function( Base, Uploader, Dnd ) {
1450         var $ = Base.$;
1451     
1452         Uploader.options.dnd = '';
1453     
1454         /**
1455          * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器,如果不指定,则不启动。
1456          * @namespace options
1457          * @for Uploader
1458          */
1459         
1460         /**
1461          * @property {Selector} [disableGlobalDnd=false]  是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。
1462          * @namespace options
1463          * @for Uploader
1464          */
1465     
1466         /**
1467          * @event dndAccept
1468          * @param {DataTransferItemList} items DataTransferItem
1469          * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。
1470          * @for  Uploader
1471          */
1472         return Uploader.register({
1473             name: 'dnd',
1474             
1475             init: function( opts ) {
1476     
1477                 if ( !opts.dnd ||
1478                         this.request('predict-runtime-type') !== 'html5' ) {
1479                     return;
1480                 }
1481     
1482                 var me = this,
1483                     deferred = Base.Deferred(),
1484                     options = $.extend({}, {
1485                         disableGlobalDnd: opts.disableGlobalDnd,
1486                         container: opts.dnd,
1487                         accept: opts.accept
1488                     }),
1489                     dnd;
1490     
1491                 this.dnd = dnd = new Dnd( options );
1492     
1493                 dnd.once( 'ready', deferred.resolve );
1494                 dnd.on( 'drop', function( files ) {
1495                     me.request( 'add-file', [ files ]);
1496                 });
1497     
1498                 // 检测文件是否全部允许添加。
1499                 dnd.on( 'accept', function( items ) {
1500                     return me.owner.trigger( 'dndAccept', items );
1501                 });
1502     
1503                 dnd.init();
1504     
1505                 return deferred.promise();
1506             },
1507     
1508             destroy: function() {
1509                 this.dnd && this.dnd.destroy();
1510             }
1511         });
1512     });
1513     
1514     /**
1515      * @fileOverview 错误信息
1516      */
1517     define('lib/filepaste',[
1518         'base',
1519         'mediator',
1520         'runtime/client'
1521     ], function( Base, Mediator, RuntimeClent ) {
1522     
1523         var $ = Base.$;
1524     
1525         function FilePaste( opts ) {
1526             opts = this.options = $.extend({}, opts );
1527             opts.container = $( opts.container || document.body );
1528             RuntimeClent.call( this, 'FilePaste' );
1529         }
1530     
1531         Base.inherits( RuntimeClent, {
1532             constructor: FilePaste,
1533     
1534             init: function() {
1535                 var me = this;
1536     
1537                 me.connectRuntime( me.options, function() {
1538                     me.exec('init');
1539                     me.trigger('ready');
1540                 });
1541             }
1542         });
1543     
1544         Mediator.installTo( FilePaste.prototype );
1545     
1546         return FilePaste;
1547     });
1548     /**
1549      * @fileOverview 组件基类。
1550      */
1551     define('widgets/filepaste',[
1552         'base',
1553         'uploader',
1554         'lib/filepaste',
1555         'widgets/widget'
1556     ], function( Base, Uploader, FilePaste ) {
1557         var $ = Base.$;
1558     
1559         /**
1560          * @property {Selector} [paste=undefined]  指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.
1561          * @namespace options
1562          * @for Uploader
1563          */
1564         return Uploader.register({
1565             name: 'paste',
1566             
1567             init: function( opts ) {
1568     
1569                 if ( !opts.paste ||
1570                         this.request('predict-runtime-type') !== 'html5' ) {
1571                     return;
1572                 }
1573     
1574                 var me = this,
1575                     deferred = Base.Deferred(),
1576                     options = $.extend({}, {
1577                         container: opts.paste,
1578                         accept: opts.accept
1579                     }),
1580                     paste;
1581     
1582                 this.paste = paste = new FilePaste( options );
1583     
1584                 paste.once( 'ready', deferred.resolve );
1585                 paste.on( 'paste', function( files ) {
1586                     me.owner.request( 'add-file', [ files ]);
1587                 });
1588                 paste.init();
1589     
1590                 return deferred.promise();
1591             },
1592     
1593             destroy: function() {
1594                 this.paste && this.paste.destroy();
1595             }
1596         });
1597     });
1598     /**
1599      * @fileOverview Blob
1600      */
1601     define('lib/blob',[
1602         'base',
1603         'runtime/client'
1604     ], function( Base, RuntimeClient ) {
1605     
1606         function Blob( ruid, source ) {
1607             var me = this;
1608     
1609             me.source = source;
1610             me.ruid = ruid;
1611             this.size = source.size || 0;
1612     
1613             // 如果没有指定 mimetype, 但是知道文件后缀。
1614             if ( !source.type && this.ext &&
1615                     ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) {
1616                 this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext);
1617             } else {
1618                 this.type = source.type || 'application/octet-stream';
1619             }
1620     
1621             RuntimeClient.call( me, 'Blob' );
1622             this.uid = source.uid || this.uid;
1623     
1624             if ( ruid ) {
1625                 me.connectRuntime( ruid );
1626             }
1627         }
1628     
1629         Base.inherits( RuntimeClient, {
1630             constructor: Blob,
1631     
1632             slice: function( start, end ) {
1633                 return this.exec( 'slice', start, end );
1634             },
1635     
1636             getSource: function() {
1637                 return this.source;
1638             }
1639         });
1640     
1641         return Blob;
1642     });
1643     /**
1644      * 为了统一化Flash的File和HTML5的File而存在。
1645      * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。
1646      * @fileOverview File
1647      */
1648     define('lib/file',[
1649         'base',
1650         'lib/blob'
1651     ], function( Base, Blob ) {
1652     
1653         var uid = 1,
1654             rExt = /\.([^.]+)$/;
1655     
1656         function File( ruid, file ) {
1657             var ext;
1658     
1659             this.name = file.name || ('untitled' + uid++);
1660             ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';
1661     
1662             // todo 支持其他类型文件的转换。
1663             // 如果有 mimetype, 但是文件名里面没有找出后缀规律
1664             if ( !ext && file.type ) {
1665                 ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ?
1666                         RegExp.$1.toLowerCase() : '';
1667                 this.name += '.' + ext;
1668             }
1669     
1670             this.ext = ext;
1671             this.lastModifiedDate = file.lastModifiedDate ||
1672                     (new Date()).toLocaleString();
1673     
1674             Blob.apply( this, arguments );
1675         }
1676     
1677         return Base.inherits( Blob, File );
1678     });
1679     
1680     /**
1681      * @fileOverview 错误信息
1682      */
1683     define('lib/filepicker',[
1684         'base',
1685         'runtime/client',
1686         'lib/file'
1687     ], function( Base, RuntimeClent, File ) {
1688     
1689         var $ = Base.$;
1690     
1691         function FilePicker( opts ) {
1692             opts = this.options = $.extend({}, FilePicker.options, opts );
1693     
1694             opts.container = $( opts.id );
1695     
1696             if ( !opts.container.length ) {
1697                 throw new Error('按钮指定错误');
1698             }
1699     
1700             opts.innerHTML = opts.innerHTML || opts.label ||
1701                     opts.container.html() || '';
1702     
1703             opts.button = $( opts.button || document.createElement('div') );
1704             opts.button.html( opts.innerHTML );
1705             opts.container.html( opts.button );
1706     
1707             RuntimeClent.call( this, 'FilePicker', true );
1708         }
1709     
1710         FilePicker.options = {
1711             button: null,
1712             container: null,
1713             label: null,
1714             innerHTML: null,
1715             multiple: true,
1716             accept: null,
1717             name: 'file'
1718         };
1719     
1720         Base.inherits( RuntimeClent, {
1721             constructor: FilePicker,
1722     
1723             init: function() {
1724                 var me = this,
1725                     opts = me.options,
1726                     button = opts.button;
1727     
1728                 button.addClass('webuploader-pick');
1729     
1730                 me.on( 'all', function( type ) {
1731                     var files;
1732     
1733                     switch ( type ) {
1734                         case 'mouseenter':
1735                             button.addClass('webuploader-pick-hover');
1736                             break;
1737     
1738                         case 'mouseleave':
1739                             button.removeClass('webuploader-pick-hover');
1740                             break;
1741     
1742                         case 'change':
1743                             files = me.exec('getFiles');
1744                             me.trigger( 'select', $.map( files, function( file ) {
1745                                 file = new File( me.getRuid(), file );
1746     
1747                                 // 记录来源。
1748                                 file._refer = opts.container;
1749                                 return file;
1750                             }), opts.container );
1751                             break;
1752                     }
1753                 });
1754     
1755                 me.connectRuntime( opts, function() {
1756                     me.refresh();
1757                     me.exec( 'init', opts );
1758                     me.trigger('ready');
1759                 });
1760     
1761                 this._resizeHandler = Base.bindFn( this.refresh, this );
1762                 $( window ).on( 'resize', this._resizeHandler );
1763             },
1764     
1765             refresh: function() {
1766                 var shimContainer = this.getRuntime().getContainer(),
1767                     button = this.options.button,
1768                     width = button.outerWidth ?
1769                             button.outerWidth() : button.width(),
1770     
1771                     height = button.outerHeight ?
1772                             button.outerHeight() : button.height(),
1773     
1774                     pos = button.offset();
1775     
1776                 width && height && shimContainer.css({
1777                     bottom: 'auto',
1778                     right: 'auto',
1779                     width: width + 'px',
1780                     height: height + 'px'
1781                 }).offset( pos );
1782             },
1783     
1784             enable: function() {
1785                 var btn = this.options.button;
1786     
1787                 btn.removeClass('webuploader-pick-disable');
1788                 this.refresh();
1789             },
1790     
1791             disable: function() {
1792                 var btn = this.options.button;
1793     
1794                 this.getRuntime().getContainer().css({
1795                     top: '-99999px'
1796                 });
1797     
1798                 btn.addClass('webuploader-pick-disable');
1799             },
1800     
1801             destroy: function() {
1802                 var btn = this.options.button;
1803                 $( window ).off( 'resize', this._resizeHandler );
1804                 btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' +
1805                     'webuploader-pick');
1806             }
1807         });
1808     
1809         return FilePicker;
1810     });
1811     
1812     /**
1813      * @fileOverview 文件选择相关
1814      */
1815     define('widgets/filepicker',[
1816         'base',
1817         'uploader',
1818         'lib/filepicker',
1819         'widgets/widget'
1820     ], function( Base, Uploader, FilePicker ) {
1821         var $ = Base.$;
1822     
1823         $.extend( Uploader.options, {
1824     
1825             /**
1826              * @property {Selector | Object} [pick=undefined]
1827              * @namespace options
1828              * @for Uploader
1829              * @description 指定选择文件的按钮容器,不指定则不创建按钮。
1830              *
1831              * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。
1832              * * `label` {String} 请采用 `innerHTML` 代替
1833              * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。
1834              * * `multiple` {Boolean} 是否开起同时选择多个文件能力。
1835              */
1836             pick: null,
1837     
1838             /**
1839              * @property {Arroy} [accept=null]
1840              * @namespace options
1841              * @for Uploader
1842              * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。
1843              *
1844              * * `title` {String} 文字描述
1845              * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。
1846              * * `mimeTypes` {String} 多个用逗号分割。
1847              *
1848              * 如:
1849              *
1850              * ```
1851              * {
1852              *     title: 'Images',
1853              *     extensions: 'gif,jpg,jpeg,bmp,png',
1854              *     mimeTypes: 'image/*'
1855              * }
1856              * ```
1857              */
1858             accept: null/*{
1859                 title: 'Images',
1860                 extensions: 'gif,jpg,jpeg,bmp,png',
1861                 mimeTypes: 'image/*'
1862             }*/
1863         });
1864     
1865         return Uploader.register({
1866             name: 'picker',
1867     
1868             init: function( opts ) {
1869                 this.pickers = [];
1870                 return opts.pick && this.addBtn( opts.pick );
1871             },
1872     
1873             refresh: function() {
1874                 $.each( this.pickers, function() {
1875                     this.refresh();
1876                 });
1877             },
1878     
1879             /**
1880              * @method addButton
1881              * @for Uploader
1882              * @grammar addButton( pick ) => Promise
1883              * @description
1884              * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。
1885              * @example
1886              * uploader.addButton({
1887              *     id: '#btnContainer',
1888              *     innerHTML: '选择文件'
1889              * });
1890              */
1891             addBtn: function( pick ) {
1892                 var me = this,
1893                     opts = me.options,
1894                     accept = opts.accept,
1895                     promises = [];
1896     
1897                 if ( !pick ) {
1898                     return;
1899                 }
1900     
1901                 $.isPlainObject( pick ) || (pick = {
1902                     id: pick
1903                 });
1904     
1905                 $( pick.id ).each(function() {
1906                     var options, picker, deferred;
1907     
1908                     deferred = Base.Deferred();
1909     
1910                     options = $.extend({}, pick, {
1911                         accept: $.isPlainObject( accept ) ? [ accept ] : accept,
1912                         swf: opts.swf,
1913                         runtimeOrder: opts.runtimeOrder,
1914                         id: this
1915                     });
1916     
1917                     picker = new FilePicker( options );
1918     
1919                     picker.once( 'ready', deferred.resolve );
1920                     picker.on( 'select', function( files ) {
1921                         me.owner.request( 'add-file', [ files ]);
1922                     });
1923                     picker.init();
1924     
1925                     me.pickers.push( picker );
1926     
1927                     promises.push( deferred.promise() );
1928                 });
1929     
1930                 return Base.when.apply( Base, promises );
1931             },
1932     
1933             disable: function() {
1934                 $.each( this.pickers, function() {
1935                     this.disable();
1936                 });
1937             },
1938     
1939             enable: function() {
1940                 $.each( this.pickers, function() {
1941                     this.enable();
1942                 });
1943             },
1944     
1945             destroy: function() {
1946                 $.each( this.pickers, function() {
1947                     this.destroy();
1948                 });
1949                 this.pickers = null;
1950             }
1951         });
1952     });
1953     /**
1954      * @fileOverview Image
1955      */
1956     define('lib/image',[
1957         'base',
1958         'runtime/client',
1959         'lib/blob'
1960     ], function( Base, RuntimeClient, Blob ) {
1961         var $ = Base.$;
1962     
1963         // 构造器。
1964         function Image( opts ) {
1965             this.options = $.extend({}, Image.options, opts );
1966             RuntimeClient.call( this, 'Image' );
1967     
1968             this.on( 'load', function() {
1969                 this._info = this.exec('info');
1970                 this._meta = this.exec('meta');
1971             });
1972         }
1973     
1974         // 默认选项。
1975         Image.options = {
1976     
1977             // 默认的图片处理质量
1978             quality: 90,
1979     
1980             // 是否裁剪
1981             crop: false,
1982     
1983             // 是否保留头部信息
1984             preserveHeaders: false,
1985     
1986             // 是否允许放大。
1987             allowMagnify: false
1988         };
1989     
1990         // 继承RuntimeClient.
1991         Base.inherits( RuntimeClient, {
1992             constructor: Image,
1993     
1994             info: function( val ) {
1995     
1996                 // setter
1997                 if ( val ) {
1998                     this._info = val;
1999                     return this;
2000                 }
2001     
2002                 // getter
2003                 return this._info;
2004             },
2005     
2006             meta: function( val ) {
2007     
2008                 // setter
2009                 if ( val ) {
2010                     this._meta = val;
2011                     return this;
2012                 }
2013     
2014                 // getter
2015                 return this._meta;
2016             },
2017     
2018             loadFromBlob: function( blob ) {
2019                 var me = this,
2020                     ruid = blob.getRuid();
2021     
2022                 this.connectRuntime( ruid, function() {
2023                     me.exec( 'init', me.options );
2024                     me.exec( 'loadFromBlob', blob );
2025                 });
2026             },
2027     
2028             resize: function() {
2029                 var args = Base.slice( arguments );
2030                 return this.exec.apply( this, [ 'resize' ].concat( args ) );
2031             },
2032     
2033             crop: function() {
2034                 var args = Base.slice( arguments );
2035                 return this.exec.apply( this, [ 'crop' ].concat( args ) );
2036             },
2037     
2038             getAsDataUrl: function( type ) {
2039                 return this.exec( 'getAsDataUrl', type );
2040             },
2041     
2042             getAsBlob: function( type ) {
2043                 var blob = this.exec( 'getAsBlob', type );
2044     
2045                 return new Blob( this.getRuid(), blob );
2046             }
2047         });
2048     
2049         return Image;
2050     });
2051     /**
2052      * @fileOverview 图片操作, 负责预览图片和上传前压缩图片
2053      */
2054     define('widgets/image',[
2055         'base',
2056         'uploader',
2057         'lib/image',
2058         'widgets/widget'
2059     ], function( Base, Uploader, Image ) {
2060     
2061         var $ = Base.$,
2062             throttle;
2063     
2064         // 根据要处理的文件大小来节流,一次不能处理太多,会卡。
2065         throttle = (function( max ) {
2066             var occupied = 0,
2067                 waiting = [],
2068                 tick = function() {
2069                     var item;
2070     
2071                     while ( waiting.length && occupied < max ) {
2072                         item = waiting.shift();
2073                         occupied += item[ 0 ];
2074                         item[ 1 ]();
2075                     }
2076                 };
2077     
2078             return function( emiter, size, cb ) {
2079                 waiting.push([ size, cb ]);
2080                 emiter.once( 'destroy', function() {
2081                     occupied -= size;
2082                     setTimeout( tick, 1 );
2083                 });
2084                 setTimeout( tick, 1 );
2085             };
2086         })( 5 * 1024 * 1024 );
2087     
2088         $.extend( Uploader.options, {
2089     
2090             /**
2091              * @property {Object} [thumb]
2092              * @namespace options
2093              * @for Uploader
2094              * @description 配置生成缩略图的选项。
2095              *
2096              * 默认为:
2097              *
2098              * ```javascript
2099              * {
2100              *     width: 110,
2101              *     height: 110,
2102              *
2103              *     // 图片质量,只有type为`image/jpeg`的时候才有效。
2104              *     quality: 70,
2105              *
2106              *     // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
2107              *     allowMagnify: true,
2108              *
2109              *     // 是否允许裁剪。
2110              *     crop: true,
2111              *
2112              *     // 为空的话则保留原有图片格式。
2113              *     // 否则强制转换成指定的类型。
2114              *     type: 'image/jpeg'
2115              * }
2116              * ```
2117              */
2118             thumb: {
2119                 width: 110,
2120                 height: 110,
2121                 quality: 70,
2122                 allowMagnify: true,
2123                 crop: true,
2124                 preserveHeaders: false,
2125     
2126                 // 为空的话则保留原有图片格式。
2127                 // 否则强制转换成指定的类型。
2128                 // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可
2129                 // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg
2130                 type: 'image/jpeg'
2131             },
2132     
2133             /**
2134              * @property {Object} [compress]
2135              * @namespace options
2136              * @for Uploader
2137              * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。
2138              *
2139              * 默认为:
2140              *
2141              * ```javascript
2142              * {
2143              *     width: 1600,
2144              *     height: 1600,
2145              *
2146              *     // 图片质量,只有type为`image/jpeg`的时候才有效。
2147              *     quality: 90,
2148              *
2149              *     // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
2150              *     allowMagnify: false,
2151              *
2152              *     // 是否允许裁剪。
2153              *     crop: false,
2154              *
2155              *     // 是否保留头部meta信息。
2156              *     preserveHeaders: true,
2157              *
2158              *     // 如果发现压缩后文件大小比原来还大,则使用原来图片
2159              *     // 此属性可能会影响图片自动纠正功能
2160              *     noCompressIfLarger: false,
2161              *
2162              *     // 单位字节,如果图片大小小于此值,不会采用压缩。
2163              *     compressSize: 0
2164              * }
2165              * ```
2166              */
2167             compress: {
2168                 width: 1600,
2169                 height: 1600,
2170                 quality: 90,
2171                 allowMagnify: false,
2172                 crop: false,
2173                 preserveHeaders: true
2174             }
2175         });
2176     
2177         return Uploader.register({
2178     
2179             name: 'image',
2180     
2181     
2182             /**
2183              * 生成缩略图,此过程为异步,所以需要传入`callback`。
2184              * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。
2185              *
2186              * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。
2187              *
2188              * `callback`中可以接收到两个参数。
2189              * * 第一个为error,如果生成缩略图有错误,此error将为真。
2190              * * 第二个为ret, 缩略图的Data URL值。
2191              *
2192              * **注意**
2193              * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。
2194              * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。
2195              *
2196              * @method makeThumb
2197              * @grammar makeThumb( file, callback ) => undefined
2198              * @grammar makeThumb( file, callback, width, height ) => undefined
2199              * @for Uploader
2200              * @example
2201              *
2202              * uploader.on( 'fileQueued', function( file ) {
2203              *     var $li = ...;
2204              *
2205              *     uploader.makeThumb( file, function( error, ret ) {
2206              *         if ( error ) {
2207              *             $li.text('预览错误');
2208              *         } else {
2209              *             $li.append('<img alt="" src="' + ret + '" />');
2210              *         }
2211              *     });
2212              *
2213              * });
2214              */
2215             makeThumb: function( file, cb, width, height ) {
2216                 var opts, image;
2217     
2218                 file = this.request( 'get-file', file );
2219     
2220                 // 只预览图片格式。
2221                 if ( !file.type.match( /^image/ ) ) {
2222                     cb( true );
2223                     return;
2224                 }
2225     
2226                 opts = $.extend({}, this.options.thumb );
2227     
2228                 // 如果传入的是object.
2229                 if ( $.isPlainObject( width ) ) {
2230                     opts = $.extend( opts, width );
2231                     width = null;
2232                 }
2233     
2234                 width = width || opts.width;
2235                 height = height || opts.height;
2236     
2237                 image = new Image( opts );
2238     
2239                 image.once( 'load', function() {
2240                     file._info = file._info || image.info();
2241                     file._meta = file._meta || image.meta();
2242     
2243                     // 如果 width 的值介于 0 - 1
2244                     // 说明设置的是百分比。
2245                     if ( width <= 1 && width > 0 ) {
2246                         width = file._info.width * width;
2247                     }
2248     
2249                     // 同样的规则应用于 height
2250                     if ( height <= 1 && height > 0 ) {
2251                         height = file._info.height * height;
2252                     }
2253     
2254                     image.resize( width, height );
2255                 });
2256     
2257                 // 当 resize 完后
2258                 image.once( 'complete', function() {
2259                     cb( false, image.getAsDataUrl( opts.type ) );
2260                     image.destroy();
2261                 });
2262     
2263                 image.once( 'error', function( reason ) {
2264                     cb( reason || true );
2265                     image.destroy();
2266                 });
2267     
2268                 throttle( image, file.source.size, function() {
2269                     file._info && image.info( file._info );
2270                     file._meta && image.meta( file._meta );
2271                     image.loadFromBlob( file.source );
2272                 });
2273             },
2274     
2275             beforeSendFile: function( file ) {
2276                 var opts = this.options.compress || this.options.resize,
2277                     compressSize = opts && opts.compressSize || 0,
2278                     noCompressIfLarger = opts && opts.noCompressIfLarger || false,
2279                     image, deferred;
2280     
2281                 file = this.request( 'get-file', file );
2282     
2283                 // 只压缩 jpeg 图片格式。
2284                 // gif 可能会丢失针
2285                 // bmp png 基本上尺寸都不大,且压缩比比较小。
2286                 if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||
2287                         file.size < compressSize ||
2288                         file._compressed ) {
2289                     return;
2290                 }
2291     
2292                 opts = $.extend({}, opts );
2293                 deferred = Base.Deferred();
2294     
2295                 image = new Image( opts );
2296     
2297                 deferred.always(function() {
2298                     image.destroy();
2299                     image = null;
2300                 });
2301                 image.once( 'error', deferred.reject );
2302                 image.once( 'load', function() {
2303                     var width = opts.width,
2304                         height = opts.height;
2305     
2306                     file._info = file._info || image.info();
2307                     file._meta = file._meta || image.meta();
2308     
2309                     // 如果 width 的值介于 0 - 1
2310                     // 说明设置的是百分比。
2311                     if ( width <= 1 && width > 0 ) {
2312                         width = file._info.width * width;
2313                     }
2314     
2315                     // 同样的规则应用于 height
2316                     if ( height <= 1 && height > 0 ) {
2317                         height = file._info.height * height;
2318                     }
2319     
2320                     image.resize( width, height );
2321                 });
2322     
2323                 image.once( 'complete', function() {
2324                     var blob, size;
2325     
2326                     // 移动端 UC / qq 浏览器的无图模式下
2327                     // ctx.getImageData 处理大图的时候会报 Exception
2328                     // INDEX_SIZE_ERR: DOM Exception 1
2329                     try {
2330                         blob = image.getAsBlob( opts.type );
2331     
2332                         size = file.size;
2333     
2334                         // 如果压缩后,比原来还大则不用压缩后的。
2335                         if ( !noCompressIfLarger || blob.size < size ) {
2336                             // file.source.destroy && file.source.destroy();
2337                             file.source = blob;
2338                             file.size = blob.size;
2339     
2340                             file.trigger( 'resize', blob.size, size );
2341                         }
2342     
2343                         // 标记,避免重复压缩。
2344                         file._compressed = true;
2345                         deferred.resolve();
2346                     } catch ( e ) {
2347                         // 出错了直接继续,让其上传原始图片
2348                         deferred.resolve();
2349                     }
2350                 });
2351     
2352                 file._info && image.info( file._info );
2353                 file._meta && image.meta( file._meta );
2354     
2355                 image.loadFromBlob( file.source );
2356                 return deferred.promise();
2357             }
2358         });
2359     });
2360     /**
2361      * @fileOverview 文件属性封装
2362      */
2363     define('file',[
2364         'base',
2365         'mediator'
2366     ], function( Base, Mediator ) {
2367     
2368         var $ = Base.$,
2369             idPrefix = 'WU_FILE_',
2370             idSuffix = 0,
2371             rExt = /\.([^.]+)$/,
2372             statusMap = {};
2373     
2374         function gid() {
2375             return idPrefix + idSuffix++;
2376         }
2377     
2378         /**
2379          * 文件类
2380          * @class File
2381          * @constructor 构造函数
2382          * @grammar new File( source ) => File
2383          * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。
2384          */
2385         function WUFile( source ) {
2386     
2387             /**
2388              * 文件名,包括扩展名(后缀)
2389              * @property name
2390              * @type {string}
2391              */
2392             this.name = source.name || 'Untitled';
2393     
2394             /**
2395              * 文件体积(字节)
2396              * @property size
2397              * @type {uint}
2398              * @default 0
2399              */
2400             this.size = source.size || 0;
2401     
2402             /**
2403              * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)
2404              * @property type
2405              * @type {string}
2406              * @default 'application/octet-stream'
2407              */
2408             this.type = source.type || 'application/octet-stream';
2409     
2410             /**
2411              * 文件最后修改日期
2412              * @property lastModifiedDate
2413              * @type {int}
2414              * @default 当前时间戳
2415              */
2416             this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);
2417     
2418             /**
2419              * 文件ID,每个对象具有唯一ID,与文件名无关
2420              * @property id
2421              * @type {string}
2422              */
2423             this.id = gid();
2424     
2425             /**
2426              * 文件扩展名,通过文件名获取,例如test.png的扩展名为png
2427              * @property ext
2428              * @type {string}
2429              */
2430             this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';
2431     
2432     
2433             /**
2434              * 状态文字说明。在不同的status语境下有不同的用途。
2435              * @property statusText
2436              * @type {string}
2437              */
2438             this.statusText = '';
2439     
2440             // 存储文件状态,防止通过属性直接修改
2441             statusMap[ this.id ] = WUFile.Status.INITED;
2442     
2443             this.source = source;
2444             this.loaded = 0;
2445     
2446             this.on( 'error', function( msg ) {
2447                 this.setStatus( WUFile.Status.ERROR, msg );
2448             });
2449         }
2450     
2451         $.extend( WUFile.prototype, {
2452     
2453             /**
2454              * 设置状态,状态变化时会触发`change`事件。
2455              * @method setStatus
2456              * @grammar setStatus( status[, statusText] );
2457              * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)
2458              * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。
2459              */
2460             setStatus: function( status, text ) {
2461     
2462                 var prevStatus = statusMap[ this.id ];
2463     
2464                 typeof text !== 'undefined' && (this.statusText = text);
2465     
2466                 if ( status !== prevStatus ) {
2467                     statusMap[ this.id ] = status;
2468                     /**
2469                      * 文件状态变化
2470                      * @event statuschange
2471                      */
2472                     this.trigger( 'statuschange', status, prevStatus );
2473                 }
2474     
2475             },
2476     
2477             /**
2478              * 获取文件状态
2479              * @return {File.Status}
2480              * @example
2481                      文件状态具体包括以下几种类型:
2482                      {
2483                          // 初始化
2484                         INITED:     0,
2485                         // 已入队列
2486                         QUEUED:     1,
2487                         // 正在上传
2488                         PROGRESS:     2,
2489                         // 上传出错
2490                         ERROR:         3,
2491                         // 上传成功
2492                         COMPLETE:     4,
2493                         // 上传取消
2494                         CANCELLED:     5
2495                     }
2496              */
2497             getStatus: function() {
2498                 return statusMap[ this.id ];
2499             },
2500     
2501             /**
2502              * 获取文件原始信息。
2503              * @return {*}
2504              */
2505             getSource: function() {
2506                 return this.source;
2507             },
2508     
2509             destroy: function() {
2510                 this.off();
2511                 delete statusMap[ this.id ];
2512             }
2513         });
2514     
2515         Mediator.installTo( WUFile.prototype );
2516     
2517         /**
2518          * 文件状态值,具体包括以下几种类型:
2519          * * `inited` 初始状态
2520          * * `queued` 已经进入队列, 等待上传
2521          * * `progress` 上传中
2522          * * `complete` 上传完成。
2523          * * `error` 上传出错,可重试
2524          * * `interrupt` 上传中断,可续传。
2525          * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。
2526          * * `cancelled` 文件被移除。
2527          * @property {Object} Status
2528          * @namespace File
2529          * @class File
2530          * @static
2531          */
2532         WUFile.Status = {
2533             INITED:     'inited',    // 初始状态
2534             QUEUED:     'queued',    // 已经进入队列, 等待上传
2535             PROGRESS:   'progress',    // 上传中
2536             ERROR:      'error',    // 上传出错,可重试
2537             COMPLETE:   'complete',    // 上传完成。
2538             CANCELLED:  'cancelled',    // 上传取消。
2539             INTERRUPT:  'interrupt',    // 上传中断,可续传。
2540             INVALID:    'invalid'    // 文件不合格,不能重试上传。
2541         };
2542     
2543         return WUFile;
2544     });
2545     
2546     /**
2547      * @fileOverview 文件队列
2548      */
2549     define('queue',[
2550         'base',
2551         'mediator',
2552         'file'
2553     ], function( Base, Mediator, WUFile ) {
2554     
2555         var $ = Base.$,
2556             STATUS = WUFile.Status;
2557     
2558         /**
2559          * 文件队列, 用来存储各个状态中的文件。
2560          * @class Queue
2561          * @extends Mediator
2562          */
2563         function Queue() {
2564     
2565             /**
2566              * 统计文件数。
2567              * * `numOfQueue` 队列中的文件数。
2568              * * `numOfSuccess` 上传成功的文件数
2569              * * `numOfCancel` 被取消的文件数
2570              * * `numOfProgress` 正在上传中的文件数
2571              * * `numOfUploadFailed` 上传错误的文件数。
2572              * * `numOfInvalid` 无效的文件数。
2573              * * `numofDeleted` 被移除的文件数。
2574              * @property {Object} stats
2575              */
2576             this.stats = {
2577                 numOfQueue: 0,
2578                 numOfSuccess: 0,
2579                 numOfCancel: 0,
2580                 numOfProgress: 0,
2581                 numOfUploadFailed: 0,
2582                 numOfInvalid: 0,
2583                 numofDeleted: 0,
2584                 numofInterrupt: 0
2585             };
2586     
2587             // 上传队列,仅包括等待上传的文件
2588             this._queue = [];
2589     
2590             // 存储所有文件
2591             this._map = {};
2592         }
2593     
2594         $.extend( Queue.prototype, {
2595     
2596             /**
2597              * 将新文件加入对队列尾部
2598              *
2599              * @method append
2600              * @param  {File} file   文件对象
2601              */
2602             append: function( file ) {
2603                 this._queue.push( file );
2604                 this._fileAdded( file );
2605                 return this;
2606             },
2607     
2608             /**
2609              * 将新文件加入对队列头部
2610              *
2611              * @method prepend
2612              * @param  {File} file   文件对象
2613              */
2614             prepend: function( file ) {
2615                 this._queue.unshift( file );
2616                 this._fileAdded( file );
2617                 return this;
2618             },
2619     
2620             /**
2621              * 获取文件对象
2622              *
2623              * @method getFile
2624              * @param  {String} fileId   文件ID
2625              * @return {File}
2626              */
2627             getFile: function( fileId ) {
2628                 if ( typeof fileId !== 'string' ) {
2629                     return fileId;
2630                 }
2631                 return this._map[ fileId ];
2632             },
2633     
2634             /**
2635              * 从队列中取出一个指定状态的文件。
2636              * @grammar fetch( status ) => File
2637              * @method fetch
2638              * @param {String} status [文件状态值](#WebUploader:File:File.Status)
2639              * @return {File} [File](#WebUploader:File)
2640              */
2641             fetch: function( status ) {
2642                 var len = this._queue.length,
2643                     i, file;
2644     
2645                 status = status || STATUS.QUEUED;
2646     
2647                 for ( i = 0; i < len; i++ ) {
2648                     file = this._queue[ i ];
2649     
2650                     if ( status === file.getStatus() ) {
2651                         return file;
2652                     }
2653                 }
2654     
2655                 return null;
2656             },
2657     
2658             /**
2659              * 对队列进行排序,能够控制文件上传顺序。
2660              * @grammar sort( fn ) => undefined
2661              * @method sort
2662              * @param {Function} fn 排序方法
2663              */
2664             sort: function( fn ) {
2665                 if ( typeof fn === 'function' ) {
2666                     this._queue.sort( fn );
2667                 }
2668             },
2669     
2670             /**
2671              * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。
2672              * @grammar getFiles( [status1[, status2 ...]] ) => Array
2673              * @method getFiles
2674              * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)
2675              */
2676             getFiles: function() {
2677                 var sts = [].slice.call( arguments, 0 ),
2678                     ret = [],
2679                     i = 0,
2680                     len = this._queue.length,
2681                     file;
2682     
2683                 for ( ; i < len; i++ ) {
2684                     file = this._queue[ i ];
2685     
2686                     if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
2687                         continue;
2688                     }
2689     
2690                     ret.push( file );
2691                 }
2692     
2693                 return ret;
2694             },
2695     
2696             /**
2697              * 在队列中删除文件。
2698              * @grammar removeFile( file ) => Array
2699              * @method removeFile
2700              * @param {File} 文件对象。
2701              */
2702             removeFile: function( file ) {
2703                 var me = this,
2704                     existing = this._map[ file.id ];
2705     
2706                 if ( existing ) {
2707                     delete this._map[ file.id ];
2708                     file.destroy();
2709                     this.stats.numofDeleted++;
2710                 }
2711             },
2712     
2713             _fileAdded: function( file ) {
2714                 var me = this,
2715                     existing = this._map[ file.id ];
2716     
2717                 if ( !existing ) {
2718                     this._map[ file.id ] = file;
2719     
2720                     file.on( 'statuschange', function( cur, pre ) {
2721                         me._onFileStatusChange( cur, pre );
2722                     });
2723                 }
2724             },
2725     
2726             _onFileStatusChange: function( curStatus, preStatus ) {
2727                 var stats = this.stats;
2728     
2729                 switch ( preStatus ) {
2730                     case STATUS.PROGRESS:
2731                         stats.numOfProgress--;
2732                         break;
2733     
2734                     case STATUS.QUEUED:
2735                         stats.numOfQueue --;
2736                         break;
2737     
2738                     case STATUS.ERROR:
2739                         stats.numOfUploadFailed--;
2740                         break;
2741     
2742                     case STATUS.INVALID:
2743                         stats.numOfInvalid--;
2744                         break;
2745     
2746                     case STATUS.INTERRUPT:
2747                         stats.numofInterrupt--;
2748                         break;
2749                 }
2750     
2751                 switch ( curStatus ) {
2752                     case STATUS.QUEUED:
2753                         stats.numOfQueue++;
2754                         break;
2755     
2756                     case STATUS.PROGRESS:
2757                         stats.numOfProgress++;
2758                         break;
2759     
2760                     case STATUS.ERROR:
2761                         stats.numOfUploadFailed++;
2762                         break;
2763     
2764                     case STATUS.COMPLETE:
2765                         stats.numOfSuccess++;
2766                         break;
2767     
2768                     case STATUS.CANCELLED:
2769                         stats.numOfCancel++;
2770                         break;
2771     
2772     
2773                     case STATUS.INVALID:
2774                         stats.numOfInvalid++;
2775                         break;
2776     
2777                     case STATUS.INTERRUPT:
2778                         stats.numofInterrupt++;
2779                         break;
2780                 }
2781             }
2782     
2783         });
2784     
2785         Mediator.installTo( Queue.prototype );
2786     
2787         return Queue;
2788     });
2789     /**
2790      * @fileOverview 队列
2791      */
2792     define('widgets/queue',[
2793         'base',
2794         'uploader',
2795         'queue',
2796         'file',
2797         'lib/file',
2798         'runtime/client',
2799         'widgets/widget'
2800     ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {
2801     
2802         var $ = Base.$,
2803             rExt = /\.\w+$/,
2804             Status = WUFile.Status;
2805     
2806         return Uploader.register({
2807             name: 'queue',
2808     
2809             init: function( opts ) {
2810                 var me = this,
2811                     deferred, len, i, item, arr, accept, runtime;
2812     
2813                 if ( $.isPlainObject( opts.accept ) ) {
2814                     opts.accept = [ opts.accept ];
2815                 }
2816     
2817                 // accept中的中生成匹配正则。
2818                 if ( opts.accept ) {
2819                     arr = [];
2820     
2821                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
2822                         item = opts.accept[ i ].extensions;
2823                         item && arr.push( item );
2824                     }
2825     
2826                     if ( arr.length ) {
2827                         accept = '\\.' + arr.join(',')
2828                                 .replace( /,/g, '$|\\.' )
2829                                 .replace( /\*/g, '.*' ) + '$';
2830                     }
2831     
2832                     me.accept = new RegExp( accept, 'i' );
2833                 }
2834     
2835                 me.queue = new Queue();
2836                 me.stats = me.queue.stats;
2837     
2838                 // 如果当前不是html5运行时,那就算了。
2839                 // 不执行后续操作
2840                 if ( this.request('predict-runtime-type') !== 'html5' ) {
2841                     return;
2842                 }
2843     
2844                 // 创建一个 html5 运行时的 placeholder
2845                 // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
2846                 deferred = Base.Deferred();
2847                 this.placeholder = runtime = new RuntimeClient('Placeholder');
2848                 runtime.connectRuntime({
2849                     runtimeOrder: 'html5'
2850                 }, function() {
2851                     me._ruid = runtime.getRuid();
2852                     deferred.resolve();
2853                 });
2854                 return deferred.promise();
2855             },
2856     
2857     
2858             // 为了支持外部直接添加一个原生File对象。
2859             _wrapFile: function( file ) {
2860                 if ( !(file instanceof WUFile) ) {
2861     
2862                     if ( !(file instanceof File) ) {
2863                         if ( !this._ruid ) {
2864                             throw new Error('Can\'t add external files.');
2865                         }
2866                         file = new File( this._ruid, file );
2867                     }
2868     
2869                     file = new WUFile( file );
2870                 }
2871     
2872                 return file;
2873             },
2874     
2875             // 判断文件是否可以被加入队列
2876             acceptFile: function( file ) {
2877                 var invalid = !file || !file.size || this.accept &&
2878     
2879                         // 如果名字中有后缀,才做后缀白名单处理。
2880                         rExt.exec( file.name ) && !this.accept.test( file.name );
2881     
2882                 return !invalid;
2883             },
2884     
2885     
2886             /**
2887              * @event beforeFileQueued
2888              * @param {File} file File对象
2889              * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。
2890              * @for  Uploader
2891              */
2892     
2893             /**
2894              * @event fileQueued
2895              * @param {File} file File对象
2896              * @description 当文件被加入队列以后触发。
2897              * @for  Uploader
2898              */
2899     
2900             _addFile: function( file ) {
2901                 var me = this;
2902     
2903                 file = me._wrapFile( file );
2904     
2905                 // 不过类型判断允许不允许,先派送 `beforeFileQueued`
2906                 if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {
2907                     return;
2908                 }
2909     
2910                 // 类型不匹配,则派送错误事件,并返回。
2911                 if ( !me.acceptFile( file ) ) {
2912                     me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );
2913                     return;
2914                 }
2915     
2916                 me.queue.append( file );
2917                 me.owner.trigger( 'fileQueued', file );
2918                 return file;
2919             },
2920     
2921             getFile: function( fileId ) {
2922                 return this.queue.getFile( fileId );
2923             },
2924     
2925             /**
2926              * @event filesQueued
2927              * @param {File} files 数组,内容为原始File(lib/File)对象。
2928              * @description 当一批文件添加进队列以后触发。
2929              * @for  Uploader
2930              */
2931             
2932             /**
2933              * @property {Boolean} [auto=false]
2934              * @namespace options
2935              * @for Uploader
2936              * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。
2937              * 
2938              */
2939     
2940             /**
2941              * @method addFiles
2942              * @grammar addFiles( file ) => undefined
2943              * @grammar addFiles( [file1, file2 ...] ) => undefined
2944              * @param {Array of File or File} [files] Files 对象 数组
2945              * @description 添加文件到队列
2946              * @for  Uploader
2947              */
2948             addFile: function( files ) {
2949                 var me = this;
2950     
2951                 if ( !files.length ) {
2952                     files = [ files ];
2953                 }
2954     
2955                 files = $.map( files, function( file ) {
2956                     return me._addFile( file );
2957                 });
2958     
2959                 me.owner.trigger( 'filesQueued', files );
2960     
2961                 if ( me.options.auto ) {
2962                     setTimeout(function() {
2963                         me.request('start-upload');
2964                     }, 20 );
2965                 }
2966             },
2967     
2968             getStats: function() {
2969                 return this.stats;
2970             },
2971     
2972             /**
2973              * @event fileDequeued
2974              * @param {File} file File对象
2975              * @description 当文件被移除队列后触发。
2976              * @for  Uploader
2977              */
2978     
2979              /**
2980              * @method removeFile
2981              * @grammar removeFile( file ) => undefined
2982              * @grammar removeFile( id ) => undefined
2983              * @grammar removeFile( file, true ) => undefined
2984              * @grammar removeFile( id, true ) => undefined
2985              * @param {File|id} file File对象或这File对象的id
2986              * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。
2987              * @for  Uploader
2988              * @example
2989              *
2990              * $li.on('click', '.remove-this', function() {
2991              *     uploader.removeFile( file );
2992              * })
2993              */
2994             removeFile: function( file, remove ) {
2995                 var me = this;
2996     
2997                 file = file.id ? file : me.queue.getFile( file );
2998     
2999                 this.request( 'cancel-file', file );
3000     
3001                 if ( remove ) {
3002                     this.queue.removeFile( file );
3003                 }
3004             },
3005     
3006             /**
3007              * @method getFiles
3008              * @grammar getFiles() => Array
3009              * @grammar getFiles( status1, status2, status... ) => Array
3010              * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。
3011              * @for  Uploader
3012              * @example
3013              * console.log( uploader.getFiles() );    // => all files
3014              * console.log( uploader.getFiles('error') )    // => all error files.
3015              */
3016             getFiles: function() {
3017                 return this.queue.getFiles.apply( this.queue, arguments );
3018             },
3019     
3020             fetchFile: function() {
3021                 return this.queue.fetch.apply( this.queue, arguments );
3022             },
3023     
3024             /**
3025              * @method retry
3026              * @grammar retry() => undefined
3027              * @grammar retry( file ) => undefined
3028              * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。
3029              * @for  Uploader
3030              * @example
3031              * function retry() {
3032              *     uploader.retry();
3033              * }
3034              */
3035             retry: function( file, noForceStart ) {
3036                 var me = this,
3037                     files, i, len;
3038     
3039                 if ( file ) {
3040                     file = file.id ? file : me.queue.getFile( file );
3041                     file.setStatus( Status.QUEUED );
3042                     noForceStart || me.request('start-upload');
3043                     return;
3044                 }
3045     
3046                 files = me.queue.getFiles( Status.ERROR );
3047                 i = 0;
3048                 len = files.length;
3049     
3050                 for ( ; i < len; i++ ) {
3051                     file = files[ i ];
3052                     file.setStatus( Status.QUEUED );
3053                 }
3054     
3055                 me.request('start-upload');
3056             },
3057     
3058             /**
3059              * @method sort
3060              * @grammar sort( fn ) => undefined
3061              * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。
3062              * @for  Uploader
3063              */
3064             sortFiles: function() {
3065                 return this.queue.sort.apply( this.queue, arguments );
3066             },
3067     
3068             /**
3069              * @event reset
3070              * @description 当 uploader 被重置的时候触发。
3071              * @for  Uploader
3072              */
3073     
3074             /**
3075              * @method reset
3076              * @grammar reset() => undefined
3077              * @description 重置uploader。目前只重置了队列。
3078              * @for  Uploader
3079              * @example
3080              * uploader.reset();
3081              */
3082             reset: function() {
3083                 this.owner.trigger('reset');
3084                 this.queue = new Queue();
3085                 this.stats = this.queue.stats;
3086             },
3087     
3088             destroy: function() {
3089                 this.reset();
3090                 this.placeholder && this.placeholder.destroy();
3091             }
3092         });
3093     
3094     });
3095     /**
3096      * @fileOverview 添加获取Runtime相关信息的方法。
3097      */
3098     define('widgets/runtime',[
3099         'uploader',
3100         'runtime/runtime',
3101         'widgets/widget'
3102     ], function( Uploader, Runtime ) {
3103     
3104         Uploader.support = function() {
3105             return Runtime.hasRuntime.apply( Runtime, arguments );
3106         };
3107     
3108         /**
3109          * @property {Object} [runtimeOrder=html5,flash]
3110          * @namespace options
3111          * @for Uploader
3112          * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash.
3113          *
3114          * 可以将此值设置成 `flash`,来强制使用 flash 运行时。
3115          */
3116     
3117         return Uploader.register({
3118             name: 'runtime',
3119     
3120             init: function() {
3121                 if ( !this.predictRuntimeType() ) {
3122                     throw Error('Runtime Error');
3123                 }
3124             },
3125     
3126             /**
3127              * 预测Uploader将采用哪个`Runtime`
3128              * @grammar predictRuntimeType() => String
3129              * @method predictRuntimeType
3130              * @for  Uploader
3131              */
3132             predictRuntimeType: function() {
3133                 var orders = this.options.runtimeOrder || Runtime.orders,
3134                     type = this.type,
3135                     i, len;
3136     
3137                 if ( !type ) {
3138                     orders = orders.split( /\s*,\s*/g );
3139     
3140                     for ( i = 0, len = orders.length; i < len; i++ ) {
3141                         if ( Runtime.hasRuntime( orders[ i ] ) ) {
3142                             this.type = type = orders[ i ];
3143                             break;
3144                         }
3145                     }
3146                 }
3147     
3148                 return type;
3149             }
3150         });
3151     });
3152     /**
3153      * @fileOverview Transport
3154      */
3155     define('lib/transport',[
3156         'base',
3157         'runtime/client',
3158         'mediator'
3159     ], function( Base, RuntimeClient, Mediator ) {
3160     
3161         var $ = Base.$;
3162     
3163         function Transport( opts ) {
3164             var me = this;
3165     
3166             opts = me.options = $.extend( true, {}, Transport.options, opts || {} );
3167             RuntimeClient.call( this, 'Transport' );
3168     
3169             this._blob = null;
3170             this._formData = opts.formData || {};
3171             this._headers = opts.headers || {};
3172     
3173             this.on( 'progress', this._timeout );
3174             this.on( 'load error', function() {
3175                 me.trigger( 'progress', 1 );
3176                 clearTimeout( me._timer );
3177             });
3178         }
3179     
3180         Transport.options = {
3181             server: '',
3182             method: 'POST',
3183     
3184             // 跨域时,是否允许携带cookie, 只有html5 runtime才有效
3185             withCredentials: false,
3186             fileVal: 'file',
3187             timeout: 2 * 60 * 1000,    // 2分钟
3188             formData: {},
3189             headers: {},
3190             sendAsBinary: false
3191         };
3192     
3193         $.extend( Transport.prototype, {
3194     
3195             // 添加Blob, 只能添加一次,最后一次有效。
3196             appendBlob: function( key, blob, filename ) {
3197                 var me = this,
3198                     opts = me.options;
3199     
3200                 if ( me.getRuid() ) {
3201                     me.disconnectRuntime();
3202                 }
3203     
3204                 // 连接到blob归属的同一个runtime.
3205                 me.connectRuntime( blob.ruid, function() {
3206                     me.exec('init');
3207                 });
3208     
3209                 me._blob = blob;
3210                 opts.fileVal = key || opts.fileVal;
3211                 opts.filename = filename || opts.filename;
3212             },
3213     
3214             // 添加其他字段
3215             append: function( key, value ) {
3216                 if ( typeof key === 'object' ) {
3217                     $.extend( this._formData, key );
3218                 } else {
3219                     this._formData[ key ] = value;
3220                 }
3221             },
3222     
3223             setRequestHeader: function( key, value ) {
3224                 if ( typeof key === 'object' ) {
3225                     $.extend( this._headers, key );
3226                 } else {
3227                     this._headers[ key ] = value;
3228                 }
3229             },
3230     
3231             send: function( method ) {
3232                 this.exec( 'send', method );
3233                 this._timeout();
3234             },
3235     
3236             abort: function() {
3237                 clearTimeout( this._timer );
3238                 return this.exec('abort');
3239             },
3240     
3241             destroy: function() {
3242                 this.trigger('destroy');
3243                 this.off();
3244                 this.exec('destroy');
3245                 this.disconnectRuntime();
3246             },
3247     
3248             getResponse: function() {
3249                 return this.exec('getResponse');
3250             },
3251     
3252             getResponseAsJson: function() {
3253                 return this.exec('getResponseAsJson');
3254             },
3255     
3256             getStatus: function() {
3257                 return this.exec('getStatus');
3258             },
3259     
3260             _timeout: function() {
3261                 var me = this,
3262                     duration = me.options.timeout;
3263     
3264                 if ( !duration ) {
3265                     return;
3266                 }
3267     
3268                 clearTimeout( me._timer );
3269                 me._timer = setTimeout(function() {
3270                     me.abort();
3271                     me.trigger( 'error', 'timeout' );
3272                 }, duration );
3273             }
3274     
3275         });
3276     
3277         // 让Transport具备事件功能。
3278         Mediator.installTo( Transport.prototype );
3279     
3280         return Transport;
3281     });
3282     /**
3283      * @fileOverview 负责文件上传相关。
3284      */
3285     define('widgets/upload',[
3286         'base',
3287         'uploader',
3288         'file',
3289         'lib/transport',
3290         'widgets/widget'
3291     ], function( Base, Uploader, WUFile, Transport ) {
3292     
3293         var $ = Base.$,
3294             isPromise = Base.isPromise,
3295             Status = WUFile.Status;
3296     
3297         // 添加默认配置项
3298         $.extend( Uploader.options, {
3299     
3300     
3301             /**
3302              * @property {Boolean} [prepareNextFile=false]
3303              * @namespace options
3304              * @for Uploader
3305              * @description 是否允许在文件传输时提前把下一个文件准备好。
3306              * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。
3307              * 如果能提前在当前文件传输期处理,可以节省总体耗时。
3308              */
3309             prepareNextFile: false,
3310     
3311             /**
3312              * @property {Boolean} [chunked=false]
3313              * @namespace options
3314              * @for Uploader
3315              * @description 是否要分片处理大文件上传。
3316              */
3317             chunked: false,
3318     
3319             /**
3320              * @property {Boolean} [chunkSize=5242880]
3321              * @namespace options
3322              * @for Uploader
3323              * @description 如果要分片,分多大一片? 默认大小为5M.
3324              */
3325             chunkSize: 5 * 1024 * 1024,
3326     
3327             /**
3328              * @property {Boolean} [chunkRetry=2]
3329              * @namespace options
3330              * @for Uploader
3331              * @description 如果某个分片由于网络问题出错,允许自动重传多少次?
3332              */
3333             chunkRetry: 2,
3334     
3335             /**
3336              * @property {Boolean} [threads=3]
3337              * @namespace options
3338              * @for Uploader
3339              * @description 上传并发数。允许同时最大上传进程数。
3340              */
3341             threads: 3,
3342     
3343     
3344             /**
3345              * @property {Object} [formData={}]
3346              * @namespace options
3347              * @for Uploader
3348              * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。
3349              */
3350             formData: {}
3351     
3352             /**
3353              * @property {Object} [fileVal='file']
3354              * @namespace options
3355              * @for Uploader
3356              * @description 设置文件上传域的name。
3357              */
3358     
3359             /**
3360              * @property {Object} [method='POST']
3361              * @namespace options
3362              * @for Uploader
3363              * @description 文件上传方式,`POST`或者`GET`。
3364              */
3365     
3366             /**
3367              * @property {Object} [sendAsBinary=false]
3368              * @namespace options
3369              * @for Uploader
3370              * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容,
3371              * 其他参数在$_GET数组中。
3372              */
3373         });
3374     
3375         // 负责将文件切片。
3376         function CuteFile( file, chunkSize ) {
3377             var pending = [],
3378                 blob = file.source,
3379                 total = blob.size,
3380                 chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
3381                 start = 0,
3382                 index = 0,
3383                 len, api;
3384     
3385             api = {
3386                 file: file,
3387     
3388                 has: function() {
3389                     return !!pending.length;
3390                 },
3391     
3392                 shift: function() {
3393                     return pending.shift();
3394                 },
3395     
3396                 unshift: function( block ) {
3397                     pending.unshift( block );
3398                 }
3399             };
3400     
3401             while ( index < chunks ) {
3402                 len = Math.min( chunkSize, total - start );
3403     
3404                 pending.push({
3405                     file: file,
3406                     start: start,
3407                     end: chunkSize ? (start + len) : total,
3408                     total: total,
3409                     chunks: chunks,
3410                     chunk: index++,
3411                     cuted: api
3412                 });
3413                 start += len;
3414             }
3415     
3416             file.blocks = pending.concat();
3417             file.remaning = pending.length;
3418     
3419             return api;
3420         }
3421     
3422         Uploader.register({
3423             name: 'upload',
3424     
3425             init: function() {
3426                 var owner = this.owner,
3427                     me = this;
3428     
3429                 this.runing = false;
3430                 this.progress = false;
3431     
3432                 owner
3433                     .on( 'startUpload', function() {
3434                         me.progress = true;
3435                     })
3436                     .on( 'uploadFinished', function() {
3437                         me.progress = false;
3438                     });
3439     
3440                 // 记录当前正在传的数据,跟threads相关
3441                 this.pool = [];
3442     
3443                 // 缓存分好片的文件。
3444                 this.stack = [];
3445     
3446                 // 缓存即将上传的文件。
3447                 this.pending = [];
3448     
3449                 // 跟踪还有多少分片在上传中但是没有完成上传。
3450                 this.remaning = 0;
3451                 this.__tick = Base.bindFn( this._tick, this );
3452     
3453                 owner.on( 'uploadComplete', function( file ) {
3454     
3455                     // 把其他块取消了。
3456                     file.blocks && $.each( file.blocks, function( _, v ) {
3457                         v.transport && (v.transport.abort(), v.transport.destroy());
3458                         delete v.transport;
3459                     });
3460     
3461                     delete file.blocks;
3462                     delete file.remaning;
3463                 });
3464             },
3465     
3466             reset: function() {
3467                 this.request( 'stop-upload', true );
3468                 this.runing = false;
3469                 this.pool = [];
3470                 this.stack = [];
3471                 this.pending = [];
3472                 this.remaning = 0;
3473                 this._trigged = false;
3474                 this._promise = null;
3475             },
3476     
3477             /**
3478              * @event startUpload
3479              * @description 当开始上传流程时触发。
3480              * @for  Uploader
3481              */
3482     
3483             /**
3484              * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。
3485              *
3486              * 可以指定开始某一个文件。
3487              * @grammar upload() => undefined
3488              * @grammar upload( file | fileId) => undefined
3489              * @method upload
3490              * @for  Uploader
3491              */
3492             startUpload: function(file) {
3493                 var me = this;
3494     
3495                 // 移出invalid的文件
3496                 $.each( me.request( 'get-files', Status.INVALID ), function() {
3497                     me.request( 'remove-file', this );
3498                 });
3499     
3500                 // 如果指定了开始某个文件,则只开始指定文件。
3501                 if ( file ) {
3502                     file = file.id ? file : me.request( 'get-file', file );
3503     
3504                     if (file.getStatus() === Status.INTERRUPT) {
3505                         $.each( me.pool, function( _, v ) {
3506     
3507                             // 之前暂停过。
3508                             if (v.file !== file) {
3509                                 return;
3510                             }
3511     
3512                             v.transport && v.transport.send();
3513                         });
3514     
3515                         file.setStatus( Status.QUEUED );
3516                     } else if (file.getStatus() === Status.PROGRESS) {
3517                         return;
3518                     } else {
3519                         file.setStatus( Status.QUEUED );
3520                     }
3521                 } else {
3522                     $.each( me.request( 'get-files', [ Status.INITED ] ), function() {
3523                         this.setStatus( Status.QUEUED );
3524                     });
3525                 }
3526     
3527                 if ( me.runing ) {
3528                     return;
3529                 }
3530     
3531                 me.runing = true;
3532     
3533                 var files = [];
3534     
3535                 // 如果有暂停的,则续传
3536                 $.each( me.pool, function( _, v ) {
3537                     var file = v.file;
3538     
3539                     if ( file.getStatus() === Status.INTERRUPT ) {
3540                         files.push(file);
3541                         me._trigged = false;
3542                         v.transport && v.transport.send();
3543                     }
3544                 });
3545     
3546                 var file;
3547                 while ( (file = files.shift()) ) {
3548                     file.setStatus( Status.PROGRESS );
3549                 }
3550     
3551                 file || $.each( me.request( 'get-files',
3552                         Status.INTERRUPT ), function() {
3553                     this.setStatus( Status.PROGRESS );
3554                 });
3555     
3556                 me._trigged = false;
3557                 Base.nextTick( me.__tick );
3558                 me.owner.trigger('startUpload');
3559             },
3560     
3561             /**
3562              * @event stopUpload
3563              * @description 当开始上传流程暂停时触发。
3564              * @for  Uploader
3565              */
3566     
3567             /**
3568              * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。
3569              *
3570              * 如果第一个参数是文件,则只暂停指定文件。
3571              * @grammar stop() => undefined
3572              * @grammar stop( true ) => undefined
3573              * @grammar stop( file ) => undefined
3574              * @method stop
3575              * @for  Uploader
3576              */
3577             stopUpload: function( file, interrupt ) {
3578                 var me = this;
3579     
3580                 if (file === true) {
3581                     interrupt = file;
3582                     file = null;
3583                 }
3584     
3585                 if ( me.runing === false ) {
3586                     return;
3587                 }
3588     
3589                 // 如果只是暂停某个文件。
3590                 if ( file ) {
3591                     file = file.id ? file : me.request( 'get-file', file );
3592     
3593                     if ( file.getStatus() !== Status.PROGRESS &&
3594                             file.getStatus() !== Status.QUEUED ) {
3595                         return;
3596                     }
3597     
3598                     file.setStatus( Status.INTERRUPT );
3599                     $.each( me.pool, function( _, v ) {
3600     
3601                         // 只 abort 指定的文件。
3602                         if (v.file !== file) {
3603                             return;
3604                         }
3605     
3606                         v.transport && v.transport.abort();
3607                         me._putback(v);
3608                         me._popBlock(v);
3609                     });
3610     
3611                     return Base.nextTick( me.__tick );
3612                 }
3613     
3614                 me.runing = false;
3615     
3616                 if (this._promise && this._promise.file) {
3617                     this._promise.file.setStatus( Status.INTERRUPT );
3618                 }
3619     
3620                 interrupt && $.each( me.pool, function( _, v ) {
3621                     v.transport && v.transport.abort();
3622                     v.file.setStatus( Status.INTERRUPT );
3623                 });
3624     
3625                 me.owner.trigger('stopUpload');
3626             },
3627     
3628             /**
3629              * @method cancelFile
3630              * @grammar cancelFile( file ) => undefined
3631              * @grammar cancelFile( id ) => undefined
3632              * @param {File|id} file File对象或这File对象的id
3633              * @description 标记文件状态为已取消, 同时将中断文件传输。
3634              * @for  Uploader
3635              * @example
3636              *
3637              * $li.on('click', '.remove-this', function() {
3638              *     uploader.cancelFile( file );
3639              * })
3640              */
3641             cancelFile: function( file ) {
3642                 file = file.id ? file : this.request( 'get-file', file );
3643     
3644                 // 如果正在上传。
3645                 file.blocks && $.each( file.blocks, function( _, v ) {
3646                     var _tr = v.transport;
3647     
3648                     if ( _tr ) {
3649                         _tr.abort();
3650                         _tr.destroy();
3651                         delete v.transport;
3652                     }
3653                 });
3654     
3655                 file.setStatus( Status.CANCELLED );
3656                 this.owner.trigger( 'fileDequeued', file );
3657             },
3658     
3659             /**
3660              * 判断`Uplaode`r是否正在上传中。
3661              * @grammar isInProgress() => Boolean
3662              * @method isInProgress
3663              * @for  Uploader
3664              */
3665             isInProgress: function() {
3666                 return !!this.progress;
3667             },
3668     
3669             _getStats: function() {
3670                 return this.request('get-stats');
3671             },
3672     
3673             /**
3674              * 掉过一个文件上传,直接标记指定文件为已上传状态。
3675              * @grammar skipFile( file ) => undefined
3676              * @method skipFile
3677              * @for  Uploader
3678              */
3679             skipFile: function( file, status ) {
3680                 file = file.id ? file : this.request( 'get-file', file );
3681     
3682                 file.setStatus( status || Status.COMPLETE );
3683                 file.skipped = true;
3684     
3685                 // 如果正在上传。
3686                 file.blocks && $.each( file.blocks, function( _, v ) {
3687                     var _tr = v.transport;
3688     
3689                     if ( _tr ) {
3690                         _tr.abort();
3691                         _tr.destroy();
3692                         delete v.transport;
3693                     }
3694                 });
3695     
3696                 this.owner.trigger( 'uploadSkip', file );
3697             },
3698     
3699             /**
3700              * @event uploadFinished
3701              * @description 当所有文件上传结束时触发。
3702              * @for  Uploader
3703              */
3704             _tick: function() {
3705                 var me = this,
3706                     opts = me.options,
3707                     fn, val;
3708     
3709                 // 上一个promise还没有结束,则等待完成后再执行。
3710                 if ( me._promise ) {
3711                     return me._promise.always( me.__tick );
3712                 }
3713     
3714                 // 还有位置,且还有文件要处理的话。
3715                 if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
3716                     me._trigged = false;
3717     
3718                     fn = function( val ) {
3719                         me._promise = null;
3720     
3721                         // 有可能是reject过来的,所以要检测val的类型。
3722                         val && val.file && me._startSend( val );
3723                         Base.nextTick( me.__tick );
3724                     };
3725     
3726                     me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
3727     
3728                 // 没有要上传的了,且没有正在传输的了。
3729                 } else if ( !me.remaning && !me._getStats().numOfQueue &&
3730                     !me._getStats().numofInterrupt ) {
3731                     me.runing = false;
3732     
3733                     me._trigged || Base.nextTick(function() {
3734                         me.owner.trigger('uploadFinished');
3735                     });
3736                     me._trigged = true;
3737                 }
3738             },
3739     
3740             _putback: function(block) {
3741                 var idx;
3742     
3743                 block.cuted.unshift(block);
3744                 idx = this.stack.indexOf(block.cuted);
3745     
3746                 if (!~idx) {
3747                     this.stack.unshift(block.cuted);
3748                 }
3749             },
3750     
3751             _getStack: function() {
3752                 var i = 0,
3753                     act;
3754     
3755                 while ( (act = this.stack[ i++ ]) ) {
3756                     if ( act.has() && act.file.getStatus() === Status.PROGRESS ) {
3757                         return act;
3758                     } else if (!act.has() ||
3759                             act.file.getStatus() !== Status.PROGRESS &&
3760                             act.file.getStatus() !== Status.INTERRUPT ) {
3761     
3762                         // 把已经处理完了的,或者,状态为非 progress(上传中)、
3763                         // interupt(暂停中) 的移除。
3764                         this.stack.splice( --i, 1 );
3765                     }
3766                 }
3767     
3768                 return null;
3769             },
3770     
3771             _nextBlock: function() {
3772                 var me = this,
3773                     opts = me.options,
3774                     act, next, done, preparing;
3775     
3776                 // 如果当前文件还有没有需要传输的,则直接返回剩下的。
3777                 if ( (act = this._getStack()) ) {
3778     
3779                     // 是否提前准备下一个文件
3780                     if ( opts.prepareNextFile && !me.pending.length ) {
3781                         me._prepareNextFile();
3782                     }
3783     
3784                     return act.shift();
3785     
3786                 // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。
3787                 } else if ( me.runing ) {
3788     
3789                     // 如果缓存中有,则直接在缓存中取,没有则去queue中取。
3790                     if ( !me.pending.length && me._getStats().numOfQueue ) {
3791                         me._prepareNextFile();
3792                     }
3793     
3794                     next = me.pending.shift();
3795                     done = function( file ) {
3796                         if ( !file ) {
3797                             return null;
3798                         }
3799     
3800                         act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
3801                         me.stack.push(act);
3802                         return act.shift();
3803                     };
3804     
3805                     // 文件可能还在prepare中,也有可能已经完全准备好了。
3806                     if ( isPromise( next) ) {
3807                         preparing = next.file;
3808                         next = next[ next.pipe ? 'pipe' : 'then' ]( done );
3809                         next.file = preparing;
3810                         return next;
3811                     }
3812     
3813                     return done( next );
3814                 }
3815             },
3816     
3817     
3818             /**
3819              * @event uploadStart
3820              * @param {File} file File对象
3821              * @description 某个文件开始上传前触发,一个文件只会触发一次。
3822              * @for  Uploader
3823              */
3824             _prepareNextFile: function() {
3825                 var me = this,
3826                     file = me.request('fetch-file'),
3827                     pending = me.pending,
3828                     promise;
3829     
3830                 if ( file ) {
3831                     promise = me.request( 'before-send-file', file, function() {
3832     
3833                         // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued.
3834                         if ( file.getStatus() === Status.PROGRESS ||
3835                             file.getStatus() === Status.INTERRUPT ) {
3836                             return file;
3837                         }
3838     
3839                         return me._finishFile( file );
3840                     });
3841     
3842                     me.owner.trigger( 'uploadStart', file );
3843                     file.setStatus( Status.PROGRESS );
3844     
3845                     promise.file = file;
3846     
3847                     // 如果还在pending中,则替换成文件本身。
3848                     promise.done(function() {
3849                         var idx = $.inArray( promise, pending );
3850     
3851                         ~idx && pending.splice( idx, 1, file );
3852                     });
3853     
3854                     // befeore-send-file的钩子就有错误发生。
3855                     promise.fail(function( reason ) {
3856                         file.setStatus( Status.ERROR, reason );
3857                         me.owner.trigger( 'uploadError', file, reason );
3858                         me.owner.trigger( 'uploadComplete', file );
3859                     });
3860     
3861                     pending.push( promise );
3862                 }
3863             },
3864     
3865             // 让出位置了,可以让其他分片开始上传
3866             _popBlock: function( block ) {
3867                 var idx = $.inArray( block, this.pool );
3868     
3869                 this.pool.splice( idx, 1 );
3870                 block.file.remaning--;
3871                 this.remaning--;
3872             },
3873     
3874             // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。
3875             _startSend: function( block ) {
3876                 var me = this,
3877                     file = block.file,
3878                     promise;
3879     
3880                 // 有可能在 before-send-file 的 promise 期间改变了文件状态。
3881                 // 如:暂停,取消
3882                 // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。
3883                 if ( file.getStatus() !== Status.PROGRESS ) {
3884     
3885                     // 如果是中断,则还需要放回去。
3886                     if (file.getStatus() === Status.INTERRUPT) {
3887                         me._putback(block);
3888                     }
3889     
3890                     return;
3891                 }
3892     
3893                 me.pool.push( block );
3894                 me.remaning++;
3895     
3896                 // 如果没有分片,则直接使用原始的。
3897                 // 不会丢失content-type信息。
3898                 block.blob = block.chunks === 1 ? file.source :
3899                         file.source.slice( block.start, block.end );
3900     
3901                 // hook, 每个分片发送之前可能要做些异步的事情。
3902                 promise = me.request( 'before-send', block, function() {
3903     
3904                     // 有可能文件已经上传出错了,所以不需要再传输了。
3905                     if ( file.getStatus() === Status.PROGRESS ) {
3906                         me._doSend( block );
3907                     } else {
3908                         me._popBlock( block );
3909                         Base.nextTick( me.__tick );
3910                     }
3911                 });
3912     
3913                 // 如果为fail了,则跳过此分片。
3914                 promise.fail(function() {
3915                     if ( file.remaning === 1 ) {
3916                         me._finishFile( file ).always(function() {
3917                             block.percentage = 1;
3918                             me._popBlock( block );
3919                             me.owner.trigger( 'uploadComplete', file );
3920                             Base.nextTick( me.__tick );
3921                         });
3922                     } else {
3923                         block.percentage = 1;
3924                         me.updateFileProgress( file );
3925                         me._popBlock( block );
3926                         Base.nextTick( me.__tick );
3927                     }
3928                 });
3929             },
3930     
3931     
3932             /**
3933              * @event uploadBeforeSend
3934              * @param {Object} object
3935              * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。
3936              * @param {Object} headers 可以扩展此对象来控制上传头部。
3937              * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。
3938              * @for  Uploader
3939              */
3940     
3941             /**
3942              * @event uploadAccept
3943              * @param {Object} object
3944              * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。
3945              * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。
3946              * @for  Uploader
3947              */
3948     
3949             /**
3950              * @event uploadProgress
3951              * @param {File} file File对象
3952              * @param {Number} percentage 上传进度
3953              * @description 上传过程中触发,携带上传进度。
3954              * @for  Uploader
3955              */
3956     
3957     
3958             /**
3959              * @event uploadError
3960              * @param {File} file File对象
3961              * @param {String} reason 出错的code
3962              * @description 当文件上传出错时触发。
3963              * @for  Uploader
3964              */
3965     
3966             /**
3967              * @event uploadSuccess
3968              * @param {File} file File对象
3969              * @param {Object} response 服务端返回的数据
3970              * @description 当文件上传成功时触发。
3971              * @for  Uploader
3972              */
3973     
3974             /**
3975              * @event uploadComplete
3976              * @param {File} [file] File对象
3977              * @description 不管成功或者失败,文件上传完成时触发。
3978              * @for  Uploader
3979              */
3980     
3981             // 做上传操作。
3982             _doSend: function( block ) {
3983                 var me = this,
3984                     owner = me.owner,
3985                     opts = me.options,
3986                     file = block.file,
3987                     tr = new Transport( opts ),
3988                     data = $.extend({}, opts.formData ),
3989                     headers = $.extend({}, opts.headers ),
3990                     requestAccept, ret;
3991     
3992                 block.transport = tr;
3993     
3994                 tr.on( 'destroy', function() {
3995                     delete block.transport;
3996                     me._popBlock( block );
3997                     Base.nextTick( me.__tick );
3998                 });
3999     
4000                 // 广播上传进度。以文件为单位。
4001                 tr.on( 'progress', function( percentage ) {
4002                     block.percentage = percentage;
4003                     me.updateFileProgress( file );
4004                 });
4005     
4006                 // 用来询问,是否返回的结果是有错误的。
4007                 requestAccept = function( reject ) {
4008                     var fn;
4009     
4010                     ret = tr.getResponseAsJson() || {};
4011                     ret._raw = tr.getResponse();
4012                     fn = function( value ) {
4013                         reject = value;
4014                     };
4015     
4016                     // 服务端响应了,不代表成功了,询问是否响应正确。
4017                     if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {
4018                         reject = reject || 'server';
4019                     }
4020     
4021                     return reject;
4022                 };
4023     
4024                 // 尝试重试,然后广播文件上传出错。
4025                 tr.on( 'error', function( type, flag ) {
4026                     block.retried = block.retried || 0;
4027     
4028                     // 自动重试
4029                     if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&
4030                             block.retried < opts.chunkRetry ) {
4031     
4032                         block.retried++;
4033                         tr.send();
4034     
4035                     } else {
4036     
4037                         // http status 500 ~ 600
4038                         if ( !flag && type === 'server' ) {
4039                             type = requestAccept( type );
4040                         }
4041     
4042                         file.setStatus( Status.ERROR, type );
4043                         owner.trigger( 'uploadError', file, type );
4044                         owner.trigger( 'uploadComplete', file );
4045                     }
4046                 });
4047     
4048                 // 上传成功
4049                 tr.on( 'load', function() {
4050                     var reason;
4051     
4052                     // 如果非预期,转向上传出错。
4053                     if ( (reason = requestAccept()) ) {
4054                         tr.trigger( 'error', reason, true );
4055                         return;
4056                     }
4057     
4058                     // 全部上传完成。
4059                     if ( file.remaning === 1 ) {
4060                         me._finishFile( file, ret );
4061                     } else {
4062                         tr.destroy();
4063                     }
4064                 });
4065     
4066                 // 配置默认的上传字段。
4067                 data = $.extend( data, {
4068                     id: file.id,
4069                     name: file.name,
4070                     type: file.type,
4071                     lastModifiedDate: file.lastModifiedDate,
4072                     size: file.size
4073                 });
4074     
4075                 block.chunks > 1 && $.extend( data, {
4076                     chunks: block.chunks,
4077                     chunk: block.chunk
4078                 });
4079     
4080                 // 在发送之间可以添加字段什么的。。。
4081                 // 如果默认的字段不够使用,可以通过监听此事件来扩展
4082                 owner.trigger( 'uploadBeforeSend', block, data, headers );
4083     
4084                 // 开始发送。
4085                 tr.appendBlob( opts.fileVal, block.blob, file.name );
4086                 tr.append( data );
4087                 tr.setRequestHeader( headers );
4088                 tr.send();
4089             },
4090     
4091             // 完成上传。
4092             _finishFile: function( file, ret, hds ) {
4093                 var owner = this.owner;
4094     
4095                 return owner
4096                         .request( 'after-send-file', arguments, function() {
4097                             file.setStatus( Status.COMPLETE );
4098                             owner.trigger( 'uploadSuccess', file, ret, hds );
4099                         })
4100                         .fail(function( reason ) {
4101     
4102                             // 如果外部已经标记为invalid什么的,不再改状态。
4103                             if ( file.getStatus() === Status.PROGRESS ) {
4104                                 file.setStatus( Status.ERROR, reason );
4105                             }
4106     
4107                             owner.trigger( 'uploadError', file, reason );
4108                         })
4109                         .always(function() {
4110                             owner.trigger( 'uploadComplete', file );
4111                         });
4112             },
4113     
4114             updateFileProgress: function(file) {
4115                 var totalPercent = 0,
4116                     uploaded = 0;
4117     
4118                 if (!file.blocks) {
4119                     return;
4120                 }
4121     
4122                 $.each( file.blocks, function( _, v ) {
4123                     uploaded += (v.percentage || 0) * (v.end - v.start);
4124                 });
4125     
4126                 totalPercent = uploaded / file.size;
4127                 this.owner.trigger( 'uploadProgress', file, totalPercent || 0 );
4128             }
4129     
4130         });
4131     });
4132     /**
4133      * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。
4134      */
4135     
4136     define('widgets/validator',[
4137         'base',
4138         'uploader',
4139         'file',
4140         'widgets/widget'
4141     ], function( Base, Uploader, WUFile ) {
4142     
4143         var $ = Base.$,
4144             validators = {},
4145             api;
4146     
4147         /**
4148          * @event error
4149          * @param {String} type 错误类型。
4150          * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。
4151          *
4152          * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。
4153          * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。
4154          * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。
4155          * @for  Uploader
4156          */
4157     
4158         // 暴露给外面的api
4159         api = {
4160     
4161             // 添加验证器
4162             addValidator: function( type, cb ) {
4163                 validators[ type ] = cb;
4164             },
4165     
4166             // 移除验证器
4167             removeValidator: function( type ) {
4168                 delete validators[ type ];
4169             }
4170         };
4171     
4172         // 在Uploader初始化的时候启动Validators的初始化
4173         Uploader.register({
4174             name: 'validator',
4175     
4176             init: function() {
4177                 var me = this;
4178                 Base.nextTick(function() {
4179                     $.each( validators, function() {
4180                         this.call( me.owner );
4181                     });
4182                 });
4183             }
4184         });
4185     
4186         /**
4187          * @property {int} [fileNumLimit=undefined]
4188          * @namespace options
4189          * @for Uploader
4190          * @description 验证文件总数量, 超出则不允许加入队列。
4191          */
4192         api.addValidator( 'fileNumLimit', function() {
4193             var uploader = this,
4194                 opts = uploader.options,
4195                 count = 0,
4196                 max = parseInt( opts.fileNumLimit, 10 ),
4197                 flag = true;
4198     
4199             if ( !max ) {
4200                 return;
4201             }
4202     
4203             uploader.on( 'beforeFileQueued', function( file ) {
4204     
4205                 if ( count >= max && flag ) {
4206                     flag = false;
4207                     this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );
4208                     setTimeout(function() {
4209                         flag = true;
4210                     }, 1 );
4211                 }
4212     
4213                 return count >= max ? false : true;
4214             });
4215     
4216             uploader.on( 'fileQueued', function() {
4217                 count++;
4218             });
4219     
4220             uploader.on( 'fileDequeued', function() {
4221                 count--;
4222             });
4223     
4224             uploader.on( 'reset', function() {
4225                 count = 0;
4226             });
4227         });
4228     
4229     
4230         /**
4231          * @property {int} [fileSizeLimit=undefined]
4232          * @namespace options
4233          * @for Uploader
4234          * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。
4235          */
4236         api.addValidator( 'fileSizeLimit', function() {
4237             var uploader = this,
4238                 opts = uploader.options,
4239                 count = 0,
4240                 max = parseInt( opts.fileSizeLimit, 10 ),
4241                 flag = true;
4242     
4243             if ( !max ) {
4244                 return;
4245             }
4246     
4247             uploader.on( 'beforeFileQueued', function( file ) {
4248                 var invalid = count + file.size > max;
4249     
4250                 if ( invalid && flag ) {
4251                     flag = false;
4252                     this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );
4253                     setTimeout(function() {
4254                         flag = true;
4255                     }, 1 );
4256                 }
4257     
4258                 return invalid ? false : true;
4259             });
4260     
4261             uploader.on( 'fileQueued', function( file ) {
4262                 count += file.size;
4263             });
4264     
4265             uploader.on( 'fileDequeued', function( file ) {
4266                 count -= file.size;
4267             });
4268     
4269             uploader.on( 'reset', function() {
4270                 count = 0;
4271             });
4272         });
4273     
4274         /**
4275          * @property {int} [fileSingleSizeLimit=undefined]
4276          * @namespace options
4277          * @for Uploader
4278          * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。
4279          */
4280         api.addValidator( 'fileSingleSizeLimit', function() {
4281             var uploader = this,
4282                 opts = uploader.options,
4283                 max = opts.fileSingleSizeLimit;
4284     
4285             if ( !max ) {
4286                 return;
4287             }
4288     
4289             uploader.on( 'beforeFileQueued', function( file ) {
4290     
4291                 if ( file.size > max ) {
4292                     file.setStatus( WUFile.Status.INVALID, 'exceed_size' );
4293                     this.trigger( 'error', 'F_EXCEED_SIZE', max, file );
4294                     return false;
4295                 }
4296     
4297             });
4298     
4299         });
4300     
4301         /**
4302          * @property {Boolean} [duplicate=undefined]
4303          * @namespace options
4304          * @for Uploader
4305          * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key.
4306          */
4307         api.addValidator( 'duplicate', function() {
4308             var uploader = this,
4309                 opts = uploader.options,
4310                 mapping = {};
4311     
4312             if ( opts.duplicate ) {
4313                 return;
4314             }
4315     
4316             function hashString( str ) {
4317                 var hash = 0,
4318                     i = 0,
4319                     len = str.length,
4320                     _char;
4321     
4322                 for ( ; i < len; i++ ) {
4323                     _char = str.charCodeAt( i );
4324                     hash = _char + (hash << 6) + (hash << 16) - hash;
4325                 }
4326     
4327                 return hash;
4328             }
4329     
4330             uploader.on( 'beforeFileQueued', function( file ) {
4331                 var hash = file.__hash || (file.__hash = hashString( file.name +
4332                         file.size + file.lastModifiedDate ));
4333     
4334                 // 已经重复了
4335                 if ( mapping[ hash ] ) {
4336                     this.trigger( 'error', 'F_DUPLICATE', file );
4337                     return false;
4338                 }
4339             });
4340     
4341             uploader.on( 'fileQueued', function( file ) {
4342                 var hash = file.__hash;
4343     
4344                 hash && (mapping[ hash ] = true);
4345             });
4346     
4347             uploader.on( 'fileDequeued', function( file ) {
4348                 var hash = file.__hash;
4349     
4350                 hash && (delete mapping[ hash ]);
4351             });
4352     
4353             uploader.on( 'reset', function() {
4354                 mapping = {};
4355             });
4356         });
4357     
4358         return api;
4359     });
4360     
4361     /**
4362      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
4363      */
4364     define('runtime/compbase',[],function() {
4365     
4366         function CompBase( owner, runtime ) {
4367     
4368             this.owner = owner;
4369             this.options = owner.options;
4370     
4371             this.getRuntime = function() {
4372                 return runtime;
4373             };
4374     
4375             this.getRuid = function() {
4376                 return runtime.uid;
4377             };
4378     
4379             this.trigger = function() {
4380                 return owner.trigger.apply( owner, arguments );
4381             };
4382         }
4383     
4384         return CompBase;
4385     });
4386     /**
4387      * @fileOverview Html5Runtime
4388      */
4389     define('runtime/html5/runtime',[
4390         'base',
4391         'runtime/runtime',
4392         'runtime/compbase'
4393     ], function( Base, Runtime, CompBase ) {
4394     
4395         var type = 'html5',
4396             components = {};
4397     
4398         function Html5Runtime() {
4399             var pool = {},
4400                 me = this,
4401                 destroy = this.destroy;
4402     
4403             Runtime.apply( me, arguments );
4404             me.type = type;
4405     
4406     
4407             // 这个方法的调用者,实际上是RuntimeClient
4408             me.exec = function( comp, fn/*, args...*/) {
4409                 var client = this,
4410                     uid = client.uid,
4411                     args = Base.slice( arguments, 2 ),
4412                     instance;
4413     
4414                 if ( components[ comp ] ) {
4415                     instance = pool[ uid ] = pool[ uid ] ||
4416                             new components[ comp ]( client, me );
4417     
4418                     if ( instance[ fn ] ) {
4419                         return instance[ fn ].apply( instance, args );
4420                     }
4421                 }
4422             };
4423     
4424             me.destroy = function() {
4425                 // @todo 删除池子中的所有实例
4426                 return destroy && destroy.apply( this, arguments );
4427             };
4428         }
4429     
4430         Base.inherits( Runtime, {
4431             constructor: Html5Runtime,
4432     
4433             // 不需要连接其他程序,直接执行callback
4434             init: function() {
4435                 var me = this;
4436                 setTimeout(function() {
4437                     me.trigger('ready');
4438                 }, 1 );
4439             }
4440     
4441         });
4442     
4443         // 注册Components
4444         Html5Runtime.register = function( name, component ) {
4445             var klass = components[ name ] = Base.inherits( CompBase, component );
4446             return klass;
4447         };
4448     
4449         // 注册html5运行时。
4450         // 只有在支持的前提下注册。
4451         if ( window.Blob && window.FileReader && window.DataView ) {
4452             Runtime.addRuntime( type, Html5Runtime );
4453         }
4454     
4455         return Html5Runtime;
4456     });
4457     /**
4458      * @fileOverview Blob Html实现
4459      */
4460     define('runtime/html5/blob',[
4461         'runtime/html5/runtime',
4462         'lib/blob'
4463     ], function( Html5Runtime, Blob ) {
4464     
4465         return Html5Runtime.register( 'Blob', {
4466             slice: function( start, end ) {
4467                 var blob = this.owner.source,
4468                     slice = blob.slice || blob.webkitSlice || blob.mozSlice;
4469     
4470                 blob = slice.call( blob, start, end );
4471     
4472                 return new Blob( this.getRuid(), blob );
4473             }
4474         });
4475     });
4476     /**
4477      * @fileOverview FilePaste
4478      */
4479     define('runtime/html5/dnd',[
4480         'base',
4481         'runtime/html5/runtime',
4482         'lib/file'
4483     ], function( Base, Html5Runtime, File ) {
4484     
4485         var $ = Base.$,
4486             prefix = 'webuploader-dnd-';
4487     
4488         return Html5Runtime.register( 'DragAndDrop', {
4489             init: function() {
4490                 var elem = this.elem = this.options.container;
4491     
4492                 this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );
4493                 this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );
4494                 this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );
4495                 this.dropHandler = Base.bindFn( this._dropHandler, this );
4496                 this.dndOver = false;
4497     
4498                 elem.on( 'dragenter', this.dragEnterHandler );
4499                 elem.on( 'dragover', this.dragOverHandler );
4500                 elem.on( 'dragleave', this.dragLeaveHandler );
4501                 elem.on( 'drop', this.dropHandler );
4502     
4503                 if ( this.options.disableGlobalDnd ) {
4504                     $( document ).on( 'dragover', this.dragOverHandler );
4505                     $( document ).on( 'drop', this.dropHandler );
4506                 }
4507             },
4508     
4509             _dragEnterHandler: function( e ) {
4510                 var me = this,
4511                     denied = me._denied || false,
4512                     items;
4513     
4514                 e = e.originalEvent || e;
4515     
4516                 if ( !me.dndOver ) {
4517                     me.dndOver = true;
4518     
4519                     // 注意只有 chrome 支持。
4520                     items = e.dataTransfer.items;
4521     
4522                     if ( items && items.length ) {
4523                         me._denied = denied = !me.trigger( 'accept', items );
4524                     }
4525     
4526                     me.elem.addClass( prefix + 'over' );
4527                     me.elem[ denied ? 'addClass' :
4528                             'removeClass' ]( prefix + 'denied' );
4529                 }
4530     
4531                 e.dataTransfer.dropEffect = denied ? 'none' : 'copy';
4532     
4533                 return false;
4534             },
4535     
4536             _dragOverHandler: function( e ) {
4537                 // 只处理框内的。
4538                 var parentElem = this.elem.parent().get( 0 );
4539                 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
4540                     return false;
4541                 }
4542     
4543                 clearTimeout( this._leaveTimer );
4544                 this._dragEnterHandler.call( this, e );
4545     
4546                 return false;
4547             },
4548     
4549             _dragLeaveHandler: function() {
4550                 var me = this,
4551                     handler;
4552     
4553                 handler = function() {
4554                     me.dndOver = false;
4555                     me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );
4556                 };
4557     
4558                 clearTimeout( me._leaveTimer );
4559                 me._leaveTimer = setTimeout( handler, 100 );
4560                 return false;
4561             },
4562     
4563             _dropHandler: function( e ) {
4564                 var me = this,
4565                     ruid = me.getRuid(),
4566                     parentElem = me.elem.parent().get( 0 ),
4567                     dataTransfer, data;
4568     
4569                 // 只处理框内的。
4570                 if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
4571                     return false;
4572                 }
4573     
4574                 e = e.originalEvent || e;
4575                 dataTransfer = e.dataTransfer;
4576     
4577                 // 如果是页面内拖拽,还不能处理,不阻止事件。
4578                 // 此处 ie11 下会报参数错误,
4579                 try {
4580                     data = dataTransfer.getData('text/html');
4581                 } catch( err ) {
4582                 }
4583     
4584                 if ( data ) {
4585                     return;
4586                 }
4587     
4588                 me._getTansferFiles( dataTransfer, function( results ) {
4589                     me.trigger( 'drop', $.map( results, function( file ) {
4590                         return new File( ruid, file );
4591                     }) );
4592                 });
4593     
4594                 me.dndOver = false;
4595                 me.elem.removeClass( prefix + 'over' );
4596                 return false;
4597             },
4598     
4599             // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。
4600             _getTansferFiles: function( dataTransfer, callback ) {
4601                 var results  = [],
4602                     promises = [],
4603                     items, files, file, item, i, len, canAccessFolder;
4604     
4605                 items = dataTransfer.items;
4606                 files = dataTransfer.files;
4607     
4608                 canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);
4609     
4610                 for ( i = 0, len = files.length; i < len; i++ ) {
4611                     file = files[ i ];
4612                     item = items && items[ i ];
4613     
4614                     if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {
4615     
4616                         promises.push( this._traverseDirectoryTree(
4617                                 item.webkitGetAsEntry(), results ) );
4618                     } else {
4619                         results.push( file );
4620                     }
4621                 }
4622     
4623                 Base.when.apply( Base, promises ).done(function() {
4624     
4625                     if ( !results.length ) {
4626                         return;
4627                     }
4628     
4629                     callback( results );
4630                 });
4631             },
4632     
4633             _traverseDirectoryTree: function( entry, results ) {
4634                 var deferred = Base.Deferred(),
4635                     me = this;
4636     
4637                 if ( entry.isFile ) {
4638                     entry.file(function( file ) {
4639                         results.push( file );
4640                         deferred.resolve();
4641                     });
4642                 } else if ( entry.isDirectory ) {
4643                     entry.createReader().readEntries(function( entries ) {
4644                         var len = entries.length,
4645                             promises = [],
4646                             arr = [],    // 为了保证顺序。
4647                             i;
4648     
4649                         for ( i = 0; i < len; i++ ) {
4650                             promises.push( me._traverseDirectoryTree(
4651                                     entries[ i ], arr ) );
4652                         }
4653     
4654                         Base.when.apply( Base, promises ).then(function() {
4655                             results.push.apply( results, arr );
4656                             deferred.resolve();
4657                         }, deferred.reject );
4658                     });
4659                 }
4660     
4661                 return deferred.promise();
4662             },
4663     
4664             destroy: function() {
4665                 var elem = this.elem;
4666     
4667                 // 还没 init 就调用 destroy
4668                 if (!elem) {
4669                     return;
4670                 }
4671                 
4672                 elem.off( 'dragenter', this.dragEnterHandler );
4673                 elem.off( 'dragover', this.dragOverHandler );
4674                 elem.off( 'dragleave', this.dragLeaveHandler );
4675                 elem.off( 'drop', this.dropHandler );
4676     
4677                 if ( this.options.disableGlobalDnd ) {
4678                     $( document ).off( 'dragover', this.dragOverHandler );
4679                     $( document ).off( 'drop', this.dropHandler );
4680                 }
4681             }
4682         });
4683     });
4684     
4685     /**
4686      * @fileOverview FilePaste
4687      */
4688     define('runtime/html5/filepaste',[
4689         'base',
4690         'runtime/html5/runtime',
4691         'lib/file'
4692     ], function( Base, Html5Runtime, File ) {
4693     
4694         return Html5Runtime.register( 'FilePaste', {
4695             init: function() {
4696                 var opts = this.options,
4697                     elem = this.elem = opts.container,
4698                     accept = '.*',
4699                     arr, i, len, item;
4700     
4701                 // accetp的mimeTypes中生成匹配正则。
4702                 if ( opts.accept ) {
4703                     arr = [];
4704     
4705                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
4706                         item = opts.accept[ i ].mimeTypes;
4707                         item && arr.push( item );
4708                     }
4709     
4710                     if ( arr.length ) {
4711                         accept = arr.join(',');
4712                         accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' );
4713                     }
4714                 }
4715                 this.accept = accept = new RegExp( accept, 'i' );
4716                 this.hander = Base.bindFn( this._pasteHander, this );
4717                 elem.on( 'paste', this.hander );
4718             },
4719     
4720             _pasteHander: function( e ) {
4721                 var allowed = [],
4722                     ruid = this.getRuid(),
4723                     items, item, blob, i, len;
4724     
4725                 e = e.originalEvent || e;
4726                 items = e.clipboardData.items;
4727     
4728                 for ( i = 0, len = items.length; i < len; i++ ) {
4729                     item = items[ i ];
4730     
4731                     if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {
4732                         continue;
4733                     }
4734     
4735                     allowed.push( new File( ruid, blob ) );
4736                 }
4737     
4738                 if ( allowed.length ) {
4739                     // 不阻止非文件粘贴(文字粘贴)的事件冒泡
4740                     e.preventDefault();
4741                     e.stopPropagation();
4742                     this.trigger( 'paste', allowed );
4743                 }
4744             },
4745     
4746             destroy: function() {
4747                 this.elem.off( 'paste', this.hander );
4748             }
4749         });
4750     });
4751     
4752     /**
4753      * @fileOverview FilePicker
4754      */
4755     define('runtime/html5/filepicker',[
4756         'base',
4757         'runtime/html5/runtime'
4758     ], function( Base, Html5Runtime ) {
4759     
4760         var $ = Base.$;
4761     
4762         return Html5Runtime.register( 'FilePicker', {
4763             init: function() {
4764                 var container = this.getRuntime().getContainer(),
4765                     me = this,
4766                     owner = me.owner,
4767                     opts = me.options,
4768                     label = this.label = $( document.createElement('label') ),
4769                     input =  this.input = $( document.createElement('input') ),
4770                     arr, i, len, mouseHandler;
4771     
4772                 input.attr( 'type', 'file' );
4773                 input.attr( 'name', opts.name );
4774                 input.addClass('webuploader-element-invisible');
4775     
4776                 label.on( 'click', function() {
4777                     input.trigger('click');
4778                 });
4779     
4780                 label.css({
4781                     opacity: 0,
4782                     width: '100%',
4783                     height: '100%',
4784                     display: 'block',
4785                     cursor: 'pointer',
4786                     background: '#ffffff'
4787                 });
4788     
4789                 if ( opts.multiple ) {
4790                     input.attr( 'multiple', 'multiple' );
4791                 }
4792     
4793                 // @todo Firefox不支持单独指定后缀
4794                 if ( opts.accept && opts.accept.length > 0 ) {
4795                     arr = [];
4796     
4797                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
4798                         arr.push( opts.accept[ i ].mimeTypes );
4799                     }
4800     
4801                     input.attr( 'accept', arr.join(',') );
4802                 }
4803     
4804                 container.append( input );
4805                 container.append( label );
4806     
4807                 mouseHandler = function( e ) {
4808                     owner.trigger( e.type );
4809                 };
4810     
4811                 input.on( 'change', function( e ) {
4812                     var fn = arguments.callee,
4813                         clone;
4814     
4815                     me.files = e.target.files;
4816     
4817                     // reset input
4818                     clone = this.cloneNode( true );
4819                     clone.value = null;
4820                     this.parentNode.replaceChild( clone, this );
4821     
4822                     input.off();
4823                     input = $( clone ).on( 'change', fn )
4824                             .on( 'mouseenter mouseleave', mouseHandler );
4825     
4826                     owner.trigger('change');
4827                 });
4828     
4829                 label.on( 'mouseenter mouseleave', mouseHandler );
4830     
4831             },
4832     
4833     
4834             getFiles: function() {
4835                 return this.files;
4836             },
4837     
4838             destroy: function() {
4839                 this.input.off();
4840                 this.label.off();
4841             }
4842         });
4843     });
4844     /**
4845      * Terms:
4846      *
4847      * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer
4848      * @fileOverview Image控件
4849      */
4850     define('runtime/html5/util',[
4851         'base'
4852     ], function( Base ) {
4853     
4854         var urlAPI = window.createObjectURL && window ||
4855                 window.URL && URL.revokeObjectURL && URL ||
4856                 window.webkitURL,
4857             createObjectURL = Base.noop,
4858             revokeObjectURL = createObjectURL;
4859     
4860         if ( urlAPI ) {
4861     
4862             // 更安全的方式调用,比如android里面就能把context改成其他的对象。
4863             createObjectURL = function() {
4864                 return urlAPI.createObjectURL.apply( urlAPI, arguments );
4865             };
4866     
4867             revokeObjectURL = function() {
4868                 return urlAPI.revokeObjectURL.apply( urlAPI, arguments );
4869             };
4870         }
4871     
4872         return {
4873             createObjectURL: createObjectURL,
4874             revokeObjectURL: revokeObjectURL,
4875     
4876             dataURL2Blob: function( dataURI ) {
4877                 var byteStr, intArray, ab, i, mimetype, parts;
4878     
4879                 parts = dataURI.split(',');
4880     
4881                 if ( ~parts[ 0 ].indexOf('base64') ) {
4882                     byteStr = atob( parts[ 1 ] );
4883                 } else {
4884                     byteStr = decodeURIComponent( parts[ 1 ] );
4885                 }
4886     
4887                 ab = new ArrayBuffer( byteStr.length );
4888                 intArray = new Uint8Array( ab );
4889     
4890                 for ( i = 0; i < byteStr.length; i++ ) {
4891                     intArray[ i ] = byteStr.charCodeAt( i );
4892                 }
4893     
4894                 mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];
4895     
4896                 return this.arrayBufferToBlob( ab, mimetype );
4897             },
4898     
4899             dataURL2ArrayBuffer: function( dataURI ) {
4900                 var byteStr, intArray, i, parts;
4901     
4902                 parts = dataURI.split(',');
4903     
4904                 if ( ~parts[ 0 ].indexOf('base64') ) {
4905                     byteStr = atob( parts[ 1 ] );
4906                 } else {
4907                     byteStr = decodeURIComponent( parts[ 1 ] );
4908                 }
4909     
4910                 intArray = new Uint8Array( byteStr.length );
4911     
4912                 for ( i = 0; i < byteStr.length; i++ ) {
4913                     intArray[ i ] = byteStr.charCodeAt( i );
4914                 }
4915     
4916                 return intArray.buffer;
4917             },
4918     
4919             arrayBufferToBlob: function( buffer, type ) {
4920                 var builder = window.BlobBuilder || window.WebKitBlobBuilder,
4921                     bb;
4922     
4923                 // android不支持直接new Blob, 只能借助blobbuilder.
4924                 if ( builder ) {
4925                     bb = new builder();
4926                     bb.append( buffer );
4927                     return bb.getBlob( type );
4928                 }
4929     
4930                 return new Blob([ buffer ], type ? { type: type } : {} );
4931             },
4932     
4933             // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.
4934             // 你得到的结果是png.
4935             canvasToDataUrl: function( canvas, type, quality ) {
4936                 return canvas.toDataURL( type, quality / 100 );
4937             },
4938     
4939             // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。
4940             parseMeta: function( blob, callback ) {
4941                 callback( false, {});
4942             },
4943     
4944             // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。
4945             updateImageHead: function( data ) {
4946                 return data;
4947             }
4948         };
4949     });
4950     /**
4951      * Terms:
4952      *
4953      * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer
4954      * @fileOverview Image控件
4955      */
4956     define('runtime/html5/imagemeta',[
4957         'runtime/html5/util'
4958     ], function( Util ) {
4959     
4960         var api;
4961     
4962         api = {
4963             parsers: {
4964                 0xffe1: []
4965             },
4966     
4967             maxMetaDataSize: 262144,
4968     
4969             parse: function( blob, cb ) {
4970                 var me = this,
4971                     fr = new FileReader();
4972     
4973                 fr.onload = function() {
4974                     cb( false, me._parse( this.result ) );
4975                     fr = fr.onload = fr.onerror = null;
4976                 };
4977     
4978                 fr.onerror = function( e ) {
4979                     cb( e.message );
4980                     fr = fr.onload = fr.onerror = null;
4981                 };
4982     
4983                 blob = blob.slice( 0, me.maxMetaDataSize );
4984                 fr.readAsArrayBuffer( blob.getSource() );
4985             },
4986     
4987             _parse: function( buffer, noParse ) {
4988                 if ( buffer.byteLength < 6 ) {
4989                     return;
4990                 }
4991     
4992                 var dataview = new DataView( buffer ),
4993                     offset = 2,
4994                     maxOffset = dataview.byteLength - 4,
4995                     headLength = offset,
4996                     ret = {},
4997                     markerBytes, markerLength, parsers, i;
4998     
4999                 if ( dataview.getUint16( 0 ) === 0xffd8 ) {
5000     
5001                     while ( offset < maxOffset ) {
5002                         markerBytes = dataview.getUint16( offset );
5003     
5004                         if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||
5005                                 markerBytes === 0xfffe ) {
5006     
5007                             markerLength = dataview.getUint16( offset + 2 ) + 2;
5008     
5009                             if ( offset + markerLength > dataview.byteLength ) {
5010                                 break;
5011                             }
5012     
5013                             parsers = api.parsers[ markerBytes ];
5014     
5015                             if ( !noParse && parsers ) {
5016                                 for ( i = 0; i < parsers.length; i += 1 ) {
5017                                     parsers[ i ].call( api, dataview, offset,
5018                                             markerLength, ret );
5019                                 }
5020                             }
5021     
5022                             offset += markerLength;
5023                             headLength = offset;
5024                         } else {
5025                             break;
5026                         }
5027                     }
5028     
5029                     if ( headLength > 6 ) {
5030                         if ( buffer.slice ) {
5031                             ret.imageHead = buffer.slice( 2, headLength );
5032                         } else {
5033                             // Workaround for IE10, which does not yet
5034                             // support ArrayBuffer.slice:
5035                             ret.imageHead = new Uint8Array( buffer )
5036                                     .subarray( 2, headLength );
5037                         }
5038                     }
5039                 }
5040     
5041                 return ret;
5042             },
5043     
5044             updateImageHead: function( buffer, head ) {
5045                 var data = this._parse( buffer, true ),
5046                     buf1, buf2, bodyoffset;
5047     
5048     
5049                 bodyoffset = 2;
5050                 if ( data.imageHead ) {
5051                     bodyoffset = 2 + data.imageHead.byteLength;
5052                 }
5053     
5054                 if ( buffer.slice ) {
5055                     buf2 = buffer.slice( bodyoffset );
5056                 } else {
5057                     buf2 = new Uint8Array( buffer ).subarray( bodyoffset );
5058                 }
5059     
5060                 buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );
5061     
5062                 buf1[ 0 ] = 0xFF;
5063                 buf1[ 1 ] = 0xD8;
5064                 buf1.set( new Uint8Array( head ), 2 );
5065                 buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );
5066     
5067                 return buf1.buffer;
5068             }
5069         };
5070     
5071         Util.parseMeta = function() {
5072             return api.parse.apply( api, arguments );
5073         };
5074     
5075         Util.updateImageHead = function() {
5076             return api.updateImageHead.apply( api, arguments );
5077         };
5078     
5079         return api;
5080     });
5081     /**
5082      * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image
5083      * 暂时项目中只用了orientation.
5084      *
5085      * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.
5086      * @fileOverview EXIF解析
5087      */
5088     
5089     // Sample
5090     // ====================================
5091     // Make : Apple
5092     // Model : iPhone 4S
5093     // Orientation : 1
5094     // XResolution : 72 [72/1]
5095     // YResolution : 72 [72/1]
5096     // ResolutionUnit : 2
5097     // Software : QuickTime 7.7.1
5098     // DateTime : 2013:09:01 22:53:55
5099     // ExifIFDPointer : 190
5100     // ExposureTime : 0.058823529411764705 [1/17]
5101     // FNumber : 2.4 [12/5]
5102     // ExposureProgram : Normal program
5103     // ISOSpeedRatings : 800
5104     // ExifVersion : 0220
5105     // DateTimeOriginal : 2013:09:01 22:52:51
5106     // DateTimeDigitized : 2013:09:01 22:52:51
5107     // ComponentsConfiguration : YCbCr
5108     // ShutterSpeedValue : 4.058893515764426
5109     // ApertureValue : 2.5260688216892597 [4845/1918]
5110     // BrightnessValue : -0.3126686601998395
5111     // MeteringMode : Pattern
5112     // Flash : Flash did not fire, compulsory flash mode
5113     // FocalLength : 4.28 [107/25]
5114     // SubjectArea : [4 values]
5115     // FlashpixVersion : 0100
5116     // ColorSpace : 1
5117     // PixelXDimension : 2448
5118     // PixelYDimension : 3264
5119     // SensingMethod : One-chip color area sensor
5120     // ExposureMode : 0
5121     // WhiteBalance : Auto white balance
5122     // FocalLengthIn35mmFilm : 35
5123     // SceneCaptureType : Standard
5124     define('runtime/html5/imagemeta/exif',[
5125         'base',
5126         'runtime/html5/imagemeta'
5127     ], function( Base, ImageMeta ) {
5128     
5129         var EXIF = {};
5130     
5131         EXIF.ExifMap = function() {
5132             return this;
5133         };
5134     
5135         EXIF.ExifMap.prototype.map = {
5136             'Orientation': 0x0112
5137         };
5138     
5139         EXIF.ExifMap.prototype.get = function( id ) {
5140             return this[ id ] || this[ this.map[ id ] ];
5141         };
5142     
5143         EXIF.exifTagTypes = {
5144             // byte, 8-bit unsigned int:
5145             1: {
5146                 getValue: function( dataView, dataOffset ) {
5147                     return dataView.getUint8( dataOffset );
5148                 },
5149                 size: 1
5150             },
5151     
5152             // ascii, 8-bit byte:
5153             2: {
5154                 getValue: function( dataView, dataOffset ) {
5155                     return String.fromCharCode( dataView.getUint8( dataOffset ) );
5156                 },
5157                 size: 1,
5158                 ascii: true
5159             },
5160     
5161             // short, 16 bit int:
5162             3: {
5163                 getValue: function( dataView, dataOffset, littleEndian ) {
5164                     return dataView.getUint16( dataOffset, littleEndian );
5165                 },
5166                 size: 2
5167             },
5168     
5169             // long, 32 bit int:
5170             4: {
5171                 getValue: function( dataView, dataOffset, littleEndian ) {
5172                     return dataView.getUint32( dataOffset, littleEndian );
5173                 },
5174                 size: 4
5175             },
5176     
5177             // rational = two long values,
5178             // first is numerator, second is denominator:
5179             5: {
5180                 getValue: function( dataView, dataOffset, littleEndian ) {
5181                     return dataView.getUint32( dataOffset, littleEndian ) /
5182                         dataView.getUint32( dataOffset + 4, littleEndian );
5183                 },
5184                 size: 8
5185             },
5186     
5187             // slong, 32 bit signed int:
5188             9: {
5189                 getValue: function( dataView, dataOffset, littleEndian ) {
5190                     return dataView.getInt32( dataOffset, littleEndian );
5191                 },
5192                 size: 4
5193             },
5194     
5195             // srational, two slongs, first is numerator, second is denominator:
5196             10: {
5197                 getValue: function( dataView, dataOffset, littleEndian ) {
5198                     return dataView.getInt32( dataOffset, littleEndian ) /
5199                         dataView.getInt32( dataOffset + 4, littleEndian );
5200                 },
5201                 size: 8
5202             }
5203         };
5204     
5205         // undefined, 8-bit byte, value depending on field:
5206         EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];
5207     
5208         EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,
5209                 littleEndian ) {
5210     
5211             var tagType = EXIF.exifTagTypes[ type ],
5212                 tagSize, dataOffset, values, i, str, c;
5213     
5214             if ( !tagType ) {
5215                 Base.log('Invalid Exif data: Invalid tag type.');
5216                 return;
5217             }
5218     
5219             tagSize = tagType.size * length;
5220     
5221             // Determine if the value is contained in the dataOffset bytes,
5222             // or if the value at the dataOffset is a pointer to the actual data:
5223             dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,
5224                     littleEndian ) : (offset + 8);
5225     
5226             if ( dataOffset + tagSize > dataView.byteLength ) {
5227                 Base.log('Invalid Exif data: Invalid data offset.');
5228                 return;
5229             }
5230     
5231             if ( length === 1 ) {
5232                 return tagType.getValue( dataView, dataOffset, littleEndian );
5233             }
5234     
5235             values = [];
5236     
5237             for ( i = 0; i < length; i += 1 ) {
5238                 values[ i ] = tagType.getValue( dataView,
5239                         dataOffset + i * tagType.size, littleEndian );
5240             }
5241     
5242             if ( tagType.ascii ) {
5243                 str = '';
5244     
5245                 // Concatenate the chars:
5246                 for ( i = 0; i < values.length; i += 1 ) {
5247                     c = values[ i ];
5248     
5249                     // Ignore the terminating NULL byte(s):
5250                     if ( c === '\u0000' ) {
5251                         break;
5252                     }
5253                     str += c;
5254                 }
5255     
5256                 return str;
5257             }
5258             return values;
5259         };
5260     
5261         EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,
5262                 data ) {
5263     
5264             var tag = dataView.getUint16( offset, littleEndian );
5265             data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,
5266                     dataView.getUint16( offset + 2, littleEndian ),    // tag type
5267                     dataView.getUint32( offset + 4, littleEndian ),    // tag length
5268                     littleEndian );
5269         };
5270     
5271         EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,
5272                 littleEndian, data ) {
5273     
5274             var tagsNumber, dirEndOffset, i;
5275     
5276             if ( dirOffset + 6 > dataView.byteLength ) {
5277                 Base.log('Invalid Exif data: Invalid directory offset.');
5278                 return;
5279             }
5280     
5281             tagsNumber = dataView.getUint16( dirOffset, littleEndian );
5282             dirEndOffset = dirOffset + 2 + 12 * tagsNumber;
5283     
5284             if ( dirEndOffset + 4 > dataView.byteLength ) {
5285                 Base.log('Invalid Exif data: Invalid directory size.');
5286                 return;
5287             }
5288     
5289             for ( i = 0; i < tagsNumber; i += 1 ) {
5290                 this.parseExifTag( dataView, tiffOffset,
5291                         dirOffset + 2 + 12 * i,    // tag offset
5292                         littleEndian, data );
5293             }
5294     
5295             // Return the offset to the next directory:
5296             return dataView.getUint32( dirEndOffset, littleEndian );
5297         };
5298     
5299         // EXIF.getExifThumbnail = function(dataView, offset, length) {
5300         //     var hexData,
5301         //         i,
5302         //         b;
5303         //     if (!length || offset + length > dataView.byteLength) {
5304         //         Base.log('Invalid Exif data: Invalid thumbnail data.');
5305         //         return;
5306         //     }
5307         //     hexData = [];
5308         //     for (i = 0; i < length; i += 1) {
5309         //         b = dataView.getUint8(offset + i);
5310         //         hexData.push((b < 16 ? '0' : '') + b.toString(16));
5311         //     }
5312         //     return 'data:image/jpeg,%' + hexData.join('%');
5313         // };
5314     
5315         EXIF.parseExifData = function( dataView, offset, length, data ) {
5316     
5317             var tiffOffset = offset + 10,
5318                 littleEndian, dirOffset;
5319     
5320             // Check for the ASCII code for "Exif" (0x45786966):
5321             if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {
5322                 // No Exif data, might be XMP data instead
5323                 return;
5324             }
5325             if ( tiffOffset + 8 > dataView.byteLength ) {
5326                 Base.log('Invalid Exif data: Invalid segment size.');
5327                 return;
5328             }
5329     
5330             // Check for the two null bytes:
5331             if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {
5332                 Base.log('Invalid Exif data: Missing byte alignment offset.');
5333                 return;
5334             }
5335     
5336             // Check the byte alignment:
5337             switch ( dataView.getUint16( tiffOffset ) ) {
5338                 case 0x4949:
5339                     littleEndian = true;
5340                     break;
5341     
5342                 case 0x4D4D:
5343                     littleEndian = false;
5344                     break;
5345     
5346                 default:
5347                     Base.log('Invalid Exif data: Invalid byte alignment marker.');
5348                     return;
5349             }
5350     
5351             // Check for the TIFF tag marker (0x002A):
5352             if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {
5353                 Base.log('Invalid Exif data: Missing TIFF marker.');
5354                 return;
5355             }
5356     
5357             // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
5358             dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );
5359             // Create the exif object to store the tags:
5360             data.exif = new EXIF.ExifMap();
5361             // Parse the tags of the main image directory and retrieve the
5362             // offset to the next directory, usually the thumbnail directory:
5363             dirOffset = EXIF.parseExifTags( dataView, tiffOffset,
5364                     tiffOffset + dirOffset, littleEndian, data );
5365     
5366             // 尝试读取缩略图
5367             // if ( dirOffset ) {
5368             //     thumbnailData = {exif: {}};
5369             //     dirOffset = EXIF.parseExifTags(
5370             //         dataView,
5371             //         tiffOffset,
5372             //         tiffOffset + dirOffset,
5373             //         littleEndian,
5374             //         thumbnailData
5375             //     );
5376     
5377             //     // Check for JPEG Thumbnail offset:
5378             //     if (thumbnailData.exif[0x0201]) {
5379             //         data.exif.Thumbnail = EXIF.getExifThumbnail(
5380             //             dataView,
5381             //             tiffOffset + thumbnailData.exif[0x0201],
5382             //             thumbnailData.exif[0x0202] // Thumbnail data length
5383             //         );
5384             //     }
5385             // }
5386         };
5387     
5388         ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );
5389         return EXIF;
5390     });
5391     /**
5392      * @fileOverview Image
5393      */
5394     define('runtime/html5/image',[
5395         'base',
5396         'runtime/html5/runtime',
5397         'runtime/html5/util'
5398     ], function( Base, Html5Runtime, Util ) {
5399     
5400         var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';
5401     
5402         return Html5Runtime.register( 'Image', {
5403     
5404             // flag: 标记是否被修改过。
5405             modified: false,
5406     
5407             init: function() {
5408                 var me = this,
5409                     img = new Image();
5410     
5411                 img.onload = function() {
5412     
5413                     me._info = {
5414                         type: me.type,
5415                         width: this.width,
5416                         height: this.height
5417                     };
5418     
5419                     // 读取meta信息。
5420                     if ( !me._metas && 'image/jpeg' === me.type ) {
5421                         Util.parseMeta( me._blob, function( error, ret ) {
5422                             me._metas = ret;
5423                             me.owner.trigger('load');
5424                         });
5425                     } else {
5426                         me.owner.trigger('load');
5427                     }
5428                 };
5429     
5430                 img.onerror = function() {
5431                     me.owner.trigger('error');
5432                 };
5433     
5434                 me._img = img;
5435             },
5436     
5437             loadFromBlob: function( blob ) {
5438                 var me = this,
5439                     img = me._img;
5440     
5441                 me._blob = blob;
5442                 me.type = blob.type;
5443                 img.src = Util.createObjectURL( blob.getSource() );
5444                 me.owner.once( 'load', function() {
5445                     Util.revokeObjectURL( img.src );
5446                 });
5447             },
5448     
5449             resize: function( width, height ) {
5450                 var canvas = this._canvas ||
5451                         (this._canvas = document.createElement('canvas'));
5452     
5453                 this._resize( this._img, canvas, width, height );
5454                 this._blob = null;    // 没用了,可以删掉了。
5455                 this.modified = true;
5456                 this.owner.trigger( 'complete', 'resize' );
5457             },
5458     
5459             crop: function( x, y, w, h, s ) {
5460                 var cvs = this._canvas ||
5461                         (this._canvas = document.createElement('canvas')),
5462                     opts = this.options,
5463                     img = this._img,
5464                     iw = img.naturalWidth,
5465                     ih = img.naturalHeight,
5466                     orientation = this.getOrientation();
5467     
5468                 s = s || 1;
5469     
5470                 // todo 解决 orientation 的问题。
5471                 // values that require 90 degree rotation
5472                 // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {
5473     
5474                 //     switch ( orientation ) {
5475                 //         case 6:
5476                 //             tmp = x;
5477                 //             x = y;
5478                 //             y = iw * s - tmp - w;
5479                 //             console.log(ih * s, tmp, w)
5480                 //             break;
5481                 //     }
5482     
5483                 //     (w ^= h, h ^= w, w ^= h);
5484                 // }
5485     
5486                 cvs.width = w;
5487                 cvs.height = h;
5488     
5489                 opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );
5490                 this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s );
5491     
5492                 this._blob = null;    // 没用了,可以删掉了。
5493                 this.modified = true;
5494                 this.owner.trigger( 'complete', 'crop' );
5495             },
5496     
5497             getAsBlob: function( type ) {
5498                 var blob = this._blob,
5499                     opts = this.options,
5500                     canvas;
5501     
5502                 type = type || this.type;
5503     
5504                 // blob需要重新生成。
5505                 if ( this.modified || this.type !== type ) {
5506                     canvas = this._canvas;
5507     
5508                     if ( type === 'image/jpeg' ) {
5509     
5510                         blob = Util.canvasToDataUrl( canvas, type, opts.quality );
5511     
5512                         if ( opts.preserveHeaders && this._metas &&
5513                                 this._metas.imageHead ) {
5514     
5515                             blob = Util.dataURL2ArrayBuffer( blob );
5516                             blob = Util.updateImageHead( blob,
5517                                     this._metas.imageHead );
5518                             blob = Util.arrayBufferToBlob( blob, type );
5519                             return blob;
5520                         }
5521                     } else {
5522                         blob = Util.canvasToDataUrl( canvas, type );
5523                     }
5524     
5525                     blob = Util.dataURL2Blob( blob );
5526                 }
5527     
5528                 return blob;
5529             },
5530     
5531             getAsDataUrl: function( type ) {
5532                 var opts = this.options;
5533     
5534                 type = type || this.type;
5535     
5536                 if ( type === 'image/jpeg' ) {
5537                     return Util.canvasToDataUrl( this._canvas, type, opts.quality );
5538                 } else {
5539                     return this._canvas.toDataURL( type );
5540                 }
5541             },
5542     
5543             getOrientation: function() {
5544                 return this._metas && this._metas.exif &&
5545                         this._metas.exif.get('Orientation') || 1;
5546             },
5547     
5548             info: function( val ) {
5549     
5550                 // setter
5551                 if ( val ) {
5552                     this._info = val;
5553                     return this;
5554                 }
5555     
5556                 // getter
5557                 return this._info;
5558             },
5559     
5560             meta: function( val ) {
5561     
5562                 // setter
5563                 if ( val ) {
5564                     this._meta = val;
5565                     return this;
5566                 }
5567     
5568                 // getter
5569                 return this._meta;
5570             },
5571     
5572             destroy: function() {
5573                 var canvas = this._canvas;
5574                 this._img.onload = null;
5575     
5576                 if ( canvas ) {
5577                     canvas.getContext('2d')
5578                             .clearRect( 0, 0, canvas.width, canvas.height );
5579                     canvas.width = canvas.height = 0;
5580                     this._canvas = null;
5581                 }
5582     
5583                 // 释放内存。非常重要,否则释放不了image的内存。
5584                 this._img.src = BLANK;
5585                 this._img = this._blob = null;
5586             },
5587     
5588             _resize: function( img, cvs, width, height ) {
5589                 var opts = this.options,
5590                     naturalWidth = img.width,
5591                     naturalHeight = img.height,
5592                     orientation = this.getOrientation(),
5593                     scale, w, h, x, y;
5594     
5595                 // values that require 90 degree rotation
5596                 if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {
5597     
5598                     // 交换width, height的值。
5599                     width ^= height;
5600                     height ^= width;
5601                     width ^= height;
5602                 }
5603     
5604                 scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,
5605                         height / naturalHeight );
5606     
5607                 // 不允许放大。
5608                 opts.allowMagnify || (scale = Math.min( 1, scale ));
5609     
5610                 w = naturalWidth * scale;
5611                 h = naturalHeight * scale;
5612     
5613                 if ( opts.crop ) {
5614                     cvs.width = width;
5615                     cvs.height = height;
5616                 } else {
5617                     cvs.width = w;
5618                     cvs.height = h;
5619                 }
5620     
5621                 x = (cvs.width - w) / 2;
5622                 y = (cvs.height - h) / 2;
5623     
5624                 opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );
5625     
5626                 this._renderImageToCanvas( cvs, img, x, y, w, h );
5627             },
5628     
5629             _rotate2Orientaion: function( canvas, orientation ) {
5630                 var width = canvas.width,
5631                     height = canvas.height,
5632                     ctx = canvas.getContext('2d');
5633     
5634                 switch ( orientation ) {
5635                     case 5:
5636                     case 6:
5637                     case 7:
5638                     case 8:
5639                         canvas.width = height;
5640                         canvas.height = width;
5641                         break;
5642                 }
5643     
5644                 switch ( orientation ) {
5645                     case 2:    // horizontal flip
5646                         ctx.translate( width, 0 );
5647                         ctx.scale( -1, 1 );
5648                         break;
5649     
5650                     case 3:    // 180 rotate left
5651                         ctx.translate( width, height );
5652                         ctx.rotate( Math.PI );
5653                         break;
5654     
5655                     case 4:    // vertical flip
5656                         ctx.translate( 0, height );
5657                         ctx.scale( 1, -1 );
5658                         break;
5659     
5660                     case 5:    // vertical flip + 90 rotate right
5661                         ctx.rotate( 0.5 * Math.PI );
5662                         ctx.scale( 1, -1 );
5663                         break;
5664     
5665                     case 6:    // 90 rotate right
5666                         ctx.rotate( 0.5 * Math.PI );
5667                         ctx.translate( 0, -height );
5668                         break;
5669     
5670                     case 7:    // horizontal flip + 90 rotate right
5671                         ctx.rotate( 0.5 * Math.PI );
5672                         ctx.translate( width, -height );
5673                         ctx.scale( -1, 1 );
5674                         break;
5675     
5676                     case 8:    // 90 rotate left
5677                         ctx.rotate( -0.5 * Math.PI );
5678                         ctx.translate( -width, 0 );
5679                         break;
5680                 }
5681             },
5682     
5683             // https://github.com/stomita/ios-imagefile-megapixel/
5684             // blob/master/src/megapix-image.js
5685             _renderImageToCanvas: (function() {
5686     
5687                 // 如果不是ios, 不需要这么复杂!
5688                 if ( !Base.os.ios ) {
5689                     return function( canvas ) {
5690                         var args = Base.slice( arguments, 1 ),
5691                             ctx = canvas.getContext('2d');
5692     
5693                         ctx.drawImage.apply( ctx, args );
5694                     };
5695                 }
5696     
5697                 /**
5698                  * Detecting vertical squash in loaded image.
5699                  * Fixes a bug which squash image vertically while drawing into
5700                  * canvas for some images.
5701                  */
5702                 function detectVerticalSquash( img, iw, ih ) {
5703                     var canvas = document.createElement('canvas'),
5704                         ctx = canvas.getContext('2d'),
5705                         sy = 0,
5706                         ey = ih,
5707                         py = ih,
5708                         data, alpha, ratio;
5709     
5710     
5711                     canvas.width = 1;
5712                     canvas.height = ih;
5713                     ctx.drawImage( img, 0, 0 );
5714                     data = ctx.getImageData( 0, 0, 1, ih ).data;
5715     
5716                     // search image edge pixel position in case
5717                     // it is squashed vertically.
5718                     while ( py > sy ) {
5719                         alpha = data[ (py - 1) * 4 + 3 ];
5720     
5721                         if ( alpha === 0 ) {
5722                             ey = py;
5723                         } else {
5724                             sy = py;
5725                         }
5726     
5727                         py = (ey + sy) >> 1;
5728                     }
5729     
5730                     ratio = (py / ih);
5731                     return (ratio === 0) ? 1 : ratio;
5732                 }
5733     
5734                 // fix ie7 bug
5735                 // http://stackoverflow.com/questions/11929099/
5736                 // html5-canvas-drawimage-ratio-bug-ios
5737                 if ( Base.os.ios >= 7 ) {
5738                     return function( canvas, img, x, y, w, h ) {
5739                         var iw = img.naturalWidth,
5740                             ih = img.naturalHeight,
5741                             vertSquashRatio = detectVerticalSquash( img, iw, ih );
5742     
5743                         return canvas.getContext('2d').drawImage( img, 0, 0,
5744                                 iw * vertSquashRatio, ih * vertSquashRatio,
5745                                 x, y, w, h );
5746                     };
5747                 }
5748     
5749                 /**
5750                  * Detect subsampling in loaded image.
5751                  * In iOS, larger images than 2M pixels may be
5752                  * subsampled in rendering.
5753                  */
5754                 function detectSubsampling( img ) {
5755                     var iw = img.naturalWidth,
5756                         ih = img.naturalHeight,
5757                         canvas, ctx;
5758     
5759                     // subsampling may happen overmegapixel image
5760                     if ( iw * ih > 1024 * 1024 ) {
5761                         canvas = document.createElement('canvas');
5762                         canvas.width = canvas.height = 1;
5763                         ctx = canvas.getContext('2d');
5764                         ctx.drawImage( img, -iw + 1, 0 );
5765     
5766                         // subsampled image becomes half smaller in rendering size.
5767                         // check alpha channel value to confirm image is covering
5768                         // edge pixel or not. if alpha value is 0
5769                         // image is not covering, hence subsampled.
5770                         return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;
5771                     } else {
5772                         return false;
5773                     }
5774                 }
5775     
5776     
5777                 return function( canvas, img, x, y, width, height ) {
5778                     var iw = img.naturalWidth,
5779                         ih = img.naturalHeight,
5780                         ctx = canvas.getContext('2d'),
5781                         subsampled = detectSubsampling( img ),
5782                         doSquash = this.type === 'image/jpeg',
5783                         d = 1024,
5784                         sy = 0,
5785                         dy = 0,
5786                         tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;
5787     
5788                     if ( subsampled ) {
5789                         iw /= 2;
5790                         ih /= 2;
5791                     }
5792     
5793                     ctx.save();
5794                     tmpCanvas = document.createElement('canvas');
5795                     tmpCanvas.width = tmpCanvas.height = d;
5796     
5797                     tmpCtx = tmpCanvas.getContext('2d');
5798                     vertSquashRatio = doSquash ?
5799                             detectVerticalSquash( img, iw, ih ) : 1;
5800     
5801                     dw = Math.ceil( d * width / iw );
5802                     dh = Math.ceil( d * height / ih / vertSquashRatio );
5803     
5804                     while ( sy < ih ) {
5805                         sx = 0;
5806                         dx = 0;
5807                         while ( sx < iw ) {
5808                             tmpCtx.clearRect( 0, 0, d, d );
5809                             tmpCtx.drawImage( img, -sx, -sy );
5810                             ctx.drawImage( tmpCanvas, 0, 0, d, d,
5811                                     x + dx, y + dy, dw, dh );
5812                             sx += d;
5813                             dx += dw;
5814                         }
5815                         sy += d;
5816                         dy += dh;
5817                     }
5818                     ctx.restore();
5819                     tmpCanvas = tmpCtx = null;
5820                 };
5821             })()
5822         });
5823     });
5824     /**
5825      * @fileOverview Transport
5826      * @todo 支持chunked传输,优势:
5827      * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分,
5828      * 而不需要重头再传一次。另外断点续传也需要用chunked方式。
5829      */
5830     define('runtime/html5/transport',[
5831         'base',
5832         'runtime/html5/runtime'
5833     ], function( Base, Html5Runtime ) {
5834     
5835         var noop = Base.noop,
5836             $ = Base.$;
5837     
5838         return Html5Runtime.register( 'Transport', {
5839             init: function() {
5840                 this._status = 0;
5841                 this._response = null;
5842             },
5843     
5844             send: function() {
5845                 var owner = this.owner,
5846                     opts = this.options,
5847                     xhr = this._initAjax(),
5848                     blob = owner._blob,
5849                     server = opts.server,
5850                     formData, binary, fr;
5851     
5852                 if ( opts.sendAsBinary ) {
5853                     server += (/\?/.test( server ) ? '&' : '?') +
5854                             $.param( owner._formData );
5855     
5856                     binary = blob.getSource();
5857                 } else {
5858                     formData = new FormData();
5859                     $.each( owner._formData, function( k, v ) {
5860                         formData.append( k, v );
5861                     });
5862     
5863                     formData.append( opts.fileVal, blob.getSource(),
5864                             opts.filename || owner._formData.name || '' );
5865                 }
5866     
5867                 if ( opts.withCredentials && 'withCredentials' in xhr ) {
5868                     xhr.open( opts.method, server, true );
5869                     xhr.withCredentials = true;
5870                 } else {
5871                     xhr.open( opts.method, server );
5872                 }
5873     
5874                 this._setRequestHeader( xhr, opts.headers );
5875     
5876                 if ( binary ) {
5877                     // 强制设置成 content-type 为文件流。
5878                     xhr.overrideMimeType &&
5879                             xhr.overrideMimeType('application/octet-stream');
5880     
5881                     // android直接发送blob会导致服务端接收到的是空文件。
5882                     // bug详情。
5883                     // https://code.google.com/p/android/issues/detail?id=39882
5884                     // 所以先用fileReader读取出来再通过arraybuffer的方式发送。
5885                     if ( Base.os.android ) {
5886                         fr = new FileReader();
5887     
5888                         fr.onload = function() {
5889                             xhr.send( this.result );
5890                             fr = fr.onload = null;
5891                         };
5892     
5893                         fr.readAsArrayBuffer( binary );
5894                     } else {
5895                         xhr.send( binary );
5896                     }
5897                 } else {
5898                     xhr.send( formData );
5899                 }
5900             },
5901     
5902             getResponse: function() {
5903                 return this._response;
5904             },
5905     
5906             getResponseAsJson: function() {
5907                 return this._parseJson( this._response );
5908             },
5909     
5910             getStatus: function() {
5911                 return this._status;
5912             },
5913     
5914             abort: function() {
5915                 var xhr = this._xhr;
5916     
5917                 if ( xhr ) {
5918                     xhr.upload.onprogress = noop;
5919                     xhr.onreadystatechange = noop;
5920                     xhr.abort();
5921     
5922                     this._xhr = xhr = null;
5923                 }
5924             },
5925     
5926             destroy: function() {
5927                 this.abort();
5928             },
5929     
5930             _initAjax: function() {
5931                 var me = this,
5932                     xhr = new XMLHttpRequest(),
5933                     opts = this.options;
5934     
5935                 if ( opts.withCredentials && !('withCredentials' in xhr) &&
5936                         typeof XDomainRequest !== 'undefined' ) {
5937                     xhr = new XDomainRequest();
5938                 }
5939     
5940                 xhr.upload.onprogress = function( e ) {
5941                     var percentage = 0;
5942     
5943                     if ( e.lengthComputable ) {
5944                         percentage = e.loaded / e.total;
5945                     }
5946     
5947                     return me.trigger( 'progress', percentage );
5948                 };
5949     
5950                 xhr.onreadystatechange = function() {
5951     
5952                     if ( xhr.readyState !== 4 ) {
5953                         return;
5954                     }
5955     
5956                     xhr.upload.onprogress = noop;
5957                     xhr.onreadystatechange = noop;
5958                     me._xhr = null;
5959                     me._status = xhr.status;
5960     
5961                     if ( xhr.status >= 200 && xhr.status < 300 ) {
5962                         me._response = xhr.responseText;
5963                         return me.trigger('load');
5964                     } else if ( xhr.status >= 500 && xhr.status < 600 ) {
5965                         me._response = xhr.responseText;
5966                         return me.trigger( 'error', 'server' );
5967                     }
5968     
5969     
5970                     return me.trigger( 'error', me._status ? 'http' : 'abort' );
5971                 };
5972     
5973                 me._xhr = xhr;
5974                 return xhr;
5975             },
5976     
5977             _setRequestHeader: function( xhr, headers ) {
5978                 $.each( headers, function( key, val ) {
5979                     xhr.setRequestHeader( key, val );
5980                 });
5981             },
5982     
5983             _parseJson: function( str ) {
5984                 var json;
5985     
5986                 try {
5987                     json = JSON.parse( str );
5988                 } catch ( ex ) {
5989                     json = {};
5990                 }
5991     
5992                 return json;
5993             }
5994         });
5995     });
5996     /**
5997      * @fileOverview 只有html5实现的文件版本。
5998      */
5999     define('preset/html5only',[
6000         'base',
6001     
6002         // widgets
6003         'widgets/filednd',
6004         'widgets/filepaste',
6005         'widgets/filepicker',
6006         'widgets/image',
6007         'widgets/queue',
6008         'widgets/runtime',
6009         'widgets/upload',
6010         'widgets/validator',
6011     
6012         // runtimes
6013         // html5
6014         'runtime/html5/blob',
6015         'runtime/html5/dnd',
6016         'runtime/html5/filepaste',
6017         'runtime/html5/filepicker',
6018         'runtime/html5/imagemeta/exif',
6019         'runtime/html5/image',
6020         'runtime/html5/transport'
6021     ], function( Base ) {
6022         return Base;
6023     });
6024     define('webuploader',[
6025         'preset/html5only'
6026     ], function( preset ) {
6027         return preset;
6028     });
6029     return require('webuploader');
6030 });