Path: blob/trunk/third_party/closure/goog/net/imageloader.js
2868 views
// Copyright 2008 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Image loader utility class. Useful when an application needs16* to preload multiple images, for example so they can be sized.17*18* @author [email protected] (Attila Bodis)19*/2021goog.provide('goog.net.ImageLoader');2223goog.require('goog.array');24goog.require('goog.dom');25goog.require('goog.dom.TagName');26goog.require('goog.events.EventHandler');27goog.require('goog.events.EventTarget');28goog.require('goog.events.EventType');29goog.require('goog.net.EventType');30goog.require('goog.object');31goog.require('goog.userAgent');32333435/**36* Image loader utility class. Raises a {@link goog.events.EventType.LOAD}37* event for each image loaded, with an {@link Image} object as the target of38* the event, normalized to have {@code naturalHeight} and {@code naturalWidth}39* attributes.40*41* To use this class, run:42*43* <pre>44* var imageLoader = new goog.net.ImageLoader();45* goog.events.listen(imageLoader, goog.net.EventType.COMPLETE,46* function(e) { ... });47* imageLoader.addImage("image_id", "http://path/to/image.gif");48* imageLoader.start();49* </pre>50*51* The start() method must be called to start image loading. Images can be52* added and removed after loading has started, but only those images added53* before start() was called will be loaded until start() is called again.54* A goog.net.EventType.COMPLETE event will be dispatched only once all55* outstanding images have completed uploading.56*57* @param {Element=} opt_parent An optional parent element whose document object58* should be used to load images.59* @constructor60* @extends {goog.events.EventTarget}61* @final62*/63goog.net.ImageLoader = function(opt_parent) {64goog.events.EventTarget.call(this);6566/**67* Map of image IDs to their request including their image src, used to keep68* track of the images to load. Once images have started loading, they're69* removed from this map.70* @type {!Object<!goog.net.ImageLoader.ImageRequest_>}71* @private72*/73this.imageIdToRequestMap_ = {};7475/**76* Map of image IDs to their image element, used only for images that are in77* the process of loading. Used to clean-up event listeners and to know78* when we've completed loading images.79* @type {!Object<string, !Element>}80* @private81*/82this.imageIdToImageMap_ = {};8384/**85* Event handler object, used to keep track of onload and onreadystatechange86* listeners.87* @type {!goog.events.EventHandler<!goog.net.ImageLoader>}88* @private89*/90this.handler_ = new goog.events.EventHandler(this);9192/**93* The parent element whose document object will be used to load images.94* Useful if you want to load the images from a window other than the current95* window in order to control the Referer header sent when the image is96* loaded.97* @type {Element|undefined}98* @private99*/100this.parent_ = opt_parent;101};102goog.inherits(goog.net.ImageLoader, goog.events.EventTarget);103104105/**106* The type of image request to dispatch, if this is a CORS-enabled image107* request. CORS-enabled images can be reused in canvas elements without them108* being tainted. The server hosting the image should include the appropriate109* CORS header.110* @see https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image111* @enum {string}112*/113goog.net.ImageLoader.CorsRequestType = {114ANONYMOUS: 'anonymous',115USE_CREDENTIALS: 'use-credentials'116};117118119/**120* Describes a request for an image. This includes its URL and its CORS-request121* type, if any.122* @typedef {{123* src: string,124* corsRequestType: ?goog.net.ImageLoader.CorsRequestType125* }}126* @private127*/128goog.net.ImageLoader.ImageRequest_;129130131/**132* An array of event types to listen to on images. This is browser dependent.133*134* For IE 10 and below, Internet Explorer doesn't reliably raise LOAD events135* on images, so we must use READY_STATE_CHANGE. Since the image is cached136* locally, IE won't fire the LOAD event while the onreadystate event is fired137* always. On the other hand, the ERROR event is always fired whenever the image138* is not loaded successfully no matter whether it's cached or not.139*140* In IE 11, onreadystatechange is removed and replaced with onload:141*142* http://msdn.microsoft.com/en-us/library/ie/ms536957(v=vs.85).aspx143* http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx144*145* @type {!Array<string>}146* @private147*/148goog.net.ImageLoader.IMAGE_LOAD_EVENTS_ = [149goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('11') ?150goog.net.EventType.READY_STATE_CHANGE :151goog.events.EventType.LOAD,152goog.net.EventType.ABORT, goog.net.EventType.ERROR153];154155156/**157* Adds an image to the image loader, and associates it with the given ID158* string. If an image with that ID already exists, it is silently replaced.159* When the image in question is loaded, the target of the LOAD event will be160* an {@code Image} object with {@code id} and {@code src} attributes based on161* these arguments.162* @param {string} id The ID of the image to load.163* @param {string|Image} image Either the source URL of the image or the HTML164* image element itself (or any object with a {@code src} property, really).165* @param {!goog.net.ImageLoader.CorsRequestType=} opt_corsRequestType The type166* of CORS request to use, if any.167*/168goog.net.ImageLoader.prototype.addImage = function(169id, image, opt_corsRequestType) {170var src = goog.isString(image) ? image : image.src;171if (src) {172// For now, we just store the source URL for the image.173this.imageIdToRequestMap_[id] = {174src: src,175corsRequestType: goog.isDef(opt_corsRequestType) ? opt_corsRequestType :176null177};178}179};180181182/**183* Removes the image associated with the given ID string from the image loader.184* If the image was previously loading, removes any listeners for its events185* and dispatches a COMPLETE event if all remaining images have now completed.186* @param {string} id The ID of the image to remove.187*/188goog.net.ImageLoader.prototype.removeImage = function(id) {189delete this.imageIdToRequestMap_[id];190191var image = this.imageIdToImageMap_[id];192if (image) {193delete this.imageIdToImageMap_[id];194195// Stop listening for events on the image.196this.handler_.unlisten(197image, goog.net.ImageLoader.IMAGE_LOAD_EVENTS_, this.onNetworkEvent_);198199// If this was the last image, raise a COMPLETE event.200if (goog.object.isEmpty(this.imageIdToImageMap_) &&201goog.object.isEmpty(this.imageIdToRequestMap_)) {202this.dispatchEvent(goog.net.EventType.COMPLETE);203}204}205};206207208/**209* Starts loading all images in the image loader in parallel. Raises a LOAD210* event each time an image finishes loading, and a COMPLETE event after all211* images have finished loading.212*/213goog.net.ImageLoader.prototype.start = function() {214// Iterate over the keys, rather than the full object, to essentially clone215// the initial queued images in case any event handlers decide to add more216// images before this loop has finished executing.217var imageIdToRequestMap = this.imageIdToRequestMap_;218goog.array.forEach(goog.object.getKeys(imageIdToRequestMap), function(id) {219var imageRequest = imageIdToRequestMap[id];220if (imageRequest) {221delete imageIdToRequestMap[id];222this.loadImage_(imageRequest, id);223}224}, this);225};226227228/**229* Creates an {@code Image} object with the specified ID and source URL, and230* listens for network events raised as the image is loaded.231* @param {!goog.net.ImageLoader.ImageRequest_} imageRequest The request data.232* @param {string} id The unique ID of the image to load.233* @private234*/235goog.net.ImageLoader.prototype.loadImage_ = function(imageRequest, id) {236if (this.isDisposed()) {237// When loading an image in IE7 (and maybe IE8), the error handler238// may fire before we yield JS control. If the error handler239// dispose the ImageLoader, this method will throw exception.240return;241}242243var image;244if (this.parent_) {245var dom = goog.dom.getDomHelper(this.parent_);246image = dom.createDom(goog.dom.TagName.IMG);247} else {248image = new Image();249}250251if (imageRequest.corsRequestType) {252image.crossOrigin = imageRequest.corsRequestType;253}254255this.handler_.listen(256image, goog.net.ImageLoader.IMAGE_LOAD_EVENTS_, this.onNetworkEvent_);257this.imageIdToImageMap_[id] = image;258259image.id = id;260image.src = imageRequest.src;261};262263264/**265* Handles net events (READY_STATE_CHANGE, LOAD, ABORT, and ERROR).266* @param {goog.events.Event} evt The network event to handle.267* @private268*/269goog.net.ImageLoader.prototype.onNetworkEvent_ = function(evt) {270var image = /** @type {Element} */ (evt.currentTarget);271272if (!image) {273return;274}275276if (evt.type == goog.net.EventType.READY_STATE_CHANGE) {277// This implies that the user agent is IE; see loadImage_().278// Noe that this block is used to check whether the image is ready to279// dispatch the COMPLETE event.280if (image.readyState == goog.net.EventType.COMPLETE) {281// This is the IE equivalent of a LOAD event.282evt.type = goog.events.EventType.LOAD;283} else {284// This may imply that the load failed.285// Note that the image has only the following states:286// * uninitialized287// * loading288// * complete289// When the ERROR or the ABORT event is fired, the readyState290// will be either uninitialized or loading and we'd ignore those states291// since they will be handled separately (eg: evt.type = 'ERROR').292293// Notes from MSDN : The states through which an object passes are294// determined by that object. An object can skip certain states295// (for example, interactive) if the state does not apply to that object.296// see http://msdn.microsoft.com/en-us/library/ms534359(VS.85).aspx297298// The image is not loaded, ignore.299return;300}301}302303// Add natural width/height properties for non-Gecko browsers.304if (typeof image.naturalWidth == 'undefined') {305if (evt.type == goog.events.EventType.LOAD) {306image.naturalWidth = image.width;307image.naturalHeight = image.height;308} else {309// This implies that the image fails to be loaded.310image.naturalWidth = 0;311image.naturalHeight = 0;312}313}314315// Redispatch the event on behalf of the image. Note that the external316// listener may dispose this instance.317this.dispatchEvent({type: evt.type, target: image});318319if (this.isDisposed()) {320// If instance was disposed by listener, exit this function.321return;322}323324this.removeImage(image.id);325};326327328/** @override */329goog.net.ImageLoader.prototype.disposeInternal = function() {330delete this.imageIdToRequestMap_;331delete this.imageIdToImageMap_;332goog.dispose(this.handler_);333334goog.net.ImageLoader.superClass_.disposeInternal.call(this);335};336337338