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