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