Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/module/moduleloader.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 The module loader for loading modules across the network.
17
*
18
* Browsers do not guarantee that scripts appended to the document
19
* are executed in the order they are added. For production mode, we use
20
* XHRs to load scripts, because they do not have this problem and they
21
* have superior mechanisms for handling failure. However, XHR-evaled
22
* scripts are harder to debug.
23
*
24
* In debugging mode, we use normal script tags. In order to make this work,
25
* we load the scripts in serial: we do not execute script B to the document
26
* until we are certain that script A is finished loading.
27
*
28
*/
29
30
goog.provide('goog.module.ModuleLoader');
31
32
goog.require('goog.Timer');
33
goog.require('goog.array');
34
goog.require('goog.events');
35
goog.require('goog.events.Event');
36
goog.require('goog.events.EventHandler');
37
goog.require('goog.events.EventId');
38
goog.require('goog.events.EventTarget');
39
goog.require('goog.labs.userAgent.browser');
40
goog.require('goog.log');
41
goog.require('goog.module.AbstractModuleLoader');
42
goog.require('goog.net.BulkLoader');
43
goog.require('goog.net.EventType');
44
goog.require('goog.net.jsloader');
45
goog.require('goog.userAgent');
46
goog.require('goog.userAgent.product');
47
48
49
50
/**
51
* A class that loads Javascript modules.
52
* @constructor
53
* @extends {goog.events.EventTarget}
54
* @implements {goog.module.AbstractModuleLoader}
55
*/
56
goog.module.ModuleLoader = function() {
57
goog.module.ModuleLoader.base(this, 'constructor');
58
59
/**
60
* Event handler for managing handling events.
61
* @type {goog.events.EventHandler<!goog.module.ModuleLoader>}
62
* @private
63
*/
64
this.eventHandler_ = new goog.events.EventHandler(this);
65
66
/**
67
* A map from module IDs to goog.module.ModuleLoader.LoadStatus.
68
* @type {!Object<Array<string>, goog.module.ModuleLoader.LoadStatus>}
69
* @private
70
*/
71
this.loadingModulesStatus_ = {};
72
};
73
goog.inherits(goog.module.ModuleLoader, goog.events.EventTarget);
74
75
76
/**
77
* A logger.
78
* @type {goog.log.Logger}
79
* @protected
80
*/
81
goog.module.ModuleLoader.prototype.logger =
82
goog.log.getLogger('goog.module.ModuleLoader');
83
84
85
/**
86
* Whether debug mode is enabled.
87
* @type {boolean}
88
* @private
89
*/
90
goog.module.ModuleLoader.prototype.debugMode_ = false;
91
92
93
/**
94
* Whether source url injection is enabled.
95
* @type {boolean}
96
* @private
97
*/
98
goog.module.ModuleLoader.prototype.sourceUrlInjection_ = false;
99
100
101
/**
102
* @return {boolean} Whether sourceURL affects stack traces.
103
*/
104
goog.module.ModuleLoader.supportsSourceUrlStackTraces = function() {
105
return goog.userAgent.product.CHROME ||
106
(goog.labs.userAgent.browser.isFirefox() &&
107
goog.labs.userAgent.browser.isVersionOrHigher('36'));
108
};
109
110
111
/**
112
* @return {boolean} Whether sourceURL affects the debugger.
113
*/
114
goog.module.ModuleLoader.supportsSourceUrlDebugger = function() {
115
return goog.userAgent.product.CHROME || goog.userAgent.GECKO;
116
};
117
118
119
/**
120
* Gets the debug mode for the loader.
121
* @return {boolean} Whether the debug mode is enabled.
122
*/
123
goog.module.ModuleLoader.prototype.getDebugMode = function() {
124
return this.debugMode_;
125
};
126
127
128
/**
129
* Sets the debug mode for the loader.
130
* @param {boolean} debugMode Whether the debug mode is enabled.
131
*/
132
goog.module.ModuleLoader.prototype.setDebugMode = function(debugMode) {
133
this.debugMode_ = debugMode;
134
};
135
136
137
/**
138
* When enabled, we will add a sourceURL comment to the end of all scripts
139
* to mark their origin.
140
*
141
* On WebKit, stack traces will reflect the sourceURL comment, so this is
142
* useful for debugging webkit stack traces in production.
143
*
144
* Notice that in debug mode, we will use source url injection + eval rather
145
* then appending script nodes to the DOM, because the scripts will load far
146
* faster. (Appending script nodes is very slow, because we can't parallelize
147
* the downloading and evaling of the script).
148
*
149
* The cost of appending sourceURL information is negligible when compared to
150
* the cost of evaling the script. Almost all clients will want this on.
151
*
152
* TODO(nicksantos): Turn this on by default. We may want to turn this off
153
* for clients that inject their own sourceURL.
154
*
155
* @param {boolean} enabled Whether source url injection is enabled.
156
*/
157
goog.module.ModuleLoader.prototype.setSourceUrlInjection = function(enabled) {
158
this.sourceUrlInjection_ = enabled;
159
};
160
161
162
/**
163
* @return {boolean} Whether we're using source url injection.
164
* @private
165
*/
166
goog.module.ModuleLoader.prototype.usingSourceUrlInjection_ = function() {
167
return this.sourceUrlInjection_ ||
168
(this.getDebugMode() &&
169
goog.module.ModuleLoader.supportsSourceUrlStackTraces());
170
};
171
172
173
/** @override */
174
goog.module.ModuleLoader.prototype.loadModules = function(
175
ids, moduleInfoMap, opt_successFn, opt_errorFn, opt_timeoutFn,
176
opt_forceReload) {
177
var loadStatus = this.loadingModulesStatus_[ids] ||
178
new goog.module.ModuleLoader.LoadStatus();
179
loadStatus.loadRequested = true;
180
loadStatus.successFn = opt_successFn || null;
181
loadStatus.errorFn = opt_errorFn || null;
182
183
if (!this.loadingModulesStatus_[ids]) {
184
// Modules were not prefetched.
185
this.loadingModulesStatus_[ids] = loadStatus;
186
this.downloadModules_(ids, moduleInfoMap);
187
// TODO(user): Need to handle timeouts in the module loading code.
188
} else if (goog.isDefAndNotNull(loadStatus.responseTexts)) {
189
// Modules prefetch is complete.
190
this.evaluateCode_(ids);
191
}
192
// Otherwise modules prefetch is in progress, and these modules will be
193
// executed after the prefetch is complete.
194
};
195
196
197
/**
198
* Evaluate the JS code.
199
* @param {Array<string>} moduleIds The module ids.
200
* @private
201
*/
202
goog.module.ModuleLoader.prototype.evaluateCode_ = function(moduleIds) {
203
this.dispatchEvent(
204
new goog.module.ModuleLoader.RequestSuccessEvent(moduleIds));
205
206
goog.log.info(this.logger, 'evaluateCode ids:' + moduleIds);
207
var loadStatus = this.loadingModulesStatus_[moduleIds];
208
var uris = loadStatus.requestUris;
209
var texts = loadStatus.responseTexts;
210
var error = null;
211
try {
212
if (this.usingSourceUrlInjection_()) {
213
for (var i = 0; i < uris.length; i++) {
214
var uri = uris[i];
215
goog.globalEval(texts[i] + ' //# sourceURL=' + uri);
216
}
217
} else {
218
goog.globalEval(texts.join('\n'));
219
}
220
} catch (e) {
221
error = e;
222
// TODO(user): Consider throwing an exception here.
223
goog.log.warning(
224
this.logger, 'Loaded incomplete code for module(s): ' + moduleIds, e);
225
}
226
227
this.dispatchEvent(new goog.module.ModuleLoader.EvaluateCodeEvent(moduleIds));
228
229
if (error) {
230
this.handleErrorHelper_(
231
moduleIds, loadStatus.errorFn, null /* status */, error);
232
} else if (loadStatus.successFn) {
233
loadStatus.successFn();
234
}
235
delete this.loadingModulesStatus_[moduleIds];
236
};
237
238
239
/**
240
* Handles a successful response to a request for prefetch or load one or more
241
* modules.
242
*
243
* @param {goog.net.BulkLoader} bulkLoader The bulk loader.
244
* @param {Array<string>} moduleIds The ids of the modules requested.
245
* @private
246
*/
247
goog.module.ModuleLoader.prototype.handleSuccess_ = function(
248
bulkLoader, moduleIds) {
249
goog.log.info(this.logger, 'Code loaded for module(s): ' + moduleIds);
250
251
var loadStatus = this.loadingModulesStatus_[moduleIds];
252
loadStatus.responseTexts = bulkLoader.getResponseTexts();
253
254
if (loadStatus.loadRequested) {
255
this.evaluateCode_(moduleIds);
256
}
257
258
// NOTE: A bulk loader instance is used for loading a set of module ids.
259
// Once these modules have been loaded successfully or in error the bulk
260
// loader should be disposed as it is not needed anymore. A new bulk loader
261
// is instantiated for any new modules to be loaded. The dispose is called
262
// on a timer so that the bulkloader has a chance to release its
263
// objects.
264
goog.Timer.callOnce(bulkLoader.dispose, 5, bulkLoader);
265
};
266
267
268
/** @override */
269
goog.module.ModuleLoader.prototype.prefetchModule = function(id, moduleInfo) {
270
// Do not prefetch in debug mode.
271
if (this.getDebugMode()) {
272
return;
273
}
274
var loadStatus = this.loadingModulesStatus_[[id]];
275
if (loadStatus) {
276
return;
277
}
278
279
var moduleInfoMap = {};
280
moduleInfoMap[id] = moduleInfo;
281
this.loadingModulesStatus_[[id]] = new goog.module.ModuleLoader.LoadStatus();
282
this.downloadModules_([id], moduleInfoMap);
283
};
284
285
286
/**
287
* Downloads a list of JavaScript modules.
288
*
289
* @param {Array<string>} ids The module ids in dependency order.
290
* @param {Object} moduleInfoMap A mapping from module id to ModuleInfo object.
291
* @private
292
*/
293
goog.module.ModuleLoader.prototype.downloadModules_ = function(
294
ids, moduleInfoMap) {
295
var uris = [];
296
for (var i = 0; i < ids.length; i++) {
297
goog.array.extend(uris, moduleInfoMap[ids[i]].getUris());
298
}
299
goog.log.info(this.logger, 'downloadModules ids:' + ids + ' uris:' + uris);
300
301
if (this.getDebugMode() && !this.usingSourceUrlInjection_()) {
302
// In debug mode use <script> tags rather than XHRs to load the files.
303
// This makes it possible to debug and inspect stack traces more easily.
304
// It's also possible to use it to load JavaScript files that are hosted on
305
// another domain.
306
// The scripts need to load serially, so this is much slower than parallel
307
// script loads with source url injection.
308
goog.net.jsloader.loadMany(uris);
309
} else {
310
var loadStatus = this.loadingModulesStatus_[ids];
311
loadStatus.requestUris = uris;
312
313
var bulkLoader = new goog.net.BulkLoader(uris);
314
315
var eventHandler = this.eventHandler_;
316
eventHandler.listen(
317
bulkLoader, goog.net.EventType.SUCCESS,
318
goog.bind(this.handleSuccess_, this, bulkLoader, ids));
319
eventHandler.listen(
320
bulkLoader, goog.net.EventType.ERROR,
321
goog.bind(this.handleError_, this, bulkLoader, ids));
322
bulkLoader.load();
323
}
324
};
325
326
327
/**
328
* Handles an error during a request for one or more modules.
329
* @param {goog.net.BulkLoader} bulkLoader The bulk loader.
330
* @param {Array<string>} moduleIds The ids of the modules requested.
331
* @param {number} status The response status.
332
* @private
333
*/
334
goog.module.ModuleLoader.prototype.handleError_ = function(
335
bulkLoader, moduleIds, status) {
336
var loadStatus = this.loadingModulesStatus_[moduleIds];
337
// The bulk loader doesn't cancel other requests when a request fails. We will
338
// delete the loadStatus in the first failure, so it will be undefined in
339
// subsequent errors.
340
if (loadStatus) {
341
delete this.loadingModulesStatus_[moduleIds];
342
this.handleErrorHelper_(moduleIds, loadStatus.errorFn, status);
343
}
344
345
// NOTE: A bulk loader instance is used for loading a set of module ids. Once
346
// these modules have been loaded successfully or in error the bulk loader
347
// should be disposed as it is not needed anymore. A new bulk loader is
348
// instantiated for any new modules to be loaded. The dispose is called
349
// on another thread so that the bulkloader has a chance to release its
350
// objects.
351
goog.Timer.callOnce(bulkLoader.dispose, 5, bulkLoader);
352
};
353
354
355
/**
356
* Handles an error during a request for one or more modules.
357
* @param {Array<string>} moduleIds The ids of the modules requested.
358
* @param {?function(?number)} errorFn The function to call on failure.
359
* @param {?number} status The response status.
360
* @param {!Error=} opt_error The error encountered, if available.
361
* @private
362
*/
363
goog.module.ModuleLoader.prototype.handleErrorHelper_ = function(
364
moduleIds, errorFn, status, opt_error) {
365
this.dispatchEvent(
366
new goog.module.ModuleLoader.RequestErrorEvent(moduleIds, opt_error));
367
368
goog.log.warning(this.logger, 'Request failed for module(s): ' + moduleIds);
369
370
if (errorFn) {
371
errorFn(status);
372
}
373
};
374
375
376
/** @override */
377
goog.module.ModuleLoader.prototype.disposeInternal = function() {
378
goog.module.ModuleLoader.superClass_.disposeInternal.call(this);
379
380
this.eventHandler_.dispose();
381
this.eventHandler_ = null;
382
};
383
384
385
/**
386
* Events dispatched by the ModuleLoader.
387
* @const
388
*/
389
goog.module.ModuleLoader.EventType = {
390
/**
391
* @const {!goog.events.EventId<
392
* !goog.module.ModuleLoader.EvaluateCodeEvent>} Called after the code for
393
* a module is evaluated.
394
*/
395
EVALUATE_CODE:
396
new goog.events.EventId(goog.events.getUniqueId('evaluateCode')),
397
398
/**
399
* @const {!goog.events.EventId<
400
* !goog.module.ModuleLoader.RequestSuccessEvent>} Called when the
401
* BulkLoader finishes successfully.
402
*/
403
REQUEST_SUCCESS:
404
new goog.events.EventId(goog.events.getUniqueId('requestSuccess')),
405
406
/**
407
* @const {!goog.events.EventId<
408
* !goog.module.ModuleLoader.RequestErrorEvent>} Called when the
409
* BulkLoader fails, or code loading fails.
410
*/
411
REQUEST_ERROR:
412
new goog.events.EventId(goog.events.getUniqueId('requestError'))
413
};
414
415
416
417
/**
418
* @param {Array<string>} moduleIds The ids of the modules being evaluated.
419
* @constructor
420
* @extends {goog.events.Event}
421
* @final
422
* @protected
423
*/
424
goog.module.ModuleLoader.EvaluateCodeEvent = function(moduleIds) {
425
goog.module.ModuleLoader.EvaluateCodeEvent.base(
426
this, 'constructor', goog.module.ModuleLoader.EventType.EVALUATE_CODE);
427
428
/**
429
* @type {Array<string>}
430
*/
431
this.moduleIds = moduleIds;
432
};
433
goog.inherits(goog.module.ModuleLoader.EvaluateCodeEvent, goog.events.Event);
434
435
436
437
/**
438
* @param {Array<string>} moduleIds The ids of the modules being evaluated.
439
* @constructor
440
* @extends {goog.events.Event}
441
* @final
442
* @protected
443
*/
444
goog.module.ModuleLoader.RequestSuccessEvent = function(moduleIds) {
445
goog.module.ModuleLoader.RequestSuccessEvent.base(
446
this, 'constructor', goog.module.ModuleLoader.EventType.REQUEST_SUCCESS);
447
448
/**
449
* @type {Array<string>}
450
*/
451
this.moduleIds = moduleIds;
452
};
453
goog.inherits(goog.module.ModuleLoader.RequestSuccessEvent, goog.events.Event);
454
455
456
457
/**
458
* @param {Array<string>} moduleIds The ids of the modules being evaluated.
459
* @param {!Error=} opt_error The error encountered, if available.
460
* @constructor
461
* @extends {goog.events.Event}
462
* @final
463
* @protected
464
*/
465
goog.module.ModuleLoader.RequestErrorEvent = function(moduleIds, opt_error) {
466
goog.module.ModuleLoader.RequestErrorEvent.base(
467
this, 'constructor', goog.module.ModuleLoader.EventType.REQUEST_ERROR);
468
469
/**
470
* @type {Array<string>}
471
*/
472
this.moduleIds = moduleIds;
473
474
/** @type {?Error} */
475
this.error = opt_error || null;
476
};
477
goog.inherits(goog.module.ModuleLoader.RequestErrorEvent, goog.events.Event);
478
479
480
481
/**
482
* A class that keeps the state of the module during the loading process. It is
483
* used to save loading information between modules download and evaluation.
484
* @constructor
485
* @final
486
*/
487
goog.module.ModuleLoader.LoadStatus = function() {
488
/**
489
* The request uris.
490
* @type {Array<string>}
491
*/
492
this.requestUris = null;
493
494
/**
495
* The response texts.
496
* @type {Array<string>}
497
*/
498
this.responseTexts = null;
499
500
/**
501
* Whether loadModules was called for the set of modules referred by this
502
* status.
503
* @type {boolean}
504
*/
505
this.loadRequested = false;
506
507
/**
508
* Success callback.
509
* @type {?function()}
510
*/
511
this.successFn = null;
512
513
/**
514
* Error callback.
515
* @type {?function(?number)}
516
*/
517
this.errorFn = null;
518
};
519
520