123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- // Copyright 2008 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview Image loader utility class. Useful when an application needs
- * to preload multiple images, for example so they can be sized.
- *
- * @author attila@google.com (Attila Bodis)
- */
- goog.provide('goog.net.ImageLoader');
- goog.require('goog.array');
- goog.require('goog.dom');
- goog.require('goog.dom.TagName');
- goog.require('goog.events.EventHandler');
- goog.require('goog.events.EventTarget');
- goog.require('goog.events.EventType');
- goog.require('goog.net.EventType');
- goog.require('goog.object');
- goog.require('goog.userAgent');
- /**
- * Image loader utility class. Raises a {@link goog.events.EventType.LOAD}
- * event for each image loaded, with an {@link Image} object as the target of
- * the event, normalized to have {@code naturalHeight} and {@code naturalWidth}
- * attributes.
- *
- * To use this class, run:
- *
- * <pre>
- * var imageLoader = new goog.net.ImageLoader();
- * goog.events.listen(imageLoader, goog.net.EventType.COMPLETE,
- * function(e) { ... });
- * imageLoader.addImage("image_id", "http://path/to/image.gif");
- * imageLoader.start();
- * </pre>
- *
- * The start() method must be called to start image loading. Images can be
- * added and removed after loading has started, but only those images added
- * before start() was called will be loaded until start() is called again.
- * A goog.net.EventType.COMPLETE event will be dispatched only once all
- * outstanding images have completed uploading.
- *
- * @param {Element=} opt_parent An optional parent element whose document object
- * should be used to load images.
- * @constructor
- * @extends {goog.events.EventTarget}
- * @final
- */
- goog.net.ImageLoader = function(opt_parent) {
- goog.events.EventTarget.call(this);
- /**
- * Map of image IDs to their request including their image src, used to keep
- * track of the images to load. Once images have started loading, they're
- * removed from this map.
- * @type {!Object<!goog.net.ImageLoader.ImageRequest_>}
- * @private
- */
- this.imageIdToRequestMap_ = {};
- /**
- * Map of image IDs to their image element, used only for images that are in
- * the process of loading. Used to clean-up event listeners and to know
- * when we've completed loading images.
- * @type {!Object<string, !Element>}
- * @private
- */
- this.imageIdToImageMap_ = {};
- /**
- * Event handler object, used to keep track of onload and onreadystatechange
- * listeners.
- * @type {!goog.events.EventHandler<!goog.net.ImageLoader>}
- * @private
- */
- this.handler_ = new goog.events.EventHandler(this);
- /**
- * The parent element whose document object will be used to load images.
- * Useful if you want to load the images from a window other than the current
- * window in order to control the Referer header sent when the image is
- * loaded.
- * @type {Element|undefined}
- * @private
- */
- this.parent_ = opt_parent;
- };
- goog.inherits(goog.net.ImageLoader, goog.events.EventTarget);
- /**
- * The type of image request to dispatch, if this is a CORS-enabled image
- * request. CORS-enabled images can be reused in canvas elements without them
- * being tainted. The server hosting the image should include the appropriate
- * CORS header.
- * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image
- * @enum {string}
- */
- goog.net.ImageLoader.CorsRequestType = {
- ANONYMOUS: 'anonymous',
- USE_CREDENTIALS: 'use-credentials'
- };
- /**
- * Describes a request for an image. This includes its URL and its CORS-request
- * type, if any.
- * @typedef {{
- * src: string,
- * corsRequestType: ?goog.net.ImageLoader.CorsRequestType
- * }}
- * @private
- */
- goog.net.ImageLoader.ImageRequest_;
- /**
- * An array of event types to listen to on images. This is browser dependent.
- *
- * For IE 10 and below, Internet Explorer doesn't reliably raise LOAD events
- * on images, so we must use READY_STATE_CHANGE. Since the image is cached
- * locally, IE won't fire the LOAD event while the onreadystate event is fired
- * always. On the other hand, the ERROR event is always fired whenever the image
- * is not loaded successfully no matter whether it's cached or not.
- *
- * In IE 11, onreadystatechange is removed and replaced with onload:
- *
- * http://msdn.microsoft.com/en-us/library/ie/ms536957(v=vs.85).aspx
- * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx
- *
- * @type {!Array<string>}
- * @private
- */
- goog.net.ImageLoader.IMAGE_LOAD_EVENTS_ = [
- goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('11') ?
- goog.net.EventType.READY_STATE_CHANGE :
- goog.events.EventType.LOAD,
- goog.net.EventType.ABORT, goog.net.EventType.ERROR
- ];
- /**
- * Adds an image to the image loader, and associates it with the given ID
- * string. If an image with that ID already exists, it is silently replaced.
- * When the image in question is loaded, the target of the LOAD event will be
- * an {@code Image} object with {@code id} and {@code src} attributes based on
- * these arguments.
- * @param {string} id The ID of the image to load.
- * @param {string|Image} image Either the source URL of the image or the HTML
- * image element itself (or any object with a {@code src} property, really).
- * @param {!goog.net.ImageLoader.CorsRequestType=} opt_corsRequestType The type
- * of CORS request to use, if any.
- */
- goog.net.ImageLoader.prototype.addImage = function(
- id, image, opt_corsRequestType) {
- var src = goog.isString(image) ? image : image.src;
- if (src) {
- // For now, we just store the source URL for the image.
- this.imageIdToRequestMap_[id] = {
- src: src,
- corsRequestType: goog.isDef(opt_corsRequestType) ? opt_corsRequestType :
- null
- };
- }
- };
- /**
- * Removes the image associated with the given ID string from the image loader.
- * If the image was previously loading, removes any listeners for its events
- * and dispatches a COMPLETE event if all remaining images have now completed.
- * @param {string} id The ID of the image to remove.
- */
- goog.net.ImageLoader.prototype.removeImage = function(id) {
- delete this.imageIdToRequestMap_[id];
- var image = this.imageIdToImageMap_[id];
- if (image) {
- delete this.imageIdToImageMap_[id];
- // Stop listening for events on the image.
- this.handler_.unlisten(
- image, goog.net.ImageLoader.IMAGE_LOAD_EVENTS_, this.onNetworkEvent_);
- // If this was the last image, raise a COMPLETE event.
- if (goog.object.isEmpty(this.imageIdToImageMap_) &&
- goog.object.isEmpty(this.imageIdToRequestMap_)) {
- this.dispatchEvent(goog.net.EventType.COMPLETE);
- }
- }
- };
- /**
- * Starts loading all images in the image loader in parallel. Raises a LOAD
- * event each time an image finishes loading, and a COMPLETE event after all
- * images have finished loading.
- */
- goog.net.ImageLoader.prototype.start = function() {
- // Iterate over the keys, rather than the full object, to essentially clone
- // the initial queued images in case any event handlers decide to add more
- // images before this loop has finished executing.
- var imageIdToRequestMap = this.imageIdToRequestMap_;
- goog.array.forEach(goog.object.getKeys(imageIdToRequestMap), function(id) {
- var imageRequest = imageIdToRequestMap[id];
- if (imageRequest) {
- delete imageIdToRequestMap[id];
- this.loadImage_(imageRequest, id);
- }
- }, this);
- };
- /**
- * Creates an {@code Image} object with the specified ID and source URL, and
- * listens for network events raised as the image is loaded.
- * @param {!goog.net.ImageLoader.ImageRequest_} imageRequest The request data.
- * @param {string} id The unique ID of the image to load.
- * @private
- */
- goog.net.ImageLoader.prototype.loadImage_ = function(imageRequest, id) {
- if (this.isDisposed()) {
- // When loading an image in IE7 (and maybe IE8), the error handler
- // may fire before we yield JS control. If the error handler
- // dispose the ImageLoader, this method will throw exception.
- return;
- }
- /** @type {!HTMLImageElement} */
- var image;
- if (this.parent_) {
- var dom = goog.dom.getDomHelper(this.parent_);
- image = dom.createDom(goog.dom.TagName.IMG);
- } else {
- image = new Image();
- }
- if (imageRequest.corsRequestType) {
- image.crossOrigin = imageRequest.corsRequestType;
- }
- this.handler_.listen(
- image, goog.net.ImageLoader.IMAGE_LOAD_EVENTS_, this.onNetworkEvent_);
- this.imageIdToImageMap_[id] = image;
- image.id = id;
- image.src = imageRequest.src;
- };
- /**
- * Handles net events (READY_STATE_CHANGE, LOAD, ABORT, and ERROR).
- * @param {goog.events.Event} evt The network event to handle.
- * @private
- */
- goog.net.ImageLoader.prototype.onNetworkEvent_ = function(evt) {
- var image = /** @type {Element} */ (evt.currentTarget);
- if (!image) {
- return;
- }
- if (evt.type == goog.net.EventType.READY_STATE_CHANGE) {
- // This implies that the user agent is IE; see loadImage_().
- // Noe that this block is used to check whether the image is ready to
- // dispatch the COMPLETE event.
- if (image.readyState == goog.net.EventType.COMPLETE) {
- // This is the IE equivalent of a LOAD event.
- evt.type = goog.events.EventType.LOAD;
- } else {
- // This may imply that the load failed.
- // Note that the image has only the following states:
- // * uninitialized
- // * loading
- // * complete
- // When the ERROR or the ABORT event is fired, the readyState
- // will be either uninitialized or loading and we'd ignore those states
- // since they will be handled separately (eg: evt.type = 'ERROR').
- // Notes from MSDN : The states through which an object passes are
- // determined by that object. An object can skip certain states
- // (for example, interactive) if the state does not apply to that object.
- // see http://msdn.microsoft.com/en-us/library/ms534359(VS.85).aspx
- // The image is not loaded, ignore.
- return;
- }
- }
- // Add natural width/height properties for non-Gecko browsers.
- if (typeof image.naturalWidth == 'undefined') {
- if (evt.type == goog.events.EventType.LOAD) {
- image.naturalWidth = image.width;
- image.naturalHeight = image.height;
- } else {
- // This implies that the image fails to be loaded.
- image.naturalWidth = 0;
- image.naturalHeight = 0;
- }
- }
- // Redispatch the event on behalf of the image. Note that the external
- // listener may dispose this instance.
- this.dispatchEvent({type: evt.type, target: image});
- if (this.isDisposed()) {
- // If instance was disposed by listener, exit this function.
- return;
- }
- this.removeImage(image.id);
- };
- /** @override */
- goog.net.ImageLoader.prototype.disposeInternal = function() {
- delete this.imageIdToRequestMap_;
- delete this.imageIdToImageMap_;
- goog.dispose(this.handler_);
- goog.net.ImageLoader.superClass_.disposeInternal.call(this);
- };
|