blob: b829afead2c5981fed8f29177b3a7f258cca07c2 [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 /**
Mandy Chen20b48f62019-09-18 19:28:22 +0000153 * @param {string} scriptName
154 * @param {string=} base
155 * @return {string}
156 */
157 static getResourceURL(scriptName, base) {
158 const sourceURL = (base || self._importScriptPathPrefix) + scriptName;
159 const schemaIndex = sourceURL.indexOf('://') + 3;
160 let pathIndex = sourceURL.indexOf('/', schemaIndex);
161 if (pathIndex === -1)
162 pathIndex = sourceURL.length;
163 return sourceURL.substring(0, pathIndex) + Runtime.normalizePath(sourceURL.substring(pathIndex));
164 }
165
166 /**
Blink Reformat4c46d092018-04-07 15:32:37 +0000167 * @param {!Array.<string>} scriptNames
168 * @param {string=} base
169 * @return {!Promise.<undefined>}
170 */
171 static _loadScriptsPromise(scriptNames, base) {
172 /** @type {!Array<!Promise<undefined>>} */
173 const promises = [];
174 /** @type {!Array<string>} */
175 const urls = [];
176 const sources = new Array(scriptNames.length);
177 let scriptToEval = 0;
178 for (let i = 0; i < scriptNames.length; ++i) {
179 const scriptName = scriptNames[i];
Mandy Chen20b48f62019-09-18 19:28:22 +0000180 const sourceURL = Runtime.getResourceURL(scriptName, base);
Blink Reformat4c46d092018-04-07 15:32:37 +0000181
182 if (_loadedScripts[sourceURL])
183 continue;
184 urls.push(sourceURL);
Patrick Hulceb8c09f32018-06-11 21:28:22 +0000185 const loadResourcePromise =
186 base ? Runtime.loadResourcePromiseWithFallback(sourceURL) : Runtime.loadResourcePromise(sourceURL);
187 promises.push(
188 loadResourcePromise.then(scriptSourceLoaded.bind(null, i), scriptSourceLoaded.bind(null, i, undefined)));
Blink Reformat4c46d092018-04-07 15:32:37 +0000189 }
190 return Promise.all(promises).then(undefined);
191
192 /**
193 * @param {number} scriptNumber
194 * @param {string=} scriptSource
195 */
196 function scriptSourceLoaded(scriptNumber, scriptSource) {
197 sources[scriptNumber] = scriptSource || '';
198 // Eval scripts as fast as possible.
199 while (typeof sources[scriptToEval] !== 'undefined') {
200 evaluateScript(urls[scriptToEval], sources[scriptToEval]);
201 ++scriptToEval;
202 }
203 }
204
205 /**
206 * @param {string} sourceURL
207 * @param {string=} scriptSource
208 */
209 function evaluateScript(sourceURL, scriptSource) {
210 _loadedScripts[sourceURL] = true;
211 if (!scriptSource) {
212 // Do not reject, as this is normal in the hosted mode.
213 console.error('Empty response arrived for script \'' + sourceURL + '\'');
214 return;
215 }
216 self.eval(scriptSource + '\n//# sourceURL=' + sourceURL);
217 }
218 }
219
220 /**
221 * @param {string} url
222 * @param {boolean} appendSourceURL
223 * @return {!Promise<undefined>}
224 */
225 static _loadResourceIntoCache(url, appendSourceURL) {
226 return Runtime.loadResourcePromise(url).then(
227 cacheResource.bind(this, url), cacheResource.bind(this, url, undefined));
228
229 /**
230 * @param {string} path
231 * @param {string=} content
232 */
233 function cacheResource(path, content) {
234 if (!content) {
235 console.error('Failed to load resource: ' + path);
236 return;
237 }
238 const sourceURL = appendSourceURL ? Runtime.resolveSourceURL(path) : '';
239 Runtime.cachedResources[path] = content + sourceURL;
240 }
241 }
242
243 /**
244 * @return {!Promise}
245 */
Pavel Feldman63e89eb2018-11-25 05:47:09 +0000246 static async appStarted() {
247 return Runtime._appStartedPromise;
Blink Reformat4c46d092018-04-07 15:32:37 +0000248 }
249
250 /**
251 * @param {string} appName
252 * @return {!Promise.<undefined>}
253 */
254 static async startApplication(appName) {
255 console.timeStamp('Runtime.startApplication');
256
257 const allDescriptorsByName = {};
258 for (let i = 0; i < allDescriptors.length; ++i) {
259 const d = allDescriptors[i];
260 allDescriptorsByName[d['name']] = d;
261 }
262
263 if (!applicationDescriptor) {
264 let data = await Runtime.loadResourcePromise(appName + '.json');
265 applicationDescriptor = JSON.parse(data);
266 let descriptor = applicationDescriptor;
267 while (descriptor.extends) {
268 data = await Runtime.loadResourcePromise(descriptor.extends + '.json');
269 descriptor = JSON.parse(data);
270 applicationDescriptor.modules = descriptor.modules.concat(applicationDescriptor.modules);
271 }
272 }
273
274 const configuration = applicationDescriptor.modules;
275 const moduleJSONPromises = [];
276 const coreModuleNames = [];
277 for (let i = 0; i < configuration.length; ++i) {
278 const descriptor = configuration[i];
279 const name = descriptor['name'];
280 const moduleJSON = allDescriptorsByName[name];
281 if (moduleJSON)
282 moduleJSONPromises.push(Promise.resolve(moduleJSON));
283 else
284 moduleJSONPromises.push(Runtime.loadResourcePromise(name + '/module.json').then(JSON.parse.bind(JSON)));
285 if (descriptor['type'] === 'autostart')
286 coreModuleNames.push(name);
287 }
288
289 const moduleDescriptors = await Promise.all(moduleJSONPromises);
290
291 for (let i = 0; i < moduleDescriptors.length; ++i) {
292 moduleDescriptors[i].name = configuration[i]['name'];
293 moduleDescriptors[i].condition = configuration[i]['condition'];
294 moduleDescriptors[i].remote = configuration[i]['type'] === 'remote';
295 }
296 self.runtime = new Runtime(moduleDescriptors);
297 if (coreModuleNames)
Pavel Feldman63e89eb2018-11-25 05:47:09 +0000298 await self.runtime._loadAutoStartModules(coreModuleNames);
299 Runtime._appStartedPromiseCallback();
Blink Reformat4c46d092018-04-07 15:32:37 +0000300 }
301
302 /**
303 * @param {string} appName
304 * @return {!Promise.<undefined>}
305 */
306 static startWorker(appName) {
307 return Runtime.startApplication(appName).then(sendWorkerReady);
308
309 function sendWorkerReady() {
310 self.postMessage('workerReady');
311 }
312 }
313
314 /**
315 * @param {string} name
316 * @return {?string}
317 */
318 static queryParam(name) {
Ingvar Stepanyand48ae082018-11-07 04:38:32 +0000319 return Runtime._queryParamsObject.get(name);
Blink Reformat4c46d092018-04-07 15:32:37 +0000320 }
321
322 /**
323 * @return {string}
324 */
325 static queryParamsString() {
326 return location.search;
327 }
328
329 /**
330 * @return {!Object}
331 */
332 static _experimentsSetting() {
333 try {
334 return /** @type {!Object} */ (
335 JSON.parse(self.localStorage && self.localStorage['experiments'] ? self.localStorage['experiments'] : '{}'));
336 } catch (e) {
337 console.error('Failed to parse localStorage[\'experiments\']');
338 return {};
339 }
340 }
341
342 static _assert(value, message) {
343 if (value)
344 return;
345 Runtime._originalAssert.call(Runtime._console, value, message + ' ' + new Error().stack);
346 }
347
348 /**
349 * @param {string} platform
350 */
351 static setPlatform(platform) {
352 Runtime._platform = platform;
353 }
354
355 /**
356 * @param {!Object} descriptor
357 * @return {boolean}
358 */
359 static _isDescriptorEnabled(descriptor) {
360 const activatorExperiment = descriptor['experiment'];
361 if (activatorExperiment === '*')
362 return Runtime.experiments.supportEnabled();
363 if (activatorExperiment && activatorExperiment.startsWith('!') &&
364 Runtime.experiments.isEnabled(activatorExperiment.substring(1)))
365 return false;
366 if (activatorExperiment && !activatorExperiment.startsWith('!') &&
367 !Runtime.experiments.isEnabled(activatorExperiment))
368 return false;
369 const condition = descriptor['condition'];
370 if (condition && !condition.startsWith('!') && !Runtime.queryParam(condition))
371 return false;
372 if (condition && condition.startsWith('!') && Runtime.queryParam(condition.substring(1)))
373 return false;
374 return true;
375 }
376
377 /**
378 * @param {string} path
379 * @return {string}
380 */
381 static resolveSourceURL(path) {
382 let sourceURL = self.location.href;
383 if (self.location.search)
384 sourceURL = sourceURL.replace(self.location.search, '');
385 sourceURL = sourceURL.substring(0, sourceURL.lastIndexOf('/') + 1) + path;
386 return '\n/*# sourceURL=' + sourceURL + ' */';
387 }
388
Vidal Diazleal0b6aff42019-04-20 01:15:43 +0000389 /**
390 * @param {function(string):string} localizationFunction
391 */
392 static setL10nCallback(localizationFunction) {
393 Runtime._l10nCallback = localizationFunction;
394 }
395
Blink Reformat4c46d092018-04-07 15:32:37 +0000396 useTestBase() {
397 Runtime._remoteBase = 'http://localhost:8000/inspector-sources/';
398 if (Runtime.queryParam('debugFrontend'))
399 Runtime._remoteBase += 'debug/';
400 }
401
402 /**
Mandy Chen20b48f62019-09-18 19:28:22 +0000403 * @param {string} moduleName
404 * @return {!Runtime.Module}
405 */
406 module(moduleName) {
407 return this._modulesMap[moduleName];
408 }
409
410 /**
Blink Reformat4c46d092018-04-07 15:32:37 +0000411 * @param {!Runtime.ModuleDescriptor} descriptor
412 */
413 _registerModule(descriptor) {
414 const module = new Runtime.Module(this, descriptor);
415 this._modules.push(module);
416 this._modulesMap[descriptor['name']] = module;
417 }
418
419 /**
420 * @param {string} moduleName
421 * @return {!Promise.<undefined>}
422 */
423 loadModulePromise(moduleName) {
424 return this._modulesMap[moduleName]._loadPromise();
425 }
426
427 /**
428 * @param {!Array.<string>} moduleNames
429 * @return {!Promise.<!Array.<*>>}
430 */
431 _loadAutoStartModules(moduleNames) {
432 const promises = [];
433 for (let i = 0; i < moduleNames.length; ++i)
434 promises.push(this.loadModulePromise(moduleNames[i]));
435 return Promise.all(promises);
436 }
437
438 /**
439 * @param {!Runtime.Extension} extension
440 * @param {?function(function(new:Object)):boolean} predicate
441 * @return {boolean}
442 */
443 _checkExtensionApplicability(extension, predicate) {
444 if (!predicate)
445 return false;
446 const contextTypes = extension.descriptor().contextTypes;
447 if (!contextTypes)
448 return true;
449 for (let i = 0; i < contextTypes.length; ++i) {
450 const contextType = this._resolve(contextTypes[i]);
451 const isMatching = !!contextType && predicate(contextType);
452 if (isMatching)
453 return true;
454 }
455 return false;
456 }
457
458 /**
459 * @param {!Runtime.Extension} extension
460 * @param {?Object} context
461 * @return {boolean}
462 */
463 isExtensionApplicableToContext(extension, context) {
464 if (!context)
465 return true;
466 return this._checkExtensionApplicability(extension, isInstanceOf);
467
468 /**
469 * @param {!Function} targetType
470 * @return {boolean}
471 */
472 function isInstanceOf(targetType) {
473 return context instanceof targetType;
474 }
475 }
476
477 /**
478 * @param {!Runtime.Extension} extension
479 * @param {!Set.<!Function>=} currentContextTypes
480 * @return {boolean}
481 */
482 isExtensionApplicableToContextTypes(extension, currentContextTypes) {
483 if (!extension.descriptor().contextTypes)
484 return true;
485
486 return this._checkExtensionApplicability(extension, currentContextTypes ? isContextTypeKnown : null);
487
488 /**
489 * @param {!Function} targetType
490 * @return {boolean}
491 */
492 function isContextTypeKnown(targetType) {
493 return currentContextTypes.has(targetType);
494 }
495 }
496
497 /**
498 * @param {*} type
499 * @param {?Object=} context
500 * @param {boolean=} sortByTitle
501 * @return {!Array.<!Runtime.Extension>}
502 */
503 extensions(type, context, sortByTitle) {
504 return this._extensions.filter(filter).sort(sortByTitle ? titleComparator : orderComparator);
505
506 /**
507 * @param {!Runtime.Extension} extension
508 * @return {boolean}
509 */
510 function filter(extension) {
511 if (extension._type !== type && extension._typeClass() !== type)
512 return false;
513 if (!extension.enabled())
514 return false;
515 return !context || extension.isApplicable(context);
516 }
517
518 /**
519 * @param {!Runtime.Extension} extension1
520 * @param {!Runtime.Extension} extension2
521 * @return {number}
522 */
523 function orderComparator(extension1, extension2) {
524 const order1 = extension1.descriptor()['order'] || 0;
525 const order2 = extension2.descriptor()['order'] || 0;
526 return order1 - order2;
527 }
528
529 /**
530 * @param {!Runtime.Extension} extension1
531 * @param {!Runtime.Extension} extension2
532 * @return {number}
533 */
534 function titleComparator(extension1, extension2) {
535 const title1 = extension1.title() || '';
536 const title2 = extension2.title() || '';
537 return title1.localeCompare(title2);
538 }
539 }
540
541 /**
542 * @param {*} type
543 * @param {?Object=} context
544 * @return {?Runtime.Extension}
545 */
546 extension(type, context) {
547 return this.extensions(type, context)[0] || null;
548 }
549
550 /**
551 * @param {*} type
552 * @param {?Object=} context
553 * @return {!Promise.<!Array.<!Object>>}
554 */
555 allInstances(type, context) {
556 return Promise.all(this.extensions(type, context).map(extension => extension.instance()));
557 }
558
559 /**
560 * @return {?function(new:Object)}
561 */
562 _resolve(typeName) {
563 if (!this._cachedTypeClasses[typeName]) {
564 const path = typeName.split('.');
565 let object = self;
566 for (let i = 0; object && (i < path.length); ++i)
567 object = object[path[i]];
568 if (object)
569 this._cachedTypeClasses[typeName] = /** @type function(new:Object) */ (object);
570 }
571 return this._cachedTypeClasses[typeName] || null;
572 }
573
574 /**
Alexei Filippovf2e42e42019-04-04 21:40:45 +0000575 * @param {function(new:T)} constructorFunction
576 * @return {!T}
577 * @template T
Blink Reformat4c46d092018-04-07 15:32:37 +0000578 */
579 sharedInstance(constructorFunction) {
580 if (Runtime._instanceSymbol in constructorFunction &&
581 Object.getOwnPropertySymbols(constructorFunction).includes(Runtime._instanceSymbol))
582 return constructorFunction[Runtime._instanceSymbol];
583
584 const instance = new constructorFunction();
585 constructorFunction[Runtime._instanceSymbol] = instance;
586 return instance;
587 }
588};
589
Ingvar Stepanyand48ae082018-11-07 04:38:32 +0000590/** @type {!URLSearchParams} */
591Runtime._queryParamsObject = new URLSearchParams(Runtime.queryParamsString());
Blink Reformat4c46d092018-04-07 15:32:37 +0000592
593Runtime._instanceSymbol = Symbol('instance');
594
595/**
596 * @type {!Object.<string, string>}
597 */
598Runtime.cachedResources = {
599 __proto__: null
600};
601
602
603Runtime._console = console;
604Runtime._originalAssert = console.assert;
605
606
607Runtime._platform = '';
608
609
610/**
611 * @unrestricted
612 */
613Runtime.ModuleDescriptor = class {
614 constructor() {
615 /**
616 * @type {string}
617 */
618 this.name;
619
620 /**
621 * @type {!Array.<!Runtime.ExtensionDescriptor>}
622 */
623 this.extensions;
624
625 /**
626 * @type {!Array.<string>|undefined}
627 */
628 this.dependencies;
629
630 /**
631 * @type {!Array.<string>}
632 */
633 this.scripts;
634
635 /**
636 * @type {string|undefined}
637 */
638 this.condition;
639
640 /**
641 * @type {boolean|undefined}
642 */
643 this.remote;
644 }
645};
646
647/**
648 * @unrestricted
649 */
650Runtime.ExtensionDescriptor = class {
651 constructor() {
652 /**
653 * @type {string}
654 */
655 this.type;
656
657 /**
658 * @type {string|undefined}
659 */
660 this.className;
661
662 /**
663 * @type {string|undefined}
664 */
665 this.factoryName;
666
667 /**
668 * @type {!Array.<string>|undefined}
669 */
670 this.contextTypes;
671 }
672};
673
674/**
675 * @unrestricted
676 */
677Runtime.Module = class {
678 /**
679 * @param {!Runtime} manager
680 * @param {!Runtime.ModuleDescriptor} descriptor
681 */
682 constructor(manager, descriptor) {
683 this._manager = manager;
684 this._descriptor = descriptor;
685 this._name = descriptor.name;
686 /** @type {!Array<!Runtime.Extension>} */
687 this._extensions = [];
688
689 /** @type {!Map<string, !Array<!Runtime.Extension>>} */
690 this._extensionsByClassName = new Map();
691 const extensions = /** @type {?Array.<!Runtime.ExtensionDescriptor>} */ (descriptor.extensions);
692 for (let i = 0; extensions && i < extensions.length; ++i) {
693 const extension = new Runtime.Extension(this, extensions[i]);
694 this._manager._extensions.push(extension);
695 this._extensions.push(extension);
696 }
697 this._loadedForTest = false;
698 }
699
700 /**
701 * @return {string}
702 */
703 name() {
704 return this._name;
705 }
706
707 /**
708 * @return {boolean}
709 */
710 enabled() {
711 return Runtime._isDescriptorEnabled(this._descriptor);
712 }
713
714 /**
715 * @param {string} name
716 * @return {string}
717 */
718 resource(name) {
719 const fullName = this._name + '/' + name;
720 const content = Runtime.cachedResources[fullName];
721 if (!content)
722 throw new Error(fullName + ' not preloaded. Check module.json');
723 return content;
724 }
725
726 /**
727 * @return {!Promise.<undefined>}
728 */
729 _loadPromise() {
730 if (!this.enabled())
731 return Promise.reject(new Error('Module ' + this._name + ' is not enabled'));
732
733 if (this._pendingLoadPromise)
734 return this._pendingLoadPromise;
735
736 const dependencies = this._descriptor.dependencies;
737 const dependencyPromises = [];
738 for (let i = 0; dependencies && i < dependencies.length; ++i)
739 dependencyPromises.push(this._manager._modulesMap[dependencies[i]]._loadPromise());
740
741 this._pendingLoadPromise = Promise.all(dependencyPromises)
742 .then(this._loadResources.bind(this))
743 .then(this._loadScripts.bind(this))
744 .then(() => this._loadedForTest = true);
745
746 return this._pendingLoadPromise;
747 }
748
749 /**
750 * @return {!Promise.<undefined>}
751 * @this {Runtime.Module}
752 */
753 _loadResources() {
754 const resources = this._descriptor['resources'];
755 if (!resources || !resources.length)
756 return Promise.resolve();
757 const promises = [];
758 for (let i = 0; i < resources.length; ++i) {
759 const url = this._modularizeURL(resources[i]);
cjamcl@google.comc5214af2019-06-25 20:31:21 +0000760 const isHtml = url.endsWith('.html');
761 promises.push(Runtime._loadResourceIntoCache(url, !isHtml /* appendSourceURL */));
Blink Reformat4c46d092018-04-07 15:32:37 +0000762 }
763 return Promise.all(promises).then(undefined);
764 }
765
766 /**
767 * @return {!Promise.<undefined>}
768 */
769 _loadScripts() {
770 if (!this._descriptor.scripts || !this._descriptor.scripts.length)
771 return Promise.resolve();
772
773 // Module namespaces.
774 // NOTE: Update scripts/special_case_namespaces.json if you add a special cased namespace.
775 // The namespace keyword confuses clang-format.
776 // clang-format off
777 const specialCases = {
778 'sdk': 'SDK',
779 'js_sdk': 'JSSDK',
780 'browser_sdk': 'BrowserSDK',
781 'ui': 'UI',
782 'object_ui': 'ObjectUI',
Joel Einbinder3f23eb22018-05-14 23:27:51 +0000783 'javascript_metadata': 'JavaScriptMetadata',
Blink Reformat4c46d092018-04-07 15:32:37 +0000784 'perf_ui': 'PerfUI',
785 'har_importer': 'HARImporter',
786 'sdk_test_runner': 'SDKTestRunner',
787 'cpu_profiler_test_runner': 'CPUProfilerTestRunner'
788 };
789 const namespace = specialCases[this._name] || this._name.split('_').map(a => a.substring(0, 1).toUpperCase() + a.substring(1)).join('');
790 self[namespace] = self[namespace] || {};
791 // clang-format on
792 return Runtime._loadScriptsPromise(this._descriptor.scripts.map(this._modularizeURL, this), this._remoteBase());
793 }
794
795 /**
796 * @param {string} resourceName
797 */
798 _modularizeURL(resourceName) {
799 return Runtime.normalizePath(this._name + '/' + resourceName);
800 }
801
802 /**
803 * @return {string|undefined}
804 */
805 _remoteBase() {
806 return !Runtime.queryParam('debugFrontend') && this._descriptor.remote && Runtime._remoteBase || undefined;
807 }
808
809 /**
Mandy Chen20b48f62019-09-18 19:28:22 +0000810 * @param {string} resourceName
811 * @return {!Promise.<string>}
812 */
813 fetchResource(resourceName) {
814 const base = this._remoteBase();
815 const sourceURL = Runtime.getResourceURL(this._modularizeURL(resourceName), base);
816 return base ? Runtime.loadResourcePromiseWithFallback(sourceURL) : Runtime.loadResourcePromise(sourceURL);
817 }
818
819 /**
Blink Reformat4c46d092018-04-07 15:32:37 +0000820 * @param {string} value
821 * @return {string}
822 */
823 substituteURL(value) {
824 const base = this._remoteBase() || '';
825 return value.replace(/@url\(([^\)]*?)\)/g, convertURL.bind(this));
826
827 function convertURL(match, url) {
828 return base + this._modularizeURL(url);
829 }
830 }
831};
832
833
834/**
835 * @unrestricted
836 */
837Runtime.Extension = class {
838 /**
839 * @param {!Runtime.Module} module
840 * @param {!Runtime.ExtensionDescriptor} descriptor
841 */
842 constructor(module, descriptor) {
843 this._module = module;
844 this._descriptor = descriptor;
845
846 this._type = descriptor.type;
847 this._hasTypeClass = this._type.charAt(0) === '@';
848
849 /**
850 * @type {?string}
851 */
852 this._className = descriptor.className || null;
853 this._factoryName = descriptor.factoryName || null;
854 }
855
856 /**
857 * @return {!Object}
858 */
859 descriptor() {
860 return this._descriptor;
861 }
862
863 /**
864 * @return {!Runtime.Module}
865 */
866 module() {
867 return this._module;
868 }
869
870 /**
871 * @return {boolean}
872 */
873 enabled() {
874 return this._module.enabled() && Runtime._isDescriptorEnabled(this.descriptor());
875 }
876
877 /**
878 * @return {?function(new:Object)}
879 */
880 _typeClass() {
881 if (!this._hasTypeClass)
882 return null;
883 return this._module._manager._resolve(this._type.substring(1));
884 }
885
886 /**
887 * @param {?Object} context
888 * @return {boolean}
889 */
890 isApplicable(context) {
891 return this._module._manager.isExtensionApplicableToContext(this, context);
892 }
893
894 /**
895 * @return {!Promise.<!Object>}
896 */
897 instance() {
898 return this._module._loadPromise().then(this._createInstance.bind(this));
899 }
900
901 /**
902 * @return {boolean}
903 */
904 canInstantiate() {
905 return !!(this._className || this._factoryName);
906 }
907
908 /**
909 * @return {!Object}
910 */
911 _createInstance() {
912 const className = this._className || this._factoryName;
913 if (!className)
914 throw new Error('Could not instantiate extension with no class');
915 const constructorFunction = self.eval(/** @type {string} */ (className));
916 if (!(constructorFunction instanceof Function))
917 throw new Error('Could not instantiate: ' + className);
918 if (this._className)
919 return this._module._manager.sharedInstance(constructorFunction);
920 return new constructorFunction(this);
921 }
922
923 /**
924 * @return {string}
925 */
926 title() {
Vidal Diazleal0b6aff42019-04-20 01:15:43 +0000927 const title = this._descriptor['title-' + Runtime._platform] || this._descriptor['title'];
928 if (title && Runtime._l10nCallback)
929 return Runtime._l10nCallback(title);
930 return title;
Blink Reformat4c46d092018-04-07 15:32:37 +0000931 }
932
933 /**
934 * @param {function(new:Object)} contextType
935 * @return {boolean}
936 */
937 hasContextType(contextType) {
938 const contextTypes = this.descriptor().contextTypes;
939 if (!contextTypes)
940 return false;
941 for (let i = 0; i < contextTypes.length; ++i) {
942 if (contextType === this._module._manager._resolve(contextTypes[i]))
943 return true;
944 }
945 return false;
946 }
947};
948
949/**
950 * @unrestricted
951 */
952Runtime.ExperimentsSupport = class {
953 constructor() {
954 this._supportEnabled = Runtime.queryParam('experiments') !== null;
955 this._experiments = [];
956 this._experimentNames = {};
957 this._enabledTransiently = {};
Jeff Fisher85b08e12019-06-13 22:24:21 +0000958 /** @type {!Set<string>} */
959 this._serverEnabled = new Set();
Blink Reformat4c46d092018-04-07 15:32:37 +0000960 }
961
962 /**
963 * @return {!Array.<!Runtime.Experiment>}
964 */
965 allConfigurableExperiments() {
966 const result = [];
967 for (let i = 0; i < this._experiments.length; i++) {
968 const experiment = this._experiments[i];
969 if (!this._enabledTransiently[experiment.name])
970 result.push(experiment);
971 }
972 return result;
973 }
974
975 /**
976 * @return {boolean}
977 */
978 supportEnabled() {
979 return this._supportEnabled;
980 }
981
982 /**
983 * @param {!Object} value
984 */
985 _setExperimentsSetting(value) {
986 if (!self.localStorage)
987 return;
988 self.localStorage['experiments'] = JSON.stringify(value);
989 }
990
991 /**
992 * @param {string} experimentName
993 * @param {string} experimentTitle
994 * @param {boolean=} hidden
995 */
996 register(experimentName, experimentTitle, hidden) {
997 Runtime._assert(!this._experimentNames[experimentName], 'Duplicate registration of experiment ' + experimentName);
998 this._experimentNames[experimentName] = true;
999 this._experiments.push(new Runtime.Experiment(this, experimentName, experimentTitle, !!hidden));
1000 }
1001
1002 /**
1003 * @param {string} experimentName
1004 * @return {boolean}
1005 */
1006 isEnabled(experimentName) {
1007 this._checkExperiment(experimentName);
Pavel Feldmanc0395912018-08-03 02:05:00 +00001008 // Check for explicitly disabled experiments first - the code could call setEnable(false) on the experiment enabled
1009 // by default and we should respect that.
1010 if (Runtime._experimentsSetting()[experimentName] === false)
1011 return false;
Blink Reformat4c46d092018-04-07 15:32:37 +00001012 if (this._enabledTransiently[experimentName])
1013 return true;
Jeff Fisher85b08e12019-06-13 22:24:21 +00001014 if (this._serverEnabled.has(experimentName))
1015 return true;
Blink Reformat4c46d092018-04-07 15:32:37 +00001016 if (!this.supportEnabled())
1017 return false;
1018
1019 return !!Runtime._experimentsSetting()[experimentName];
1020 }
1021
1022 /**
1023 * @param {string} experimentName
1024 * @param {boolean} enabled
1025 */
1026 setEnabled(experimentName, enabled) {
1027 this._checkExperiment(experimentName);
1028 const experimentsSetting = Runtime._experimentsSetting();
1029 experimentsSetting[experimentName] = enabled;
1030 this._setExperimentsSetting(experimentsSetting);
1031 }
1032
1033 /**
1034 * @param {!Array.<string>} experimentNames
1035 */
1036 setDefaultExperiments(experimentNames) {
1037 for (let i = 0; i < experimentNames.length; ++i) {
1038 this._checkExperiment(experimentNames[i]);
1039 this._enabledTransiently[experimentNames[i]] = true;
1040 }
1041 }
1042
1043 /**
Jeff Fisher85b08e12019-06-13 22:24:21 +00001044 * @param {!Array.<string>} experimentNames
1045 */
1046 setServerEnabledExperiments(experimentNames) {
1047 for (const experiment of experimentNames) {
1048 this._checkExperiment(experiment);
1049 this._serverEnabled.add(experiment);
1050 }
1051 }
1052
1053 /**
Blink Reformat4c46d092018-04-07 15:32:37 +00001054 * @param {string} experimentName
1055 */
1056 enableForTest(experimentName) {
1057 this._checkExperiment(experimentName);
1058 this._enabledTransiently[experimentName] = true;
1059 }
1060
1061 clearForTest() {
1062 this._experiments = [];
1063 this._experimentNames = {};
1064 this._enabledTransiently = {};
Jeff Fisher85b08e12019-06-13 22:24:21 +00001065 this._serverEnabled.clear();
Blink Reformat4c46d092018-04-07 15:32:37 +00001066 }
1067
1068 cleanUpStaleExperiments() {
1069 const experimentsSetting = Runtime._experimentsSetting();
1070 const cleanedUpExperimentSetting = {};
1071 for (let i = 0; i < this._experiments.length; ++i) {
1072 const experimentName = this._experiments[i].name;
1073 if (experimentsSetting[experimentName])
1074 cleanedUpExperimentSetting[experimentName] = true;
1075 }
1076 this._setExperimentsSetting(cleanedUpExperimentSetting);
1077 }
1078
1079 /**
1080 * @param {string} experimentName
1081 */
1082 _checkExperiment(experimentName) {
1083 Runtime._assert(this._experimentNames[experimentName], 'Unknown experiment ' + experimentName);
1084 }
1085};
1086
1087/**
1088 * @unrestricted
1089 */
1090Runtime.Experiment = class {
1091 /**
1092 * @param {!Runtime.ExperimentsSupport} experiments
1093 * @param {string} name
1094 * @param {string} title
1095 * @param {boolean} hidden
1096 */
1097 constructor(experiments, name, title, hidden) {
1098 this.name = name;
1099 this.title = title;
1100 this.hidden = hidden;
1101 this._experiments = experiments;
1102 }
1103
1104 /**
1105 * @return {boolean}
1106 */
1107 isEnabled() {
1108 return this._experiments.isEnabled(this.name);
1109 }
1110
1111 /**
1112 * @param {boolean} enabled
1113 */
1114 setEnabled(enabled) {
1115 this._experiments.setEnabled(this.name, enabled);
1116 }
1117};
1118
Blink Reformat4c46d092018-04-07 15:32:37 +00001119// This must be constructed after the query parameters have been parsed.
1120Runtime.experiments = new Runtime.ExperimentsSupport();
1121
1122/** @type {Function} */
Pavel Feldman63e89eb2018-11-25 05:47:09 +00001123Runtime._appStartedPromiseCallback;
1124Runtime._appStartedPromise = new Promise(fulfil => Runtime._appStartedPromiseCallback = fulfil);
Vidal Diazleal0b6aff42019-04-20 01:15:43 +00001125
1126/** @type {function(string):string} */
1127Runtime._l10nCallback;
1128
Blink Reformat4c46d092018-04-07 15:32:37 +00001129/**
1130 * @type {?string}
1131 */
1132Runtime._remoteBase;
1133(function validateRemoteBase() {
James Lissiak821d2282019-05-15 15:32:04 +00001134 if (location.href.startsWith('devtools://devtools/bundled/') && Runtime.queryParam('remoteBase')) {
Blink Reformat4c46d092018-04-07 15:32:37 +00001135 const versionMatch = /\/serve_file\/(@[0-9a-zA-Z]+)\/?$/.exec(Runtime.queryParam('remoteBase'));
1136 if (versionMatch)
1137 Runtime._remoteBase = `${location.origin}/remote/serve_file/${versionMatch[1]}/`;
1138 }
1139})();
1140
1141
1142/**
1143 * @interface
1144 */
1145function ServicePort() {
1146}
1147
1148ServicePort.prototype = {
1149 /**
1150 * @param {function(string)} messageHandler
1151 * @param {function(string)} closeHandler
1152 */
1153 setHandlers(messageHandler, closeHandler) {},
1154
1155 /**
1156 * @param {string} message
1157 * @return {!Promise<boolean>}
1158 */
1159 send(message) {},
1160
1161 /**
1162 * @return {!Promise<boolean>}
1163 */
1164 close() {}
1165};
1166
1167/** @type {!Runtime} */
1168var runtime; // eslint-disable-line