blob: 46698e7727e5636e623f8ec436a0012377d07a64 [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
114 if (urlWithFallbackVersion === url || !url.includes('audits2_worker_module'))
115 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
381 useTestBase() {
382 Runtime._remoteBase = 'http://localhost:8000/inspector-sources/';
383 if (Runtime.queryParam('debugFrontend'))
384 Runtime._remoteBase += 'debug/';
385 }
386
387 /**
388 * @param {!Runtime.ModuleDescriptor} descriptor
389 */
390 _registerModule(descriptor) {
391 const module = new Runtime.Module(this, descriptor);
392 this._modules.push(module);
393 this._modulesMap[descriptor['name']] = module;
394 }
395
396 /**
397 * @param {string} moduleName
398 * @return {!Promise.<undefined>}
399 */
400 loadModulePromise(moduleName) {
401 return this._modulesMap[moduleName]._loadPromise();
402 }
403
404 /**
405 * @param {!Array.<string>} moduleNames
406 * @return {!Promise.<!Array.<*>>}
407 */
408 _loadAutoStartModules(moduleNames) {
409 const promises = [];
410 for (let i = 0; i < moduleNames.length; ++i)
411 promises.push(this.loadModulePromise(moduleNames[i]));
412 return Promise.all(promises);
413 }
414
415 /**
416 * @param {!Runtime.Extension} extension
417 * @param {?function(function(new:Object)):boolean} predicate
418 * @return {boolean}
419 */
420 _checkExtensionApplicability(extension, predicate) {
421 if (!predicate)
422 return false;
423 const contextTypes = extension.descriptor().contextTypes;
424 if (!contextTypes)
425 return true;
426 for (let i = 0; i < contextTypes.length; ++i) {
427 const contextType = this._resolve(contextTypes[i]);
428 const isMatching = !!contextType && predicate(contextType);
429 if (isMatching)
430 return true;
431 }
432 return false;
433 }
434
435 /**
436 * @param {!Runtime.Extension} extension
437 * @param {?Object} context
438 * @return {boolean}
439 */
440 isExtensionApplicableToContext(extension, context) {
441 if (!context)
442 return true;
443 return this._checkExtensionApplicability(extension, isInstanceOf);
444
445 /**
446 * @param {!Function} targetType
447 * @return {boolean}
448 */
449 function isInstanceOf(targetType) {
450 return context instanceof targetType;
451 }
452 }
453
454 /**
455 * @param {!Runtime.Extension} extension
456 * @param {!Set.<!Function>=} currentContextTypes
457 * @return {boolean}
458 */
459 isExtensionApplicableToContextTypes(extension, currentContextTypes) {
460 if (!extension.descriptor().contextTypes)
461 return true;
462
463 return this._checkExtensionApplicability(extension, currentContextTypes ? isContextTypeKnown : null);
464
465 /**
466 * @param {!Function} targetType
467 * @return {boolean}
468 */
469 function isContextTypeKnown(targetType) {
470 return currentContextTypes.has(targetType);
471 }
472 }
473
474 /**
475 * @param {*} type
476 * @param {?Object=} context
477 * @param {boolean=} sortByTitle
478 * @return {!Array.<!Runtime.Extension>}
479 */
480 extensions(type, context, sortByTitle) {
481 return this._extensions.filter(filter).sort(sortByTitle ? titleComparator : orderComparator);
482
483 /**
484 * @param {!Runtime.Extension} extension
485 * @return {boolean}
486 */
487 function filter(extension) {
488 if (extension._type !== type && extension._typeClass() !== type)
489 return false;
490 if (!extension.enabled())
491 return false;
492 return !context || extension.isApplicable(context);
493 }
494
495 /**
496 * @param {!Runtime.Extension} extension1
497 * @param {!Runtime.Extension} extension2
498 * @return {number}
499 */
500 function orderComparator(extension1, extension2) {
501 const order1 = extension1.descriptor()['order'] || 0;
502 const order2 = extension2.descriptor()['order'] || 0;
503 return order1 - order2;
504 }
505
506 /**
507 * @param {!Runtime.Extension} extension1
508 * @param {!Runtime.Extension} extension2
509 * @return {number}
510 */
511 function titleComparator(extension1, extension2) {
512 const title1 = extension1.title() || '';
513 const title2 = extension2.title() || '';
514 return title1.localeCompare(title2);
515 }
516 }
517
518 /**
519 * @param {*} type
520 * @param {?Object=} context
521 * @return {?Runtime.Extension}
522 */
523 extension(type, context) {
524 return this.extensions(type, context)[0] || null;
525 }
526
527 /**
528 * @param {*} type
529 * @param {?Object=} context
530 * @return {!Promise.<!Array.<!Object>>}
531 */
532 allInstances(type, context) {
533 return Promise.all(this.extensions(type, context).map(extension => extension.instance()));
534 }
535
536 /**
537 * @return {?function(new:Object)}
538 */
539 _resolve(typeName) {
540 if (!this._cachedTypeClasses[typeName]) {
541 const path = typeName.split('.');
542 let object = self;
543 for (let i = 0; object && (i < path.length); ++i)
544 object = object[path[i]];
545 if (object)
546 this._cachedTypeClasses[typeName] = /** @type function(new:Object) */ (object);
547 }
548 return this._cachedTypeClasses[typeName] || null;
549 }
550
551 /**
552 * @param {!Function} constructorFunction
553 * @return {!Object}
554 */
555 sharedInstance(constructorFunction) {
556 if (Runtime._instanceSymbol in constructorFunction &&
557 Object.getOwnPropertySymbols(constructorFunction).includes(Runtime._instanceSymbol))
558 return constructorFunction[Runtime._instanceSymbol];
559
560 const instance = new constructorFunction();
561 constructorFunction[Runtime._instanceSymbol] = instance;
562 return instance;
563 }
564};
565
Ingvar Stepanyand48ae082018-11-07 04:38:32 +0000566/** @type {!URLSearchParams} */
567Runtime._queryParamsObject = new URLSearchParams(Runtime.queryParamsString());
Blink Reformat4c46d092018-04-07 15:32:37 +0000568
569Runtime._instanceSymbol = Symbol('instance');
570
571/**
572 * @type {!Object.<string, string>}
573 */
574Runtime.cachedResources = {
575 __proto__: null
576};
577
578
579Runtime._console = console;
580Runtime._originalAssert = console.assert;
581
582
583Runtime._platform = '';
584
585
586/**
587 * @unrestricted
588 */
589Runtime.ModuleDescriptor = class {
590 constructor() {
591 /**
592 * @type {string}
593 */
594 this.name;
595
596 /**
597 * @type {!Array.<!Runtime.ExtensionDescriptor>}
598 */
599 this.extensions;
600
601 /**
602 * @type {!Array.<string>|undefined}
603 */
604 this.dependencies;
605
606 /**
607 * @type {!Array.<string>}
608 */
609 this.scripts;
610
611 /**
612 * @type {string|undefined}
613 */
614 this.condition;
615
616 /**
617 * @type {boolean|undefined}
618 */
619 this.remote;
620 }
621};
622
623/**
624 * @unrestricted
625 */
626Runtime.ExtensionDescriptor = class {
627 constructor() {
628 /**
629 * @type {string}
630 */
631 this.type;
632
633 /**
634 * @type {string|undefined}
635 */
636 this.className;
637
638 /**
639 * @type {string|undefined}
640 */
641 this.factoryName;
642
643 /**
644 * @type {!Array.<string>|undefined}
645 */
646 this.contextTypes;
647 }
648};
649
650/**
651 * @unrestricted
652 */
653Runtime.Module = class {
654 /**
655 * @param {!Runtime} manager
656 * @param {!Runtime.ModuleDescriptor} descriptor
657 */
658 constructor(manager, descriptor) {
659 this._manager = manager;
660 this._descriptor = descriptor;
661 this._name = descriptor.name;
662 /** @type {!Array<!Runtime.Extension>} */
663 this._extensions = [];
664
665 /** @type {!Map<string, !Array<!Runtime.Extension>>} */
666 this._extensionsByClassName = new Map();
667 const extensions = /** @type {?Array.<!Runtime.ExtensionDescriptor>} */ (descriptor.extensions);
668 for (let i = 0; extensions && i < extensions.length; ++i) {
669 const extension = new Runtime.Extension(this, extensions[i]);
670 this._manager._extensions.push(extension);
671 this._extensions.push(extension);
672 }
673 this._loadedForTest = false;
674 }
675
676 /**
677 * @return {string}
678 */
679 name() {
680 return this._name;
681 }
682
683 /**
684 * @return {boolean}
685 */
686 enabled() {
687 return Runtime._isDescriptorEnabled(this._descriptor);
688 }
689
690 /**
691 * @param {string} name
692 * @return {string}
693 */
694 resource(name) {
695 const fullName = this._name + '/' + name;
696 const content = Runtime.cachedResources[fullName];
697 if (!content)
698 throw new Error(fullName + ' not preloaded. Check module.json');
699 return content;
700 }
701
702 /**
703 * @return {!Promise.<undefined>}
704 */
705 _loadPromise() {
706 if (!this.enabled())
707 return Promise.reject(new Error('Module ' + this._name + ' is not enabled'));
708
709 if (this._pendingLoadPromise)
710 return this._pendingLoadPromise;
711
712 const dependencies = this._descriptor.dependencies;
713 const dependencyPromises = [];
714 for (let i = 0; dependencies && i < dependencies.length; ++i)
715 dependencyPromises.push(this._manager._modulesMap[dependencies[i]]._loadPromise());
716
717 this._pendingLoadPromise = Promise.all(dependencyPromises)
718 .then(this._loadResources.bind(this))
719 .then(this._loadScripts.bind(this))
720 .then(() => this._loadedForTest = true);
721
722 return this._pendingLoadPromise;
723 }
724
725 /**
726 * @return {!Promise.<undefined>}
727 * @this {Runtime.Module}
728 */
729 _loadResources() {
730 const resources = this._descriptor['resources'];
731 if (!resources || !resources.length)
732 return Promise.resolve();
733 const promises = [];
734 for (let i = 0; i < resources.length; ++i) {
735 const url = this._modularizeURL(resources[i]);
736 promises.push(Runtime._loadResourceIntoCache(url, true));
737 }
738 return Promise.all(promises).then(undefined);
739 }
740
741 /**
742 * @return {!Promise.<undefined>}
743 */
744 _loadScripts() {
745 if (!this._descriptor.scripts || !this._descriptor.scripts.length)
746 return Promise.resolve();
747
748 // Module namespaces.
749 // NOTE: Update scripts/special_case_namespaces.json if you add a special cased namespace.
750 // The namespace keyword confuses clang-format.
751 // clang-format off
752 const specialCases = {
753 'sdk': 'SDK',
754 'js_sdk': 'JSSDK',
755 'browser_sdk': 'BrowserSDK',
756 'ui': 'UI',
757 'object_ui': 'ObjectUI',
Joel Einbinder3f23eb22018-05-14 23:27:51 +0000758 'javascript_metadata': 'JavaScriptMetadata',
Blink Reformat4c46d092018-04-07 15:32:37 +0000759 'perf_ui': 'PerfUI',
760 'har_importer': 'HARImporter',
761 'sdk_test_runner': 'SDKTestRunner',
762 'cpu_profiler_test_runner': 'CPUProfilerTestRunner'
763 };
764 const namespace = specialCases[this._name] || this._name.split('_').map(a => a.substring(0, 1).toUpperCase() + a.substring(1)).join('');
765 self[namespace] = self[namespace] || {};
766 // clang-format on
767 return Runtime._loadScriptsPromise(this._descriptor.scripts.map(this._modularizeURL, this), this._remoteBase());
768 }
769
770 /**
771 * @param {string} resourceName
772 */
773 _modularizeURL(resourceName) {
774 return Runtime.normalizePath(this._name + '/' + resourceName);
775 }
776
777 /**
778 * @return {string|undefined}
779 */
780 _remoteBase() {
781 return !Runtime.queryParam('debugFrontend') && this._descriptor.remote && Runtime._remoteBase || undefined;
782 }
783
784 /**
785 * @param {string} value
786 * @return {string}
787 */
788 substituteURL(value) {
789 const base = this._remoteBase() || '';
790 return value.replace(/@url\(([^\)]*?)\)/g, convertURL.bind(this));
791
792 function convertURL(match, url) {
793 return base + this._modularizeURL(url);
794 }
795 }
796};
797
798
799/**
800 * @unrestricted
801 */
802Runtime.Extension = class {
803 /**
804 * @param {!Runtime.Module} module
805 * @param {!Runtime.ExtensionDescriptor} descriptor
806 */
807 constructor(module, descriptor) {
808 this._module = module;
809 this._descriptor = descriptor;
810
811 this._type = descriptor.type;
812 this._hasTypeClass = this._type.charAt(0) === '@';
813
814 /**
815 * @type {?string}
816 */
817 this._className = descriptor.className || null;
818 this._factoryName = descriptor.factoryName || null;
819 }
820
821 /**
822 * @return {!Object}
823 */
824 descriptor() {
825 return this._descriptor;
826 }
827
828 /**
829 * @return {!Runtime.Module}
830 */
831 module() {
832 return this._module;
833 }
834
835 /**
836 * @return {boolean}
837 */
838 enabled() {
839 return this._module.enabled() && Runtime._isDescriptorEnabled(this.descriptor());
840 }
841
842 /**
843 * @return {?function(new:Object)}
844 */
845 _typeClass() {
846 if (!this._hasTypeClass)
847 return null;
848 return this._module._manager._resolve(this._type.substring(1));
849 }
850
851 /**
852 * @param {?Object} context
853 * @return {boolean}
854 */
855 isApplicable(context) {
856 return this._module._manager.isExtensionApplicableToContext(this, context);
857 }
858
859 /**
860 * @return {!Promise.<!Object>}
861 */
862 instance() {
863 return this._module._loadPromise().then(this._createInstance.bind(this));
864 }
865
866 /**
867 * @return {boolean}
868 */
869 canInstantiate() {
870 return !!(this._className || this._factoryName);
871 }
872
873 /**
874 * @return {!Object}
875 */
876 _createInstance() {
877 const className = this._className || this._factoryName;
878 if (!className)
879 throw new Error('Could not instantiate extension with no class');
880 const constructorFunction = self.eval(/** @type {string} */ (className));
881 if (!(constructorFunction instanceof Function))
882 throw new Error('Could not instantiate: ' + className);
883 if (this._className)
884 return this._module._manager.sharedInstance(constructorFunction);
885 return new constructorFunction(this);
886 }
887
888 /**
889 * @return {string}
890 */
891 title() {
892 // FIXME: should be Common.UIString() but runtime is not l10n aware yet.
893 return this._descriptor['title-' + Runtime._platform] || this._descriptor['title'];
894 }
895
896 /**
897 * @param {function(new:Object)} contextType
898 * @return {boolean}
899 */
900 hasContextType(contextType) {
901 const contextTypes = this.descriptor().contextTypes;
902 if (!contextTypes)
903 return false;
904 for (let i = 0; i < contextTypes.length; ++i) {
905 if (contextType === this._module._manager._resolve(contextTypes[i]))
906 return true;
907 }
908 return false;
909 }
910};
911
912/**
913 * @unrestricted
914 */
915Runtime.ExperimentsSupport = class {
916 constructor() {
917 this._supportEnabled = Runtime.queryParam('experiments') !== null;
918 this._experiments = [];
919 this._experimentNames = {};
920 this._enabledTransiently = {};
921 }
922
923 /**
924 * @return {!Array.<!Runtime.Experiment>}
925 */
926 allConfigurableExperiments() {
927 const result = [];
928 for (let i = 0; i < this._experiments.length; i++) {
929 const experiment = this._experiments[i];
930 if (!this._enabledTransiently[experiment.name])
931 result.push(experiment);
932 }
933 return result;
934 }
935
936 /**
937 * @return {boolean}
938 */
939 supportEnabled() {
940 return this._supportEnabled;
941 }
942
943 /**
944 * @param {!Object} value
945 */
946 _setExperimentsSetting(value) {
947 if (!self.localStorage)
948 return;
949 self.localStorage['experiments'] = JSON.stringify(value);
950 }
951
952 /**
953 * @param {string} experimentName
954 * @param {string} experimentTitle
955 * @param {boolean=} hidden
956 */
957 register(experimentName, experimentTitle, hidden) {
958 Runtime._assert(!this._experimentNames[experimentName], 'Duplicate registration of experiment ' + experimentName);
959 this._experimentNames[experimentName] = true;
960 this._experiments.push(new Runtime.Experiment(this, experimentName, experimentTitle, !!hidden));
961 }
962
963 /**
964 * @param {string} experimentName
965 * @return {boolean}
966 */
967 isEnabled(experimentName) {
968 this._checkExperiment(experimentName);
Pavel Feldmanc0395912018-08-03 02:05:00 +0000969 // Check for explicitly disabled experiments first - the code could call setEnable(false) on the experiment enabled
970 // by default and we should respect that.
971 if (Runtime._experimentsSetting()[experimentName] === false)
972 return false;
Blink Reformat4c46d092018-04-07 15:32:37 +0000973 if (this._enabledTransiently[experimentName])
974 return true;
975 if (!this.supportEnabled())
976 return false;
977
978 return !!Runtime._experimentsSetting()[experimentName];
979 }
980
981 /**
982 * @param {string} experimentName
983 * @param {boolean} enabled
984 */
985 setEnabled(experimentName, enabled) {
986 this._checkExperiment(experimentName);
987 const experimentsSetting = Runtime._experimentsSetting();
988 experimentsSetting[experimentName] = enabled;
989 this._setExperimentsSetting(experimentsSetting);
990 }
991
992 /**
993 * @param {!Array.<string>} experimentNames
994 */
995 setDefaultExperiments(experimentNames) {
996 for (let i = 0; i < experimentNames.length; ++i) {
997 this._checkExperiment(experimentNames[i]);
998 this._enabledTransiently[experimentNames[i]] = true;
999 }
1000 }
1001
1002 /**
1003 * @param {string} experimentName
1004 */
1005 enableForTest(experimentName) {
1006 this._checkExperiment(experimentName);
1007 this._enabledTransiently[experimentName] = true;
1008 }
1009
1010 clearForTest() {
1011 this._experiments = [];
1012 this._experimentNames = {};
1013 this._enabledTransiently = {};
1014 }
1015
1016 cleanUpStaleExperiments() {
1017 const experimentsSetting = Runtime._experimentsSetting();
1018 const cleanedUpExperimentSetting = {};
1019 for (let i = 0; i < this._experiments.length; ++i) {
1020 const experimentName = this._experiments[i].name;
1021 if (experimentsSetting[experimentName])
1022 cleanedUpExperimentSetting[experimentName] = true;
1023 }
1024 this._setExperimentsSetting(cleanedUpExperimentSetting);
1025 }
1026
1027 /**
1028 * @param {string} experimentName
1029 */
1030 _checkExperiment(experimentName) {
1031 Runtime._assert(this._experimentNames[experimentName], 'Unknown experiment ' + experimentName);
1032 }
1033};
1034
1035/**
1036 * @unrestricted
1037 */
1038Runtime.Experiment = class {
1039 /**
1040 * @param {!Runtime.ExperimentsSupport} experiments
1041 * @param {string} name
1042 * @param {string} title
1043 * @param {boolean} hidden
1044 */
1045 constructor(experiments, name, title, hidden) {
1046 this.name = name;
1047 this.title = title;
1048 this.hidden = hidden;
1049 this._experiments = experiments;
1050 }
1051
1052 /**
1053 * @return {boolean}
1054 */
1055 isEnabled() {
1056 return this._experiments.isEnabled(this.name);
1057 }
1058
1059 /**
1060 * @param {boolean} enabled
1061 */
1062 setEnabled(enabled) {
1063 this._experiments.setEnabled(this.name, enabled);
1064 }
1065};
1066
Blink Reformat4c46d092018-04-07 15:32:37 +00001067// This must be constructed after the query parameters have been parsed.
1068Runtime.experiments = new Runtime.ExperimentsSupport();
1069
1070/** @type {Function} */
Pavel Feldman63e89eb2018-11-25 05:47:09 +00001071Runtime._appStartedPromiseCallback;
1072Runtime._appStartedPromise = new Promise(fulfil => Runtime._appStartedPromiseCallback = fulfil);
Blink Reformat4c46d092018-04-07 15:32:37 +00001073/**
1074 * @type {?string}
1075 */
1076Runtime._remoteBase;
1077(function validateRemoteBase() {
1078 if (location.href.startsWith('chrome-devtools://devtools/bundled/') && Runtime.queryParam('remoteBase')) {
1079 const versionMatch = /\/serve_file\/(@[0-9a-zA-Z]+)\/?$/.exec(Runtime.queryParam('remoteBase'));
1080 if (versionMatch)
1081 Runtime._remoteBase = `${location.origin}/remote/serve_file/${versionMatch[1]}/`;
1082 }
1083})();
1084
1085
1086/**
1087 * @interface
1088 */
1089function ServicePort() {
1090}
1091
1092ServicePort.prototype = {
1093 /**
1094 * @param {function(string)} messageHandler
1095 * @param {function(string)} closeHandler
1096 */
1097 setHandlers(messageHandler, closeHandler) {},
1098
1099 /**
1100 * @param {string} message
1101 * @return {!Promise<boolean>}
1102 */
1103 send(message) {},
1104
1105 /**
1106 * @return {!Promise<boolean>}
1107 */
1108 close() {}
1109};
1110
1111/** @type {!Runtime} */
1112var runtime; // eslint-disable-line