Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/net/imageloader.js
2868 views
1
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Image loader utility class. Useful when an application needs
17
* to preload multiple images, for example so they can be sized.
18
*
19
* @author [email protected] (Attila Bodis)
20
*/
21
22
goog.provide('goog.net.ImageLoader');
23
24
goog.require('goog.array');
25
goog.require('goog.dom');
26
goog.require('goog.dom.TagName');
27
goog.require('goog.events.EventHandler');
28
goog.require('goog.events.EventTarget');
29
goog.require('goog.events.EventType');
30
goog.require('goog.net.EventType');
31
goog.require('goog.object');
32
goog.require('goog.userAgent');
33
34
35
36
/**
37
* Image loader utility class. Raises a {@link goog.events.EventType.LOAD}
38
* event for each image loaded, with an {@link Image} object as the target of
39
* the event, normalized to have {@code naturalHeight} and {@code naturalWidth}
40
* attributes.
41
*
42
* To use this class, run:
43
*
44
* <pre>
45
* var imageLoader = new goog.net.ImageLoader();
46
* goog.events.listen(imageLoader, goog.net.EventType.COMPLETE,
47
* function(e) { ... });
48
* imageLoader.addImage("image_id", "http://path/to/image.gif");
49
* imageLoader.start();
50
* </pre>
51
*
52
* The start() method must be called to start image loading. Images can be
53
* added and removed after loading has started, but only those images added
54
* before start() was called will be loaded until start() is called again.
55
* A goog.net.EventType.COMPLETE event will be dispatched only once all
56
* outstanding images have completed uploading.
57
*
58
* @param {Element=} opt_parent An optional parent element whose document object
59
* should be used to load images.
60
* @constructor
61
* @extends {goog.events.EventTarget}
62
* @final
63
*/
64
goog.net.ImageLoader = function(opt_parent) {
65
goog.events.EventTarget.call(this);
66
67
/**
68
* Map of image IDs to their request including their image src, used to keep
69
* track of the images to load. Once images have started loading, they're
70
* removed from this map.
71
* @type {!Object<!goog.net.ImageLoader.ImageRequest_>}
72
* @private
73
*/
74
this.imageIdToRequestMap_ = {};
75
76
/**
77
* Map of image IDs to their image element, used only for images that are in
78
* the process of loading. Used to clean-up event listeners and to know
79
* when we've completed loading images.
80
* @type {!Object<string, !Element>}
81
* @private
82
*/
83
this.imageIdToImageMap_ = {};
84
85
/**
86
* Event handler object, used to keep track of onload and onreadystatechange
87
* listeners.
88
* @type {!goog.events.EventHandler<!goog.net.ImageLoader>}
89
* @private
90
*/
91
this.handler_ = new goog.events.EventHandler(this);
92
93
/**
94
* The parent element whose document object will be used to load images.
95
* Useful if you want to load the images from a window other than the current
96
* window in order to control the Referer header sent when the image is
97
* loaded.
98
* @type {Element|undefined}
99
* @private
100
*/
101
this.parent_ = opt_parent;
102
};
103
goog.inherits(goog.net.ImageLoader, goog.events.EventTarget);
104
105
106
/**
107
* The type of image request to dispatch, if this is a CORS-enabled image
108
* request. CORS-enabled images can be reused in canvas elements without them
109
* being tainted. The server hosting the image should include the appropriate
110
* CORS header.
111
* @see https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image
112
* @enum {string}
113
*/
114
goog.net.ImageLoader.CorsRequestType = {
115
ANONYMOUS: 'anonymous',
116
USE_CREDENTIALS: 'use-credentials'
117
};
118
119
120
/**
121
* Describes a request for an image. This includes its URL and its CORS-request
122
* type, if any.
123
* @typedef {{
124
* src: string,
125
* corsRequestType: ?goog.net.ImageLoader.CorsRequestType
126
* }}
127
* @private
128
*/
129
goog.net.ImageLoader.ImageRequest_;
130
131
132
/**
133
* An array of event types to listen to on images. This is browser dependent.
134
*
135
* For IE 10 and below, Internet Explorer doesn't reliably raise LOAD events
136
* on images, so we must use READY_STATE_CHANGE. Since the image is cached
137
* locally, IE won't fire the LOAD event while the onreadystate event is fired
138
* always. On the other hand, the ERROR event is always fired whenever the image
139
* is not loaded successfully no matter whether it's cached or not.
140
*
141
* In IE 11, onreadystatechange is removed and replaced with onload:
142
*
143
* http://msdn.microsoft.com/en-us/library/ie/ms536957(v=vs.85).aspx
144
* http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx
145
*
146
* @type {!Array<string>}
147
* @private
148
*/
149
goog.net.ImageLoader.IMAGE_LOAD_EVENTS_ = [
150
goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('11') ?
151
goog.net.EventType.READY_STATE_CHANGE :
152
goog.events.EventType.LOAD,
153
goog.net.EventType.ABORT, goog.net.EventType.ERROR
154
];
155
156
157
/**
158
* Adds an image to the image loader, and associates it with the given ID
159
* string. If an image with that ID already exists, it is silently replaced.
160
* When the image in question is loaded, the target of the LOAD event will be
161
* an {@code Image} object with {@code id} and {@code src} attributes based on
162
* these arguments.
163
* @param {string} id The ID of the image to load.
164
* @param {string|Image} image Either the source URL of the image or the HTML
165
* image element itself (or any object with a {@code src} property, really).
166
* @param {!goog.net.ImageLoader.CorsRequestType=} opt_corsRequestType The type
167
* of CORS request to use, if any.
168
*/
169
goog.net.ImageLoader.prototype.addImage = function(
170
id, image, opt_corsRequestType) {
171
var src = goog.isString(image) ? image : image.src;
172
if (src) {
173
// For now, we just store the source URL for the image.
174
this.imageIdToRequestMap_[id] = {
175
src: src,
176
corsRequestType: goog.isDef(opt_corsRequestType) ? opt_corsRequestType :
177
null
178
};
179
}
180
};
181
182
183
/**
184
* Removes the image associated with the given ID string from the image loader.
185
* If the image was previously loading, removes any listeners for its events
186
* and dispatches a COMPLETE event if all remaining images have now completed.
187
* @param {string} id The ID of the image to remove.
188
*/
189
goog.net.ImageLoader.prototype.removeImage = function(id) {
190
delete this.imageIdToRequestMap_[id];
191
192
var image = this.imageIdToImageMap_[id];
193
if (image) {
194
delete this.imageIdToImageMap_[id];
195
196
// Stop listening for events on the image.
197
this.handler_.unlisten(
198
image, goog.net.ImageLoader.IMAGE_LOAD_EVENTS_, this.onNetworkEvent_);
199
200
// If this was the last image, raise a COMPLETE event.
201
if (goog.object.isEmpty(this.imageIdToImageMap_) &&
202
goog.object.isEmpty(this.imageIdToRequestMap_)) {
203
this.dispatchEvent(goog.net.EventType.COMPLETE);
204
}
205
}
206
};
207
208
209
/**
210
* Starts loading all images in the image loader in parallel. Raises a LOAD
211
* event each time an image finishes loading, and a COMPLETE event after all
212
* images have finished loading.
213
*/
214
goog.net.ImageLoader.prototype.start = function() {
215
// Iterate over the keys, rather than the full object, to essentially clone
216
// the initial queued images in case any event handlers decide to add more
217
// images before this loop has finished executing.
218
var imageIdToRequestMap = this.imageIdToRequestMap_;
219
goog.array.forEach(goog.object.getKeys(imageIdToRequestMap), function(id) {
220
var imageRequest = imageIdToRequestMap[id];
221
if (imageRequest) {
222
delete imageIdToRequestMap[id];
223
this.loadImage_(imageRequest, id);
224
}
225
}, this);
226
};
227
228
229
/**
230
* Creates an {@code Image} object with the specified ID and source URL, and
231
* listens for network events raised as the image is loaded.
232
* @param {!goog.net.ImageLoader.ImageRequest_} imageRequest The request data.
233
* @param {string} id The unique ID of the image to load.
234
* @private
235
*/
236
goog.net.ImageLoader.prototype.loadImage_ = function(imageRequest, id) {
237
if (this.isDisposed()) {
238
// When loading an image in IE7 (and maybe IE8), the error handler
239
// may fire before we yield JS control. If the error handler
240
// dispose the ImageLoader, this method will throw exception.
241
return;
242
}
243
244
var image;
245
if (this.parent_) {
246
var dom = goog.dom.getDomHelper(this.parent_);
247
image = dom.createDom(goog.dom.TagName.IMG);
248
} else {
249
image = new Image();
250
}
251
252
if (imageRequest.corsRequestType) {
253
image.crossOrigin = imageRequest.corsRequestType;
254
}
255
256
this.handler_.listen(
257
image, goog.net.ImageLoader.IMAGE_LOAD_EVENTS_, this.onNetworkEvent_);
258
this.imageIdToImageMap_[id] = image;
259
260
image.id = id;
261
image.src = imageRequest.src;
262
};
263
264
265
/**
266
* Handles net events (READY_STATE_CHANGE, LOAD, ABORT, and ERROR).
267
* @param {goog.events.Event} evt The network event to handle.
268
* @private
269
*/
270
goog.net.ImageLoader.prototype.onNetworkEvent_ = function(evt) {
271
var image = /** @type {Element} */ (evt.currentTarget);
272
273
if (!image) {
274
return;
275
}
276
277
if (evt.type == goog.net.EventType.READY_STATE_CHANGE) {
278
// This implies that the user agent is IE; see loadImage_().
279
// Noe that this block is used to check whether the image is ready to
280
// dispatch the COMPLETE event.
281
if (image.readyState == goog.net.EventType.COMPLETE) {
282
// This is the IE equivalent of a LOAD event.
283
evt.type = goog.events.EventType.LOAD;
284
} else {
285
// This may imply that the load failed.
286
// Note that the image has only the following states:
287
// * uninitialized
288
// * loading
289
// * complete
290
// When the ERROR or the ABORT event is fired, the readyState
291
// will be either uninitialized or loading and we'd ignore those states
292
// since they will be handled separately (eg: evt.type = 'ERROR').
293
294
// Notes from MSDN : The states through which an object passes are
295
// determined by that object. An object can skip certain states
296
// (for example, interactive) if the state does not apply to that object.
297
// see http://msdn.microsoft.com/en-us/library/ms534359(VS.85).aspx
298
299
// The image is not loaded, ignore.
300
return;
301
}
302
}
303
304
// Add natural width/height properties for non-Gecko browsers.
305
if (typeof image.naturalWidth == 'undefined') {
306
if (evt.type == goog.events.EventType.LOAD) {
307
image.naturalWidth = image.width;
308
image.naturalHeight = image.height;
309
} else {
310
// This implies that the image fails to be loaded.
311
image.naturalWidth = 0;
312
image.naturalHeight = 0;
313
}
314
}
315
316
// Redispatch the event on behalf of the image. Note that the external
317
// listener may dispose this instance.
318
this.dispatchEvent({type: evt.type, target: image});
319
320
if (this.isDisposed()) {
321
// If instance was disposed by listener, exit this function.
322
return;
323
}
324
325
this.removeImage(image.id);
326
};
327
328
329
/** @override */
330
goog.net.ImageLoader.prototype.disposeInternal = function() {
331
delete this.imageIdToRequestMap_;
332
delete this.imageIdToImageMap_;
333
goog.dispose(this.handler_);
334
335
goog.net.ImageLoader.superClass_.disposeInternal.call(this);
336
};
337
338