Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/net/jsloader.js
2868 views
1
// Copyright 2011 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 A utility to load JavaScript files via DOM script tags.
17
* Refactored from goog.net.Jsonp. Works cross-domain.
18
*
19
*/
20
21
goog.provide('goog.net.jsloader');
22
goog.provide('goog.net.jsloader.Error');
23
goog.provide('goog.net.jsloader.ErrorCode');
24
goog.provide('goog.net.jsloader.Options');
25
26
goog.require('goog.array');
27
goog.require('goog.async.Deferred');
28
goog.require('goog.debug.Error');
29
goog.require('goog.dom');
30
goog.require('goog.dom.TagName');
31
goog.require('goog.html.TrustedResourceUrl');
32
goog.require('goog.html.legacyconversions');
33
goog.require('goog.object');
34
35
36
/**
37
* The name of the property of goog.global under which the JavaScript
38
* verification object is stored by the loaded script.
39
* @private {string}
40
*/
41
goog.net.jsloader.GLOBAL_VERIFY_OBJS_ = 'closure_verification';
42
43
44
/**
45
* The default length of time, in milliseconds, we are prepared to wait for a
46
* load request to complete.
47
* @type {number}
48
*/
49
goog.net.jsloader.DEFAULT_TIMEOUT = 5000;
50
51
52
/**
53
* Optional parameters for goog.net.jsloader.send.
54
* timeout: The length of time, in milliseconds, we are prepared to wait
55
* for a load request to complete, or 0 or negative for no timeout. Default
56
* is 5 seconds.
57
* document: The HTML document under which to load the JavaScript. Default is
58
* the current document.
59
* cleanupWhenDone: If true clean up the script tag after script completes to
60
* load. This is important if you just want to read data from the JavaScript
61
* and then throw it away. Default is false.
62
* attributes: Additional attributes to set on the script tag.
63
*
64
* @typedef {{
65
* timeout: (number|undefined),
66
* document: (HTMLDocument|undefined),
67
* cleanupWhenDone: (boolean|undefined),
68
* attributes: (!Object<string, string>|undefined)
69
* }}
70
*/
71
goog.net.jsloader.Options;
72
73
74
/**
75
* Scripts (URIs) waiting to be loaded.
76
* @private {!Array<!goog.html.TrustedResourceUrl>}
77
*/
78
goog.net.jsloader.scriptsToLoad_ = [];
79
80
81
/**
82
* The deferred result of loading the URIs in scriptsToLoad_.
83
* We need to return this to a caller that wants to load URIs while
84
* a deferred is already working on them.
85
* @private {!goog.async.Deferred<null>}
86
*/
87
goog.net.jsloader.scriptLoadingDeferred_;
88
89
90
91
/**
92
* This is deprecated, please use safeLoadMany.
93
*
94
* @param {Array<string>} uris The URIs to load.
95
* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
96
* goog.net.jsloader.options documentation for details.
97
* @return {!goog.async.Deferred} The deferred result, that may be used to add
98
* callbacks
99
* @deprecated
100
*/
101
goog.net.jsloader.loadMany = function(uris, opt_options) {
102
var trustedUris = goog.array.map(
103
uris, goog.html.legacyconversions.trustedResourceUrlFromString);
104
return goog.net.jsloader.safeLoadMany(trustedUris, opt_options);
105
};
106
107
108
/**
109
* Loads and evaluates the JavaScript files at the specified URIs, guaranteeing
110
* the order of script loads.
111
*
112
* Because we have to load the scripts in serial (load script 1, exec script 1,
113
* load script 2, exec script 2, and so on), this will be slower than doing
114
* the network fetches in parallel.
115
*
116
* If you need to load a large number of scripts but dependency order doesn't
117
* matter, you should just call goog.net.jsloader.load N times.
118
*
119
* If you need to load a large number of scripts on the same domain,
120
* you may want to use goog.module.ModuleLoader.
121
*
122
* @param {Array<!goog.html.TrustedResourceUrl>} trustedUris The URIs to load.
123
* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
124
* goog.net.jsloader.options documentation for details.
125
* @return {!goog.async.Deferred} The deferred result, that may be used to add
126
* callbacks
127
*/
128
goog.net.jsloader.safeLoadMany = function(trustedUris, opt_options) {
129
// Loading the scripts in serial introduces asynchronosity into the flow.
130
// Therefore, there are race conditions where client A can kick off the load
131
// sequence for client B, even though client A's scripts haven't all been
132
// loaded yet.
133
//
134
// To work around this issue, all module loads share a queue.
135
if (!trustedUris.length) {
136
return goog.async.Deferred.succeed(null);
137
}
138
139
var isAnotherModuleLoading = goog.net.jsloader.scriptsToLoad_.length;
140
goog.array.extend(goog.net.jsloader.scriptsToLoad_, trustedUris);
141
if (isAnotherModuleLoading) {
142
// jsloader is still loading some other scripts.
143
// In order to prevent the race condition noted above, we just add
144
// these URIs to the end of the scripts' queue and return the deferred
145
// result of the ongoing script load, so the caller knows when they
146
// finish loading.
147
return goog.net.jsloader.scriptLoadingDeferred_;
148
}
149
150
trustedUris = goog.net.jsloader.scriptsToLoad_;
151
var popAndLoadNextScript = function() {
152
var trustedUri = trustedUris.shift();
153
var deferred = goog.net.jsloader.safeLoad(trustedUri, opt_options);
154
if (trustedUris.length) {
155
deferred.addBoth(popAndLoadNextScript);
156
}
157
return deferred;
158
};
159
goog.net.jsloader.scriptLoadingDeferred_ = popAndLoadNextScript();
160
return goog.net.jsloader.scriptLoadingDeferred_;
161
};
162
163
164
/**
165
* This is deprecated, please use safeLoad instead.
166
*
167
* @param {string} uri The URI of the JavaScript.
168
* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
169
* goog.net.jsloader.Options documentation for details.
170
* @return {!goog.async.Deferred} The deferred result, that may be used to add
171
* callbacks and/or cancel the transmission.
172
* The error callback will be called with a single goog.net.jsloader.Error
173
* parameter.
174
*/
175
goog.net.jsloader.load = function(uri, opt_options) {
176
var trustedUri =
177
goog.html.legacyconversions.trustedResourceUrlFromString(uri);
178
return goog.net.jsloader.safeLoad(trustedUri, opt_options);
179
};
180
181
182
/**
183
* Loads and evaluates a JavaScript file.
184
* When the script loads, a user callback is called.
185
* It is the client's responsibility to verify that the script ran successfully.
186
*
187
* @param {!goog.html.TrustedResourceUrl} trustedUri The URI of the JavaScript.
188
* @param {goog.net.jsloader.Options=} opt_options Optional parameters. See
189
* goog.net.jsloader.Options documentation for details.
190
* @return {!goog.async.Deferred} The deferred result, that may be used to add
191
* callbacks and/or cancel the transmission.
192
* The error callback will be called with a single goog.net.jsloader.Error
193
* parameter.
194
*/
195
goog.net.jsloader.safeLoad = function(trustedUri, opt_options) {
196
var options = opt_options || {};
197
var doc = options.document || document;
198
var uri = goog.html.TrustedResourceUrl.unwrap(trustedUri);
199
200
var script = goog.dom.createElement(goog.dom.TagName.SCRIPT);
201
var request = {script_: script, timeout_: undefined};
202
var deferred = new goog.async.Deferred(goog.net.jsloader.cancel_, request);
203
204
// Set a timeout.
205
var timeout = null;
206
var timeoutDuration = goog.isDefAndNotNull(options.timeout) ?
207
options.timeout :
208
goog.net.jsloader.DEFAULT_TIMEOUT;
209
if (timeoutDuration > 0) {
210
timeout = window.setTimeout(function() {
211
goog.net.jsloader.cleanup_(script, true);
212
deferred.errback(
213
new goog.net.jsloader.Error(
214
goog.net.jsloader.ErrorCode.TIMEOUT,
215
'Timeout reached for loading script ' + uri));
216
}, timeoutDuration);
217
request.timeout_ = timeout;
218
}
219
220
// Hang the user callback to be called when the script completes to load.
221
// NOTE(user): This callback will be called in IE even upon error. In any
222
// case it is the client's responsibility to verify that the script ran
223
// successfully.
224
script.onload = script.onreadystatechange = function() {
225
if (!script.readyState || script.readyState == 'loaded' ||
226
script.readyState == 'complete') {
227
var removeScriptNode = options.cleanupWhenDone || false;
228
goog.net.jsloader.cleanup_(script, removeScriptNode, timeout);
229
deferred.callback(null);
230
}
231
};
232
233
// Add an error callback.
234
// NOTE(user): Not supported in IE.
235
script.onerror = function() {
236
goog.net.jsloader.cleanup_(script, true, timeout);
237
deferred.errback(
238
new goog.net.jsloader.Error(
239
goog.net.jsloader.ErrorCode.LOAD_ERROR,
240
'Error while loading script ' + uri));
241
};
242
243
var properties = options.attributes || {};
244
goog.object.extend(properties, {
245
'type': 'text/javascript',
246
'charset': 'UTF-8',
247
// NOTE(user): Safari never loads the script if we don't set
248
// the src attribute before appending.
249
'src': uri
250
});
251
goog.dom.setProperties(script, properties);
252
var scriptParent = goog.net.jsloader.getScriptParentElement_(doc);
253
scriptParent.appendChild(script);
254
255
return deferred;
256
};
257
258
259
/**
260
* This function is deprecated, please use safeLoadAndVerify instead.
261
*
262
* @param {string} uri The URI of the JavaScript.
263
* @param {string} verificationObjName The name of the verification object that
264
* the loaded script should set.
265
* @param {goog.net.jsloader.Options} options Optional parameters. See
266
* goog.net.jsloader.Options documentation for details.
267
* @return {!goog.async.Deferred} The deferred result, that may be used to add
268
* callbacks and/or cancel the transmission.
269
* The success callback will be called with a single parameter containing
270
* the value of the verification object.
271
* The error callback will be called with a single goog.net.jsloader.Error
272
* parameter.
273
* @deprecated
274
*/
275
goog.net.jsloader.loadAndVerify = function(uri, verificationObjName, options) {
276
var trustedUri =
277
goog.html.legacyconversions.trustedResourceUrlFromString(uri);
278
return goog.net.jsloader.safeLoadAndVerify(
279
trustedUri, verificationObjName, options);
280
};
281
282
283
/**
284
* Loads a JavaScript file and verifies it was evaluated successfully, using a
285
* verification object.
286
* The verification object is set by the loaded JavaScript at the end of the
287
* script.
288
* We verify this object was set and return its value in the success callback.
289
* If the object is not defined we trigger an error callback.
290
*
291
* @param {!goog.html.TrustedResourceUrl} trustedUri The URI of the JavaScript.
292
* @param {string} verificationObjName The name of the verification object that
293
* the loaded script should set.
294
* @param {goog.net.jsloader.Options} options Optional parameters. See
295
* goog.net.jsloader.Options documentation for details.
296
* @return {!goog.async.Deferred} The deferred result, that may be used to add
297
* callbacks and/or cancel the transmission.
298
* The success callback will be called with a single parameter containing
299
* the value of the verification object.
300
* The error callback will be called with a single goog.net.jsloader.Error
301
* parameter.
302
*/
303
goog.net.jsloader.safeLoadAndVerify = function(
304
trustedUri, verificationObjName, options) {
305
// Define the global objects variable.
306
if (!goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_]) {
307
goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_] = {};
308
}
309
var verifyObjs = goog.global[goog.net.jsloader.GLOBAL_VERIFY_OBJS_];
310
var uri = goog.html.TrustedResourceUrl.unwrap(trustedUri);
311
312
// Verify that the expected object does not exist yet.
313
if (goog.isDef(verifyObjs[verificationObjName])) {
314
// TODO(user): Error or reset variable?
315
return goog.async.Deferred.fail(
316
new goog.net.jsloader.Error(
317
goog.net.jsloader.ErrorCode.VERIFY_OBJECT_ALREADY_EXISTS,
318
'Verification object ' + verificationObjName +
319
' already defined.'));
320
}
321
322
// Send request to load the JavaScript.
323
var sendDeferred = goog.net.jsloader.safeLoad(trustedUri, options);
324
325
// Create a deferred object wrapping the send result.
326
var deferred =
327
new goog.async.Deferred(goog.bind(sendDeferred.cancel, sendDeferred));
328
329
// Call user back with object that was set by the script.
330
sendDeferred.addCallback(function() {
331
var result = verifyObjs[verificationObjName];
332
if (goog.isDef(result)) {
333
deferred.callback(result);
334
delete verifyObjs[verificationObjName];
335
} else {
336
// Error: script was not loaded properly.
337
deferred.errback(
338
new goog.net.jsloader.Error(
339
goog.net.jsloader.ErrorCode.VERIFY_ERROR, 'Script ' + uri +
340
' loaded, but verification object ' + verificationObjName +
341
' was not defined.'));
342
}
343
});
344
345
// Pass error to new deferred object.
346
sendDeferred.addErrback(function(error) {
347
if (goog.isDef(verifyObjs[verificationObjName])) {
348
delete verifyObjs[verificationObjName];
349
}
350
deferred.errback(error);
351
});
352
353
return deferred;
354
};
355
356
357
/**
358
* Gets the DOM element under which we should add new script elements.
359
* How? Take the first head element, and if not found take doc.documentElement,
360
* which always exists.
361
*
362
* @param {!HTMLDocument} doc The relevant document.
363
* @return {!Element} The script parent element.
364
* @private
365
*/
366
goog.net.jsloader.getScriptParentElement_ = function(doc) {
367
var headElements = goog.dom.getElementsByTagName(goog.dom.TagName.HEAD, doc);
368
if (!headElements || goog.array.isEmpty(headElements)) {
369
return doc.documentElement;
370
} else {
371
return headElements[0];
372
}
373
};
374
375
376
/**
377
* Cancels a given request.
378
* @this {{script_: Element, timeout_: number}} The request context.
379
* @private
380
*/
381
goog.net.jsloader.cancel_ = function() {
382
var request = this;
383
if (request && request.script_) {
384
var scriptNode = request.script_;
385
if (scriptNode && scriptNode.tagName == goog.dom.TagName.SCRIPT) {
386
goog.net.jsloader.cleanup_(scriptNode, true, request.timeout_);
387
}
388
}
389
};
390
391
392
/**
393
* Removes the script node and the timeout.
394
*
395
* @param {Node} scriptNode The node to be cleaned up.
396
* @param {boolean} removeScriptNode If true completely remove the script node.
397
* @param {?number=} opt_timeout The timeout handler to cleanup.
398
* @private
399
*/
400
goog.net.jsloader.cleanup_ = function(
401
scriptNode, removeScriptNode, opt_timeout) {
402
if (goog.isDefAndNotNull(opt_timeout)) {
403
goog.global.clearTimeout(opt_timeout);
404
}
405
406
scriptNode.onload = goog.nullFunction;
407
scriptNode.onerror = goog.nullFunction;
408
scriptNode.onreadystatechange = goog.nullFunction;
409
410
// Do this after a delay (removing the script node of a running script can
411
// confuse older IEs).
412
if (removeScriptNode) {
413
window.setTimeout(function() { goog.dom.removeNode(scriptNode); }, 0);
414
}
415
};
416
417
418
/**
419
* Possible error codes for jsloader.
420
* @enum {number}
421
*/
422
goog.net.jsloader.ErrorCode = {
423
LOAD_ERROR: 0,
424
TIMEOUT: 1,
425
VERIFY_ERROR: 2,
426
VERIFY_OBJECT_ALREADY_EXISTS: 3
427
};
428
429
430
431
/**
432
* A jsloader error.
433
*
434
* @param {goog.net.jsloader.ErrorCode} code The error code.
435
* @param {string=} opt_message Additional message.
436
* @constructor
437
* @extends {goog.debug.Error}
438
* @final
439
*/
440
goog.net.jsloader.Error = function(code, opt_message) {
441
var msg = 'Jsloader error (code #' + code + ')';
442
if (opt_message) {
443
msg += ': ' + opt_message;
444
}
445
goog.net.jsloader.Error.base(this, 'constructor', msg);
446
447
/**
448
* The code for this error.
449
*
450
* @type {goog.net.jsloader.ErrorCode}
451
*/
452
this.code = code;
453
};
454
goog.inherits(goog.net.jsloader.Error, goog.debug.Error);
455
456