text: /*! WebUploader 0.1.2 */
  
  
  /**
   * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。
   *
   * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。
   */
  (function( root, factory ) {
      var modules = {},
  
          // 内部require, 简单不完全实现。
          // https://github.com/amdjs/amdjs-api/wiki/require
          _require = function( deps, callback ) {
              var args, len, i;
  
              // 如果deps不是数组，则直接返回指定module
              if ( typeof deps === 'string' ) {
                  return getModule( deps );
              } else {
                  args = [];
                  for( len = deps.length, i = 0; i < len; i++ ) {
                      args.push( getModule( deps[ i ] ) );
                  }
  
                  return callback.apply( null, args );
              }
          },
  
          // 内部define，暂时不支持不指定id.
          _define = function( id, deps, factory ) {
              if ( arguments.length === 2 ) {
                  factory = deps;
                  deps = null;
              }
  
              _require( deps || [], function() {
                  setModule( id, factory, arguments );
              });
          },
  
          // 设置module, 兼容CommonJs写法。
          setModule = function( id, factory, args ) {
              var module = {
                      exports: factory
                  },
                  returned;
  
              if ( typeof factory === 'function' ) {
                  args.length || (args = [ _require, module.exports, module ]);
                  returned = factory.apply( null, args );
                  returned !== undefined && (module.exports = returned);
              }
  
              modules[ id ] = module.exports;
          },
  
          // 根据id获取module
          getModule = function( id ) {
              var module = modules[ id ] || root[ id ];
  
              if ( !module ) {
                  throw new Error( '`' + id + '` is undefined' );
              }
  
              return module;
          },
  
          // 将所有modules，将路径ids装换成对象。
          exportsTo = function( obj ) {
              var key, host, parts, part, last, ucFirst;
  
              // make the first character upper case.
              ucFirst = function( str ) {
                  return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));
              };
  
              for ( key in modules ) {
                  host = obj;
  
                  if ( !modules.hasOwnProperty( key ) ) {
                      continue;
                  }
  
                  parts = key.split('/');
                  last = ucFirst( parts.pop() );
  
                  while( (part = ucFirst( parts.shift() )) ) {
                      host[ part ] = host[ part ] || {};
                      host = host[ part ];
                  }
  
                  host[ last ] = modules[ key ];
              }
          },
  
          exports = factory( root, _define, _require ),
          origin;
  
      // exports every module.
      exportsTo( exports );
  
      if ( typeof module === 'object' && typeof module.exports === 'object' ) {
  
          // For CommonJS and CommonJS-like environments where a proper window is present,
          module.exports = exports;
      } else if ( typeof define === 'function' && define.amd ) {
  
          // Allow using this built library as an AMD module
          // in another project. That other project will only
          // see this AMD call, not the internal modules in
          // the closure below.
          define([], exports );
      } else {
  
          // Browser globals case. Just assign the
          // result to a property on the global.
          origin = root.WebUploader;
          root.WebUploader = exports;
          root.WebUploader.noConflict = function() {
              root.WebUploader = origin;
          };
      }
  })( this, function( window, define, require ) {
  
  
      /**
       * @fileOverview jQuery or Zepto
       */
      define('dollar-third',[],function() {
          return window.jQuery || window.Zepto;
      });
      /**
       * @fileOverview Dom 操作相关
       */
      define('dollar',[
          'dollar-third'
      ], function( _ ) {
          return _;
      });
      /**
       * @fileOverview 使用jQuery的Promise
       */
      define('promise-third',[
          'dollar'
      ], function( $ ) {
          return {
              Deferred: $.Deferred,
              when: $.when,
      
              isPromise: function( anything ) {
                  return anything && typeof anything.then === 'function';
              }
          };
      });
      /**
       * @fileOverview Promise/A+
       */
      define('promise',[
          'promise-third'
      ], function( _ ) {
          return _;
      });
      /**
       * @fileOverview 基础类方法。
       */
      
      /**
       * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。
       *
       * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.
       * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：
       *
       * * module `base`：WebUploader.BaseController
       * * module `file`: WebUploader.File
       * * module `lib/dnd`: WebUploader.Lib.Dnd
       * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd
       *
       *
       * 以下文档将可能省略`WebUploader`前缀。
       * @module WebUploader
       * @title WebUploader API文档
       */
      define('base',[
          'dollar',
          'promise'
      ], function( $, promise ) {
      
          var noop = function() {},
              call = Function.call;
      
          // http://jsperf.com/uncurrythis
          // 反科里化
          function uncurryThis( fn ) {
              return function() {
                  return call.apply( fn, arguments );
              };
          }
      
          function bindFn( fn, context ) {
              return function() {
                  return fn.apply( context, arguments );
              };
          }
      
          function createObject( proto ) {
              var f;
      
              if ( Object.create ) {
                  return Object.create( proto );
              } else {
                  f = function() {};
                  f.prototype = proto;
                  return new f();
              }
          }
      
      
          /**
           * 基础类，提供一些简单常用的方法。
           * @class Base
           */
          return {
      
              /**
               * @property {String} version 当前版本号。
               */
              version: '0.1.2',
      
              /**
               * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。
               */
              $: $,
      
              Deferred: promise.Deferred,
      
              isPromise: promise.isPromise,
      
              when: promise.when,
      
              /**
               * @description  简单的浏览器检查结果。
               *
               * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。
               * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。
               * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**
               * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。
               * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。
               * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。
               *
               * @property {Object} [browser]
               */
              browser: (function( ua ) {
                  var ret = {},
                      webkit = ua.match( /WebKit\/([\d.]+)/ ),
                      chrome = ua.match( /Chrome\/([\d.]+)/ ) ||
                          ua.match( /CriOS\/([\d.]+)/ ),
      
                      ie = ua.match( /MSIE\s([\d\.]+)/ ) ||
                          ua.match(/(?:trident)(?:.*rv:([\w.]+))?/i),
                      firefox = ua.match( /Firefox\/([\d.]+)/ ),
                      safari = ua.match( /Safari\/([\d.]+)/ ),
                      opera = ua.match( /OPR\/([\d.]+)/ );
      
                  webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));
                  chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));
                  ie && (ret.ie = parseFloat( ie[ 1 ] ));
                  firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));
                  safari && (ret.safari = parseFloat( safari[ 1 ] ));
                  opera && (ret.opera = parseFloat( opera[ 1 ] ));
      
                  return ret;
              })( navigator.userAgent ),
      
              /**
               * @description  操作系统检查结果。
               *
               * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。
               * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。
               * @property {Object} [os]
               */
              os: (function( ua ) {
                  var ret = {},
      
                      // osx = !!ua.match( /\(Macintosh\; Intel / ),
                      android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ),
                      ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ );
      
                  // osx && (ret.osx = true);
                  android && (ret.android = parseFloat( android[ 1 ] ));
                  ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));
      
                  return ret;
              })( navigator.userAgent ),
      
              /**
               * 实现类与类之间的继承。
               * @method inherits
               * @grammar BaseController.inherits( super ) => child
               * @grammar BaseController.inherits( super, protos ) => child
               * @grammar BaseController.inherits( super, protos, statics ) => child
               * @param  {Class} super 父类
               * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。
               * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。
               * @param  {Object} [statics] 静态属性或方法。
               * @return {Class} 返回子类。
               * @example
               * function Person() {
               *     console.log( 'Super' );
               * }
               * Person.prototype.hello = function() {
               *     console.log( 'hello' );
               * };
               *
               * var Manager = BaseController.inherits( Person, {
               *     world: function() {
               *         console.log( 'World' );
               *     }
               * });
               *
               * // 因为没有指定构造器，父类的构造器将会执行。
               * var instance = new Manager();    // => Super
               *
               * // 继承子父类的方法
               * instance.hello();    // => hello
               * instance.world();    // => World
               *
               * // 子类的__super__属性指向父类
               * console.log( Manager.__super__ === Person );    // => true
               */
              inherits: function( Super, protos, staticProtos ) {
                  var child;
      
                  if ( typeof protos === 'function' ) {
                      child = protos;
                      protos = null;
                  } else if ( protos && protos.hasOwnProperty('constructor') ) {
                      child = protos.constructor;
                  } else {
                      child = function() {
                          return Super.apply( this, arguments );
                      };
                  }
      
                  // 复制静态方法
                  $.extend( true, child, Super, staticProtos || {} );
      
                  /* jshint camelcase: false */
      
                  // 让子类的__super__属性指向父类。
                  child.__super__ = Super.prototype;
      
                  // 构建原型，添加原型方法或属性。
                  // 暂时用Object.create实现。
                  child.prototype = createObject( Super.prototype );
                  protos && $.extend( true, child.prototype, protos );
      
                  return child;
              },
      
              /**
               * 一个不做任何事情的方法。可以用来赋值给默认的callback.
               * @method noop
               */
              noop: noop,
      
              /**
               * 返回一个新的方法，此方法将已指定的`context`来执行。
               * @grammar BaseController.bindFn( fn, context ) => Function
               * @method bindFn
               * @example
               * var doSomething = function() {
               *         console.log( this.name );
               *     },
               *     obj = {
               *         name: 'Object Name'
               *     },
               *     aliasFn = BaseController.bind( doSomething, obj );
               *
               *  aliasFn();    // => Object Name
               *
               */
              bindFn: bindFn,
      
              /**
               * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:BaseController.log)。
               * @grammar BaseController.log( args... ) => undefined
               * @method log
               */
              log: (function() {
                  if ( window.console ) {
                      return bindFn( console.log, console );
                  }
                  return noop;
              })(),
      
              nextTick: (function() {
      
                  return function( cb ) {
                      setTimeout( cb, 1 );
                  };
      
                  // @bug 当浏览器不在当前窗口时就停了。
                  // var next = window.requestAnimationFrame ||
                  //     window.webkitRequestAnimationFrame ||
                  //     window.mozRequestAnimationFrame ||
                  //     function( cb ) {
                  //         window.setTimeout( cb, 1000 / 60 );
                  //     };
      
                  // // fix: Uncaught TypeError: Illegal invocation
                  // return bindFn( next, window );
              })(),
      
              /**
               * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。
               * 将用来将非数组对象转化成数组对象。
               * @grammar BaseController.slice( target, start[, end] ) => Array
               * @method slice
               * @example
               * function doSomthing() {
               *     var args = BaseController.slice( arguments, 1 );
               *     console.log( args );
               * }
               *
               * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array ["arg2", "arg3"]
               */
              slice: uncurryThis( [].slice ),
      
              /**
               * 生成唯一的ID
               * @method guid
               * @grammar BaseController.guid() => String
               * @grammar BaseController.guid( prefx ) => String
               */
              guid: (function() {
                  var counter = 0;
      
                  return function( prefix ) {
                      var guid = (+new Date()).toString( 32 ),
                          i = 0;
      
                      for ( ; i < 5; i++ ) {
                          guid += Math.floor( Math.random() * 65535 ).toString( 32 );
                      }
      
                      return (prefix || 'wu_') + guid + (counter++).toString( 32 );
                  };
              })(),
      
              /**
               * 格式化文件大小, 输出成带单位的字符串
               * @method formatSize
               * @grammar BaseController.formatSize( size ) => String
               * @grammar BaseController.formatSize( size, pointLength ) => String
               * @grammar BaseController.formatSize( size, pointLength, units ) => String
               * @param {Number} size 文件大小
               * @param {Number} [pointLength=2] 精确到的小数点数。
               * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.
               * @example
               * console.log( BaseController.formatSize( 100 ) );    // => 100B
               * console.log( BaseController.formatSize( 1024 ) );    // => 1.00K
               * console.log( BaseController.formatSize( 1024, 0 ) );    // => 1K
               * console.log( BaseController.formatSize( 1024 * 1024 ) );    // => 1.00M
               * console.log( BaseController.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G
               * console.log( BaseController.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB
               */
              formatSize: function( size, pointLength, units ) {
                  var unit;
      
                  units = units || [ 'B', 'K', 'M', 'G', 'TB' ];
      
                  while ( (unit = units.shift()) && size > 1024 ) {
                      size = size / 1024;
                  }
      
                  return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +
                          unit;
              }
          };
      });
      /**
       * 事件处理类，可以独立使用，也可以扩展给对象使用。
       * @fileOverview Mediator
       */
      define('mediator',[
          'base'
      ], function( Base ) {
          var $ = Base.$,
              slice = [].slice,
              separator = /\s+/,
              protos;
      
          // 根据条件过滤出事件handlers.
          function findHandlers( arr, name, callback, context ) {
              return $.grep( arr, function( handler ) {
                  return handler &&
                          (!name || handler.e === name) &&
                          (!callback || handler.cb === callback ||
                          handler.cb._cb === callback) &&
                          (!context || handler.ctx === context);
              });
          }
      
          function eachEvent( events, callback, iterator ) {
              // 不支持对象，只支持多个event用空格隔开
              $.each( (events || '').split( separator ), function( _, key ) {
                  iterator( key, callback );
              });
          }
      
          function triggerHanders( events, args ) {
              var stoped = false,
                  i = -1,
                  len = events.length,
                  handler;
      
              while ( ++i < len ) {
                  handler = events[ i ];
      
                  if ( handler.cb.apply( handler.ctx2, args ) === false ) {
                      stoped = true;
                      break;
                  }
              }
      
              return !stoped;
          }
      
          protos = {
      
              /**
               * 绑定事件。
               *
               * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如
               * ```javascript
               * var obj = {};
               *
               * // 使得obj有事件行为
               * Mediator.installTo( obj );
               *
               * obj.on( 'testa', function( arg1, arg2 ) {
               *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'
               * });
               *
               * obj.trigger( 'testa', 'arg1', 'arg2' );
               * ```
               *
               * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。
               * 切会影响到`trigger`方法的返回值，为`false`。
               *
               * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，
               * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。
               * ```javascript
               * obj.on( 'all', function( type, arg1, arg2 ) {
               *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'
               * });
               * ```
               *
               * @method on
               * @grammar on( name, callback[, context] ) => self
               * @param  {String}   name     事件名，支持多个事件用空格隔开
               * @param  {Function} callback 事件处理器
               * @param  {Object}   [context]  事件处理器的上下文。
               * @return {self} 返回自身，方便链式
               * @chainable
               * @class Mediator
               */
              on: function( name, callback, context ) {
                  var me = this,
                      set;
      
                  if ( !callback ) {
                      return this;
                  }
      
                  set = this._events || (this._events = []);
      
                  eachEvent( name, callback, function( name, callback ) {
                      var handler = { e: name };
      
                      handler.cb = callback;
                      handler.ctx = context;
                      handler.ctx2 = context || me;
                      handler.id = set.length;
      
                      set.push( handler );
                  });
      
                  return this;
              },
      
              /**
               * 绑定事件，且当handler执行完后，自动解除绑定。
               * @method once
               * @grammar once( name, callback[, context] ) => self
               * @param  {String}   name     事件名
               * @param  {Function} callback 事件处理器
               * @param  {Object}   [context]  事件处理器的上下文。
               * @return {self} 返回自身，方便链式
               * @chainable
               */
              once: function( name, callback, context ) {
                  var me = this;
      
                  if ( !callback ) {
                      return me;
                  }
      
                  eachEvent( name, callback, function( name, callback ) {
                      var once = function() {
                              me.off( name, once );
                              return callback.apply( context || me, arguments );
                          };
      
                      once._cb = callback;
                      me.on( name, once, context );
                  });
      
                  return me;
              },
      
              /**
               * 解除事件绑定
               * @method off
               * @grammar off( [name[, callback[, context] ] ] ) => self
               * @param  {String}   [name]     事件名
               * @param  {Function} [callback] 事件处理器
               * @param  {Object}   [context]  事件处理器的上下文。
               * @return {self} 返回自身，方便链式
               * @chainable
               */
              off: function( name, cb, ctx ) {
                  var events = this._events;
      
                  if ( !events ) {
                      return this;
                  }
      
                  if ( !name && !cb && !ctx ) {
                      this._events = [];
                      return this;
                  }
      
                  eachEvent( name, cb, function( name, cb ) {
                      $.each( findHandlers( events, name, cb, ctx ), function() {
                          delete events[ this.id ];
                      });
                  });
      
                  return this;
              },
      
              /**
               * 触发事件
               * @method trigger
               * @grammar trigger( name[, args...] ) => self
               * @param  {String}   type     事件名
               * @param  {*} [...] 任意参数
               * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true
               */
              trigger: function( type ) {
                  var args, events, allEvents;
      
                  if ( !this._events || !type ) {
                      return this;
                  }
      
                  args = slice.call( arguments, 1 );
                  events = findHandlers( this._events, type );
                  allEvents = findHandlers( this._events, 'all' );
      
                  return triggerHanders( events, args ) &&
                          triggerHanders( allEvents, arguments );
              }
          };
      
          /**
           * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。
           * 主要目的是负责模块与模块之间的合作，降低耦合度。
           *
           * @class Mediator
           */
          return $.extend({
      
              /**
               * 可以通过这个接口，使任何对象具备事件功能。
               * @method installTo
               * @param  {Object} obj 需要具备事件行为的对象。
               * @return {Object} 返回obj.
               */
              installTo: function( obj ) {
                  return $.extend( obj, protos );
              }
      
          }, protos );
      });
      /**
       * @fileOverview Uploader上传类
       */
      define('uploader',[
          'base',
          'mediator'
      ], function( Base, Mediator ) {
      
          var $ = Base.$;
      
          /**
           * 上传入口类。
           * @class Uploader
           * @constructor
           * @grammar new Uploader( opts ) => Uploader
           * @example
           * var uploader = WebUploader.Uploader({
           *     swf: 'path_of_swf/Uploader.swf',
           *
           *     // 开起分片上传。
           *     chunked: true
           * });
           */
          function Uploader( opts ) {
              this.options = $.extend( true, {}, Uploader.options, opts );
              this._init( this.options );
          }
      
          // default Options
          // widgets中有相应扩展
          Uploader.options = {};
          Mediator.installTo( Uploader.prototype );
      
          // 批量添加纯命令式方法。
          $.each({
              upload: 'start-upload',
              stop: 'stop-upload',
              getFile: 'get-file',
              getFiles: 'get-files',
              addFile: 'add-file',
              addFiles: 'add-file',
              sort: 'sort-files',
              removeFile: 'remove-file',
              skipFile: 'skip-file',
              retry: 'retry',
              isInProgress: 'is-in-progress',
              makeThumb: 'make-thumb',
              getDimension: 'get-dimension',
              addButton: 'add-btn',
              getRuntimeType: 'get-runtime-type',
              refresh: 'refresh',
              disable: 'disable',
              enable: 'enable',
              reset: 'reset'
          }, function( fn, command ) {
              Uploader.prototype[ fn ] = function() {
                  return this.request( command, arguments );
              };
          });
      
          $.extend( Uploader.prototype, {
              state: 'pending',
      
              _init: function( opts ) {
                  var me = this;
      
                  me.request( 'init', opts, function() {
                      me.state = 'ready';
                      me.trigger('ready');
                  });
              },
      
              /**
               * 获取或者设置Uploader配置项。
               * @method option
               * @grammar option( key ) => *
               * @grammar option( key, val ) => self
               * @example
               *
               * // 初始状态图片上传前不会压缩
               * var uploader = new WebUploader.Uploader({
               *     resize: null;
               * });
               *
               * // 修改后图片上传前，尝试将图片压缩到1600 * 1600
               * uploader.options( 'resize', {
               *     width: 1600,
               *     height: 1600
               * });
               */
              option: function( key, val ) {
                  var opts = this.options;
      
                  // setter
                  if ( arguments.length > 1 ) {
      
                      if ( $.isPlainObject( val ) &&
                              $.isPlainObject( opts[ key ] ) ) {
                          $.extend( opts[ key ], val );
                      } else {
                          opts[ key ] = val;
                      }
      
                  } else {    // getter
                      return key ? opts[ key ] : opts;
                  }
              },
      
              /**
               * 获取文件统计信息。返回一个包含一下信息的对象。
               * * `successNum` 上传成功的文件数
               * * `uploadFailNum` 上传失败的文件数
               * * `cancelNum` 被删除的文件数
               * * `invalidNum` 无效的文件数
               * * `queueNum` 还在队列中的文件数
               * @method getStats
               * @grammar getStats() => Object
               */
              getStats: function() {
                  // return this._mgr.getStats.apply( this._mgr, arguments );
                  var stats = this.request('get-stats');
      
                  return {
                      successNum: stats.numOfSuccess,
      
                      // who care?
                      // queueFailNum: 0,
                      cancelNum: stats.numOfCancel,
                      invalidNum: stats.numOfInvalid,
                      uploadFailNum: stats.numOfUploadFailed,
                      queueNum: stats.numOfQueue
                  };
              },
      
              // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
              trigger: function( type/*, args...*/ ) {
                  var args = [].slice.call( arguments, 1 ),
                      opts = this.options,
                      name = 'on' + type.substring( 0, 1 ).toUpperCase() +
                          type.substring( 1 );
      
                  if (
                          // 调用通过on方法注册的handler.
                          Mediator.trigger.apply( this, arguments ) === false ||
      
                          // 调用opts.onEvent
                          $.isFunction( opts[ name ] ) &&
                          opts[ name ].apply( this, args ) === false ||
      
                          // 调用this.onEvent
                          $.isFunction( this[ name ] ) &&
                          this[ name ].apply( this, args ) === false ||
      
                          // 广播所有uploader的事件。
                          Mediator.trigger.apply( Mediator,
                          [ this, type ].concat( args ) ) === false ) {
      
                      return false;
                  }
      
                  return true;
              },
      
              // widgets/widget.js将补充此方法的详细文档。
              request: Base.noop
          });
      
          /**
           * 创建Uploader实例，等同于new Uploader( opts );
           * @method create
           * @class Base
           * @static
           * @grammar BaseController.create( opts ) => Uploader
           */
          Base.create = Uploader.create = function( opts ) {
              return new Uploader( opts );
          };
      
          // 暴露Uploader，可以通过它来扩展业务逻辑。
          Base.Uploader = Uploader;
      
          return Uploader;
      });
      /**
       * @fileOverview Runtime管理器，负责Runtime的选择, 连接
       */
      define('runtime/runtime',[
          'base',
          'mediator'
      ], function( Base, Mediator ) {
      
          var $ = Base.$,
              factories = {},
      
              // 获取对象的第一个key
              getFirstKey = function( obj ) {
                  for ( var key in obj ) {
                      if ( obj.hasOwnProperty( key ) ) {
                          return key;
                      }
                  }
                  return null;
              };
      
          // 接口类。
          function Runtime( options ) {
              this.options = $.extend({
                  container: document.body
              }, options );
              this.uid = Base.guid('rt_');
          }
      
          $.extend( Runtime.prototype, {
      
              getContainer: function() {
                  var opts = this.options,
                      parent, container;
      
                  if ( this._container ) {
                      return this._container;
                  }
      
                  parent = $( opts.container || document.body );
                  container = $( document.createElement('div') );
      
                  container.attr( 'id', 'rt_' + this.uid );
                  container.css({
                      position: 'absolute',
                      top: '0px',
                      left: '0px',
                      width: '1px',
                      height: '1px',
                      overflow: 'hidden'
                  });
      
                  parent.append( container );
                  parent.addClass('webuploader-container');
                  this._container = container;
                  return container;
              },
      
              init: Base.noop,
              exec: Base.noop,
      
              destroy: function() {
                  if ( this._container ) {
                      this._container.parentNode.removeChild( this.__container );
                  }
      
                  this.off();
              }
          });
      
          Runtime.orders = 'html5,flash';
      
      
          /**
           * 添加Runtime实现。
           * @param {String} type    类型
           * @param {Runtime} factory 具体Runtime实现。
           */
          Runtime.addRuntime = function( type, factory ) {
              factories[ type ] = factory;
          };
      
          Runtime.hasRuntime = function( type ) {
              return !!(type ? factories[ type ] : getFirstKey( factories ));
          };
      
          Runtime.create = function( opts, orders ) {
              var type, runtime;
      
              orders = orders || Runtime.orders;
              $.each( orders.split( /\s*,\s*/g ), function() {
                  if ( factories[ this ] ) {
                      type = this;
                      return false;
                  }
              });
      
              type = type || getFirstKey( factories );
      
              if ( !type ) {
                  throw new Error('Runtime Error');
              }
      
              runtime = new factories[ type ]( opts );
              return runtime;
          };
      
          Mediator.installTo( Runtime.prototype );
          return Runtime;
      });
      
      /**
       * @fileOverview Runtime管理器，负责Runtime的选择, 连接
       */
      define('runtime/client',[
          'base',
          'mediator',
          'runtime/runtime'
      ], function( Base, Mediator, Runtime ) {
      
          var cache;
      
          cache = (function() {
              var obj = {};
      
              return {
                  add: function( runtime ) {
                      obj[ runtime.uid ] = runtime;
                  },
      
                  get: function( ruid, standalone ) {
                      var i;
      
                      if ( ruid ) {
                          return obj[ ruid ];
                      }
      
                      for ( i in obj ) {
                          // 有些类型不能重用，比如filepicker.
                          if ( standalone && obj[ i ].__standalone ) {
                              continue;
                          }
      
                          return obj[ i ];
                      }
      
                      return null;
                  },
      
                  remove: function( runtime ) {
                      delete obj[ runtime.uid ];
                  }
              };
          })();
      
          function RuntimeClient( component, standalone ) {
              var deferred = Base.Deferred(),
                  runtime;
      
              this.uid = Base.guid('client_');
      
              // 允许runtime没有初始化之前，注册一些方法在初始化后执行。
              this.runtimeReady = function( cb ) {
                  return deferred.done( cb );
              };
      
              this.connectRuntime = function( opts, cb ) {
      
                  // already connected.
                  if ( runtime ) {
                      throw new Error('already connected!');
                  }
      
                  deferred.done( cb );
      
                  if ( typeof opts === 'string' && cache.get( opts ) ) {
                      runtime = cache.get( opts );
                  }
      
                  // 像filePicker只能独立存在，不能公用。
                  runtime = runtime || cache.get( null, standalone );
      
                  // 需要创建
                  if ( !runtime ) {
                      runtime = Runtime.create( opts, opts.runtimeOrder );
                      runtime.__promise = deferred.promise();
                      runtime.once( 'ready', deferred.resolve );
                      runtime.init();
                      cache.add( runtime );
                      runtime.__client = 1;
                  } else {
                      // 来自cache
                      Base.$.extend( runtime.options, opts );
                      runtime.__promise.then( deferred.resolve );
                      runtime.__client++;
                  }
      
                  standalone && (runtime.__standalone = standalone);
                  return runtime;
              };
      
              this.getRuntime = function() {
                  return runtime;
              };
      
              this.disconnectRuntime = function() {
                  if ( !runtime ) {
                      return;
                  }
      
                  runtime.__client--;
      
                  if ( runtime.__client <= 0 ) {
                      cache.remove( runtime );
                      delete runtime.__promise;
                      runtime.destroy();
                  }
      
                  runtime = null;
              };
      
              this.exec = function() {
                  if ( !runtime ) {
                      return;
                  }
      
                  var args = Base.slice( arguments );
                  component && args.unshift( component );
      
                  return runtime.exec.apply( this, args );
              };
      
              this.getRuid = function() {
                  return runtime && runtime.uid;
              };
      
              this.destroy = (function( destroy ) {
                  return function() {
                      destroy && destroy.apply( this, arguments );
                      this.trigger('destroy');
                      this.off();
                      this.exec('destroy');
                      this.disconnectRuntime();
                  };
              })( this.destroy );
          }
      
          Mediator.installTo( RuntimeClient.prototype );
          return RuntimeClient;
      });
      /**
       * @fileOverview 错误信息
       */
      define('lib/dnd',[
          'base',
          'mediator',
          'runtime/client'
      ], function( Base, Mediator, RuntimeClent ) {
      
          var $ = Base.$;
      
          function DragAndDrop( opts ) {
              opts = this.options = $.extend({}, DragAndDrop.options, opts );
      
              opts.container = $( opts.container );
      
              if ( !opts.container.length ) {
                  return;
              }
      
              RuntimeClent.call( this, 'DragAndDrop' );
          }
      
          DragAndDrop.options = {
              accept: null,
              disableGlobalDnd: false
          };
      
          Base.inherits( RuntimeClent, {
              constructor: DragAndDrop,
      
              init: function() {
                  var me = this;
      
                  me.connectRuntime( me.options, function() {
                      me.exec('init');
                      me.trigger('ready');
                  });
              },
      
              destroy: function() {
                  this.disconnectRuntime();
              }
          });
      
          Mediator.installTo( DragAndDrop.prototype );
      
          return DragAndDrop;
      });
      /**
       * @fileOverview 组件基类。
       */
      define('widgets/widget',[
          'base',
          'uploader'
      ], function( Base, Uploader ) {
      
          var $ = Base.$,
              _init = Uploader.prototype._init,
              IGNORE = {},
              widgetClass = [];
      
          function isArrayLike( obj ) {
              if ( !obj ) {
                  return false;
              }
      
              var length = obj.length,
                  type = $.type( obj );
      
              if ( obj.nodeType === 1 && length ) {
                  return true;
              }
      
              return type === 'array' || type !== 'function' && type !== 'string' &&
                      (length === 0 || typeof length === 'number' && length > 0 &&
                      (length - 1) in obj);
          }
      
          function Widget( uploader ) {
              this.owner = uploader;
              this.options = uploader.options;
          }
      
          $.extend( Widget.prototype, {
      
              init: Base.noop,
      
              // 类Backbone的事件监听声明，监听uploader实例上的事件
              // widget直接无法监听事件，事件只能通过uploader来传递
              invoke: function( apiName, args ) {
      
                  /*
                      {
                          'make-thumb': 'makeThumb'
                      }
                   */
                  var map = this.responseMap;
      
                  // 如果无API响应声明则忽略
                  if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||
                          !$.isFunction( this[ map[ apiName ] ] ) ) {
      
                      return IGNORE;
                  }
      
                  return this[ map[ apiName ] ].apply( this, args );
      
              },
      
              /**
               * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。
               * @method request
               * @grammar request( command, args ) => * | Promise
               * @grammar request( command, args, callback ) => Promise
               * @for  Uploader
               */
              request: function() {
                  return this.owner.request.apply( this.owner, arguments );
              }
          });
      
          // 扩展Uploader.
          $.extend( Uploader.prototype, {
      
              // 覆写_init用来初始化widgets
              _init: function() {
                  var me = this,
                      widgets = me._widgets = [];
      
                  $.each( widgetClass, function( _, klass ) {
                      widgets.push( new klass( me ) );
                  });
      
                  return _init.apply( me, arguments );
              },
      
              request: function( apiName, args, callback ) {
                  var i = 0,
                      widgets = this._widgets,
                      len = widgets.length,
                      rlts = [],
                      dfds = [],
                      widget, rlt, promise, key;
      
                  args = isArrayLike( args ) ? args : [ args ];
      
                  for ( ; i < len; i++ ) {
                      widget = widgets[ i ];
                      rlt = widget.invoke( apiName, args );
      
                      if ( rlt !== IGNORE ) {
      
                          // Deferred对象
                          if ( Base.isPromise( rlt ) ) {
                              dfds.push( rlt );
                          } else {
                              rlts.push( rlt );
                          }
                      }
                  }
      
                  // 如果有callback，则用异步方式。
                  if ( callback || dfds.length ) {
                      promise = Base.when.apply( Base, dfds );
                      key = promise.pipe ? 'pipe' : 'then';
      
                      // 很重要不能删除。删除了会死循环。
                      // 保证执行顺序。让callback总是在下一个tick中执行。
                      return promise[ key ](function() {
                                  var deferred = Base.Deferred(),
                                      args = arguments;
      
                                  setTimeout(function() {
                                      deferred.resolve.apply( deferred, args );
                                  }, 1 );
      
                                  return deferred.promise();
                              })[ key ]( callback || Base.noop );
                  } else {
                      return rlts[ 0 ];
                  }
              }
          });
      
          /**
           * 添加组件
           * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义
           * @param  {object} responseMap API名称与函数实现的映射
           * @example
           *     Uploader.register( {
           *         init: function( options ) {},
           *         makeThumb: function() {}
           *     }, {
           *         'make-thumb': 'makeThumb'
           *     } );
           */
          Uploader.register = Widget.register = function( responseMap, widgetProto ) {
              var map = { init: 'init' },
                  klass;
      
              if ( arguments.length === 1 ) {
                  widgetProto = responseMap;
                  widgetProto.responseMap = map;
              } else {
                  widgetProto.responseMap = $.extend( map, responseMap );
              }
      
              klass = Base.inherits( Widget, widgetProto );
              widgetClass.push( klass );
      
              return klass;
          };
      
          return Widget;
      });
      /**
       * @fileOverview DragAndDrop Widget。
       */
      define('widgets/filednd',[
          'base',
          'uploader',
          'lib/dnd',
          'widgets/widget'
      ], function( Base, Uploader, Dnd ) {
          var $ = Base.$;
      
          Uploader.options.dnd = '';
      
          /**
           * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器，如果不指定，则不启动。
           * @namespace options
           * @for Uploader
           */
      
          /**
           * @event dndAccept
           * @param {DataTransferItemList} items DataTransferItem
           * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API，且只能通过 mime-type 验证。
           * @for  Uploader
           */
          return Uploader.register({
              init: function( opts ) {
      
                  if ( !opts.dnd ||
                          this.request('predict-runtime-type') !== 'html5' ) {
                      return;
                  }
      
                  var me = this,
                      deferred = Base.Deferred(),
                      options = $.extend({}, {
                          disableGlobalDnd: opts.disableGlobalDnd,
                          container: opts.dnd,
                          accept: opts.accept
                      }),
                      dnd;
      
                  dnd = new Dnd( options );
      
                  dnd.once( 'ready', deferred.resolve );
                  dnd.on( 'drop', function( files ) {
                      me.request( 'add-file', [ files ]);
                  });
      
                  // 检测文件是否全部允许添加。
                  dnd.on( 'accept', function( items ) {
                      return me.owner.trigger( 'dndAccept', items );
                  });
      
                  dnd.init();
      
                  return deferred.promise();
              }
          });
      });
      
      /**
       * @fileOverview 错误信息
       */
      define('lib/filepaste',[
          'base',
          'mediator',
          'runtime/client'
      ], function( Base, Mediator, RuntimeClent ) {
      
          var $ = Base.$;
      
          function FilePaste( opts ) {
              opts = this.options = $.extend({}, opts );
              opts.container = $( opts.container || document.body );
              RuntimeClent.call( this, 'FilePaste' );
          }
      
          Base.inherits( RuntimeClent, {
              constructor: FilePaste,
      
              init: function() {
                  var me = this;
      
                  me.connectRuntime( me.options, function() {
                      me.exec('init');
                      me.trigger('ready');
                  });
              },
      
              destroy: function() {
                  this.exec('destroy');
                  this.disconnectRuntime();
                  this.off();
              }
          });
      
          Mediator.installTo( FilePaste.prototype );
      
          return FilePaste;
      });
      /**
       * @fileOverview 组件基类。
       */
      define('widgets/filepaste',[
          'base',
          'uploader',
          'lib/filepaste',
          'widgets/widget'
      ], function( Base, Uploader, FilePaste ) {
          var $ = Base.$;
      
          /**
           * @property {Selector} [paste=undefined]  指定监听paste事件的容器，如果不指定，不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.
           * @namespace options
           * @for Uploader
           */
          return Uploader.register({
              init: function( opts ) {
      
                  if ( !opts.paste ||
                          this.request('predict-runtime-type') !== 'html5' ) {
                      return;
                  }
      
                  var me = this,
                      deferred = Base.Deferred(),
                      options = $.extend({}, {
                          container: opts.paste,
                          accept: opts.accept
                      }),
                      paste;
      
                  paste = new FilePaste( options );
      
                  paste.once( 'ready', deferred.resolve );
                  paste.on( 'paste', function( files ) {
                      me.owner.request( 'add-file', [ files ]);
                  });
                  paste.init();
      
                  return deferred.promise();
              }
          });
      });
      /**
       * @fileOverview Blob
       */
      define('lib/blob',[
          'base',
          'runtime/client'
      ], function( Base, RuntimeClient ) {
      
          function Blob( ruid, source ) {
              var me = this;
      
              me.source = source;
              me.ruid = ruid;
      
              RuntimeClient.call( me, 'Blob' );
      
              this.uid = source.uid || this.uid;
              this.type = source.type || '';
              this.size = source.size || 0;
      
              if ( ruid ) {
                  me.connectRuntime( ruid );
              }
          }
      
          Base.inherits( RuntimeClient, {
              constructor: Blob,
      
              slice: function( start, end ) {
                  return this.exec( 'slice', start, end );
              },
      
              getSource: function() {
                  return this.source;
              }
          });
      
          return Blob;
      });
      /**
       * 为了统一化Flash的File和HTML5的File而存在。
       * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。
       * @fileOverview File
       */
      define('lib/file',[
          'base',
          'lib/blob'
      ], function( Base, Blob ) {
      
          var uid = 1,
              rExt = /\.([^.]+)$/;
      
          function File( ruid, file ) {
              var ext;
      
              Blob.apply( this, arguments );
              this.name = file.name || ('untitled' + uid++);
              ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';
      
              // todo 支持其他类型文件的转换。
      
              // 如果有mimetype, 但是文件名里面没有找出后缀规律
              if ( !ext && this.type ) {
                  ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?
                          RegExp.$1.toLowerCase() : '';
                  this.name += '.' + ext;
              }
      
              // 如果没有指定mimetype, 但是知道文件后缀。
              if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {
                  this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);
              }
      
              this.ext = ext;
              this.lastModifiedDate = file.lastModifiedDate ||
                      (new Date()).toLocaleString();
          }
      
          return Base.inherits( Blob, File );
      });
      
      /**
       * @fileOverview 错误信息
       */
      define('lib/filepicker',[
          'base',
          'runtime/client',
          'lib/file'
      ], function( Base, RuntimeClent, File ) {
      
          var $ = Base.$;
      
          function FilePicker( opts ) {
              opts = this.options = $.extend({}, FilePicker.options, opts );
      
              opts.container = $( opts.id );
      
              if ( !opts.container.length ) {
                  throw new Error('按钮指定错误');
              }
      
              opts.innerHTML = opts.innerHTML || opts.label ||
                      opts.container.html() || '';
      
              opts.button = $( opts.button || document.createElement('div') );
              opts.button.html( opts.innerHTML );
              opts.container.html( opts.button );
      
              RuntimeClent.call( this, 'FilePicker', true );
          }
      
          FilePicker.options = {
              button: null,
              container: null,
              label: null,
              innerHTML: null,
              multiple: true,
              accept: null,
              name: 'file'
          };
      
          Base.inherits( RuntimeClent, {
              constructor: FilePicker,
      
              init: function() {
                  var me = this,
                      opts = me.options,
                      button = opts.button;
      
                  button.addClass('webuploader-pick');
      
                  me.on( 'all', function( type ) {
                      var files;
      
                      switch ( type ) {
                          case 'mouseenter':
                              button.addClass('webuploader-pick-hover');
                              break;
      
                          case 'mouseleave':
                              button.removeClass('webuploader-pick-hover');
                              break;
      
                          case 'change':
                              files = me.exec('getFiles');
                              me.trigger( 'select', $.map( files, function( file ) {
                                  file = new File( me.getRuid(), file );
      
                                  // 记录来源。
                                  file._refer = opts.container;
                                  return file;
                              }), opts.container );
                              break;
                      }
                  });
      
                  me.connectRuntime( opts, function() {
                      me.refresh();
                      me.exec( 'init', opts );
                      me.trigger('ready');
                  });
      
                  $( window ).on( 'resize', function() {
                      me.refresh();
                  });
              },
      
              refresh: function() {
                  var shimContainer = this.getRuntime().getContainer(),
                      button = this.options.button,
                      width = button.outerWidth ?
                              button.outerWidth() : button.width(),
      
                      height = button.outerHeight ?
                              button.outerHeight() : button.height(),
      
                      pos = button.offset();
      
                  width && height && shimContainer.css({
                      bottom: 'auto',
                      right: 'auto',
                      width: width + 'px',
                      height: height + 'px'
                  }).offset( pos );
              },
      
              enable: function() {
                  var btn = this.options.button;
      
                  btn.removeClass('webuploader-pick-disable');
                  this.refresh();
              },
      
              disable: function() {
                  var btn = this.options.button;
      
                  this.getRuntime().getContainer().css({
                      top: '-99999px'
                  });
      
                  btn.addClass('webuploader-pick-disable');
              },
      
              destroy: function() {
                  if ( this.runtime ) {
                      this.exec('destroy');
                      this.disconnectRuntime();
                  }
              }
          });
      
          return FilePicker;
      });
      
      /**
       * @fileOverview 文件选择相关
       */
      define('widgets/filepicker',[
          'base',
          'uploader',
          'lib/filepicker',
          'widgets/widget'
      ], function( Base, Uploader, FilePicker ) {
          var $ = Base.$;
      
          $.extend( Uploader.options, {
      
              /**
               * @property {Selector | Object} [pick=undefined]
               * @namespace options
               * @for Uploader
               * @description 指定选择文件的按钮容器，不指定则不创建按钮。
               *
               * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。
               * * `label` {String} 请采用 `innerHTML` 代替
               * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。
               * * `multiple` {Boolean} 是否开起同时选择多个文件能力。
               */
              pick: null,
      
              /**
               * @property {Arroy} [accept=null]
               * @namespace options
               * @for Uploader
               * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。
               *
               * * `title` {String} 文字描述
               * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。
               * * `mimeTypes` {String} 多个用逗号分割。
               *
               * 如：
               *
               * ```
               * {
               *     title: 'Images',
               *     extensions: 'gif,jpg,jpeg,bmp,png',
               *     mimeTypes: 'image/*'
               * }
               * ```
               */
              accept: null/*{
                  title: 'Images',
                  extensions: 'gif,jpg,jpeg,bmp,png',
                  mimeTypes: 'image/*'
              }*/
          });
      
          return Uploader.register({
              'add-btn': 'addButton',
              refresh: 'refresh',
              disable: 'disable',
              enable: 'enable'
          }, {
      
              init: function( opts ) {
                  this.pickers = [];
                  return opts.pick && this.addButton( opts.pick );
              },
      
              refresh: function() {
                  $.each( this.pickers, function() {
                      this.refresh();
                  });
              },
      
              /**
               * @method addButton
               * @for Uploader
               * @grammar addButton( pick ) => Promise
               * @description
               * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。
               * @example
               * uploader.addButton({
               *     id: '#btnContainer',
               *     innerHTML: '选择文件'
               * });
               */
              addButton: function( pick ) {
                  var me = this,
                      opts = me.options,
                      accept = opts.accept,
                      options, picker, deferred;
      
                  if ( !pick ) {
                      return;
                  }
      
                  deferred = Base.Deferred();
                  $.isPlainObject( pick ) || (pick = {
                      id: pick
                  });
      
                  options = $.extend({}, pick, {
                      accept: $.isPlainObject( accept ) ? [ accept ] : accept,
                      swf: opts.swf,
                      runtimeOrder: opts.runtimeOrder
                  });
      
                  picker = new FilePicker( options );
      
                  picker.once( 'ready', deferred.resolve );
                  picker.on( 'select', function( files ) {
                      me.owner.request( 'add-file', [ files ]);
                  });
                  picker.init();
      
                  this.pickers.push( picker );
      
                  return deferred.promise();
              },
      
              disable: function() {
                  $.each( this.pickers, function() {
                      this.disable();
                  });
              },
      
              enable: function() {
                  $.each( this.pickers, function() {
                      this.enable();
                  });
              }
          });
      });
      /**
       * @fileOverview 文件属性封装
       */
      define('file',[
          'base',
          'mediator'
      ], function( Base, Mediator ) {
      
          var $ = Base.$,
              idPrefix = 'WU_FILE_',
              idSuffix = 0,
              rExt = /\.([^.]+)$/,
              statusMap = {};
      
          function gid() {
              return idPrefix + idSuffix++;
          }
      
          /**
           * 文件类
           * @class File
           * @constructor 构造函数
           * @grammar new File( source ) => File
           * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。
           */
          function WUFile( source ) {
      
              /**
               * 文件名，包括扩展名（后缀）
               * @property name
               * @type {string}
               */
              this.name = source.name || 'Untitled';
      
              /**
               * 文件体积（字节）
               * @property size
               * @type {uint}
               * @default 0
               */
              this.size = source.size || 0;
      
              /**
               * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)
               * @property type
               * @type {string}
               * @default 'application'
               */
              this.type = source.type || 'application';
      
              /**
               * 文件最后修改日期
               * @property lastModifiedDate
               * @type {int}
               * @default 当前时间戳
               */
              this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);
      
              /**
               * 文件ID，每个对象具有唯一ID，与文件名无关
               * @property id
               * @type {string}
               */
              this.id = gid();
      
              /**
               * 文件扩展名，通过文件名获取，例如test.png的扩展名为png
               * @property ext
               * @type {string}
               */
              this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';
      
      
              /**
               * 状态文字说明。在不同的status语境下有不同的用途。
               * @property statusText
               * @type {string}
               */
              this.statusText = '';
      
              // 存储文件状态，防止通过属性直接修改
              statusMap[ this.id ] = WUFile.Status.INITED;
      
              this.source = source;
              this.loaded = 0;
      
              this.on( 'error', function( msg ) {
                  this.setStatus( WUFile.Status.ERROR, msg );
              });
          }
      
          $.extend( WUFile.prototype, {
      
              /**
               * 设置状态，状态变化时会触发`change`事件。
               * @method setStatus
               * @grammar setStatus( status[, statusText] );
               * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)
               * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。
               */
              setStatus: function( status, text ) {
      
                  var prevStatus = statusMap[ this.id ];
      
                  typeof text !== 'undefined' && (this.statusText = text);
      
                  if ( status !== prevStatus ) {
                      statusMap[ this.id ] = status;
                      /**
                       * 文件状态变化
                       * @event statuschange
                       */
                      this.trigger( 'statuschange', status, prevStatus );
                  }
      
              },
      
              /**
               * 获取文件状态
               * @return {File.Status}
               * @example
                       文件状态具体包括以下几种类型：
                       {
                           // 初始化
                          INITED:     0,
                          // 已入队列
                          QUEUED:     1,
                          // 正在上传
                          PROGRESS:     2,
                          // 上传出错
                          ERROR:         3,
                          // 上传成功
                          COMPLETE:     4,
                          // 上传取消
                          CANCELLED:     5
                      }
               */
              getStatus: function() {
                  return statusMap[ this.id ];
              },
      
              /**
               * 获取文件原始信息。
               * @return {*}
               */
              getSource: function() {
                  return this.source;
              },
      
              destory: function() {
                  delete statusMap[ this.id ];
              }
          });
      
          Mediator.installTo( WUFile.prototype );
      
          /**
           * 文件状态值，具体包括以下几种类型：
           * * `inited` 初始状态
           * * `queued` 已经进入队列, 等待上传
           * * `progress` 上传中
           * * `complete` 上传完成。
           * * `error` 上传出错，可重试
           * * `interrupt` 上传中断，可续传。
           * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。
           * * `cancelled` 文件被移除。
           * @property {Object} Status
           * @namespace File
           * @class File
           * @static
           */
          WUFile.Status = {
              INITED:     'inited',    // 初始状态
              QUEUED:     'queued',    // 已经进入队列, 等待上传
              PROGRESS:   'progress',    // 上传中
              ERROR:      'error',    // 上传出错，可重试
              COMPLETE:   'complete',    // 上传完成。
              CANCELLED:  'cancelled',    // 上传取消。
              INTERRUPT:  'interrupt',    // 上传中断，可续传。
              INVALID:    'invalid'    // 文件不合格，不能重试上传。
          };
      
          return WUFile;
      });
      
      /**
       * @fileOverview 文件队列
       */
      define('queue',[
          'base',
          'mediator',
          'file'
      ], function( Base, Mediator, WUFile ) {
      
          var $ = Base.$,
              STATUS = WUFile.Status;
      
          /**
           * 文件队列, 用来存储各个状态中的文件。
           * @class Queue
           * @extends Mediator
           */
          function Queue() {
      
              /**
               * 统计文件数。
               * * `numOfQueue` 队列中的文件数。
               * * `numOfSuccess` 上传成功的文件数
               * * `numOfCancel` 被移除的文件数
               * * `numOfProgress` 正在上传中的文件数
               * * `numOfUploadFailed` 上传错误的文件数。
               * * `numOfInvalid` 无效的文件数。
               * @property {Object} stats
               */
              this.stats = {
                  numOfQueue: 0,
                  numOfSuccess: 0,
                  numOfCancel: 0,
                  numOfProgress: 0,
                  numOfUploadFailed: 0,
                  numOfInvalid: 0
              };
      
              // 上传队列，仅包括等待上传的文件
              this._queue = [];
      
              // 存储所有文件
              this._map = {};
          }
      
          $.extend( Queue.prototype, {
      
              /**
               * 将新文件加入对队列尾部
               *
               * @method append
               * @param  {File} file   文件对象
               */
              append: function( file ) {
                  this._queue.push( file );
                  this._fileAdded( file );
                  return this;
              },
      
              /**
               * 将新文件加入对队列头部
               *
               * @method prepend
               * @param  {File} file   文件对象
               */
              prepend: function( file ) {
                  this._queue.unshift( file );
                  this._fileAdded( file );
                  return this;
              },
      
              /**
               * 获取文件对象
               *
               * @method getFile
               * @param  {String} fileId   文件ID
               * @return {File}
               */
              getFile: function( fileId ) {
                  if ( typeof fileId !== 'string' ) {
                      return fileId;
                  }
                  return this._map[ fileId ];
              },
      
              /**
               * 从队列中取出一个指定状态的文件。
               * @grammar fetch( status ) => File
               * @method fetch
               * @param {String} status [文件状态值](#WebUploader:File:File.Status)
               * @return {File} [File](#WebUploader:File)
               */
              fetch: function( status ) {
                  var len = this._queue.length,
                      i, file;
      
                  status = status || STATUS.QUEUED;
      
                  for ( i = 0; i < len; i++ ) {
                      file = this._queue[ i ];
      
                      if ( status === file.getStatus() ) {
                          return file;
                      }
                  }
      
                  return null;
              },
      
              /**
               * 对队列进行排序，能够控制文件上传顺序。
               * @grammar sort( fn ) => undefined
               * @method sort
               * @param {Function} fn 排序方法
               */
              sort: function( fn ) {
                  if ( typeof fn === 'function' ) {
                      this._queue.sort( fn );
                  }
              },
      
              /**
               * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。
               * @grammar getFiles( [status1[, status2 ...]] ) => Array
               * @method getFiles
               * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)
               */
              getFiles: function() {
                  var sts = [].slice.call( arguments, 0 ),
                      ret = [],
                      i = 0,
                      len = this._queue.length,
                      file;
      
                  for ( ; i < len; i++ ) {
                      file = this._queue[ i ];
      
                      if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
                          continue;
                      }
      
                      ret.push( file );
                  }
      
                  return ret;
              },
      
              _fileAdded: function( file ) {
                  var me = this,
                      existing = this._map[ file.id ];
      
                  if ( !existing ) {
                      this._map[ file.id ] = file;
      
                      file.on( 'statuschange', function( cur, pre ) {
                          me._onFileStatusChange( cur, pre );
                      });
                  }
      
                  file.setStatus( STATUS.QUEUED );
              },
      
              _onFileStatusChange: function( curStatus, preStatus ) {
                  var stats = this.stats;
      
                  switch ( preStatus ) {
                      case STATUS.PROGRESS:
                          stats.numOfProgress--;
                          break;
      
                      case STATUS.QUEUED:
                          stats.numOfQueue --;
                          break;
      
                      case STATUS.ERROR:
                          stats.numOfUploadFailed--;
                          break;
      
                      case STATUS.INVALID:
                          stats.numOfInvalid--;
                          break;
                  }
      
                  switch ( curStatus ) {
                      case STATUS.QUEUED:
                          stats.numOfQueue++;
                          break;
      
                      case STATUS.PROGRESS:
                          stats.numOfProgress++;
                          break;
      
                      case STATUS.ERROR:
                          stats.numOfUploadFailed++;
                          break;
      
                      case STATUS.COMPLETE:
                          stats.numOfSuccess++;
                          break;
      
                      case STATUS.CANCELLED:
                          stats.numOfCancel++;
                          break;
      
                      case STATUS.INVALID:
                          stats.numOfInvalid++;
                          break;
                  }
              }
      
          });
      
          Mediator.installTo( Queue.prototype );
      
          return Queue;
      });
      /**
       * @fileOverview 队列
       */
      define('widgets/queue',[
          'base',
          'uploader',
          'queue',
          'file',
          'lib/file',
          'runtime/client',
          'widgets/widget'
      ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {
      
          var $ = Base.$,
              rExt = /\.\w+$/,
              Status = WUFile.Status;
      
          return Uploader.register({
              'sort-files': 'sortFiles',
              'add-file': 'addFiles',
              'get-file': 'getFile',
              'fetch-file': 'fetchFile',
              'get-stats': 'getStats',
              'get-files': 'getFiles',
              'remove-file': 'removeFile',
              'retry': 'retry',
              'reset': 'reset',
              'accept-file': 'acceptFile'
          }, {
      
              init: function( opts ) {
                  var me = this,
                      deferred, len, i, item, arr, accept, runtime;
      
                  if ( $.isPlainObject( opts.accept ) ) {
                      opts.accept = [ opts.accept ];
                  }
      
                  // accept中的中生成匹配正则。
                  if ( opts.accept ) {
                      arr = [];
      
                      for ( i = 0, len = opts.accept.length; i < len; i++ ) {
                          item = opts.accept[ i ].extensions;
                          item && arr.push( item );
                      }
      
                      if ( arr.length ) {
                          accept = '\\.' + arr.join(',')
                                  .replace( /,/g, '$|\\.' )
                                  .replace( /\*/g, '.*' ) + '$';
                      }
      
                      me.accept = new RegExp( accept, 'i' );
                  }
      
                  me.queue = new Queue();
                  me.stats = me.queue.stats;
      
                  // 如果当前不是html5运行时，那就算了。
                  // 不执行后续操作
                  if ( this.request('predict-runtime-type') !== 'html5' ) {
                      return;
                  }
      
                  // 创建一个 html5 运行时的 placeholder
                  // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
                  deferred = Base.Deferred();
                  runtime = new RuntimeClient('Placeholder');
                  runtime.connectRuntime({
                      runtimeOrder: 'html5'
                  }, function() {
                      me._ruid = runtime.getRuid();
                      deferred.resolve();
                  });
                  return deferred.promise();
              },
      
      
              // 为了支持外部直接添加一个原生File对象。
              _wrapFile: function( file ) {
                  if ( !(file instanceof WUFile) ) {
      
                      if ( !(file instanceof File) ) {
                          if ( !this._ruid ) {
                              throw new Error('Can\'t add external files.');
                          }
                          file = new File( this._ruid, file );
                      }
      
                      file = new WUFile( file );
                  }
      
                  return file;
              },
      
              // 判断文件是否可以被加入队列
              acceptFile: function( file ) {
                  var invalid = !file || file.size < 6 || this.accept &&
      
                          // 如果名字中有后缀，才做后缀白名单处理。
                          rExt.exec( file.name ) && !this.accept.test( file.name );
      
                  return !invalid;
              },
      
      
              /**
               * @event beforeFileQueued
               * @param {File} file File对象
               * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。
               * @for  Uploader
               */
      
              /**
               * @event fileQueued
               * @param {File} file File对象
               * @description 当文件被加入队列以后触发。
               * @for  Uploader
               */
      
              _addFile: function( file ) {
                  var me = this;
      
                  file = me._wrapFile( file );
      
                  // 不过类型判断允许不允许，先派送 `beforeFileQueued`
                  if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {
                      return;
                  }
      
                  // 类型不匹配，则派送错误事件，并返回。
                  if ( !me.acceptFile( file ) ) {
                      me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );
                      return;
                  }
      
                  me.queue.append( file );
                  me.owner.trigger( 'fileQueued', file );
                  return file;
              },
      
              getFile: function( fileId ) {
                  return this.queue.getFile( fileId );
              },
      
              /**
               * @event filesQueued
               * @param {File} files 数组，内容为原始File(lib/File）对象。
               * @description 当一批文件添加进队列以后触发。
               * @for  Uploader
               */
      
              /**
               * @method addFiles
               * @grammar addFiles( file ) => undefined
               * @grammar addFiles( [file1, file2 ...] ) => undefined
               * @param {Array of File or File} [files] Files 对象 数组
               * @description 添加文件到队列
               * @for  Uploader
               */
              addFiles: function( files ) {
                  var me = this;
      
                  if ( !files.length ) {
                      files = [ files ];
                  }
      
                  files = $.map( files, function( file ) {
                      return me._addFile( file );
                  });
      
                  me.owner.trigger( 'filesQueued', files );
      
                  if ( me.options.auto ) {
                      me.request('start-upload');
                  }
              },
      
              getStats: function() {
                  return this.stats;
              },
      
              /**
               * @event fileDequeued
               * @param {File} file File对象
               * @description 当文件被移除队列后触发。
               * @for  Uploader
               */
      
              /**
               * @method removeFile
               * @grammar removeFile( file ) => undefined
               * @grammar removeFile( id ) => undefined
               * @param {File|id} file File对象或这File对象的id
               * @description 移除某一文件。
               * @for  Uploader
               * @example
               *
               * $li.on('click', '.remove-this', function() {
               *     uploader.removeFile( file );
               * })
               */
              removeFile: function( file ) {
                  var me = this;
      
                  file = file.id ? file : me.queue.getFile( file );
      
                  file.setStatus( Status.CANCELLED );
                  me.owner.trigger( 'fileDequeued', file );
              },
      
              /**
               * @method getFiles
               * @grammar getFiles() => Array
               * @grammar getFiles( status1, status2, status... ) => Array
               * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。
               * @for  Uploader
               * @example
               * console.log( uploader.getFiles() );    // => all files
               * console.log( uploader.getFiles('error') )    // => all error files.
               */
              getFiles: function() {
                  return this.queue.getFiles.apply( this.queue, arguments );
              },
      
              fetchFile: function() {
                  return this.queue.fetch.apply( this.queue, arguments );
              },
      
              /**
               * @method retry
               * @grammar retry() => undefined
               * @grammar retry( file ) => undefined
               * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。
               * @for  Uploader
               * @example
               * function retry() {
               *     uploader.retry();
               * }
               */
              retry: function( file, noForceStart ) {
                  var me = this,
                      files, i, len;
      
                  if ( file ) {
                      file = file.id ? file : me.queue.getFile( file );
                      file.setStatus( Status.QUEUED );
                      noForceStart || me.request('start-upload');
                      return;
                  }
      
                  files = me.queue.getFiles( Status.ERROR );
                  i = 0;
                  len = files.length;
      
                  for ( ; i < len; i++ ) {
                      file = files[ i ];
                      file.setStatus( Status.QUEUED );
                  }
      
                  me.request('start-upload');
              },
      
              /**
               * @method sort
               * @grammar sort( fn ) => undefined
               * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。
               * @for  Uploader
               */
              sortFiles: function() {
                  return this.queue.sort.apply( this.queue, arguments );
              },
      
              /**
               * @method reset
               * @grammar reset() => undefined
               * @description 重置uploader。目前只重置了队列。
               * @for  Uploader
               * @example
               * uploader.reset();
               */
              reset: function() {
                  this.queue = new Queue();
                  this.stats = this.queue.stats;
              }
          });
      
      });
      /**
       * @fileOverview 添加获取Runtime相关信息的方法。
       */
      define('widgets/runtime',[
          'uploader',
          'runtime/runtime',
          'widgets/widget'
      ], function( Uploader, Runtime ) {
      
          Uploader.support = function() {
              return Runtime.hasRuntime.apply( Runtime, arguments );
          };
      
          return Uploader.register({
              'predict-runtime-type': 'predictRuntmeType'
          }, {
      
              init: function() {
                  if ( !this.predictRuntmeType() ) {
                      throw Error('Runtime Error');
                  }
              },
      
              /**
               * 预测Uploader将采用哪个`Runtime`
               * @grammar predictRuntmeType() => String
               * @method predictRuntmeType
               * @for  Uploader
               */
              predictRuntmeType: function() {
                  var orders = this.options.runtimeOrder || Runtime.orders,
                      type = this.type,
                      i, len;
      
                  if ( !type ) {
                      orders = orders.split( /\s*,\s*/g );
      
                      for ( i = 0, len = orders.length; i < len; i++ ) {
                          if ( Runtime.hasRuntime( orders[ i ] ) ) {
                              this.type = type = orders[ i ];
                              break;
                          }
                      }
                  }
      
                  return type;
              }
          });
      });
      /**
       * @fileOverview Transport
       */
      define('lib/transport',[
          'base',
          'runtime/client',
          'mediator'
      ], function( Base, RuntimeClient, Mediator ) {
      
          var $ = Base.$;
      
          function Transport( opts ) {
              var me = this;
      
              opts = me.options = $.extend( true, {}, Transport.options, opts || {} );
              RuntimeClient.call( this, 'Transport' );
      
              this._blob = null;
              this._formData = opts.formData || {};
              this._headers = opts.headers || {};
      
              this.on( 'progress', this._timeout );
              this.on( 'load error', function() {
                  me.trigger( 'progress', 1 );
                  clearTimeout( me._timer );
              });
          }
      
          Transport.options = {
              server: '',
              method: 'POST',
      
              // 跨域时，是否允许携带cookie, 只有html5 runtime才有效
              withCredentials: false,
              fileVal: 'file',
              timeout: 2 * 60 * 1000,    // 2分钟
              formData: {},
              headers: {},
              sendAsBinary: false
          };
      
          $.extend( Transport.prototype, {
      
              // 添加Blob, 只能添加一次，最后一次有效。
              appendBlob: function( key, blob, filename ) {
                  var me = this,
                      opts = me.options;
      
                  if ( me.getRuid() ) {
                      me.disconnectRuntime();
                  }
      
                  // 连接到blob归属的同一个runtime.
                  me.connectRuntime( blob.ruid, function() {
                      me.exec('init');
                  });
      
                  me._blob = blob;
                  opts.fileVal = key || opts.fileVal;
                  opts.filename = filename || opts.filename;
              },
      
              // 添加其他字段
              append: function( key, value ) {
                  if ( typeof key === 'object' ) {
                      $.extend( this._formData, key );
                  } else {
                      this._formData[ key ] = value;
                  }
              },
      
              setRequestHeader: function( key, value ) {
                  if ( typeof key === 'object' ) {
                      $.extend( this._headers, key );
                  } else {
                      this._headers[ key ] = value;
                  }
              },
      
              send: function( method ) {
                  this.exec( 'send', method );
                  this._timeout();
              },
      
              abort: function() {
                  clearTimeout( this._timer );
                  return this.exec('abort');
              },
      
              destroy: function() {
                  this.trigger('destroy');
                  this.off();
                  this.exec('destroy');
                  this.disconnectRuntime();
              },
      
              getResponse: function() {
                  return this.exec('getResponse');
              },
      
              getResponseAsJson: function() {
                  return this.exec('getResponseAsJson');
              },
      
              getStatus: function() {
                  return this.exec('getStatus');
              },
      
              _timeout: function() {
                  var me = this,
                      duration = me.options.timeout;
      
                  if ( !duration ) {
                      return;
                  }
      
                  clearTimeout( me._timer );
                  me._timer = setTimeout(function() {
                      me.abort();
                      me.trigger( 'error', 'timeout' );
                  }, duration );
              }
      
          });
      
          // 让Transport具备事件功能。
          Mediator.installTo( Transport.prototype );
      
          return Transport;
      });
      /**
       * @fileOverview 负责文件上传相关。
       */
      define('widgets/upload',[
          'base',
          'uploader',
          'file',
          'lib/transport',
          'widgets/widget'
      ], function( Base, Uploader, WUFile, Transport ) {
      
          var $ = Base.$,
              isPromise = Base.isPromise,
              Status = WUFile.Status;
      
          // 添加默认配置项
          $.extend( Uploader.options, {
      
      
              /**
               * @property {Boolean} [prepareNextFile=false]
               * @namespace options
               * @for Uploader
               * @description 是否允许在文件传输时提前把下一个文件准备好。
               * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。
               * 如果能提前在当前文件传输期处理，可以节省总体耗时。
               */
              prepareNextFile: false,
      
              /**
               * @property {Boolean} [chunked=false]
               * @namespace options
               * @for Uploader
               * @description 是否要分片处理大文件上传。
               */
              chunked: false,
      
              /**
               * @property {Boolean} [chunkSize=5242880]
               * @namespace options
               * @for Uploader
               * @description 如果要分片，分多大一片？ 默认大小为5M.
               */
              chunkSize: 5 * 1024 * 1024,
      
              /**
               * @property {Boolean} [chunkRetry=2]
               * @namespace options
               * @for Uploader
               * @description 如果某个分片由于网络问题出错，允许自动重传多少次？
               */
              chunkRetry: 2,
      
              /**
               * @property {Boolean} [threads=3]
               * @namespace options
               * @for Uploader
               * @description 上传并发数。允许同时最大上传进程数。
               */
              threads: 3,
      
      
              /**
               * @property {Object} [formData]
               * @namespace options
               * @for Uploader
               * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。
               */
              formData: null
      
              /**
               * @property {Object} [fileVal='file']
               * @namespace options
               * @for Uploader
               * @description 设置文件上传域的name。
               */
      
              /**
               * @property {Object} [method='POST']
               * @namespace options
               * @for Uploader
               * @description 文件上传方式，`POST`或者`GET`。
               */
      
              /**
               * @property {Object} [sendAsBinary=false]
               * @namespace options
               * @for Uploader
               * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，
               * 其他参数在$_GET数组中。
               */
          });
      
          // 负责将文件切片。
          function CuteFile( file, chunkSize ) {
              var pending = [],
                  blob = file.source,
                  total = blob.size,
                  chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
                  start = 0,
                  index = 0,
                  len;
      
              while ( index < chunks ) {
                  len = Math.min( chunkSize, total - start );
      
                  pending.push({
                      file: file,
                      start: start,
                      end: chunkSize ? (start + len) : total,
                      total: total,
                      chunks: chunks,
                      chunk: index++
                  });
                  start += len;
              }
      
              file.blocks = pending.concat();
              file.remaning = pending.length;
      
              return {
                  file: file,
      
                  has: function() {
                      return !!pending.length;
                  },
      
                  fetch: function() {
                      return pending.shift();
                  }
              };
          }
      
          Uploader.register({
              'start-upload': 'start',
              'stop-upload': 'stop',
              'skip-file': 'skipFile',
              'is-in-progress': 'isInProgress'
          }, {
      
              init: function() {
                  var owner = this.owner;
      
                  this.runing = false;
      
                  // 记录当前正在传的数据，跟threads相关
                  this.pool = [];
      
                  // 缓存即将上传的文件。
                  this.pending = [];
      
                  // 跟踪还有多少分片没有完成上传。
                  this.remaning = 0;
                  this.__tick = Base.bindFn( this._tick, this );
      
                  owner.on( 'uploadComplete', function( file ) {
                      // 把其他块取消了。
                      file.blocks && $.each( file.blocks, function( _, v ) {
                          v.transport && (v.transport.abort(), v.transport.destroy());
                          delete v.transport;
                      });
      
                      delete file.blocks;
                      delete file.remaning;
                  });
              },
      
              /**
               * @event startUpload
               * @description 当开始上传流程时触发。
               * @for  Uploader
               */
      
              /**
               * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。
               * @grammar upload() => undefined
               * @method upload
               * @for  Uploader
               */
              start: function() {
                  var me = this;
      
                  // 移出invalid的文件
                  $.each( me.request( 'get-files', Status.INVALID ), function() {
                      me.request( 'remove-file', this );
                  });
      
                  if ( me.runing ) {
                      return;
                  }
      
                  me.runing = true;
      
                  // 如果有暂停的，则续传
                  $.each( me.pool, function( _, v ) {
                      var file = v.file;
      
                      if ( file.getStatus() === Status.INTERRUPT ) {
                          file.setStatus( Status.PROGRESS );
                          me._trigged = false;
                          v.transport && v.transport.send();
                      }
                  });
      
                  me._trigged = false;
                  me.owner.trigger('startUpload');
                  Base.nextTick( me.__tick );
              },
      
              /**
               * @event stopUpload
               * @description 当开始上传流程暂停时触发。
               * @for  Uploader
               */
      
              /**
               * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。
               * @grammar stop() => undefined
               * @grammar stop( true ) => undefined
               * @method stop
               * @for  Uploader
               */
              stop: function( interrupt ) {
                  var me = this;
      
                  if ( me.runing === false ) {
                      return;
                  }
      
                  me.runing = false;
      
                  interrupt && $.each( me.pool, function( _, v ) {
                      v.transport && v.transport.abort();
                      v.file.setStatus( Status.INTERRUPT );
                  });
      
                  me.owner.trigger('stopUpload');
              },
      
              /**
               * 判断`Uplaode`r是否正在上传中。
               * @grammar isInProgress() => Boolean
               * @method isInProgress
               * @for  Uploader
               */
              isInProgress: function() {
                  return !!this.runing;
              },
      
              getStats: function() {
                  return this.request('get-stats');
              },
      
              /**
               * 掉过一个文件上传，直接标记指定文件为已上传状态。
               * @grammar skipFile( file ) => undefined
               * @method skipFile
               * @for  Uploader
               */
              skipFile: function( file, status ) {
                  file = this.request( 'get-file', file );
      
                  file.setStatus( status || Status.COMPLETE );
                  file.skipped = true;
      
                  // 如果正在上传。
                  file.blocks && $.each( file.blocks, function( _, v ) {
                      var _tr = v.transport;
      
                      if ( _tr ) {
                          _tr.abort();
                          _tr.destroy();
                          delete v.transport;
                      }
                  });
      
                  this.owner.trigger( 'uploadSkip', file );
              },
      
              /**
               * @event uploadFinished
               * @description 当所有文件上传结束时触发。
               * @for  Uploader
               */
              _tick: function() {
                  var me = this,
                      opts = me.options,
                      fn, val;
      
                  // 上一个promise还没有结束，则等待完成后再执行。
                  if ( me._promise ) {
                      return me._promise.always( me.__tick );
                  }
      
                  // 还有位置，且还有文件要处理的话。
                  if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
                      me._trigged = false;
      
                      fn = function( val ) {
                          me._promise = null;
      
                          // 有可能是reject过来的，所以要检测val的类型。
                          val && val.file && me._startSend( val );
                          Base.nextTick( me.__tick );
                      };
      
                      me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
      
                  // 没有要上传的了，且没有正在传输的了。
                  } else if ( !me.remaning && !me.getStats().numOfQueue ) {
                      me.runing = false;
      
                      me._trigged || Base.nextTick(function() {
                          me.owner.trigger('uploadFinished');
                      });
                      me._trigged = true;
                  }
              },
      
              _nextBlock: function() {
                  var me = this,
                      act = me._act,
                      opts = me.options,
                      next, done;
      
                  // 如果当前文件还有没有需要传输的，则直接返回剩下的。
                  if ( act && act.has() &&
                          act.file.getStatus() === Status.PROGRESS ) {
      
                      // 是否提前准备下一个文件
                      if ( opts.prepareNextFile && !me.pending.length ) {
                          me._prepareNextFile();
                      }
      
                      return act.fetch();
      
                  // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。
                  } else if ( me.runing ) {
      
                      // 如果缓存中有，则直接在缓存中取，没有则去queue中取。
                      if ( !me.pending.length && me.getStats().numOfQueue ) {
                          me._prepareNextFile();
                      }
      
                      next = me.pending.shift();
                      done = function( file ) {
                          if ( !file ) {
                              return null;
                          }
      
                          act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
                          me._act = act;
                          return act.fetch();
                      };
      
                      // 文件可能还在prepare中，也有可能已经完全准备好了。
                      return isPromise( next ) ?
                              next[ next.pipe ? 'pipe' : 'then']( done ) :
                              done( next );
                  }
              },
      
      
              /**
               * @event uploadStart
               * @param {File} file File对象
               * @description 某个文件开始上传前触发，一个文件只会触发一次。
               * @for  Uploader
               */
              _prepareNextFile: function() {
                  var me = this,
                      file = me.request('fetch-file'),
                      pending = me.pending,
                      promise;
      
                  if ( file ) {
                      promise = me.request( 'before-send-file', file, function() {
      
                          // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.
                          if ( file.getStatus() === Status.QUEUED ) {
                              me.owner.trigger( 'uploadStart', file );
                              file.setStatus( Status.PROGRESS );
                              return file;
                          }
      
                          return me._finishFile( file );
                      });
      
                      // 如果还在pending中，则替换成文件本身。
                      promise.done(function() {
                          var idx = $.inArray( promise, pending );
      
                          ~idx && pending.splice( idx, 1, file );
                      });
      
                      // befeore-send-file的钩子就有错误发生。
                      promise.fail(function( reason ) {
                          file.setStatus( Status.ERROR, reason );
                          me.owner.trigger( 'uploadError', file, reason );
                          me.owner.trigger( 'uploadComplete', file );
                      });
      
                      pending.push( promise );
                  }
              },
      
              // 让出位置了，可以让其他分片开始上传
              _popBlock: function( block ) {
                  var idx = $.inArray( block, this.pool );
      
                  this.pool.splice( idx, 1 );
                  block.file.remaning--;
                  this.remaning--;
              },
      
              // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。
              _startSend: function( block ) {
                  var me = this,
                      file = block.file,
                      promise;
      
                  me.pool.push( block );
                  me.remaning++;
      
                  // 如果没有分片，则直接使用原始的。
                  // 不会丢失content-type信息。
                  block.blob = block.chunks === 1 ? file.source :
                          file.source.slice( block.start, block.end );
      
                  // hook, 每个分片发送之前可能要做些异步的事情。
                  promise = me.request( 'before-send', block, function() {
      
                      // 有可能文件已经上传出错了，所以不需要再传输了。
                      if ( file.getStatus() === Status.PROGRESS ) {
                          me._doSend( block );
                      } else {
                          me._popBlock( block );
                          Base.nextTick( me.__tick );
                      }
                  });
      
                  // 如果为fail了，则跳过此分片。
                  promise.fail(function() {
                      if ( file.remaning === 1 ) {
                          me._finishFile( file ).always(function() {
                              block.percentage = 1;
                              me._popBlock( block );
                              me.owner.trigger( 'uploadComplete', file );
                              Base.nextTick( me.__tick );
                          });
                      } else {
                          block.percentage = 1;
                          me._popBlock( block );
                          Base.nextTick( me.__tick );
                      }
                  });
              },
      
      
              /**
               * @event uploadBeforeSend
               * @param {Object} object
               * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。
               * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。
               * @for  Uploader
               */
      
              /**
               * @event uploadAccept
               * @param {Object} object
               * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。
               * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。
               * @for  Uploader
               */
      
              /**
               * @event uploadProgress
               * @param {File} file File对象
               * @param {Number} percentage 上传进度
               * @description 上传过程中触发，携带上传进度。
               * @for  Uploader
               */
      
      
              /**
               * @event uploadError
               * @param {File} file File对象
               * @param {String} reason 出错的code
               * @description 当文件上传出错时触发。
               * @for  Uploader
               */
      
              /**
               * @event uploadSuccess
               * @param {File} file File对象
               * @param {Object} response 服务端返回的数据
               * @description 当文件上传成功时触发。
               * @for  Uploader
               */
      
              /**
               * @event uploadComplete
               * @param {File} [file] File对象
               * @description 不管成功或者失败，文件上传完成时触发。
               * @for  Uploader
               */
      
              // 做上传操作。
              _doSend: function( block ) {
                  var me = this,
                      owner = me.owner,
                      opts = me.options,
                      file = block.file,
                      tr = new Transport( opts ),
                      data = $.extend({}, opts.formData ),
                      headers = $.extend({}, opts.headers ),
                      requestAccept, ret;
      
                  block.transport = tr;
      
                  tr.on( 'destroy', function() {
                      delete block.transport;
                      me._popBlock( block );
                      Base.nextTick( me.__tick );
                  });
      
                  // 广播上传进度。以文件为单位。
                  tr.on( 'progress', function( percentage ) {
                      var totalPercent = 0,
                          uploaded = 0;
      
                      // 可能没有abort掉，progress还是执行进来了。
                      // if ( !file.blocks ) {
                      //     return;
                      // }
      
                      totalPercent = block.percentage = percentage;
      
                      if ( block.chunks > 1 ) {    // 计算文件的整体速度。
                          $.each( file.blocks, function( _, v ) {
                              uploaded += (v.percentage || 0) * (v.end - v.start);
                          });
      
                          totalPercent = uploaded / file.size;
                      }
      
                      owner.trigger( 'uploadProgress', file, totalPercent || 0 );
                  });
      
                  // 用来询问，是否返回的结果是有错误的。
                  requestAccept = function( reject ) {
                      var fn;
      
                      ret = tr.getResponseAsJson() || {};
                      ret._raw = tr.getResponse();
                      fn = function( value ) {
                          reject = value;
                      };
      
                      // 服务端响应了，不代表成功了，询问是否响应正确。
                      if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {
                          reject = reject || 'server';
                      }
      
                      return reject;
                  };
      
                  // 尝试重试，然后广播文件上传出错。
                  tr.on( 'error', function( type, flag ) {
                      block.retried = block.retried || 0;
      
                      // 自动重试
                      if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&
                              block.retried < opts.chunkRetry ) {
      
                          block.retried++;
                          tr.send();
      
                      } else {
      
                          // http status 500 ~ 600
                          if ( !flag && type === 'server' ) {
                              type = requestAccept( type );
                          }
      
                          file.setStatus( Status.ERROR, type );
                          owner.trigger( 'uploadError', file, type );
                          owner.trigger( 'uploadComplete', file );
                      }
                  });
      
                  // 上传成功
                  tr.on( 'load', function() {
                      var reason;
      
                      // 如果非预期，转向上传出错。
                      if ( (reason = requestAccept()) ) {
                          tr.trigger( 'error', reason, true );
                          return;
                      }
      
                      // 全部上传完成。
                      if ( file.remaning === 1 ) {
                          me._finishFile( file, ret );
                      } else {
                          tr.destroy();
                      }
                  });
      
                  // 配置默认的上传字段。
                  data = $.extend( data, {
                      id: file.id,
                      name: file.name,
                      type: file.type,
                      lastModifiedDate: file.lastModifiedDate,
                      size: file.size
                  });
      
                  block.chunks > 1 && $.extend( data, {
                      chunks: block.chunks,
                      chunk: block.chunk
                  });
      
                  // 在发送之间可以添加字段什么的。。。
                  // 如果默认的字段不够使用，可以通过监听此事件来扩展
                  owner.trigger( 'uploadBeforeSend', block, data, headers );
      
                  // 开始发送。
                  tr.appendBlob( opts.fileVal, block.blob, file.name );
                  tr.append( data );
                  tr.setRequestHeader( headers );
                  tr.send();
              },
      
              // 完成上传。
              _finishFile: function( file, ret, hds ) {
                  var owner = this.owner;
      
                  return owner
                          .request( 'after-send-file', arguments, function() {
                              file.setStatus( Status.COMPLETE );
                              owner.trigger( 'uploadSuccess', file, ret, hds );
                          })
                          .fail(function( reason ) {
      
                              // 如果外部已经标记为invalid什么的，不再改状态。
                              if ( file.getStatus() === Status.PROGRESS ) {
                                  file.setStatus( Status.ERROR, reason );
                              }
      
                              owner.trigger( 'uploadError', file, reason );
                          })
                          .always(function() {
                              owner.trigger( 'uploadComplete', file );
                          });
              }
      
          });
      });
      /**
       * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。
       */
      
      define('widgets/validator',[
          'base',
          'uploader',
          'file',
          'widgets/widget'
      ], function( Base, Uploader, WUFile ) {
      
          var $ = Base.$,
              validators = {},
              api;
      
          /**
           * @event error
           * @param {String} type 错误类型。
           * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。
           *
           * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。
           * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。
           * @for  Uploader
           */
      
          // 暴露给外面的api
          api = {
      
              // 添加验证器
              addValidator: function( type, cb ) {
                  validators[ type ] = cb;
              },
      
              // 移除验证器
              removeValidator: function( type ) {
                  delete validators[ type ];
              }
          };
      
          // 在Uploader初始化的时候启动Validators的初始化
          Uploader.register({
              init: function() {
                  var me = this;
                  $.each( validators, function() {
                      this.call( me.owner );
                  });
              }
          });
      
          /**
           * @property {int} [fileNumLimit=undefined]
           * @namespace options
           * @for Uploader
           * @description 验证文件总数量, 超出则不允许加入队列。
           */
          api.addValidator( 'fileNumLimit', function() {
              var uploader = this,
                  opts = uploader.options,
                  count = 0,
                  max = opts.fileNumLimit >> 0,
                  flag = true;
      
              if ( !max ) {
                  return;
              }
      
              uploader.on( 'beforeFileQueued', function( file ) {
      
                  if ( count >= max && flag ) {
                      flag = false;
                      this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );
                      setTimeout(function() {
                          flag = true;
                      }, 1 );
                  }
      
                  return count >= max ? false : true;
              });
      
              uploader.on( 'fileQueued', function() {
                  count++;
              });
      
              uploader.on( 'fileDequeued', function() {
                  count--;
              });
      
              uploader.on( 'uploadFinished', function() {
                  count = 0;
              });
          });
      
      
          /**
           * @property {int} [fileSizeLimit=undefined]
           * @namespace options
           * @for Uploader
           * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。
           */
          api.addValidator( 'fileSizeLimit', function() {
              var uploader = this,
                  opts = uploader.options,
                  count = 0,
                  max = opts.fileSizeLimit >> 0,
                  flag = true;
      
              if ( !max ) {
                  return;
              }
      
              uploader.on( 'beforeFileQueued', function( file ) {
                  var invalid = count + file.size > max;
      
                  if ( invalid && flag ) {
                      flag = false;
                      this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );
                      setTimeout(function() {
                          flag = true;
                      }, 1 );
                  }
      
                  return invalid ? false : true;
              });
      
              uploader.on( 'fileQueued', function( file ) {
                  count += file.size;
              });
      
              uploader.on( 'fileDequeued', function( file ) {
                  count -= file.size;
              });
      
              uploader.on( 'uploadFinished', function() {
                  count = 0;
              });
          });
      
          /**
           * @property {int} [fileSingleSizeLimit=undefined]
           * @namespace options
           * @for Uploader
           * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。
           */
          api.addValidator( 'fileSingleSizeLimit', function() {
              var uploader = this,
                  opts = uploader.options,
                  max = opts.fileSingleSizeLimit;
      
              if ( !max ) {
                  return;
              }
      
              uploader.on( 'beforeFileQueued', function( file ) {
      
                  if ( file.size > max ) {
                      file.setStatus( WUFile.Status.INVALID, 'exceed_size' );
                      this.trigger( 'error', 'F_EXCEED_SIZE', file );
                      return false;
                  }
      
              });
      
          });
      
          /**
           * @property {int} [duplicate=undefined]
           * @namespace options
           * @for Uploader
           * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.
           */
          api.addValidator( 'duplicate', function() {
              var uploader = this,
                  opts = uploader.options,
                  mapping = {};
      
              if ( opts.duplicate ) {
                  return;
              }
      
              function hashString( str ) {
                  var hash = 0,
                      i = 0,
                      len = str.length,
                      _char;
      
                  for ( ; i < len; i++ ) {
                      _char = str.charCodeAt( i );
                      hash = _char + (hash << 6) + (hash << 16) - hash;
                  }
      
                  return hash;
              }
      
              uploader.on( 'beforeFileQueued', function( file ) {
                  var hash = file.__hash || (file.__hash = hashString( file.name +
                          file.size + file.lastModifiedDate ));
      
                  // 已经重复了
                  if ( mapping[ hash ] ) {
                      this.trigger( 'error', 'F_DUPLICATE', file );
                      return false;
                  }
              });
      
              uploader.on( 'fileQueued', function( file ) {
                  var hash = file.__hash;
      
                  hash && (mapping[ hash ] = true);
              });
      
              uploader.on( 'fileDequeued', function( file ) {
                  var hash = file.__hash;
      
                  hash && (delete mapping[ hash ]);
              });
          });
      
          return api;
      });
      
      /**
       * @fileOverview Runtime管理器，负责Runtime的选择, 连接
       */
      define('runtime/compbase',[],function() {
      
          function CompBase( owner, runtime ) {
      
              this.owner = owner;
              this.options = owner.options;
      
              this.getRuntime = function() {
                  return runtime;
              };
      
              this.getRuid = function() {
                  return runtime.uid;
              };
      
              this.trigger = function() {
                  return owner.trigger.apply( owner, arguments );
              };
          }
      
          return CompBase;
      });
      /**
       * @fileOverview Html5Runtime
       */
      define('runtime/html5/runtime',[
          'base',
          'runtime/runtime',
          'runtime/compbase'
      ], function( Base, Runtime, CompBase ) {
      
          var type = 'html5',
              components = {};
      
          function Html5Runtime() {
              var pool = {},
                  me = this,
                  destory = this.destory;
      
              Runtime.apply( me, arguments );
              me.type = type;
      
      
              // 这个方法的调用者，实际上是RuntimeClient
              me.exec = function( comp, fn/*, args...*/) {
                  var client = this,
                      uid = client.uid,
                      args = Base.slice( arguments, 2 ),
                      instance;
      
                  if ( components[ comp ] ) {
                      instance = pool[ uid ] = pool[ uid ] ||
                              new components[ comp ]( client, me );
      
                      if ( instance[ fn ] ) {
                          return instance[ fn ].apply( instance, args );
                      }
                  }
              };
      
              me.destory = function() {
                  // @todo 删除池子中的所有实例
                  return destory && destory.apply( this, arguments );
              };
          }
      
          Base.inherits( Runtime, {
              constructor: Html5Runtime,
      
              // 不需要连接其他程序，直接执行callback
              init: function() {
                  var me = this;
                  setTimeout(function() {
                      me.trigger('ready');
                  }, 1 );
              }
      
          });
      
          // 注册Components
          Html5Runtime.register = function( name, component ) {
              var klass = components[ name ] = Base.inherits( CompBase, component );
              return klass;
          };
      
          // 注册html5运行时。
          // 只有在支持的前提下注册。
          if ( window.Blob && window.FileReader && window.DataView ) {
              Runtime.addRuntime( type, Html5Runtime );
          }
      
          return Html5Runtime;
      });
      /**
       * @fileOverview Blob Html实现
       */
      define('runtime/html5/blob',[
          'runtime/html5/runtime',
          'lib/blob'
      ], function( Html5Runtime, Blob ) {
      
          return Html5Runtime.register( 'Blob', {
              slice: function( start, end ) {
                  var blob = this.owner.source,
                      slice = blob.slice || blob.webkitSlice || blob.mozSlice;
      
                  blob = slice.call( blob, start, end );
      
                  return new Blob( this.getRuid(), blob );
              }
          });
      });
      /**
       * @fileOverview FilePaste
       */
      define('runtime/html5/dnd',[
          'base',
          'runtime/html5/runtime',
          'lib/file'
      ], function( Base, Html5Runtime, File ) {
      
          var $ = Base.$,
              prefix = 'webuploader-dnd-';
      
          return Html5Runtime.register( 'DragAndDrop', {
              init: function() {
                  var elem = this.elem = this.options.container;
      
                  this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );
                  this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );
                  this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );
                  this.dropHandler = Base.bindFn( this._dropHandler, this );
                  this.dndOver = false;
      
                  elem.on( 'dragenter', this.dragEnterHandler );
                  elem.on( 'dragover', this.dragOverHandler );
                  elem.on( 'dragleave', this.dragLeaveHandler );
                  elem.on( 'drop', this.dropHandler );
      
                  if ( this.options.disableGlobalDnd ) {
                      $( document ).on( 'dragover', this.dragOverHandler );
                      $( document ).on( 'drop', this.dropHandler );
                  }
              },
      
              _dragEnterHandler: function( e ) {
                  var me = this,
                      denied = me._denied || false,
                      items;
      
                  e = e.originalEvent || e;
      
                  if ( !me.dndOver ) {
                      me.dndOver = true;
      
                      // 注意只有 chrome 支持。
                      items = e.dataTransfer.items;
      
                      if ( items && items.length ) {
                          me._denied = denied = !me.trigger( 'accept', items );
                      }
      
                      me.elem.addClass( prefix + 'over' );
                      me.elem[ denied ? 'addClass' :
                              'removeClass' ]( prefix + 'denied' );
                  }
      
      
                  e.dataTransfer.dropEffect = denied ? 'none' : 'copy';
      
                  return false;
              },
      
              _dragOverHandler: function( e ) {
                  // 只处理框内的。
                  var parentElem = this.elem.parent().get( 0 );
                  if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
                      return false;
                  }
      
                  clearTimeout( this._leaveTimer );
                  this._dragEnterHandler.call( this, e );
      
                  return false;
              },
      
              _dragLeaveHandler: function() {
                  var me = this,
                      handler;
      
                  handler = function() {
                      me.dndOver = false;
                      me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );
                  };
      
                  clearTimeout( me._leaveTimer );
                  me._leaveTimer = setTimeout( handler, 100 );
                  return false;
              },
      
              _dropHandler: function( e ) {
                  var me = this,
                      ruid = me.getRuid(),
                      parentElem = me.elem.parent().get( 0 );
      
                  // 只处理框内的。
                  if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {
                      return false;
                  }
      
                  me._getTansferFiles( e, function( results ) {
                      me.trigger( 'drop', $.map( results, function( file ) {
                          return new File( ruid, file );
                      }) );
                  });
      
                  me.dndOver = false;
                  me.elem.removeClass( prefix + 'over' );
                  return false;
              },
      
              // 如果传入 callback 则去查看文件夹，否则只管当前文件夹。
              _getTansferFiles: function( e, callback ) {
                  var results  = [],
                      promises = [],
                      items, files, dataTransfer, file, item, i, len, canAccessFolder;
      
                  e = e.originalEvent || e;
      
                  dataTransfer = e.dataTransfer;
                  items = dataTransfer.items;
                  files = dataTransfer.files;
      
                  canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);
      
                  for ( i = 0, len = files.length; i < len; i++ ) {
                      file = files[ i ];
                      item = items && items[ i ];
      
                      if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {
      
                          promises.push( this._traverseDirectoryTree(
                                  item.webkitGetAsEntry(), results ) );
                      } else {
                          results.push( file );
                      }
                  }
      
                  Base.when.apply( Base, promises ).done(function() {
      
                      if ( !results.length ) {
                          return;
                      }
      
                      callback( results );
                  });
              },
      
              _traverseDirectoryTree: function( entry, results ) {
                  var deferred = Base.Deferred(),
                      me = this;
      
                  if ( entry.isFile ) {
                      entry.file(function( file ) {
                          results.push( file );
                          deferred.resolve();
                      });
                  } else if ( entry.isDirectory ) {
                      entry.createReader().readEntries(function( entries ) {
                          var len = entries.length,
                              promises = [],
                              arr = [],    // 为了保证顺序。
                              i;
      
                          for ( i = 0; i < len; i++ ) {
                              promises.push( me._traverseDirectoryTree(
                                      entries[ i ], arr ) );
                          }
      
                          Base.when.apply( Base, promises ).then(function() {
                              results.push.apply( results, arr );
                              deferred.resolve();
                          }, deferred.reject );
                      });
                  }
      
                  return deferred.promise();
              },
      
              destroy: function() {
                  var elem = this.elem;
      
                  elem.off( 'dragenter', this.dragEnterHandler );
                  elem.off( 'dragover', this.dragEnterHandler );
                  elem.off( 'dragleave', this.dragLeaveHandler );
                  elem.off( 'drop', this.dropHandler );
      
                  if ( this.options.disableGlobalDnd ) {
                      $( document ).off( 'dragover', this.dragOverHandler );
                      $( document ).off( 'drop', this.dropHandler );
                  }
              }
          });
      });
      
      /**
       * @fileOverview FilePaste
       */
      define('runtime/html5/filepaste',[
          'base',
          'runtime/html5/runtime',
          'lib/file'
      ], function( Base, Html5Runtime, File ) {
      
          return Html5Runtime.register( 'FilePaste', {
              init: function() {
                  var opts = this.options,
                      elem = this.elem = opts.container,
                      accept = '.*',
                      arr, i, len, item;
      
                  // accetp的mimeTypes中生成匹配正则。
                  if ( opts.accept ) {
                      arr = [];
      
                      for ( i = 0, len = opts.accept.length; i < len; i++ ) {
                          item = opts.accept[ i ].mimeTypes;
                          item && arr.push( item );
                      }
      
                      if ( arr.length ) {
                          accept = arr.join(',');
                          accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' );
                      }
                  }
                  this.accept = accept = new RegExp( accept, 'i' );
                  this.hander = Base.bindFn( this._pasteHander, this );
                  elem.on( 'paste', this.hander );
              },
      
              _pasteHander: function( e ) {
                  var allowed = [],
                      ruid = this.getRuid(),
                      items, item, blob, i, len;
      
                  e = e.originalEvent || e;
                  items = e.clipboardData.items;
      
                  for ( i = 0, len = items.length; i < len; i++ ) {
                      item = items[ i ];
      
                      if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {
                          continue;
                      }
      
                      allowed.push( new File( ruid, blob ) );
                  }
      
                  if ( allowed.length ) {
                      // 不阻止非文件粘贴（文字粘贴）的事件冒泡
                      e.preventDefault();
                      e.stopPropagation();
                      this.trigger( 'paste', allowed );
                  }
              },
      
              destroy: function() {
                  this.elem.off( 'paste', this.hander );
              }
          });
      });
      
      /**
       * @fileOverview FilePicker
       */
      define('runtime/html5/filepicker',[
          'base',
          'runtime/html5/runtime'
      ], function( Base, Html5Runtime ) {
      
          var $ = Base.$;
      
          return Html5Runtime.register( 'FilePicker', {
              init: function() {
                  var container = this.getRuntime().getContainer(),
                      me = this,
                      owner = me.owner,
                      opts = me.options,
                      lable = $( document.createElement('label') ),
                      input = $( document.createElement('input') ),
                      arr, i, len, mouseHandler;
      
                  input.attr( 'type', 'file' );
                  input.attr( 'name', opts.name );
                  input.addClass('webuploader-element-invisible');
      
                  lable.on( 'click', function() {
                      input.trigger('click');
                  });
      
                  lable.css({
                      opacity: 0,
                      width: '100%',
                      height: '100%',
                      display: 'block',
                      cursor: 'pointer',
                      background: '#ffffff'
                  });
      
                  if ( opts.multiple ) {
                      input.attr( 'multiple', 'multiple' );
                  }
      
                  // @todo Firefox不支持单独指定后缀
                  if ( opts.accept && opts.accept.length > 0 ) {
                      arr = [];
      
                      for ( i = 0, len = opts.accept.length; i < len; i++ ) {
                          arr.push( opts.accept[ i ].mimeTypes );
                      }
      
                      input.attr( 'accept', arr.join(',') );
                  }
      
                  container.append( input );
                  container.append( lable );
      
                  mouseHandler = function( e ) {
                      owner.trigger( e.type );
                  };
      
                  input.on( 'change', function( e ) {
                      var fn = arguments.callee,
                          clone;
      
                      me.files = e.target.files;
      
                      // reset input
                      clone = this.cloneNode( true );
                      this.parentNode.replaceChild( clone, this );
      
                      input.off();
                      input = $( clone ).on( 'change', fn )
                              .on( 'mouseenter mouseleave', mouseHandler );
      
                      owner.trigger('change');
                  });
      
                  lable.on( 'mouseenter mouseleave', mouseHandler );
      
              },
      
      
              getFiles: function() {
                  return this.files;
              },
      
              destroy: function() {
                  // todo
              }
          });
      });
      /**
       * @fileOverview Transport
       * @todo 支持chunked传输，优势：
       * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，
       * 而不需要重头再传一次。另外断点续传也需要用chunked方式。
       */
      define('runtime/html5/transport',[
          'base',
          'runtime/html5/runtime'
      ], function( Base, Html5Runtime ) {
      
          var noop = Base.noop,
              $ = Base.$;
      
          return Html5Runtime.register( 'Transport', {
              init: function() {
                  this._status = 0;
                  this._response = null;
              },
      
              send: function() {
                  var owner = this.owner,
                      opts = this.options,
                      xhr = this._initAjax(),
                      blob = owner._blob,
                      server = opts.server,
                      formData, binary, fr;
      
                  if ( opts.sendAsBinary ) {
                      server += (/\?/.test( server ) ? '&' : '?') +
                              $.param( owner._formData );
      
                      binary = blob.getSource();
                  } else {
                      formData = new FormData();
                      $.each( owner._formData, function( k, v ) {
                          formData.append( k, v );
                      });
      
                      formData.append( opts.fileVal, blob.getSource(),
                              opts.filename || owner._formData.name || '' );
                  }
      
                  if ( opts.withCredentials && 'withCredentials' in xhr ) {
                      xhr.open( opts.method, server, true );
                      xhr.withCredentials = true;
                  } else {
                      xhr.open( opts.method, server );
                  }
      
                  this._setRequestHeader( xhr, opts.headers );
      
                  if ( binary ) {
                      xhr.overrideMimeType('application/octet-stream');
      
                      // android直接发送blob会导致服务端接收到的是空文件。
                      // bug详情。
                      // https://code.google.com/p/android/issues/detail?id=39882
                      // 所以先用fileReader读取出来再通过arraybuffer的方式发送。
                      if ( Base.os.android ) {
                          fr = new FileReader();
      
                          fr.onload = function() {
                              xhr.send( this.result );
                              fr = fr.onload = null;
                          };
      
                          fr.readAsArrayBuffer( binary );
                      } else {
                          xhr.send( binary );
                      }
                  } else {
                      xhr.send( formData );
                  }
              },
      
              getResponse: function() {
                  return this._response;
              },
      
              getResponseAsJson: function() {
                  return this._parseJson( this._response );
              },
      
              getStatus: function() {
                  return this._status;
              },
      
              abort: function() {
                  var xhr = this._xhr;
      
                  if ( xhr ) {
                      xhr.upload.onprogress = noop;
                      xhr.onreadystatechange = noop;
                      xhr.abort();
      
                      this._xhr = xhr = null;
                  }
              },
      
              destroy: function() {
                  this.abort();
              },
      
              _initAjax: function() {
                  var me = this,
                      xhr = new XMLHttpRequest(),
                      opts = this.options;
      
                  if ( opts.withCredentials && !('withCredentials' in xhr) &&
                          typeof XDomainRequest !== 'undefined' ) {
                      xhr = new XDomainRequest();
                  }
      
                  xhr.upload.onprogress = function( e ) {
                      var percentage = 0;
      
                      if ( e.lengthComputable ) {
                          percentage = e.loaded / e.total;
                      }
      
                      return me.trigger( 'progress', percentage );
                  };
      
                  xhr.onreadystatechange = function() {
      
                      if ( xhr.readyState !== 4 ) {
                          return;
                      }
      
                      xhr.upload.onprogress = noop;
                      xhr.onreadystatechange = noop;
                      me._xhr = null;
                      me._status = xhr.status;
      
                      if ( xhr.status >= 200 && xhr.status < 300 ) {
                          me._response = xhr.responseText;
                          return me.trigger('load');
                      } else if ( xhr.status >= 500 && xhr.status < 600 ) {
                          me._response = xhr.responseText;
                          return me.trigger( 'error', 'server' );
                      }
      
      
                      return me.trigger( 'error', me._status ? 'http' : 'abort' );
                  };
      
                  me._xhr = xhr;
                  return xhr;
              },
      
              _setRequestHeader: function( xhr, headers ) {
                  $.each( headers, function( key, val ) {
                      xhr.setRequestHeader( key, val );
                  });
              },
      
              _parseJson: function( str ) {
                  var json;
      
                  try {
                      json = JSON.parse( str );
                  } catch ( ex ) {
                      json = {};
                  }
      
                  return json;
              }
          });
      });
      /**
       * @fileOverview FlashRuntime
       */
      define('runtime/flash/runtime',[
          'base',
          'runtime/runtime',
          'runtime/compbase'
      ], function( Base, Runtime, CompBase ) {
      
          var $ = Base.$,
              type = 'flash',
              components = {};
      
      
          function getFlashVersion() {
              var version;
      
              try {
                  version = navigator.plugins[ 'Shockwave Flash' ];
                  version = version.description;
              } catch ( ex ) {
                  try {
                      version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')
                              .GetVariable('$version');
                  } catch ( ex2 ) {
                      version = '0.0';
                  }
              }
              version = version.match( /\d+/g );
              return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );
          }
      
          function FlashRuntime() {
              var pool = {},
                  clients = {},
                  destory = this.destory,
                  me = this,
                  jsreciver = Base.guid('webuploader_');
      
              Runtime.apply( me, arguments );
              me.type = type;
      
      
              // 这个方法的调用者，实际上是RuntimeClient
              me.exec = function( comp, fn/*, args...*/ ) {
                  var client = this,
                      uid = client.uid,
                      args = Base.slice( arguments, 2 ),
                      instance;
      
                  clients[ uid ] = client;
      
                  if ( components[ comp ] ) {
                      if ( !pool[ uid ] ) {
                          pool[ uid ] = new components[ comp ]( client, me );
                      }
      
                      instance = pool[ uid ];
      
                      if ( instance[ fn ] ) {
                          return instance[ fn ].apply( instance, args );
                      }
                  }
      
                  return me.flashExec.apply( client, arguments );
              };
      
              function handler( evt, obj ) {
                  var type = evt.type || evt,
                      parts, uid;
      
                  parts = type.split('::');
                  uid = parts[ 0 ];
                  type = parts[ 1 ];
      
                  // console.log.apply( console, arguments );
      
                  if ( type === 'Ready' && uid === me.uid ) {
                      me.trigger('ready');
                  } else if ( clients[ uid ] ) {
                      clients[ uid ].trigger( type.toLowerCase(), evt, obj );
                  }
      
                  // BaseController.log( evt, obj );
              }
      
              // flash的接受器。
              window[ jsreciver ] = function() {
                  var args = arguments;
      
                  // 为了能捕获得到。
                  setTimeout(function() {
                      handler.apply( null, args );
                  }, 1 );
              };
      
              this.jsreciver = jsreciver;
      
              this.destory = function() {
                  // @todo 删除池子中的所有实例
                  return destory && destory.apply( this, arguments );
              };
      
              this.flashExec = function( comp, fn ) {
                  var flash = me.getFlash(),
                      args = Base.slice( arguments, 2 );
      
                  return flash.exec( this.uid, comp, fn, args );
              };
      
              // @todo
          }
      
          Base.inherits( Runtime, {
              constructor: FlashRuntime,
      
              init: function() {
                  var container = this.getContainer(),
                      opts = this.options,
                      html;
      
                  // if not the minimal height, shims are not initialized
                  // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)
                  container.css({
                      position: 'absolute',
                      top: '-8px',
                      left: '-8px',
                      width: '9px',
                      height: '9px',
                      overflow: 'hidden'
                  });
      
                  // insert flash object
                  html = '<object id="' + this.uid + '" type="application/' +
                          'x-shockwave-flash" data="' +  opts.swf + '" ';
      
                  if ( Base.browser.ie ) {
                      html += 'classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" ';
                  }
      
                  html += 'width="100%" height="100%" style="outline:0">'  +
                      '<param name="movie" value="' + opts.swf + '" />' +
                      '<param name="flashvars" value="uid=' + this.uid +
                      '&jsreciver=' + this.jsreciver + '" />' +
                      '<param name="wmode" value="transparent" />' +
                      '<param name="allowscriptaccess" value="always" />' +
                  '</object>';
      
                  container.html( html );
              },
      
              getFlash: function() {
                  if ( this._flash ) {
                      return this._flash;
                  }
      
                  this._flash = $( '#' + this.uid ).get( 0 );
                  return this._flash;
              }
      
          });
      
          FlashRuntime.register = function( name, component ) {
              component = components[ name ] = Base.inherits( CompBase, $.extend({
      
                  // @todo fix this later
                  flashExec: function() {
                      var owner = this.owner,
                          runtime = this.getRuntime();
      
                      return runtime.flashExec.apply( owner, arguments );
                  }
              }, component ) );
      
              return component;
          };
      
          if ( getFlashVersion() >= 11.4 ) {
              Runtime.addRuntime( type, FlashRuntime );
          }
      
          return FlashRuntime;
      });
      /**
       * @fileOverview FilePicker
       */
      define('runtime/flash/filepicker',[
          'base',
          'runtime/flash/runtime'
      ], function( Base, FlashRuntime ) {
          var $ = Base.$;
      
          return FlashRuntime.register( 'FilePicker', {
              init: function( opts ) {
                  var copy = $.extend({}, opts ),
                      len, i;
      
                  // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.
                  len = copy.accept && copy.accept.length;
                  for (  i = 0; i < len; i++ ) {
                      if ( !copy.accept[ i ].title ) {
                          copy.accept[ i ].title = 'Files';
                      }
                  }
      
                  delete copy.button;
                  delete copy.container;
      
                  this.flashExec( 'FilePicker', 'init', copy );
              },
      
              destroy: function() {
                  // todo
              }
          });
      });
      /**
       * @fileOverview  Transport flash实现
       */
      define('runtime/flash/transport',[
          'base',
          'runtime/flash/runtime',
          'runtime/client'
      ], function( Base, FlashRuntime, RuntimeClient ) {
          var $ = Base.$;
      
          return FlashRuntime.register( 'Transport', {
              init: function() {
                  this._status = 0;
                  this._response = null;
                  this._responseJson = null;
              },
      
              send: function() {
                  var owner = this.owner,
                      opts = this.options,
                      xhr = this._initAjax(),
                      blob = owner._blob,
                      server = opts.server,
                      binary;
      
                  xhr.connectRuntime( blob.ruid );
      
                  if ( opts.sendAsBinary ) {
                      server += (/\?/.test( server ) ? '&' : '?') +
                              $.param( owner._formData );
      
                      binary = blob.uid;
                  } else {
                      $.each( owner._formData, function( k, v ) {
                          xhr.exec( 'append', k, v );
                      });
      
                      xhr.exec( 'appendBlob', opts.fileVal, blob.uid,
                              opts.filename || owner._formData.name || '' );
                  }
      
                  this._setRequestHeader( xhr, opts.headers );
                  xhr.exec( 'send', {
                      method: opts.method,
                      url: server
                  }, binary );
              },
      
              getStatus: function() {
                  return this._status;
              },
      
              getResponse: function() {
                  return this._response;
              },
      
              getResponseAsJson: function() {
                  return this._responseJson;
              },
      
              abort: function() {
                  var xhr = this._xhr;
      
                  if ( xhr ) {
                      xhr.exec('abort');
                      xhr.destroy();
                      this._xhr = xhr = null;
                  }
              },
      
              destroy: function() {
                  this.abort();
              },
      
              _initAjax: function() {
                  var me = this,
                      xhr = new RuntimeClient('XMLHttpRequest');
      
                  xhr.on( 'uploadprogress progress', function( e ) {
                      return me.trigger( 'progress', e.loaded / e.total );
                  });
      
                  xhr.on( 'load', function() {
                      var status = xhr.exec('getStatus'),
                          err = '';
      
                      xhr.off();
                      me._xhr = null;
      
                      if ( status >= 200 && status < 300 ) {
                          me._response = xhr.exec('getResponse');
                          me._responseJson = xhr.exec('getResponseAsJson');
                      } else if ( status >= 500 && status < 600 ) {
                          me._response = xhr.exec('getResponse');
                          me._responseJson = xhr.exec('getResponseAsJson');
                          err = 'server';
                      } else {
                          err = 'http';
                      }
      
                      xhr.destroy();
                      xhr = null;
      
                      return err ? me.trigger( 'error', err ) : me.trigger('load');
                  });
      
                  xhr.on( 'error', function() {
                      xhr.off();
                      me._xhr = null;
                      me.trigger( 'error', 'http' );
                  });
      
                  me._xhr = xhr;
                  return xhr;
              },
      
              _setRequestHeader: function( xhr, headers ) {
                  $.each( headers, function( key, val ) {
                      xhr.exec( 'setRequestHeader', key, val );
                  });
              }
          });
      });
      /**
       * @fileOverview 没有图像处理的版本。
       */
      define('preset/withoutimage',[
          'base',
      
          // widgets
          'widgets/filednd',
          'widgets/filepaste',
          'widgets/filepicker',
          'widgets/queue',
          'widgets/runtime',
          'widgets/upload',
          'widgets/validator',
      
          // runtimes
          // html5
          'runtime/html5/blob',
          'runtime/html5/dnd',
          'runtime/html5/filepaste',
          'runtime/html5/filepicker',
          'runtime/html5/transport',
      
          // flash
          'runtime/flash/filepicker',
          'runtime/flash/transport'
      ], function( Base ) {
          return Base;
      });
      define('webuploader',[
          'preset/withoutimage'
      ], function( preset ) {
          return preset;
      });
      return require('webuploader');
  });
  