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