1 /**
  2  * The MIT License (MIT)
  3  *
  4  * Copyright (c) 2016 DeNA Co., Ltd.
  5  *
  6  * Permission is hereby granted, free of charge, to any person obtaining a copy
  7  * of this software and associated documentation files (the "Software"), to deal
  8  * in the Software without restriction, including without limitation the rights
  9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  * copies of the Software, and to permit persons to whom the Software is
 11  * furnished to do so, subject to the following conditions:
 12  *
 13  * The above copyright notice and this permission notice shall be included in
 14  * all copies or substantial portions of the Software.
 15  *
 16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 22  * SOFTWARE.
 23  */
 24 
 25 /// <reference path="base.js"/>
 26 /// <reference path="progress_event.js"/>
 27 /// <reference path="location.js"/>
 28 /// <reference path="user_agent.js"/>
 29 /// <reference path="image_factory.js"/>
 30 /// <reference path="script_factory.js"/>
 31 /// <reference path="config.js"/>
 32 
 33 /**
 34  * A class that loads a file either from a server or from a cache.
 35  * @param {createjs.Loader.Listener} listener
 36  * @param {createjs.Loader.Item} item
 37  * @implements {EventListener}
 38  * @implements {createjs.Loader.Cache.Listener}
 39  * @constructor
 40  */
 41 createjs.Loader = function(listener, item) {
 42   /**
 43    * The listener who listens events from this loader.
 44    * @type {createjs.Loader.Listener}
 45    * @private
 46    */
 47   this.listener_ = listener;
 48 
 49   /**
 50    * The item loaded by this loader.
 51    * @type {createjs.Loader.Item}
 52    * @private
 53    */
 54   this.item_ = item;
 55 };
 56 
 57 /**
 58  * Item types.
 59  * @enum {number}
 60  */
 61 createjs.Loader.Type = {
 62   UNKNOWN: 0,
 63   IMAGE: 1,
 64   SOUND: 2,
 65   JSON: 3,
 66   CSS: 4,
 67   SCRIPT: 5,
 68   MANIFEST: 6,
 69   TEXT: 7,
 70   VIDEO: 8,
 71   HTML: 9
 72 };
 73 
 74 /**
 75  * String representations of the above item types.
 76  * @const {Array.<string>}
 77  * @private
 78  */
 79 createjs.Loader.TYPE_NAMES_ = [
 80   '',
 81   'image',
 82   'sound',
 83   'json',
 84   'css',
 85   'javascript',
 86   'manifest',
 87   'text',
 88   'video',
 89   'html'
 90 ];
 91 
 92 /**
 93  * Extension IDs.
 94  * @enum {number}
 95  */
 96 createjs.Loader.Extension = {
 97   JPEG: 0,
 98   JPG: 1,
 99   GIF: 2,
100   PNG: 3,
101   WEBP: 4,
102   BMP: 5,
103   OGG: 6,
104   MP3: 7,
105   MP4: 8,
106   M4A: 9,
107   WAV: 10,
108   JSON: 11,
109   CSS: 12,
110   JS: 13
111 };
112 
113 /**
114  * The default loaders.
115  * @const {Array.<number>}
116  * @private
117  */
118 createjs.Loader.LOADER_TYPES_ = [
119   createjs.Loader.Type.IMAGE,   // createjs.Loader.Extension.JPEG
120   createjs.Loader.Type.IMAGE,   // createjs.Loader.Extension.JPG
121   createjs.Loader.Type.IMAGE,   // createjs.Loader.Extension.GIF
122   createjs.Loader.Type.IMAGE,   // createjs.Loader.Extension.PNG
123   createjs.Loader.Type.IMAGE,   // createjs.Loader.Extension.WEBP
124   createjs.Loader.Type.IMAGE,   // createjs.Loader.Extension.BMP
125   createjs.Loader.Type.SOUND,   // createjs.Loader.Extension.OGG
126   createjs.Loader.Type.SOUND,   // createjs.Loader.Extension.MP3
127   createjs.Loader.Type.SOUND,   // createjs.Loader.Extension.MP4
128   createjs.Loader.Type.SOUND,   // createjs.Loader.Extension.M4A
129   createjs.Loader.Type.SOUND,   // createjs.Loader.Extension.WAV
130   createjs.Loader.Type.JSON,    // createjs.Loader.Extension.JSON
131   createjs.Loader.Type.CSS,     // createjs.Loader.Extension.CSS
132   createjs.Loader.Type.SCRIPT   // createjs.Loader.Extension.JS
133 ];
134 
135 /**
136  * An interface that listens events from a createjs.Loader object. This
137  * interface is used by the createjs.LoadQueue class to dispatch events to
138  * games.
139  * @interface
140  */
141 createjs.Loader.Listener = function() {};
142 
143 /**
144  * Called when a loader finishes loading a file.
145  * @param {createjs.Loader} loader
146  */
147 createjs.Loader.Listener.prototype.handleFileComplete = function(loader) {};
148 
149 /**
150  * Called when an error occurs while loading a file.
151  * @param {createjs.Loader} loader
152  * @param {string} type
153  * @param {string} message
154  */
155 createjs.Loader.Listener.prototype.handleFileError =
156     function(loader, type, message) {};
157 
158 /**
159  * A class that represents an item to be loaded by the createjs.Loader object.
160  * @constructor
161  */
162 createjs.Loader.Item = function() {
163 };
164 
165 /**
166  * An interface that listens events from a createjs.Loader.Item object. This
167  * interface allows to add a custom task after the createjs.Loader.Item class
168  * finishes loading data. (In fact, the createjs.Sound class uses this interface
169  * to decode sound data after the createjs.Loader.Item class finishes loading a
170  * sound file.)
171  * @interface
172  */
173 createjs.Loader.Item.Listener = function() {};
174 
175 /**
176  * Called when a loader finishes loading a file.
177  * @param {createjs.Loader} loader
178  * @param {ArrayBuffer} buffer
179  * @return {boolean}
180  */
181 createjs.Loader.Item.Listener.prototype.handleLoad =
182     function(loader, buffer) {};
183 
184 /**
185  * The ID used by CreateJS to identify this item.
186  * @type {string}
187  */
188 createjs.Loader.Item.prototype.id = '';
189 
190 /**
191  * The raw response from a server if this item refers to a text file.
192  * @type {string}
193  */
194 createjs.Loader.Item.prototype.resultText = '';
195 
196 /**
197  * The raw response from a server if this item refers to a binary file.
198  * @type {ArrayBuffer}
199  */
200 createjs.Loader.Item.prototype.resultBuffer = null;
201 
202 /**
203  * The loaded data.
204  * @type {*}
205  */
206 createjs.Loader.Item.prototype.resultObject = null;
207 
208 /**
209  * The source URL of this item.
210  * @type {string}
211  * @private
212  */
213 createjs.Loader.Item.prototype.source_ = '';
214 
215 /**
216  * Whether this item should encapsulate its response to an HTMLElement object.
217  * @type {boolean}
218  * @private
219  */
220 createjs.Loader.Item.prototype.tag_ = false;
221 
222 /**
223  * The content-type ID of this item.
224  * @type {number}
225  * @private
226  */
227 createjs.Loader.Item.prototype.type_ = createjs.Loader.Type.UNKNOWN;
228 
229 if (createjs.USE_CACHE) {
230   /**
231    * Whether this item should be read from our cache.
232    * @type {boolean}
233    * @private
234    */
235   createjs.Loader.Item.prototype.cache_ = false;
236 }
237 
238 /**
239  * The user-defined object. This object is to be attached to a 'complete' event
240  * so event listeners can use it.
241  * @type {Object}
242  * @private
243  */
244 createjs.Loader.Item.prototype.values_ = null;
245 
246 /**
247  * The file-extension ID of this item.
248  * @type {number}
249  * @private
250  */
251 createjs.Loader.Item.prototype.extension_ = -1;
252 
253 /**
254  * The absolute URL of this item.
255  * @type {string}
256  * @private
257  */
258 createjs.Loader.Item.prototype.path_ = '';
259 
260 /**
261  * The listener that listens events for this item.
262  * @type {createjs.Loader.Item.Listener}
263  * @private
264  */
265 createjs.Loader.Item.prototype.listener_ = null;
266 
267 /**
268  * The format of a WebGL texture to be created from this item. The WebGL
269  * renderer creates an UNSIGNED_BYTE (full-color) texture when a game does not
270  * specify the format.
271  *   +-------+------------------------+
272  *   | value | texture format         |
273  *   +-------+------------------------+
274  *   | 0     | UNSIGNED_BYTE          |
275  *   | 1     | UNSIGNED_SHORT_4_4_4_4 |
276  *   | 2     | UNSIGNED_SHORT_5_5_5_1 |
277  *   | 3     | UNSIGNED_SHORT_5_6_5   |
278  *   +-------+------------------------+
279  * @type {number}
280  * @private
281  */
282 createjs.Loader.Item.prototype.format_ = 0;
283 
284 /**
285  * Whether a loader should wait this item to be decoded.
286  * @type {boolean}
287  * @private
288  */
289 createjs.Loader.Item.prototype.synchronous_ = false;
290 
291 /**
292  * Returns an extension ID.
293  * @param {string} extension
294  * @return {number}
295  * @private
296  */
297 createjs.Loader.Item.getExtension_ = function(extension) {
298   /// <param type="string" name="extension"/>
299   /// <returns type="number"/>
300   var TYPES = {
301     'jpeg': createjs.Loader.Extension.JPEG,
302     'jpg': createjs.Loader.Extension.JPG,
303     'gif': createjs.Loader.Extension.GIF,
304     'png': createjs.Loader.Extension.PNG,
305     'webp': createjs.Loader.Extension.WEBP,
306     'bmp': createjs.Loader.Extension.BMP,
307     'ogg': createjs.Loader.Extension.OGG,
308     'mp3': createjs.Loader.Extension.MP3,
309     'mp4': createjs.Loader.Extension.MP4,
310     'm4a': createjs.Loader.Extension.M4A,
311     'wav': createjs.Loader.Extension.WAV,
312     'json': createjs.Loader.Extension.JSON,
313     'css': createjs.Loader.Extension.CSS,
314     'js': createjs.Loader.Extension.JS
315   };
316   return TYPES[extension] || -1;
317 };
318 
319 /**
320  * Returns a loader type from the specified extension ID.
321  * @param {number} extension
322  * @return {number}
323  * @private
324  */
325 createjs.Loader.Item.getType_ = function(extension) {
326   /// <param type="number" name="extension"/>
327   /// <returns type="number"/>
328   createjs.assert(0 <= extension &&
329                   extension < createjs.Loader.LOADER_TYPES_.length);
330   return createjs.Loader.LOADER_TYPES_[extension];
331 };
332 
333 /**
334  * Initializes this item. This method initializes internal variables used in
335  * loading this item.
336  * @param {Object} values
337  * @param {string} basePath
338  * @const
339  */
340 createjs.Loader.Item.prototype.initializeItem = function(values, basePath) {
341   /// <param type="Object" name="values"/>
342   /// <param type="string" name="basePath"/>
343   this.importValues(values);
344   var target = new createjs.Location(this.source_);
345   this.extension_ = createjs.Loader.Item.getExtension_(target.getExtension());
346   if (!this.type_) {
347     this.type_ = createjs.Loader.Item.getType_(this.extension_);
348     this.values_['type'] = createjs.Loader.TYPE_NAMES_[this.type_];
349   }
350   // Prepend basePath when the input path is a relative one.
351   if (!target.protocol && target.isRelative()) {
352     this.path_ = basePath + this.source_;
353   } else {
354     this.path_ = this.source_;
355   }
356   // Set whether this item needs an HTMLElement object to store the result. The
357   // createjs.Sound class always uses the WebAudio API to play audio files on
358   // iPhone devices and it does not need HTMLAudioElement objects.
359   if (this.isImage() || this.isScript() || this.isCSS() || this.isVideo()) {
360     this.tag_ = true;
361   } else if (this.isSound()) {
362     this.tag_ = !createjs.AudioContext;
363   }
364   // Set the generated ID to this item if an application has not set it.
365   if (!this.id) {
366     this.id = this.source_;
367   }
368 };
369 
370 /**
371  * Imports values from an Object.
372  * @param {Object} values
373  * @const
374  */
375 createjs.Loader.Item.prototype.importValues = function(values) {
376   /// <param type="Object" name="values"/>
377   var src = values['src'];
378   if (src) {
379     this.source_ = createjs.getString(src);
380   }
381   var type = values['type'];
382   if (type) {
383     var name = createjs.getString(type);
384     var NAMES = createjs.Loader.TYPE_NAMES_;
385     for (var i = 1; i < NAMES.length; ++i) {
386       if (NAMES[i] == name) {
387         this.type_ = i;
388         break;
389       }
390     }
391   }
392   var id = values['id'];
393   if (id != null) {
394     this.id = createjs.parseString(id);
395   }
396   if (createjs.USE_CACHE) {
397     if (createjs.Loader.getCache()) {
398       this.cache_ = !!values['cache'];
399     }
400     values['_cache'] = 0;
401   }
402   this.format_ = createjs.castNumber(values['format']) || 0;
403   this.synchronous_ = !!values['sync'];
404   this.values_ = values;
405 };
406 
407 /**
408  * Exports values to an Object.
409  * @return {Object}
410  * @const
411  */
412 createjs.Loader.Item.prototype.exportValues = function() {
413   /// <returns type="Object"/>
414   return this.values_;
415 };
416 
417 /**
418  * Retrieves the source path.
419  * @return {string}
420  * @const
421  */
422 createjs.Loader.Item.prototype.getSource = function() {
423   /// <returns type="string"/>
424   return this.source_;
425 };
426 
427 /**
428  * Sets the source path.
429  * @param {string} source
430  * @const
431  */
432 createjs.Loader.Item.prototype.setSource = function(source) {
433   /// <param type="string" name="source"/>
434   this.source_ = source;
435 };
436 
437 /**
438  * Returns whether this item is a binary file.
439  * @return {boolean}
440  * @const
441  */
442 createjs.Loader.Item.prototype.isBinary = function() {
443   /// <returns type="boolean"/>
444   return this.type_ == createjs.Loader.Type.IMAGE ||
445          this.type_ == createjs.Loader.Type.SOUND ||
446          this.type_ == createjs.Loader.Type.VIDEO;
447 };
448 
449 /**
450  * Returns whether this item is a text file.
451  * @return {boolean}
452  * @const
453  */
454 createjs.Loader.Item.prototype.isText = function() {
455   /// <returns type="boolean"/>
456   return !this.isBinary();
457 };
458 
459 /**
460  * Returns whether this item is an image file.
461  * @return {boolean}
462  * @const
463  */
464 createjs.Loader.Item.prototype.isImage = function() {
465   /// <returns type="boolean"/>
466   return this.type_ == createjs.Loader.Type.IMAGE;
467 };
468 
469 /**
470  * Returns whether this item is an audio file.
471  * @return {boolean}
472  * @const
473  */
474 createjs.Loader.Item.prototype.isSound = function() {
475   /// <returns type="boolean"/>
476   return this.type_ == createjs.Loader.Type.SOUND;
477 };
478 
479 /**
480  * Returns whether this item is a script file.
481  * @return {boolean}
482  * @const
483  */
484 createjs.Loader.Item.prototype.isScript = function() {
485   /// <returns type="boolean"/>
486   return this.type_ == createjs.Loader.Type.SCRIPT;
487 };
488 
489 /**
490  * Returns whether this item is a CSS file.
491  * @return {boolean}
492  * @const
493  */
494 createjs.Loader.Item.prototype.isCSS = function() {
495   /// <returns type="boolean"/>
496   return this.type_ == createjs.Loader.Type.CSS;
497 };
498 
499 /**
500  * Returns whether this item is a JSON file.
501  * @return {boolean}
502  * @const
503  */
504 createjs.Loader.Item.prototype.isJSON = function() {
505   /// <returns type="boolean"/>
506   return this.type_ == createjs.Loader.Type.JSON;
507 };
508 
509 /**
510  * Returns whether this item is a manifest file.
511  * @return {boolean}
512  * @const
513  */
514 createjs.Loader.Item.prototype.isManifest = function() {
515   /// <returns type="boolean"/>
516   return this.type_ == createjs.Loader.Type.MANIFEST;
517 };
518 
519 /**
520  * Returns whether this item is a video clip.
521  * @return {boolean}
522  * @const
523  */
524 createjs.Loader.Item.prototype.isVideo = function() {
525   /// <returns type="boolean"/>
526   return this.type_ == createjs.Loader.Type.VIDEO;
527 };
528 
529 /**
530  * Returns the method name.
531  * @return {string}
532  * @const
533  */
534 createjs.Loader.Item.prototype.getMethod = function() {
535   /// <returns type="string"/>
536   return 'GET';
537 };
538 
539 /**
540  * Returns the extension of this item.
541  * @return {number}
542  * @const
543  */
544 createjs.Loader.Item.prototype.getExtension = function() {
545   /// <returns type="number"/>
546   return this.extension_;
547 };
548 
549 /**
550  * Returns the listener attached to this item.
551  * @return {createjs.Loader.Item.Listener}
552  * @const
553  */
554 createjs.Loader.Item.prototype.getListener = function() {
555   /// <returns type="createjs.Loader.Item.Listener"/>
556   return this.listener_;
557 };
558 
559 /**
560  * Attaches a listener to this item.
561  * @param {createjs.Loader.Item.Listener} listener
562  * @const
563  */
564 createjs.Loader.Item.prototype.setListener = function(listener) {
565   /// <param type="createjs.Loader.Item.Listener" name="listener"/>
566   this.listener_ = listener;
567 };
568 
569 /**
570  * Returns whether this item is a PNG image.
571  * @return {boolean}
572  * @const
573  */
574 createjs.Loader.Item.prototype.isPng = function() {
575   /// <returns type="boolean"/>
576   return this.extension_ == createjs.Loader.Extension.PNG;
577 };
578 
579 /**
580  * Returns whether a listener can handle this item synchronously. The
581  * createjs.Loader class waits for a listener to handle this item if this
582  * function returns true.
583  * @return {boolean}
584  * @const
585  */
586 createjs.Loader.Item.prototype.isSynchronous = function() {
587   /// <returns type="boolean"/>
588   return this.synchronous_;
589 };
590 
591 /**
592  * Returns an mime-type string of this item.
593  * @return {string}
594  * @private
595  */
596 createjs.Loader.Item.prototype.getMime = function() {
597   /// <returns type="string"/>
598   /**
599    * The MIME types associated with extension IDs.
600    * @const {Array.<string>}
601    */
602   var MIME_TYPES = [
603     'image/jpeg',        // createjs.Loader.Extension.JPEG
604     'image/jpeg',        // createjs.Loader.Extension.JPG
605     'image/gif',         // createjs.Loader.Extension.GIF
606     'image/png',         // createjs.Loader.Extension.PNG
607     'image/webp',        // createjs.Loader.Extension.WEBP
608     'image/bmp',         // createjs.Loader.Extension.BMP
609     'audio/ogg',         // createjs.Loader.Extension.OGG
610     'audio/mpeg',        // createjs.Loader.Extension.MP3
611     'audio/mp4',         // createjs.Loader.Extension.MP4
612     'audio/mp4',         // createjs.Loader.Extension.M4A
613     'audio/wav',         // createjs.Loader.Extension.WAV
614     'application/json',  // createjs.Loader.Extension.JSON
615     'text/css',          // createjs.Loader.Extension.CSS
616     'text/javascript'    // createjs.Loader.Extension.JS
617   ];
618   var id = this.extension_;
619   createjs.assert(0 <= id && id < MIME_TYPES.length);
620   return MIME_TYPES[id];
621 };
622 
623 /**
624  * An interface that enumerates methods to access a cache storage used by the
625  * createjs.Loader object and its derivatives.
626  * @interface
627  */
628 createjs.Loader.Cache = function() {};
629 
630 /**
631  * Returns whether this cache storage is opened.
632  * @return {boolean}
633  */
634 createjs.Loader.Cache.prototype.isOpened = function() {};
635 
636 /**
637  * Writes a key-value pair.
638  * @param {createjs.Loader.Cache.Listener} listener
639  * @param {string} key
640  * @param {*} value
641  */
642 createjs.Loader.Cache.prototype.set = function(listener, key, value) {};
643 
644 /**
645  * Reads a key-value pair.
646  * @param {createjs.Loader.Cache.Listener} listener
647  * @param {string} key
648  * @return {boolean}
649  */
650 createjs.Loader.Cache.prototype.get = function(listener, key) {};
651 
652 /**
653  * Resets the cache.
654  */
655 createjs.Loader.Cache.prototype.reset = function() {};
656 
657 /**
658  * An interface that listens events from a createjs.Loader.Cache object.
659  * @interface
660  */
661 createjs.Loader.Cache.Listener = function() {};
662 
663 /**
664  * Called when a browser has successfully read a key-value pair from a cache.
665  * @param {string} key
666  * @param {*} data
667  */
668 createjs.Loader.Cache.Listener.prototype.handleGetSuccess =
669     function(key, data) {};
670 
671 /**
672  * Called when there is an error while a browser reads a key-value pair from a
673  * cache.
674  * @param {string} key
675  */
676 createjs.Loader.Cache.Listener.prototype.handleGetError = function(key) {};
677 
678 /**
679  * Called when a browser has successfully written a key-value pair to a cache.
680  * @param {string} key
681  */
682 createjs.Loader.Cache.Listener.prototype.handlePutSuccess = function(key) {};
683 
684 /**
685  * Called when there is an error while a browser writes a key-value pair to a
686  * cache.
687  * @param {string} key
688  */
689 createjs.Loader.Cache.Listener.prototype.handlePutError = function(key) {};
690 
691 /**
692  * Whether this loader has finished loading files.
693  * @type {boolean}
694  * @private
695  */
696 createjs.Loader.prototype.loaded_ = false;
697 
698 /**
699  * Whether this loader has been canceled loading files.
700  * @type {boolean}
701  * @private
702  */
703 createjs.Loader.prototype.canceled_ = false;
704 
705 /**
706  * The current load progress (percentage) for this item.
707  * @type {number}
708  * @private
709  */
710 createjs.Loader.prototype.progress_ = 0;
711 
712 /**
713  * An XMLHttpRequet object used to load content.
714  * @type {XMLHttpRequest}
715  * @private
716  */
717 createjs.Loader.prototype.request_ = null;
718 
719 /**
720  * Determines if the load has been canceled.
721  * @return {boolean}
722  * @private
723  */
724 createjs.Loader.prototype.isCanceled_ = function() {
725   /// <returns type="boolean"/>
726   return this.canceled_;
727 };
728 
729 /**
730  * Changes the loading progress.
731  * @param {number} progress
732  * @private
733  */
734 createjs.Loader.prototype.setProgress_ = function(progress) {
735   /// <param type="number" name="progress"/>
736   this.progress = progress;
737 };
738 
739 /**
740  * Creates an object URL from an ArrayBuffer object.
741  * @param {string} type
742  * @param {ArrayBuffer} buffer
743  * @return {string}
744  * @private
745  */
746 createjs.Loader.prototype.createObjectURL_ = function(type, buffer) {
747   /// <param type="string" name="type"/>
748   /// <param type="ArrayBuffer" name="buffer"/>
749   /// <returns type="string"/>
750   // Use the webkitBlobBuilder class to create a Blob object only when it is
751   // defined. This is a workaround for Android browsers (Android 4.3 or
752   // earlier.)
753   var blob;
754   if (createjs.BlobBuilder) {
755     var builder = new createjs.BlobBuilder();
756     builder.append(buffer);
757     blob = builder.getBlob(type);
758   } else {
759     blob = new Blob([buffer], { 'type': type });
760   }
761   return createjs.URL['createObjectURL'](blob);
762 };
763 
764 /**
765  * Cleans all resources attached to this object.
766  * @private
767  */
768 createjs.Loader.prototype.clean_ = function() {
769   if (this.request_) {
770     var request = this.request_;
771     request.removeEventListener('error', this, false);
772     request.removeEventListener('load', this, false);
773     this.request_ = null;
774   }
775 };
776 
777 /**
778  * Creates a new XMLHttpRequest object.
779  * @param {createjs.Loader.Item} item
780  * @param {string} path
781  * @return {XMLHttpRequest}
782  * @private
783  */
784 createjs.Loader.prototype.getRequest_ = function(item, path) {
785   /// <param type="createjs.Loader.Item" name="item"/>
786   /// <param type="string" name="path"/>
787   /// <returns type="XMLHttpRequest"/>
788   if (!createjs.USE_CACHE) {
789     // It is somehow slow for Mobile Safari on iOS 6 to draw an image with its
790     // source a Data URI. To avoid this issue, this loader always uses an
791     // HTMLImageElement object to load an image.
792     if (item.isImage() || (item.isSound() && item.tag_)) {
793       return null;
794     }
795   } else {
796     // Use XMLHttpRequest objects only when the host browser satisfies all the
797     // following conditions:
798     // * It has to write this image to a cache, and;
799     // * It can create Blob URLs.
800     if (item.isImage()) {
801       if (!item.cache_  || !createjs.URL) {
802         return null;
803       }
804     } else if (item.isSound() && item.tag_) {
805       // Do not use XMLHttpRequest objects when SoundJS uses <audio> elements
806       // to play sounds. (Android 4.4 WebViews cannot use a Blob URL as the
807       // source of an <audio> element.)
808       return null;
809     }
810   }
811   // Return an HTMLScriptElement spooled in the createjs.ScriptFactory object
812   // when an application requests the same script file multiple times.
813   if (item.isScript() && createjs.ScriptFactory.exist(path)) {
814     return null;
815   }
816   // Create an XMLHttpRequest object and open the above URL. Unfortunately, not
817   // all browsers (e.g. Android 2.3) support XMLHttpRequest Level 2, which is
818   // necessary for loading binary files. Set the URL to the src properties of
819   // the output element directly as a workaround for such browsers.
820   var request = new XMLHttpRequest();
821   if (item.isBinary() && request.responseType == null) {
822     return null;
823   }
824   return request;
825 };
826 
827 /**
828  * Sends an XML HTTP request.
829  * @private
830  */
831 createjs.Loader.prototype.sendRequest_ = function() {
832   // Format a URL to be opened by an XMLHttpRequest object.
833   var item = this.getItem();
834   var path = item.path_;
835   var request = this.getRequest_(item, path);
836   if (!request) {
837     this.handleLoadComplete_(item, path);
838     return;
839   }
840   request.open(item.getMethod(), path, true);
841 
842   // Attach this object to XMLHttpRequest events.
843   request.addEventListener('error', this, false);
844   request.addEventListener('load', this, false);
845 
846   // Add item-specific headers to this request.
847   if (item.isText()) {
848     request.overrideMimeType('text/plain; charset=utf-8');
849   }
850   if (item.isBinary()) {
851     request.responseType = 'arraybuffer';
852   }
853   request.send();
854   this.request_ = request;
855 };
856 
857 /**
858  * Creates a result string.
859  * @param {createjs.Loader.Item} item
860  * @param {*} response
861  * @return {string}
862  * @private
863  */
864 createjs.Loader.prototype.getResult_ = function(item, response) {
865   /// <param type="createjs.Loader.Item" name="item"/>
866   /// <param name="response"/>
867   /// <returns type="string"/>
868   if (!item.isBinary()) {
869     var text = createjs.getString(response);
870     item.resultText = text;
871     return text;
872   }
873   var buffer = /** @type {ArrayBuffer} */ (response);
874   item.resultBuffer = buffer;
875   if (!item.tag_) {
876     // Return an empty string if this loader does not have to create an HTML
877     // element, i.e. this item is a sound file to be played with the WebAudio
878     // API.
879     return '';
880   }
881   // Create an object URL for the response if this loader needs to create an
882   // HTML element, i.e. this item is a sound file to be set to an <audio>
883   // element or an image file to be set to an <img> element. (Object URLs are
884   // supported by all browsers except Android browsers on Android 2.3, which
885   // cannot use XMLHttpRequest objects to get binary files. So, it is safe to
886   // always create object URLs here.)
887   var type = this.request_.getResponseHeader('Content-Type');
888   return this.createObjectURL_(type, buffer);
889 };
890 
891 /**
892  * Creates a type-specific result.
893  * @param {createjs.Loader.Item} item
894  * @param {string} result
895  * @return {boolean}
896  * @private
897  */
898 createjs.Loader.prototype.setResult_ = function(item, result) {
899   /// <param type="createjs.Loader.Item" name="item"/>
900   /// <param type="string" name="result"/>
901   /// <returns type="boolean"/>
902   this.loaded_ = true;
903   if (item.isImage()) {
904     var image =
905         createjs.ImageFactory.get(item.path_, result, this, item.format_);
906     item.resultObject = image;
907     return image.complete;
908   }
909   if (item.isSound()) {
910     if (!item.tag_) {
911       // Call the listener attached to the loaded item to decode this sound
912       // so a game can play it when it receives a 'fileload' event.
913       var listener = item.getListener();
914       if (listener) {
915         var buffer = /** @type {ArrayBuffer} */ (item.resultBuffer);
916         item.resultBuffer = null;
917         return listener.handleLoad(this, buffer);
918       }
919       return true;
920     }
921     var audio =
922         /** @type {HTMLAudioElement} */ (document.createElement('audio'));
923     if (createjs.DEBUG) {
924       audio.id = item.path_;
925     }
926     audio.autoplay = false;
927     audio.src = result;
928     item.resultObject = audio;
929     return true;
930   }
931   if (item.isScript()) {
932     item.resultObject = createjs.ScriptFactory.get(item.path_, result);
933     return true;
934   }
935   if (item.isCSS()) {
936     var style =
937         /** @type {HTMLStyleElement} */ (document.createElement('style'));
938     style.type = 'text/css';
939     if (style.styleSheet) {
940       style.styleSheet.cssText = result;
941     } else {
942       style.appendChild(document.createTextNode(result));
943     }
944     document.head.appendChild(style);
945     item.resultObject = style;
946     return true;
947   }
948   if (item.isJSON() || item.isManifest()) {
949     item.resultObject = JSON.parse(result);
950     return true;
951   }
952   if (item.isVideo()) {
953     var video =
954         /** @type {HTMLVideoElement} */ (document.createElement('video'));
955     if (createjs.DEBUG) {
956       video.id = item.path_;
957     }
958     // Set 'anonymous' to the crossOrigin attribute of this <video> element to
959     // create a WebGL texture from the element. (Mobile Safari throws an
960     // exception in creating a WebGL texture from an anonymously-shared <video>
961     // element and we except this result URL must not be a Data URI.)
962     video.crossOrigin = 'anonymous';
963     video.src = result;
964     item.resultObject = video;
965     return true;
966 }
967   item.resultObject = result;
968   return true;
969 };
970 
971 /**
972  * Dispatches an error event to listeners.
973  * @param {string} type
974  * @param {string} message
975  * @private
976  */
977 createjs.Loader.prototype.dispatchError_ = function(type, message) {
978   /// <param type="string" name="type"/>
979   /// <param type="string" name="message"/>
980   this.clean_();
981   if (!this.isCanceled_()) {
982     this.listener_.handleFileError(this, type, message);
983   }
984 };
985 
986 /**
987  * Called when this loader finishes loading a file successfully.
988  * @param {createjs.Loader.Item} item
989  * @param {string} result
990  * @private
991  */
992 createjs.Loader.prototype.handleLoadComplete_ = function(item, result) {
993   /// <param type="createjs.Loader.Item" name="item"/>
994   /// <param type="string" name="result"/>
995   if (this.setResult_(item, result)) {
996     if (!this.isCanceled_()) {
997       this.listener_.handleFileComplete(this);
998       this.canceled_ = true;
999     }
1000   }
1001 };
1002 
1003 /**
1004  * Called when the XMLHttpRequest object has finished loading data.
1005  * @param {Event} event
1006  * @private
1007  */
1008 createjs.Loader.prototype.handleLoad_ = function(event) {
1009   /// <param type="Event" name="event"/>
1010   var status = createjs.getNumber(this.request_.status);
1011   if (status != 200) {
1012     this.dispatchError_('error', '');
1013     return;
1014   }
1015   var item = this.getItem();
1016   var response = this.request_.response || this.request_.responseText;
1017   var result = this.getResult_(item, response);
1018   if (createjs.USE_CACHE && item.cache_) {
1019     // Write the loaded data to the cache storage only when it is open. (Indexed
1020     // database allows adding ArrayBuffer values as well as strings.)
1021     var cache = createjs.Loader.getCache();
1022     if (cache && cache.isOpened()) {
1023       cache.set(this, item.path_, response);
1024     }
1025   }
1026   this.clean_();
1027   this.handleLoadComplete_(item, result);
1028   item.resultBuffer = null;
1029 };
1030 
1031 /**
1032  * Retrieves the current loading progress.
1033  * @return {number}
1034  * @const
1035  */
1036 createjs.Loader.prototype.getProgress = function() {
1037   /// <returns type="number"/>
1038   return this.progress_;
1039 };
1040 
1041 /**
1042  * Returns a reference to the manifest item.
1043  * @return {createjs.Loader.Item}
1044  * @const
1045  */
1046 createjs.Loader.prototype.getItem = function() {
1047   /// <returns type="createjs.Loader.Item"/>
1048   return this.item_;
1049 };
1050 
1051 /**
1052  * Starts loading the item associated with this object.
1053  * @const
1054  */
1055 createjs.Loader.prototype.load = function() {
1056   var item = this.getItem();
1057   if (!createjs.USE_CACHE) {
1058     this.sendRequest_();
1059     return;
1060   }
1061   var cache = createjs.Loader.getCache();
1062   if (!cache || !item.cache_ || !cache.get(this, this.getItem().path_)) {
1063     this.sendRequest_();
1064   }
1065 };
1066 
1067 /**
1068  * Cancels loading this item.
1069  * @const
1070  */
1071 createjs.Loader.prototype.cancel = function() {
1072   this.canceled = true;
1073   this.clean_();
1074   this.request_.abort();
1075 };
1076 
1077 /**
1078  * Sends a completion event (or an error event) to the listener attached to this
1079  * loader, i.e. a createjs.LoadQueue object that owns this loader.
1080  * @param {boolean} error
1081  * @param {AudioBuffer} buffer
1082  * @const
1083  */
1084 createjs.Loader.prototype.sendFileComplete = function(error, buffer) {
1085   /// <param type="boolean" name="error"/>
1086   /// <param type="AudioBuffer" name="buffer"/>
1087   var item = this.getItem();
1088   if (error) {
1089     this.dispatchError_('error', '');
1090   } else {
1091     item.resultObject = buffer;
1092     if (!this.isCanceled_()) {
1093       this.listener_.handleFileComplete(this);
1094     }
1095   }
1096 };
1097 
1098 /** @override */
1099 createjs.Loader.prototype.handleGetSuccess = function(key, data) {
1100   this.loaded_ = true;
1101   this.setProgress_(1);
1102   var item = this.getItem();
1103   if (!item.tag_) {
1104     item.resultBuffer = createjs.castArrayBuffer(data);
1105   }
1106   item.values_['_cache'] = 1;
1107   var result;
1108   if (item.isImage()) {
1109     // Create an object URL from the cached data so this loader can set it to an
1110     // <img> element.
1111     var type = item.getMime();
1112     result = this.createObjectURL_(type, createjs.castArrayBuffer(data));
1113   } else if (createjs.isString(data)) {
1114     // This data is one of a CSS file, a JavaScript one, or a JSON manifest.
1115     result = createjs.castString(data);
1116   } else if (item.isVideo()) {
1117     // Treat the cached data as an MPEG-4 video clip and create an object URL
1118     // from the data. It is OK to always treat the cached data as an MPEG-4
1119     // video clip because MPEG-4 is the only supported video format of this
1120     // loader.
1121     result = this.createObjectURL_('video/mp4', createjs.castArrayBuffer(data));
1122   } else {
1123     result = '';
1124   }
1125   this.handleLoadComplete_(item, result);
1126   // Set this item to the cache again to update its time stamp.
1127   var cache = createjs.Loader.getCache();
1128   cache.set(this, key, data);
1129 };
1130 
1131 /** @override */
1132 createjs.Loader.prototype.handleGetError = function(key) {
1133   this.sendRequest_();
1134 };
1135 
1136 /** @override */
1137 createjs.Loader.prototype.handlePutSuccess = function(key) {
1138 };
1139 
1140 /** @override */
1141 createjs.Loader.prototype.handlePutError = function(key) {
1142 };
1143 
1144 /** @override */
1145 createjs.Loader.prototype.handleEvent = function(event) {
1146   /// <param type="Event" name="event"/>
1147   var target = event.target;
1148   if (target instanceof HTMLImageElement) {
1149     createjs.ImageFactory.removeListeners(target, this);
1150   }
1151   var type = event.type;
1152   if (type == 'load') {
1153     if (!this.loaded_) {
1154       this.handleLoad_(event);
1155     } else {
1156       if (!this.isCanceled_()) {
1157         this.listener_.handleFileComplete(this);
1158       }
1159     }
1160   } else {
1161     this.dispatchError_(type, '');
1162   }
1163 };
1164 
1165 /**
1166  * A class that implements the createjs.Loader.Cache interface with the
1167  * IndexedDB API. This cache uses one table consisting of three columns:
1168  *   key (string), time (integer), and data (binary).
1169  * @implements {createjs.Loader.Cache}
1170  * @constructor
1171  */
1172 createjs.Loader.IndexedDB = function() {
1173 };
1174 
1175 /**
1176  * Represents whether a transaction is a read one.
1177  * @const {number}
1178  * @private
1179  */
1180 createjs.Loader.IndexedDB.READ_ = 0;
1181 
1182 /**
1183  * Represents whether a transaction is a write one.
1184  * @const {number}
1185  * @private
1186  */
1187 createjs.Loader.IndexedDB.WRITE_ = 1;
1188 
1189 /**
1190  * The interface to access the cache database.
1191  * @type {IDBDatabase}
1192  * @private
1193  */
1194 createjs.Loader.IndexedDB.database_ = null;
1195 
1196 /**
1197  * Returns the instance of the createjs.Loader.IndexedDB object.
1198  * @return {createjs.Loader.IndexedDB}
1199  * @const
1200  */
1201 createjs.Loader.IndexedDB.getInstance = function() {
1202   /// <returns type="createjs.Loader.IndexedDB"/>
1203   // Disable the Indexed Database cache on Android browsers. Even though some
1204   // Android browsers (e.g. SO-04E) provides the Indexed Database API, it throws
1205   // exceptions.
1206   if (!createjs.global.indexedDB || createjs.UserAgent.isAndroidBrowser()) {
1207     return null;
1208   }
1209   return new createjs.Loader.IndexedDB();
1210 };
1211 
1212 /**
1213  * Retrieves the database request from an Event object.
1214  * @param {Event} event
1215  * @return {IDBRequest}
1216  * @private
1217  */
1218 createjs.Loader.IndexedDB.getRequest_ = function(event) {
1219   /// <param type="Event" name="event"/>
1220   /// <returns type="IDBRequest"/>
1221   return /** @type {IDBRequest} */ (event.target);
1222 };
1223 
1224 /**
1225  * Retrieves a transaction from the specified Event object.
1226  * @param {Event} event
1227  * @return {IDBTransaction}
1228  * @private
1229  */
1230 createjs.Loader.IndexedDB.getTransaction_ = function(event) {
1231   /// <param type="Event" name="event"/>
1232   /// <returns type="IDBTransaction"/>
1233   return /** @type {IDBTransaction} */ (event.target);
1234 };
1235 
1236 /**
1237  * Retrieves a cursor from the specified IDBRequest object.
1238  * @param {IDBRequest} request
1239  * @return {IDBCursor}
1240  * @private
1241  */
1242 createjs.Loader.IndexedDB.getCursor_ = function(request) {
1243   /// <param type="IDBRequest" name="request"/>
1244   /// <returns type="IDBCursorWithValue"/>
1245   return /** @type {IDBCursor} */ (request.result);
1246 };
1247 
1248 /**
1249  * Attaches an listener to an IDBRequest object (or an IDBTransaction object).
1250  * @param {IDBTransaction|IDBRequest} request
1251  * @param {createjs.Loader.Cache.Listener} listener
1252  * @private
1253  */
1254 createjs.Loader.IndexedDB.setListener_ = function(request, listener) {
1255   /// <param type="IDBRequest" name="request"/>
1256   /// <param type="createjs.Loader.Cache.Listener" name="listener"/>
1257   request.listener_ = listener;
1258 };
1259 
1260 /**
1261  * Returns a listener attached to an IDBRequest object (or an IDBTransaction
1262  * object).
1263  * @param {IDBTransaction|IDBRequest} request
1264  * @return {createjs.Loader.Cache.Listener}
1265  * @private
1266  */
1267 createjs.Loader.IndexedDB.getListener_ = function(request) {
1268   /// <param type="IDBRequest" name="request"/>
1269   /// <returns type="createjs.Loader.Cache.Listener"/>
1270   var listener =
1271       /** @type {createjs.Loader.Cache.Listener} */ (request.listener_);
1272   if (createjs.DEBUG) {
1273     request.listener_ = null;
1274   }
1275   return listener;
1276 };
1277 
1278 /**
1279  * Attaches a key name to an IDBRequest object (or an IDBTransaction object).
1280  * @param {IDBTransaction|IDBRequest} request
1281  * @param {string} key
1282  * @private
1283  */
1284 createjs.Loader.IndexedDB.setKey_ = function(request, key) {
1285   /// <param type="IDBRequest" name="request"/>
1286   /// <param type="string" name="key"/>
1287   request.key_ = key;
1288 };
1289 
1290 /**
1291  * Returns the key name associated with an IDBRequest object (or an
1292  * IDBTransaction object).
1293  * @param {IDBTransaction|IDBRequest} request
1294  * @return {string}
1295  * @private
1296  */
1297 createjs.Loader.IndexedDB.getKey_ = function(request) {
1298   /// <param type="IDBRequest" name="request"/>
1299   /// <returns type="string"/>
1300   return /** @type {string} */ (request.key_);
1301 };
1302 
1303 /**
1304  * Attaches a key name to an IDBTransaction object.
1305  * @param {IDBRequest} request
1306  * @param {number} size
1307  * @private
1308  */
1309 createjs.Loader.IndexedDB.setSize_ = function(request, size) {
1310   /// <param type="IDBTransaction" name="transaction"/>
1311   /// <param type="number" name="size"/>
1312   request.size_ = size;
1313 };
1314 
1315 /**
1316  * Returns the key name associated with an IDBTransaction object.
1317  * @param {IDBRequest} request
1318  * @return {number}
1319  * @private
1320  */
1321 createjs.Loader.IndexedDB.getSize_ = function(request) {
1322   /// <param type="IDBRequest" name="request"/>
1323   /// <returns type="number"/>
1324   return /** @type {number} */ (request.size_);
1325 };
1326 
1327 /**
1328  * Attaches a key name to an IDBTransaction object.
1329  * @param {IDBTransaction} transaction
1330  * @param {*} data
1331  * @private
1332  */
1333 createjs.Loader.IndexedDB.setData_ = function(transaction, data) {
1334   /// <param type="IDBTransaction" name="transaction"/>
1335   /// <param name="key"/>
1336   transaction.data_ = data;
1337 };
1338 
1339 /**
1340  * Returns the key name associated with an IDBTransaction object.
1341  * @param {IDBTransaction} transaction
1342  * @return {string}
1343  * @private
1344  */
1345 createjs.Loader.IndexedDB.getData_ = function(transaction) {
1346   /// <param type="IDBTransaction" name="transaction"/>
1347   /// <returns type="string"/>
1348   return /** @type {string} */ (transaction.data_);
1349 };
1350 
1351 /**
1352  * Creates a database transaction used by this object.
1353  * @param {number} mode
1354  * @return {IDBTransaction}
1355  * @private
1356  */
1357 createjs.Loader.IndexedDB.createTransaction_ = function(mode) {
1358   /// <returns type="IDBObjectStore"/>
1359   createjs.assert(!!createjs.Loader.IndexedDB.database_);
1360   var database = createjs.Loader.IndexedDB.database_
1361   return database.transaction([createjs.CACHE_TABLE],
1362                               mode ? 'readwrite' : 'readonly');
1363 };
1364 
1365 /**
1366  * Returns the database table used by the specified transaction.
1367  * @param {IDBTransaction} transaction
1368  * @return {IDBObjectStore}
1369  * @private
1370  */
1371 createjs.Loader.IndexedDB.getStore_ = function(transaction) {
1372   /// <param type="IDBTransaction" name="transaction"/>
1373   /// <returns type="IDBObjectStore"/>
1374   return transaction.objectStore(createjs.CACHE_TABLE);
1375 };
1376 
1377 /**
1378  * Called when a browser successfully finishes retrieving a cursor from a table.
1379  * @param {Event} event
1380  * @private
1381  */
1382 createjs.Loader.IndexedDB.handleClearSuccess_ = function(event) {
1383   /// <param type="Event" name="event"/>
1384   var request = createjs.Loader.IndexedDB.getRequest_(event);
1385   var cursor = createjs.Loader.IndexedDB.getCursor_(request);
1386   if (!cursor) {
1387     return;
1388   }
1389   var data = cursor.value['data'];
1390   var size = createjs.Loader.IndexedDB.getSize_(request) - data.length;
1391   createjs.Loader.IndexedDB.setSize_(request, size);
1392   if (size > 0) {
1393     cursor['delete']();
1394     cursor['continue']();
1395   }
1396 };
1397 
1398 /**
1399  * Called when there is an error while a browser deletes old items from a table.
1400  * @param {Event} event
1401  * @private
1402  */
1403 createjs.Loader.IndexedDB.handleClearError_ = function(event) {
1404   /// <param type="Event" name="event"/>
1405   var request = createjs.Loader.IndexedDB.getRequest_(event);
1406   var listener = createjs.Loader.IndexedDB.getListener_(request);
1407   listener.handlePutError(createjs.Loader.IndexedDB.getKey_(request));
1408 };
1409 
1410 /**
1411  * Creates a clear request to the table used by this object.
1412  * @param {createjs.Loader.Cache.Listener} listener
1413  * @param {string} key
1414  * @param {number} size
1415  * @private
1416  */
1417 createjs.Loader.IndexedDB.clear_ = function(listener, key, size) {
1418   /// <param type="createjs.Loader.Cache.Listener" name="listener"/>
1419   var transaction = createjs.Loader.IndexedDB.createTransaction_(
1420       createjs.Loader.IndexedDB.WRITE_);
1421   var store = createjs.Loader.IndexedDB.getStore_(transaction);
1422   var request = store.index('time').openCursor();
1423   request.onerror = createjs.Loader.IndexedDB.handleClearError_;
1424   request.onsuccess = createjs.Loader.IndexedDB.handleClearSuccess_;
1425   createjs.Loader.IndexedDB.setListener_(request, listener);
1426   createjs.Loader.IndexedDB.setKey_(transaction, key);
1427   createjs.Loader.IndexedDB.setSize_(request, size);
1428 };
1429 
1430 /**
1431  * Called when a browser successfully finishes writing a key-value pair.
1432  * @param {Event} event
1433  * @private
1434  */
1435 createjs.Loader.IndexedDB.handlePutComplete_ = function(event) {
1436   /// <param type="Event" name="event"/>
1437   var transaction = createjs.Loader.IndexedDB.getTransaction_(event);
1438   var listener = createjs.Loader.IndexedDB.getListener_(transaction);
1439   listener.handlePutSuccess(createjs.Loader.IndexedDB.getKey_(transaction));
1440 };
1441 
1442 /**
1443  * Called when a browser aborts a database transaction.
1444  * @param {Event} event
1445  * @private
1446  */
1447 createjs.Loader.IndexedDB.handlePutAbort_ = function(event) {
1448   /// <param type="Event" name="event"/>
1449   /// <var type="Error" name="error"/>
1450   var transaction = createjs.Loader.IndexedDB.getTransaction_(event);
1451   var listener = createjs.Loader.IndexedDB.getListener_(transaction);
1452   var key = createjs.Loader.IndexedDB.getKey_(transaction);
1453   var error = event.target.error;
1454   if (error.name != 'QuotaExceededError') {
1455     listener.handlePutError(key);
1456   } else {
1457     // Round up the data size by a 4MB boundary and remove this calculated size
1458     // of data from the cache. Also retry putting the data to the cache after
1459     // the removal transaction. (The IndexedDB API serializes write transactions
1460     // and these transactions are executed sequentially.)
1461     var UNIT = 1 << 12;
1462     var data = createjs.Loader.IndexedDB.getData_(transaction);
1463     var size = ((data.length + UNIT - 1) >> 12) << 12;
1464     createjs.Loader.IndexedDB.clear_(listener, key, size);
1465     createjs.Loader.IndexedDB.put_(listener, key, data);
1466   }
1467 };
1468 
1469 /**
1470  * Creates a put request to the table used by this object.
1471  * @param {createjs.Loader.Cache.Listener} listener
1472  * @param {string} key
1473  * @param {*} data
1474  * @private
1475  */
1476 createjs.Loader.IndexedDB.put_ = function(listener, key, data) {
1477   /// <param type="createjs.Loader.Cache.Listener" name="listener"/>
1478   /// <param type="string" name="key"/>
1479   /// <param name="data"/>
1480   var transaction = createjs.Loader.IndexedDB.createTransaction_(
1481       createjs.Loader.IndexedDB.WRITE_);
1482   transaction.oncomplete = createjs.Loader.IndexedDB.handlePutComplete_;
1483   transaction.onabort = createjs.Loader.IndexedDB.handlePutAbort_;
1484   createjs.Loader.IndexedDB.setListener_(transaction, listener);
1485   createjs.Loader.IndexedDB.setKey_(transaction, key);
1486   createjs.Loader.IndexedDB.setData_(transaction, data);
1487   var value = {
1488     'time': Date.now(),
1489     'data': data
1490   };
1491   createjs.Loader.IndexedDB.getStore_(transaction).put(value, key);
1492 };
1493 
1494 /**
1495  * Called when a browser successfully finishes reading a key-value pair from a
1496  * table.
1497  * @param {Event} event
1498  * @private
1499  */
1500 createjs.Loader.IndexedDB.handleGetSuccess_ = function(event) {
1501   /// <param type="Event" name="event"/>
1502   var request = createjs.Loader.IndexedDB.getRequest_(event);
1503   var listener = createjs.Loader.IndexedDB.getListener_(request);
1504   var result = /** @type {Object} */ (request.result);
1505   if (!result || !result['data']) {
1506     listener.handleGetError(createjs.Loader.IndexedDB.getKey_(request));
1507     return;
1508   }
1509   var data = result['data'];
1510   listener.handleGetSuccess(createjs.Loader.IndexedDB.getKey_(request), data);
1511 };
1512 
1513 /**
1514  * Called when there is an error while a browser reads a key-value pair from a
1515  * table.
1516  * @param {Event} event
1517  * @private
1518  */
1519 createjs.Loader.IndexedDB.handleGetError_ = function(event) {
1520   /// <param type="Event" name="event"/>
1521   var request = createjs.Loader.IndexedDB.getRequest_(event);
1522   var listener = createjs.Loader.IndexedDB.getListener_(request);
1523   listener.handleGetError(createjs.Loader.IndexedDB.getKey_(request));
1524 };
1525 
1526 /**
1527  * Creates a get request to the table used by this object.
1528  * @param {createjs.Loader.Cache.Listener} listener
1529  * @param {string} key
1530  * @private
1531  */
1532 createjs.Loader.IndexedDB.get_ = function(listener, key) {
1533   /// <param type="createjs.Loader.Cache.Listener" name="listener"/>
1534   /// <param type="string" name="key"/>
1535   var transaction = createjs.Loader.IndexedDB.createTransaction_(
1536       createjs.Loader.IndexedDB.READ_);
1537   var request = createjs.Loader.IndexedDB.getStore_(transaction).get(key);
1538   request.onsuccess = createjs.Loader.IndexedDB.handleGetSuccess_;
1539   request.onerror = createjs.Loader.IndexedDB.handleGetError_;
1540   createjs.Loader.IndexedDB.setListener_(request, listener);
1541   createjs.Loader.IndexedDB.setKey_(request, key);
1542 };
1543 
1544 /**
1545  * Called when a browser successfully finishes opening a database.
1546  * @param {Event} event
1547  * @private
1548  */
1549 createjs.Loader.IndexedDB.handleOpenSuccess_ = function(event) {
1550   /// <param type="Event" name="event"/>
1551   var request = createjs.Loader.IndexedDB.getRequest_(event);
1552   createjs.Loader.IndexedDB.database_ =
1553       /** @type {IDBDatabase} */ (request.result);
1554 
1555   // Send a get request to get the value for the key.
1556   createjs.Loader.IndexedDB.get_(
1557       createjs.Loader.IndexedDB.getListener_(request),
1558       createjs.Loader.IndexedDB.getKey_(request));
1559 };
1560 
1561 /**
1562  * Called when there is an error while a browser opens a database.
1563  * @param {Event} event
1564  * @private
1565  */
1566 createjs.Loader.IndexedDB.handleOpenError_ = function(event) {
1567   /// <param type="Event" name="event"/>
1568   createjs.Loader.instance_ = null;
1569   createjs.Loader.IndexedDB.handleGetError_(event);
1570 };
1571 
1572 /**
1573  * Called when the specified database needs an update.
1574  * @param {Event} event
1575  * @private
1576  */
1577 createjs.Loader.IndexedDB.handleUpgradeNeeded_ = function(event) {
1578   /// <param type="Event" name="event"/>
1579   var request = createjs.Loader.IndexedDB.getRequest_(event);
1580   var database = request.result;
1581   if (database.objectStoreNames.contains(createjs.CACHE_TABLE)) {
1582     database.deleteObjectStore(createjs.CACHE_TABLE);
1583   }
1584   var store = database.createObjectStore(createjs.CACHE_TABLE);
1585   store.createIndex('time', 'time', { 'unique': false });
1586 };
1587 
1588 /**
1589  * Creates an open request to open the database used by this object.
1590  * @param {createjs.Loader.Cache.Listener} listener
1591  * @param {string} key
1592  * @param {number} version
1593  * @return {IDBRequest}
1594  * @private
1595  */
1596 createjs.Loader.IndexedDB.open_ = function(listener, key, version) {
1597   /// <param type="createjs.Loader.Cache.Listener" name="listener"/>
1598   /// <param type="string" name="key"/>
1599   /// <param type="number" name="version"/>
1600   /// <returns type="IDBRequest"/>
1601   createjs.assert(!!createjs.global.indexedDB);
1602   var request = createjs.global.indexedDB.open(
1603       createjs.CACHE_DATABASE, version);
1604   if (request) {
1605     request.onerror = createjs.Loader.IndexedDB.handleOpenError_;
1606     request.onsuccess = createjs.Loader.IndexedDB.handleOpenSuccess_;
1607     request.onupgradeneeded = createjs.Loader.IndexedDB.handleUpgradeNeeded_;
1608     createjs.Loader.IndexedDB.setListener_(request, listener);
1609     createjs.Loader.IndexedDB.setKey_(request, key);
1610   }
1611   return request;
1612 };
1613 
1614 /**
1615  * Called when a browser finishes deleting a database either successfully or
1616  * not.
1617  * @param {Event} event
1618  * @private
1619  */
1620 createjs.Loader.IndexedDB.handleDelete_ = function(event) {
1621   /// <param type="Event" name="event"/>
1622   // Create and open a new cache database.
1623   var request = createjs.Loader.IndexedDB.getRequest_(event);
1624   createjs.Loader.IndexedDB.open_(
1625       createjs.Loader.IndexedDB.getListener_(request),
1626       createjs.Loader.IndexedDB.getKey_(request),
1627       1);
1628 };
1629 
1630 /**
1631  * Creates a connection to the cache database. If this method is called with a
1632  * non-positive version number, it deletes the cache database and re-creates
1633  * another one.
1634  * @param {createjs.Loader.Cache.Listener} listener
1635  * @param {string} key
1636  * @param {number} version
1637  * @return {IDBRequest}
1638  * @private
1639  */
1640 createjs.Loader.IndexedDB.create_ = function(listener, key, version) {
1641   /// <param type="createjs.Loader.Cache.Listener" name="listener"/>
1642   /// <param type="string" name="key"/>
1643   /// <param type="number" name="version"/>
1644   /// <returns type="IDBRequest"/>
1645   // Send a delete request to wait until a browser finishes deleting the
1646   // database. (Chrome does not allow sending another request while it processes
1647   // a delete request.)
1648   if (version <= 0) {
1649     var request =
1650         createjs.global.indexedDB.deleteDatabase(createjs.CACHE_DATABASE);
1651     if (request) {
1652       request.onerror = createjs.Loader.IndexedDB.handleDelete_;
1653       request.onsuccess = createjs.Loader.IndexedDB.handleDelete_;
1654       createjs.Loader.IndexedDB.setListener_(request, listener);
1655       createjs.Loader.IndexedDB.setKey_(request, key);
1656     }
1657     return request;
1658   }
1659   return createjs.Loader.IndexedDB.open_(listener, key, version);
1660 };
1661 
1662 /**
1663  * Called when a browser finishes deleting the cache database.
1664  * @param {Event} event
1665  * @private
1666  */
1667 createjs.Loader.IndexedDB.handleReset_ = function(event) {
1668   /// <param type="Event" name="event"/>
1669 };
1670 
1671 /** @override */
1672 createjs.Loader.IndexedDB.prototype.isOpened = function() {
1673   /// <returns type="boolean"/>
1674   return !!createjs.Loader.IndexedDB.database_;
1675 };
1676 
1677 /** @override */
1678 createjs.Loader.IndexedDB.prototype.set = function(listener, key, value) {
1679   /// <param type="createjs.Loader.Cache.Listener" name="listener"/>
1680   /// <param type="string" name="key"/>
1681   /// <param name="value"/>
1682   createjs.Loader.IndexedDB.put_(listener, key, value);
1683 };
1684 
1685 /** @override */
1686 createjs.Loader.IndexedDB.prototype.get = function(listener, key) {
1687   /// <param type="createjs.Loader.Cache.Listener" name="listener"/>
1688   /// <param type="string" name="key"/>
1689   /// <returns type="boolean"/>
1690   if (!this.isOpened()) {
1691     // Retrieve the version number provided by an application. When it is a
1692     // negative number, delete the existing database and re-create a new one.
1693     // (A negative version number represents a non-persistent database.)
1694     var version = createjs.Config.getCacheVersion();
1695     var request = createjs.Loader.IndexedDB.create_(listener, key, version);
1696     if (!request) {
1697       createjs.Loader.instance_ = null;
1698       return false;
1699     }
1700   } else {
1701     createjs.Loader.IndexedDB.get_(listener, key);
1702   }
1703   return true;
1704 };
1705 
1706 /** @override */
1707 createjs.Loader.IndexedDB.prototype.reset = function() {
1708   // Close the database connection before deleting the cache database. (Chrome
1709   // cannot delete a database while there is an open connection to it.)
1710   var database = createjs.Loader.IndexedDB.database_;
1711   if (database) {
1712     database.close();
1713     createjs.Loader.IndexedDB.database_ = null;
1714   }
1715   var request =
1716       createjs.global.indexedDB.deleteDatabase(createjs.CACHE_DATABASE);
1717   if (request) {
1718     request.onerror = createjs.Loader.IndexedDB.handleReset_;
1719     request.onsuccess = createjs.Loader.IndexedDB.handleReset_;
1720   }
1721 };
1722 
1723 /**
1724  * The global instance that provides the createjs.Loader.Cache interface.
1725  * @type {createjs.Loader.Cache}
1726  * @private
1727  */
1728 createjs.Loader.instance_ = null;
1729 
1730 /**
1731  * Whether the 'createjs.Loader.instance_' variable has been initialized.
1732  * @type {boolean}
1733  * @private
1734  */
1735 createjs.Loader.initialized_ = false;
1736 
1737 /**
1738  * Retrieves an available createjs.Loader.Cache interface. This method creates
1739  * an object that implements the createjs.Loader.Cache interface and returns it.
1740  * @return {createjs.Loader.Cache}
1741  */
1742 createjs.Loader.getCache = function() {
1743   /// <returns type="createjs.Loader.Cache"/>
1744   if (!createjs.Loader.initialized_) {
1745     createjs.Loader.initialized_ = true;
1746     createjs.Loader.instance_ = createjs.Loader.IndexedDB.getInstance();
1747   }
1748   return createjs.Loader.instance_;
1749 };
1750 
1751 /**
1752  * Clears all files in the cache and resets its state.
1753  * @const
1754  */
1755 createjs.Loader.resetCache = function() {
1756   if (createjs.USE_CACHE) {
1757     if (createjs.Loader.instance_) {
1758       createjs.Loader.instance_.reset();
1759       createjs.Loader.instance_ = null;
1760     }
1761     createjs.Loader.initialized_ = false;
1762   }
1763 };
1764