blob: 2b8ac788c58883a1d1b556be6ee749e75b46c7f1 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:37 +00001/*
2 * Copyright (C) 2014 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30// This gets all concatenated module descriptors in the release mode.
31const allDescriptors = [];
32let applicationDescriptor;
33const _loadedScripts = {};
34
35// FIXME: This is a workaround to force Closure compiler provide
36// the standard ES6 runtime for all modules. This should be removed
37// once Closure provides standard externs for Map et al.
38for (const k of []) { // eslint-disable-line
39}
40
41(function() {
42const baseUrl = self.location ? self.location.origin + self.location.pathname : '';
43self._importScriptPathPrefix = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1);
44})();
45
Patrick Hulceb8c09f32018-06-11 21:28:22 +000046const REMOTE_MODULE_FALLBACK_REVISION = '@010ddcfda246975d194964ccf20038ebbdec6084';
47
Blink Reformat4c46d092018-04-07 15:32:37 +000048/**
49 * @unrestricted
50 */
51var Runtime = class { // eslint-disable-line
52 /**
53 * @param {!Array.<!Runtime.ModuleDescriptor>} descriptors
54 */
55 constructor(descriptors) {
56 /** @type {!Array<!Runtime.Module>} */
57 this._modules = [];
58 /** @type {!Object<string, !Runtime.Module>} */
59 this._modulesMap = {};
60 /** @type {!Array<!Runtime.Extension>} */
61 this._extensions = [];
62 /** @type {!Object<string, !function(new:Object)>} */
63 this._cachedTypeClasses = {};
64 /** @type {!Object<string, !Runtime.ModuleDescriptor>} */
65 this._descriptorsMap = {};
66
67 for (let i = 0; i < descriptors.length; ++i)
68 this._registerModule(descriptors[i]);
Blink Reformat4c46d092018-04-07 15:32:37 +000069 }
70
71 /**
72 * @param {string} url
73 * @return {!Promise.<string>}
74 */
75 static loadResourcePromise(url) {
76 return new Promise(load);
77
78 /**
79 * @param {function(?)} fulfill
80 * @param {function(*)} reject
81 */
82 function load(fulfill, reject) {
83 const xhr = new XMLHttpRequest();
84 xhr.open('GET', url, true);
85 xhr.onreadystatechange = onreadystatechange;
86
87 /**
88 * @param {Event} e
89 */
90 function onreadystatechange(e) {
91 if (xhr.readyState !== XMLHttpRequest.DONE)
92 return;
93
Patrick Hulceb8c09f32018-06-11 21:28:22 +000094 // DevTools Proxy server can mask 404s as 200s, check the body to be sure
95 const status = /^HTTP\/1.1 404/.test(e.target.response) ? 404 : xhr.status;
96
97 if ([0, 200, 304].indexOf(status) === -1) // Testing harness file:/// results in 0.
98 reject(new Error('While loading from url ' + url + ' server responded with a status of ' + status));
Blink Reformat4c46d092018-04-07 15:32:37 +000099 else
100 fulfill(e.target.response);
101 }
102 xhr.send(null);
103 }
104 }
105
106 /**
Patrick Hulceb8c09f32018-06-11 21:28:22 +0000107 * @param {string} url
108 * @return {!Promise.<string>}
109 */
110 static loadResourcePromiseWithFallback(url) {
111 return Runtime.loadResourcePromise(url).catch(err => {
112 const urlWithFallbackVersion = url.replace(/@[0-9a-f]{40}/, REMOTE_MODULE_FALLBACK_REVISION);
113 // TODO(phulce): mark fallbacks in module.json and modify build script instead
cjamcl@google.comaa1532c2019-05-31 03:01:24 +0000114 if (urlWithFallbackVersion === url || !url.includes('audits_worker_module'))
Patrick Hulceb8c09f32018-06-11 21:28:22 +0000115 throw err;
116 return Runtime.loadResourcePromise(urlWithFallbackVersion);
117 });
118 }
119
120 /**
Blink Reformat4c46d092018-04-07 15:32:37 +0000121 * http://tools.ietf.org/html/rfc3986#section-5.2.4
122 * @param {string} path
123 * @return {string}
124 */
125 static normalizePath(path) {
126 if (path.indexOf('..') === -1 && path.indexOf('.') === -1)
127 return path;
128
129 const normalizedSegments = [];
130 const segments = path.split('/');
131 for (let i = 0; i < segments.length; i++) {
132 const segment = segments[i];
133 if (segment === '.')
134 continue;
135 else if (segment === '..')
136 normalizedSegments.pop();
137 else if (segment)
138 normalizedSegments.push(segment);
139 }
140 let normalizedPath = normalizedSegments.join('/');
141 if (normalizedPath[normalizedPath.length - 1] === '/')
142 return normalizedPath;
143 if (path[0] === '/' && normalizedPath)
144 normalizedPath = '/' + normalizedPath;
145 if ((path[path.length - 1] === '/') || (segments[segments.length - 1] === '.') ||
146 (segments[segments.length - 1] === '..'))
147 normalizedPath = normalizedPath + '/';
148
149 return normalizedPath;
150 }
151
152 /**
153 * @param {!Array.<string>} scriptNames
154 * @param {string=} base
155 * @return {!Promise.<undefined>}
156 */
157 static _loadScriptsPromise(scriptNames, base) {
158 /** @type {!Array<!Promise<undefined>>} */
159 const promises = [];
160 /** @type {!Array<string>} */
161 const urls = [];
162 const sources = new Array(scriptNames.length);
163 let scriptToEval = 0;
164 for (let i = 0; i < scriptNames.length; ++i) {
165 const scriptName = scriptNames[i];
166 let sourceURL = (base || self._importScriptPathPrefix) + scriptName;
167
168 const schemaIndex = sourceURL.indexOf('://') + 3;
169 let pathIndex = sourceURL.indexOf('/', schemaIndex);
170 if (pathIndex === -1)
171 pathIndex = sourceURL.length;
172 sourceURL = sourceURL.substring(0, pathIndex) + Runtime.normalizePath(sourceURL.substring(pathIndex));
173
174 if (_loadedScripts[sourceURL])
175 continue;
176 urls.push(sourceURL);
Patrick Hulceb8c09f32018-06-11 21:28:22 +0000177 const loadResourcePromise =
178 base ? Runtime.loadResourcePromiseWithFallback(sourceURL) : Runtime.loadResourcePromise(sourceURL);
179 promises.push(
180 loadResourcePromise.then(scriptSourceLoaded.bind(null, i), scriptSourceLoaded.bind(null, i, undefined)));
Blink Reformat4c46d092018-04-07 15:32:37 +0000181 }
182 return Promise.all(promises).then(undefined);
183
184 /**
185 * @param {number} scriptNumber
186 * @param {string=} scriptSource
187 */
188 function scriptSourceLoaded(scriptNumber, scriptSource) {
189 sources[scriptNumber] = scriptSource || '';
190 // Eval scripts as fast as possible.
191 while (typeof sources[scriptToEval] !== 'undefined') {
192 evaluateScript(urls[scriptToEval], sources[scriptToEval]);
193 ++scriptToEval;
194 }
195 }
196
197 /**
198 * @param {string} sourceURL
199 * @param {string=} scriptSource
200 */
201 function evaluateScript(sourceURL, scriptSource) {
202 _loadedScripts[sourceURL] = true;
203 if (!scriptSource) {
204 // Do not reject, as this is normal in the hosted mode.
205 console.error('Empty response arrived for script \'' + sourceURL + '\'');
206 return;
207 }
208 self.eval(scriptSource + '\n//# sourceURL=' + sourceURL);
209 }
210 }
211
212 /**
213 * @param {string} url
214 * @param {boolean} appendSourceURL
215 * @return {!Promise<undefined>}
216 */
217 static _loadResourceIntoCache(url, appendSourceURL) {
218 return Runtime.loadResourcePromise(url).then(
219 cacheResource.bind(this, url), cacheResource.bind(this, url, undefined));
220
221 /**
222 * @param {string} path
223 * @param {string=} content
224 */
225 function cacheResource(path, content) {
226 if (!content) {
227 console.error('Failed to load resource: ' + path);
228 return;
229 }
230 const sourceURL = appendSourceURL ? Runtime.resolveSourceURL(path) : '';
231 Runtime.cachedResources[path] = content + sourceURL;
232 }
233 }
234
235 /**
236 * @return {!Promise}
237 */
Pavel Feldman63e89eb2018-11-25 05:47:09 +0000238 static async appStarted() {
239 return Runtime._appStartedPromise;
Blink Reformat4c46d092018-04-07 15:32:37 +0000240 }
241
242 /**
243 * @param {string} appName
244 * @return {!Promise.<undefined>}
245 */
246 static async startApplication(appName) {
247 console.timeStamp('Runtime.startApplication');
248
249 const allDescriptorsByName = {};
250 for (let i = 0; i < allDescriptors.length; ++i) {
251 const d = allDescriptors[i];
252 allDescriptorsByName[d['name']] = d;
253 }
254
255 if (!applicationDescriptor) {
256 let data = await Runtime.loadResourcePromise(appName + '.json');
257 applicationDescriptor = JSON.parse(data);
258 let descriptor = applicationDescriptor;
259 while (descriptor.extends) {
260 data = await Runtime.loadResourcePromise(descriptor.extends + '.json');
261 descriptor = JSON.parse(data);
262 applicationDescriptor.modules = descriptor.modules.concat(applicationDescriptor.modules);
263 }
264 }
265
266 const configuration = applicationDescriptor.modules;
267 const moduleJSONPromises = [];
268 const coreModuleNames = [];
269 for (let i = 0; i < configuration.length; ++i) {
270 const descriptor = configuration[i];
271 const name = descriptor['name'];
272 const moduleJSON = allDescriptorsByName[name];
273 if (moduleJSON)
274 moduleJSONPromises.push(Promise.resolve(moduleJSON));
275 else
276 moduleJSONPromises.push(Runtime.loadResourcePromise(name + '/module.json').then(JSON.parse.bind(JSON)));
277 if (descriptor['type'] === 'autostart')
278 coreModuleNames.push(name);
279 }
280
281 const moduleDescriptors = await Promise.all(moduleJSONPromises);
282
283 for (let i = 0; i < moduleDescriptors.length; ++i) {
284 moduleDescriptors[i].name = configuration[i]['name'];
285 moduleDescriptors[i].condition = configuration[i]['condition'];
286 moduleDescriptors[i].remote = configuration[i]['type'] === 'remote';
287 }
288 self.runtime = new Runtime(moduleDescriptors);
289 if (coreModuleNames)
Pavel Feldman63e89eb2018-11-25 05:47:09 +0000290 await self.runtime._loadAutoStartModules(coreModuleNames);
291 Runtime._appStartedPromiseCallback();
Blink Reformat4c46d092018-04-07 15:32:37 +0000292 }
293
294 /**
295 * @param {string} appName
296 * @return {!Promise.<undefined>}
297 */
298 static startWorker(appName) {
299 return Runtime.startApplication(appName).then(sendWorkerReady);
300
301 function sendWorkerReady() {
302 self.postMessage('workerReady');
303 }
304 }
305
306 /**
307 * @param {string} name
308 * @return {?string}
309 */
310 static queryParam(name) {
Ingvar Stepanyand48ae082018-11-07 04:38:32 +0000311 return Runtime._queryParamsObject.get(name);
Blink Reformat4c46d092018-04-07 15:32:37 +0000312 }
313
314 /**
315 * @return {string}
316 */
317 static queryParamsString() {
318 return location.search;
319 }
320
321 /**
322 * @return {!Object}
323 */
324 static _experimentsSetting() {
325 try {
326 return /** @type {!Object} */ (
327 JSON.parse(self.localStorage && self.localStorage['experiments'] ? self.localStorage['experiments'] : '{}'));
328 } catch (e) {
329 console.error('Failed to parse localStorage[\'experiments\']');
330 return {};
331 }
332 }
333
334 static _assert(value, message) {
335 if (value)
336 return;
337 Runtime._originalAssert.call(Runtime._console, value, message + ' ' + new Error().stack);
338 }
339
340 /**
341 * @param {string} platform
342 */
343 static setPlatform(platform) {
344 Runtime._platform = platform;
345 }
346
347 /**
348 * @param {!Object} descriptor
349 * @return {boolean}
350 */
351 static _isDescriptorEnabled(descriptor) {
352 const activatorExperiment = descriptor['experiment'];
353 if (activatorExperiment === '*')
354 return Runtime.experiments.supportEnabled();
355 if (activatorExperiment && activatorExperiment.startsWith('!') &&
356 Runtime.experiments.isEnabled(activatorExperiment.substring(1)))
357 return false;
358 if (activatorExperiment && !activatorExperiment.startsWith('!') &&
359 !Runtime.experiments.isEnabled(activatorExperiment))
360 return false;
361 const condition = descriptor['condition'];
362 if (condition && !condition.startsWith('!') && !Runtime.queryParam(condition))
363 return false;
364 if (condition && condition.startsWith('!') && Runtime.queryParam(condition.substring(1)))
365 return false;
366 return true;
367 }
368
369 /**
370 * @param {string} path
371 * @return {string}
372 */
373 static resolveSourceURL(path) {
374 let sourceURL = self.location.href;
375 if (self.location.search)
376 sourceURL = sourceURL.replace(self.location.search, '');
377 sourceURL = sourceURL.substring(0, sourceURL.lastIndexOf('/') + 1) + path;
378 return '\n/*# sourceURL=' + sourceURL + ' */';
379 }
380
Vidal Diazleal0b6aff42019-04-20 01:15:43 +0000381 /**
382 * @param {function(string):string} localizationFunction
383 */
384 static setL10nCallback(localizationFunction) {
385 Runtime._l10nCallback = localizationFunction;
386 }
387
Blink Reformat4c46d092018-04-07 15:32:37 +0000388 useTestBase() {
389 Runtime._remoteBase = 'http://localhost:8000/inspector-sources/';
390 if (Runtime.queryParam('debugFrontend'))
391 Runtime._remoteBase += 'debug/';
392 }
393
394 /**
395 * @param {!Runtime.ModuleDescriptor} descriptor
396 */
397 _registerModule(descriptor) {
398 const module = new Runtime.Module(this, descriptor);
399 this._modules.push(module);
400 this._modulesMap[descriptor['name']] = module;
401 }
402
403 /**
404 * @param {string} moduleName
405 * @return {!Promise.<undefined>}
406 */
407 loadModulePromise(moduleName) {
408 return this._modulesMap[moduleName]._loadPromise();
409 }
410
411 /**
412 * @param {!Array.<string>} moduleNames
413 * @return {!Promise.<!Array.<*>>}
414 */
415 _loadAutoStartModules(moduleNames) {
416 const promises = [];
417 for (let i = 0; i < moduleNames.length; ++i)
418 promises.push(this.loadModulePromise(moduleNames[i]));
419 return Promise.all(promises);
420 }
421
422 /**
423 * @param {!Runtime.Extension} extension
424 * @param {?function(function(new:Object)):boolean} predicate
425 * @return {boolean}
426 */
427 _checkExtensionApplicability(extension, predicate) {
428 if (!predicate)
429 return false;
430 const contextTypes = extension.descriptor().contextTypes;
431 if (!contextTypes)
432 return true;
433 for (let i = 0; i < contextTypes.length; ++i) {
434 const contextType = this._resolve(contextTypes[i]);
435 const isMatching = !!contextType && predicate(contextType);
436 if (isMatching)
437 return true;
438 }
439 return false;
440 }
441
442 /**
443 * @param {!Runtime.Extension} extension
444 * @param {?Object} context
445 * @return {boolean}
446 */
447 isExtensionApplicableToContext(extension, context) {
448 if (!context)
449 return true;
450 return this._checkExtensionApplicability(extension, isInstanceOf);
451
452 /**
453 * @param {!Function} targetType
454 * @return {boolean}
455 */
456 function isInstanceOf(targetType) {
457 return context instanceof targetType;
458 }
459 }
460
461 /**
462 * @param {!Runtime.Extension} extension
463 * @param {!Set.<!Function>=} currentContextTypes
464 * @return {boolean}
465 */
466 isExtensionApplicableToContextTypes(extension, currentContextTypes) {
467 if (!extension.descriptor().contextTypes)
468 return true;
469
470 return this._checkExtensionApplicability(extension, currentContextTypes ? isContextTypeKnown : null);
471
472 /**
473 * @param {!Function} targetType
474 * @return {boolean}
475 */
476 function isContextTypeKnown(targetType) {
477 return currentContextTypes.has(targetType);
478 }
479 }
480
481 /**
482 * @param {*} type
483 * @param {?Object=} context
484 * @param {boolean=} sortByTitle
485 * @return {!Array.<!Runtime.Extension>}
486 */
487 extensions(type, context, sortByTitle) {
488 return this._extensions.filter(filter).sort(sortByTitle ? titleComparator : orderComparator);
489
490 /**
491 * @param {!Runtime.Extension} extension
492 * @return {boolean}
493 */
494 function filter(extension) {
495 if (extension._type !== type && extension._typeClass() !== type)
496 return false;
497 if (!extension.enabled())
498 return false;
499 return !context || extension.isApplicable(context);
500 }
501
502 /**
503 * @param {!Runtime.Extension} extension1
504 * @param {!Runtime.Extension} extension2
505 * @return {number}
506 */
507 function orderComparator(extension1, extension2) {
508 const order1 = extension1.descriptor()['order'] || 0;
509 const order2 = extension2.descriptor()['order'] || 0;
510 return order1 - order2;
511 }
512
513 /**
514 * @param {!Runtime.Extension} extension1
515 * @param {!Runtime.Extension} extension2
516 * @return {number}
517 */
518 function titleComparator(extension1, extension2) {
519 const title1 = extension1.title() || '';
520 const title2 = extension2.title() || '';
521 return title1.localeCompare(title2);
522 }
523 }
524
525 /**
526 * @param {*} type
527 * @param {?Object=} context
528 * @return {?Runtime.Extension}
529 */
530 extension(type, context) {
531 return this.extensions(type, context)[0] || null;
532 }
533
534 /**
535 * @param {*} type
536 * @param {?Object=} context
537 * @return {!Promise.<!Array.<!Object>>}
538 */
539 allInstances(type, context) {
540 return Promise.all(this.extensions(type, context).map(extension => extension.instance()));
541 }
542
543 /**
544 * @return {?function(new:Object)}
545 */
546 _resolve(typeName) {
547 if (!this._cachedTypeClasses[typeName]) {
548 const path = typeName.split('.');
549 let object = self;
550 for (let i = 0; object && (i < path.length); ++i)
551 object = object[path[i]];
552 if (object)
553 this._cachedTypeClasses[typeName] = /** @type function(new:Object) */ (object);
554 }
555 return this._cachedTypeClasses[typeName] || null;
556 }
557
558 /**
Alexei Filippovf2e42e42019-04-04 21:40:45 +0000559 * @param {function(new:T)} constructorFunction
560 * @return {!T}
561 * @template T
Blink Reformat4c46d092018-04-07 15:32:37 +0000562 */
563 sharedInstance(constructorFunction) {
564 if (Runtime._instanceSymbol in constructorFunction &&
565 Object.getOwnPropertySymbols(constructorFunction).includes(Runtime._instanceSymbol))
566 return constructorFunction[Runtime._instanceSymbol];
567
568 const instance = new constructorFunction();
569 constructorFunction[Runtime._instanceSymbol] = instance;
570 return instance;
571 }
572};
573
Ingvar Stepanyand48ae082018-11-07 04:38:32 +0000574/** @type {!URLSearchParams} */
575Runtime._queryParamsObject = new URLSearchParams(Runtime.queryParamsString());
Blink Reformat4c46d092018-04-07 15:32:37 +0000576
577Runtime._instanceSymbol = Symbol('instance');
578
579/**
580 * @type {!Object.<string, string>}
581 */
582Runtime.cachedResources = {
583 __proto__: null
584};
585
586
587Runtime._console = console;
588Runtime._originalAssert = console.assert;
589
590
591Runtime._platform = '';
592
593
594/**
595 * @unrestricted
596 */
597Runtime.ModuleDescriptor = class {
598 constructor() {
599 /**
600 * @type {string}
601 */
602 this.name;
603
604 /**
605 * @type {!Array.<!Runtime.ExtensionDescriptor>}
606 */
607 this.extensions;
608
609 /**
610 * @type {!Array.<string>|undefined}
611 */
612 this.dependencies;
613
614 /**
615 * @type {!Array.<string>}
616 */
617 this.scripts;
618
619 /**
620 * @type {string|undefined}
621 */
622 this.condition;
623
624 /**
625 * @type {boolean|undefined}
626 */
627 this.remote;
628 }
629};
630
631/**
632 * @unrestricted
633 */
634Runtime.ExtensionDescriptor = class {
635 constructor() {
636 /**
637 * @type {string}
638 */
639 this.type;
640
641 /**
642 * @type {string|undefined}
643 */
644 this.className;
645
646 /**
647 * @type {string|undefined}
648 */
649 this.factoryName;
650
651 /**
652 * @type {!Array.<string>|undefined}
653 */
654 this.contextTypes;
655 }
656};
657
658/**
659 * @unrestricted
660 */
661Runtime.Module = class {
662 /**
663 * @param {!Runtime} manager
664 * @param {!Runtime.ModuleDescriptor} descriptor
665 */
666 constructor(manager, descriptor) {
667 this._manager = manager;
668 this._descriptor = descriptor;
669 this._name = descriptor.name;
670 /** @type {!Array<!Runtime.Extension>} */
671 this._extensions = [];
672
673 /** @type {!Map<string, !Array<!Runtime.Extension>>} */
674 this._extensionsByClassName = new Map();
675 const extensions = /** @type {?Array.<!Runtime.ExtensionDescriptor>} */ (descriptor.extensions);
676 for (let i = 0; extensions && i < extensions.length; ++i) {
677 const extension = new Runtime.Extension(this, extensions[i]);
678 this._manager._extensions.push(extension);
679 this._extensions.push(extension);
680 }
681 this._loadedForTest = false;
682 }
683
684 /**
685 * @return {string}
686 */
687 name() {
688 return this._name;
689 }
690
691 /**
692 * @return {boolean}
693 */
694 enabled() {
695 return Runtime._isDescriptorEnabled(this._descriptor);
696 }
697
698 /**
699 * @param {string} name
700 * @return {string}
701 */
702 resource(name) {
703 const fullName = this._name + '/' + name;
704 const content = Runtime.cachedResources[fullName];
705 if (!content)
706 throw new Error(fullName + ' not preloaded. Check module.json');
707 return content;
708 }
709
710 /**
711 * @return {!Promise.<undefined>}
712 */
713 _loadPromise() {
714 if (!this.enabled())
715 return Promise.reject(new Error('Module ' + this._name + ' is not enabled'));
716
717 if (this._pendingLoadPromise)
718 return this._pendingLoadPromise;
719
720 const dependencies = this._descriptor.dependencies;
721 const dependencyPromises = [];
722 for (let i = 0; dependencies && i < dependencies.length; ++i)
723 dependencyPromises.push(this._manager._modulesMap[dependencies[i]]._loadPromise());
724
725 this._pendingLoadPromise = Promise.all(dependencyPromises)
726 .then(this._loadResources.bind(this))
727 .then(this._loadScripts.bind(this))
728 .then(() => this._loadedForTest = true);
729
730 return this._pendingLoadPromise;
731 }
732
733 /**
734 * @return {!Promise.<undefined>}
735 * @this {Runtime.Module}
736 */
737 _loadResources() {
738 const resources = this._descriptor['resources'];
739 if (!resources || !resources.length)
740 return Promise.resolve();
741 const promises = [];
742 for (let i = 0; i < resources.length; ++i) {
743 const url = this._modularizeURL(resources[i]);
744 promises.push(Runtime._loadResourceIntoCache(url, true));
745 }
746 return Promise.all(promises).then(undefined);
747 }
748
749 /**
750 * @return {!Promise.<undefined>}
751 */
752 _loadScripts() {
753 if (!this._descriptor.scripts || !this._descriptor.scripts.length)
754 return Promise.resolve();
755
756 // Module namespaces.
757 // NOTE: Update scripts/special_case_namespaces.json if you add a special cased namespace.
758 // The namespace keyword confuses clang-format.
759 // clang-format off
760 const specialCases = {
761 'sdk': 'SDK',
762 'js_sdk': 'JSSDK',
763 'browser_sdk': 'BrowserSDK',
764 'ui': 'UI',
765 'object_ui': 'ObjectUI',
Joel Einbinder3f23eb22018-05-14 23:27:51 +0000766 'javascript_metadata': 'JavaScriptMetadata',
Blink Reformat4c46d092018-04-07 15:32:37 +0000767 'perf_ui': 'PerfUI',
768 'har_importer': 'HARImporter',
769 'sdk_test_runner': 'SDKTestRunner',
770 'cpu_profiler_test_runner': 'CPUProfilerTestRunner'
771 };
772 const namespace = specialCases[this._name] || this._name.split('_').map(a => a.substring(0, 1).toUpperCase() + a.substring(1)).join('');
773 self[namespace] = self[namespace] || {};
774 // clang-format on
775 return Runtime._loadScriptsPromise(this._descriptor.scripts.map(this._modularizeURL, this), this._remoteBase());
776 }
777
778 /**
779 * @param {string} resourceName
780 */
781 _modularizeURL(resourceName) {
782 return Runtime.normalizePath(this._name + '/' + resourceName);
783 }
784
785 /**
786 * @return {string|undefined}
787 */
788 _remoteBase() {
789 return !Runtime.queryParam('debugFrontend') && this._descriptor.remote && Runtime._remoteBase || undefined;
790 }
791
792 /**
793 * @param {string} value
794 * @return {string}
795 */
796 substituteURL(value) {
797 const base = this._remoteBase() || '';
798 return value.replace(/@url\(([^\)]*?)\)/g, convertURL.bind(this));
799
800 function convertURL(match, url) {
801 return base + this._modularizeURL(url);
802 }
803 }
804};
805
806
807/**
808 * @unrestricted
809 */
810Runtime.Extension = class {
811 /**
812 * @param {!Runtime.Module} module
813 * @param {!Runtime.ExtensionDescriptor} descriptor
814 */
815 constructor(module, descriptor) {
816 this._module = module;
817 this._descriptor = descriptor;
818
819 this._type = descriptor.type;
820 this._hasTypeClass = this._type.charAt(0) === '@';
821
822 /**
823 * @type {?string}
824 */
825 this._className = descriptor.className || null;
826 this._factoryName = descriptor.factoryName || null;
827 }
828
829 /**
830 * @return {!Object}
831 */
832 descriptor() {
833 return this._descriptor;
834 }
835
836 /**
837 * @return {!Runtime.Module}
838 */
839 module() {
840 return this._module;
841 }
842
843 /**
844 * @return {boolean}
845 */
846 enabled() {
847 return this._module.enabled() && Runtime._isDescriptorEnabled(this.descriptor());
848 }
849
850 /**
851 * @return {?function(new:Object)}
852 */
853 _typeClass() {
854 if (!this._hasTypeClass)
855 return null;
856 return this._module._manager._resolve(this._type.substring(1));
857 }
858
859 /**
860 * @param {?Object} context
861 * @return {boolean}
862 */
863 isApplicable(context) {
864 return this._module._manager.isExtensionApplicableToContext(this, context);
865 }
866
867 /**
868 * @return {!Promise.<!Object>}
869 */
870 instance() {
871 return this._module._loadPromise().then(this._createInstance.bind(this));
872 }
873
874 /**
875 * @return {boolean}
876 */
877 canInstantiate() {
878 return !!(this._className || this._factoryName);
879 }
880
881 /**
882 * @return {!Object}
883 */
884 _createInstance() {
885 const className = this._className || this._factoryName;
886 if (!className)
887 throw new Error('Could not instantiate extension with no class');
888 const constructorFunction = self.eval(/** @type {string} */ (className));
889 if (!(constructorFunction instanceof Function))
890 throw new Error('Could not instantiate: ' + className);
891 if (this._className)
892 return this._module._manager.sharedInstance(constructorFunction);
893 return new constructorFunction(this);
894 }
895
896 /**
897 * @return {string}
898 */
899 title() {
Vidal Diazleal0b6aff42019-04-20 01:15:43 +0000900 const title = this._descriptor['title-' + Runtime._platform] || this._descriptor['title'];
901 if (title && Runtime._l10nCallback)
902 return Runtime._l10nCallback(title);
903 return title;
Blink Reformat4c46d092018-04-07 15:32:37 +0000904 }
905
906 /**
907 * @param {function(new:Object)} contextType
908 * @return {boolean}
909 */
910 hasContextType(contextType) {
911 const contextTypes = this.descriptor().contextTypes;
912 if (!contextTypes)
913 return false;
914 for (let i = 0; i < contextTypes.length; ++i) {
915 if (contextType === this._module._manager._resolve(contextTypes[i]))
916 return true;
917 }
918 return false;
919 }
920};
921
922/**
923 * @unrestricted
924 */
925Runtime.ExperimentsSupport = class {
926 constructor() {
927 this._supportEnabled = Runtime.queryParam('experiments') !== null;
928 this._experiments = [];
929 this._experimentNames = {};
930 this._enabledTransiently = {};
Jeff Fisher85b08e12019-06-13 22:24:21 +0000931 /** @type {!Set<string>} */
932 this._serverEnabled = new Set();
Blink Reformat4c46d092018-04-07 15:32:37 +0000933 }
934
935 /**
936 * @return {!Array.<!Runtime.Experiment>}
937 */
938 allConfigurableExperiments() {
939 const result = [];
940 for (let i = 0; i < this._experiments.length; i++) {
941 const experiment = this._experiments[i];
942 if (!this._enabledTransiently[experiment.name])
943 result.push(experiment);
944 }
945 return result;
946 }
947
948 /**
949 * @return {boolean}
950 */
951 supportEnabled() {
952 return this._supportEnabled;
953 }
954
955 /**
956 * @param {!Object} value
957 */
958 _setExperimentsSetting(value) {
959 if (!self.localStorage)
960 return;
961 self.localStorage['experiments'] = JSON.stringify(value);
962 }
963
964 /**
965 * @param {string} experimentName
966 * @param {string} experimentTitle
967 * @param {boolean=} hidden
968 */
969 register(experimentName, experimentTitle, hidden) {
970 Runtime._assert(!this._experimentNames[experimentName], 'Duplicate registration of experiment ' + experimentName);
971 this._experimentNames[experimentName] = true;
972 this._experiments.push(new Runtime.Experiment(this, experimentName, experimentTitle, !!hidden));
973 }
974
975 /**
976 * @param {string} experimentName
977 * @return {boolean}
978 */
979 isEnabled(experimentName) {
980 this._checkExperiment(experimentName);
Pavel Feldmanc0395912018-08-03 02:05:00 +0000981 // Check for explicitly disabled experiments first - the code could call setEnable(false) on the experiment enabled
982 // by default and we should respect that.
983 if (Runtime._experimentsSetting()[experimentName] === false)
984 return false;
Blink Reformat4c46d092018-04-07 15:32:37 +0000985 if (this._enabledTransiently[experimentName])
986 return true;
Jeff Fisher85b08e12019-06-13 22:24:21 +0000987 if (this._serverEnabled.has(experimentName))
988 return true;
Blink Reformat4c46d092018-04-07 15:32:37 +0000989 if (!this.supportEnabled())
990 return false;
991
992 return !!Runtime._experimentsSetting()[experimentName];
993 }
994
995 /**
996 * @param {string} experimentName
997 * @param {boolean} enabled
998 */
999 setEnabled(experimentName, enabled) {
1000 this._checkExperiment(experimentName);
1001 const experimentsSetting = Runtime._experimentsSetting();
1002 experimentsSetting[experimentName] = enabled;
1003 this._setExperimentsSetting(experimentsSetting);
1004 }
1005
1006 /**
1007 * @param {!Array.<string>} experimentNames
1008 */
1009 setDefaultExperiments(experimentNames) {
1010 for (let i = 0; i < experimentNames.length; ++i) {
1011 this._checkExperiment(experimentNames[i]);
1012 this._enabledTransiently[experimentNames[i]] = true;
1013 }
1014 }
1015
1016 /**
Jeff Fisher85b08e12019-06-13 22:24:21 +00001017 * @param {!Array.<string>} experimentNames
1018 */
1019 setServerEnabledExperiments(experimentNames) {
1020 for (const experiment of experimentNames) {
1021 this._checkExperiment(experiment);
1022 this._serverEnabled.add(experiment);
1023 }
1024 }
1025
1026 /**
Blink Reformat4c46d092018-04-07 15:32:37 +00001027 * @param {string} experimentName
1028 */
1029 enableForTest(experimentName) {
1030 this._checkExperiment(experimentName);
1031 this._enabledTransiently[experimentName] = true;
1032 }
1033
1034 clearForTest() {
1035 this._experiments = [];
1036 this._experimentNames = {};
1037 this._enabledTransiently = {};
Jeff Fisher85b08e12019-06-13 22:24:21 +00001038 this._serverEnabled.clear();
Blink Reformat4c46d092018-04-07 15:32:37 +00001039 }
1040
1041 cleanUpStaleExperiments() {
1042 const experimentsSetting = Runtime._experimentsSetting();
1043 const cleanedUpExperimentSetting = {};
1044 for (let i = 0; i < this._experiments.length; ++i) {
1045 const experimentName = this._experiments[i].name;
1046 if (experimentsSetting[experimentName])
1047 cleanedUpExperimentSetting[experimentName] = true;
1048 }
1049 this._setExperimentsSetting(cleanedUpExperimentSetting);
1050 }
1051
1052 /**
1053 * @param {string} experimentName
1054 */
1055 _checkExperiment(experimentName) {
1056 Runtime._assert(this._experimentNames[experimentName], 'Unknown experiment ' + experimentName);
1057 }
1058};
1059
1060/**
1061 * @unrestricted
1062 */
1063Runtime.Experiment = class {
1064 /**
1065 * @param {!Runtime.ExperimentsSupport} experiments
1066 * @param {string} name
1067 * @param {string} title
1068 * @param {boolean} hidden
1069 */
1070 constructor(experiments, name, title, hidden) {
1071 this.name = name;
1072 this.title = title;
1073 this.hidden = hidden;
1074 this._experiments = experiments;
1075 }
1076
1077 /**
1078 * @return {boolean}
1079 */
1080 isEnabled() {
1081 return this._experiments.isEnabled(this.name);
1082 }
1083
1084 /**
1085 * @param {boolean} enabled
1086 */
1087 setEnabled(enabled) {
1088 this._experiments.setEnabled(this.name, enabled);
1089 }
1090};
1091
Blink Reformat4c46d092018-04-07 15:32:37 +00001092// This must be constructed after the query parameters have been parsed.
1093Runtime.experiments = new Runtime.ExperimentsSupport();
1094
1095/** @type {Function} */
Pavel Feldman63e89eb2018-11-25 05:47:09 +00001096Runtime._appStartedPromiseCallback;
1097Runtime._appStartedPromise = new Promise(fulfil => Runtime._appStartedPromiseCallback = fulfil);
Vidal Diazleal0b6aff42019-04-20 01:15:43 +00001098
1099/** @type {function(string):string} */
1100Runtime._l10nCallback;
1101
Blink Reformat4c46d092018-04-07 15:32:37 +00001102/**
1103 * @type {?string}
1104 */
1105Runtime._remoteBase;
1106(function validateRemoteBase() {
James Lissiak821d2282019-05-15 15:32:04 +00001107 if (location.href.startsWith('devtools://devtools/bundled/') && Runtime.queryParam('remoteBase')) {
Blink Reformat4c46d092018-04-07 15:32:37 +00001108 const versionMatch = /\/serve_file\/(@[0-9a-zA-Z]+)\/?$/.exec(Runtime.queryParam('remoteBase'));
1109 if (versionMatch)
1110 Runtime._remoteBase = `${location.origin}/remote/serve_file/${versionMatch[1]}/`;
1111 }
1112})();
1113
1114
1115/**
1116 * @interface
1117 */
1118function ServicePort() {
1119}
1120
1121ServicePort.prototype = {
1122 /**
1123 * @param {function(string)} messageHandler
1124 * @param {function(string)} closeHandler
1125 */
1126 setHandlers(messageHandler, closeHandler) {},
1127
1128 /**
1129 * @param {string} message
1130 * @return {!Promise<boolean>}
1131 */
1132 send(message) {},
1133
1134 /**
1135 * @return {!Promise<boolean>}
1136 */
1137 close() {}
1138};
1139
1140/** @type {!Runtime} */
1141var runtime; // eslint-disable-line