Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / Runtime.js
blob9056bd16ad155f40503f0416a86090019c83bd32
1 /*
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  */
31 // This gets all concatenated module descriptors in the release mode.
32 var allDescriptors = [];
33 var applicationDescriptor;
34 var _loadedScripts = {};
36 // FIXME: This is a workaround to force Closure compiler provide
37 // the standard ES6 runtime for all modules. This should be removed
38 // once Closure provides standard externs for Map et al.
39 for (var k of []) {};
41 /**
42  * @param {string} url
43  * @return {!Promise.<string>}
44  */
45 function loadResourcePromise(url)
47     return new Promise(load);
49     /**
50      * @param {function(?)} fulfill
51      * @param {function(*)} reject
52      */
53     function load(fulfill, reject)
54     {
55         var xhr = new XMLHttpRequest();
56         xhr.open("GET", url, true);
57         xhr.onreadystatechange = onreadystatechange;
59         /**
60          * @param {Event} e
61          */
62         function onreadystatechange(e)
63         {
64             if (xhr.readyState !== 4)
65                 return;
67             if ([0, 200, 304].indexOf(xhr.status) === -1)  // Testing harness file:/// results in 0.
68                 reject(new Error("While loading from url " + url + " server responded with a status of " + xhr.status));
69             else
70                 fulfill(e.target.response);
71         }
72         xhr.send(null);
73     }
76 /**
77  * http://tools.ietf.org/html/rfc3986#section-5.2.4
78  * @param {string} path
79  * @return {string}
80  */
81 function normalizePath(path)
83     if (path.indexOf("..") === -1 && path.indexOf('.') === -1)
84         return path;
86     var normalizedSegments = [];
87     var segments = path.split("/");
88     for (var i = 0; i < segments.length; i++) {
89         var segment = segments[i];
90         if (segment === ".")
91             continue;
92         else if (segment === "..")
93             normalizedSegments.pop();
94         else if (segment)
95             normalizedSegments.push(segment);
96     }
97     var normalizedPath = normalizedSegments.join("/");
98     if (normalizedPath[normalizedPath.length - 1] === "/")
99         return normalizedPath;
100     if (path[0] === "/" && normalizedPath)
101         normalizedPath = "/" + normalizedPath;
102     if ((path[path.length - 1] === "/") || (segments[segments.length - 1] === ".") || (segments[segments.length - 1] === ".."))
103         normalizedPath = normalizedPath + "/";
105     return normalizedPath;
109  * @param {!Array.<string>} scriptNames
110  * @param {string=} base
111  * @return {!Promise.<undefined>}
112  */
113 function loadScriptsPromise(scriptNames, base)
115     /** @type {!Array.<!Promise.<string>>} */
116     var promises = [];
117     /** @type {!Array.<string>} */
118     var urls = [];
119     var sources = new Array(scriptNames.length);
120     var scriptToEval = 0;
121     for (var i = 0; i < scriptNames.length; ++i) {
122         var scriptName = scriptNames[i];
123         var sourceURL = (base || self._importScriptPathPrefix) + scriptName;
124         var schemaIndex = sourceURL.indexOf("://") + 3;
125         sourceURL = sourceURL.substring(0, schemaIndex) + normalizePath(sourceURL.substring(schemaIndex));
126         if (_loadedScripts[sourceURL])
127             continue;
128         urls.push(sourceURL);
129         promises.push(loadResourcePromise(sourceURL).then(scriptSourceLoaded.bind(null, i), scriptSourceLoaded.bind(null, i, undefined)));
130     }
131     return Promise.all(promises).then(undefined);
133     /**
134      * @param {number} scriptNumber
135      * @param {string=} scriptSource
136      */
137     function scriptSourceLoaded(scriptNumber, scriptSource)
138     {
139         sources[scriptNumber] = scriptSource || "";
140         // Eval scripts as fast as possible.
141         while (typeof sources[scriptToEval] !== "undefined") {
142             evaluateScript(urls[scriptToEval], sources[scriptToEval]);
143             ++scriptToEval;
144         }
145     }
147     /**
148      * @param {string} sourceURL
149      * @param {string=} scriptSource
150      */
151     function evaluateScript(sourceURL, scriptSource)
152     {
153         _loadedScripts[sourceURL] = true;
154         if (!scriptSource) {
155             // Do not reject, as this is normal in the hosted mode.
156             console.error("Empty response arrived for script '" + sourceURL + "'");
157             return;
158         }
159         self.eval(scriptSource + "\n//# sourceURL=" + sourceURL);
160     }
163 (function() {
164     var baseUrl = self.location ? self.location.origin + self.location.pathname : "";
165     self._importScriptPathPrefix = baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1);
166 })();
169  * @constructor
170  * @param {!Array.<!Runtime.ModuleDescriptor>} descriptors
171  * @param {!Array.<string>=} coreModuleNames
172  */
173 function Runtime(descriptors, coreModuleNames)
175     /**
176      * @type {!Array.<!Runtime.Module>}
177      */
178     this._modules = [];
179     /**
180      * @type {!Object.<string, !Runtime.Module>}
181      */
182     this._modulesMap = {};
183     /**
184      * @type {!Array.<!Runtime.Extension>}
185      */
186     this._extensions = [];
188     /**
189      * @type {!Object.<string, !function(new:Object)>}
190      */
191     this._cachedTypeClasses = {};
193     /**
194      * @type {!Object.<string, !Runtime.ModuleDescriptor>}
195      */
196     this._descriptorsMap = {};
198     for (var i = 0; i < descriptors.length; ++i)
199         this._registerModule(descriptors[i]);
200     if (coreModuleNames)
201         this._loadAutoStartModules(coreModuleNames);
205  * @type {!Object.<string, string>}
206  */
207 Runtime._queryParamsObject = { __proto__: null };
210  * @type {!Object.<string, string>}
211  */
212 Runtime.cachedResources = { __proto__: null };
215  * @return {boolean}
216  */
217 Runtime.isReleaseMode = function()
219     return !!allDescriptors.length;
223  * @param {string} appName
224  */
225 Runtime.startApplication = function(appName)
227     console.timeStamp("Runtime.startApplication");
229     var allDescriptorsByName = {};
230     for (var i = 0; Runtime.isReleaseMode() && i < allDescriptors.length; ++i) {
231         var d = allDescriptors[i];
232         allDescriptorsByName[d["name"]] = d;
233     }
235     var applicationPromise;
236     if (applicationDescriptor)
237         applicationPromise = Promise.resolve(applicationDescriptor);
238     else
239         applicationPromise = loadResourcePromise(appName + ".json").then(JSON.parse.bind(JSON));
241     applicationPromise.then(parseModuleDescriptors);
243     /**
244      * @param {!Array.<!Object>} configuration
245      */
246     function parseModuleDescriptors(configuration)
247     {
248         var moduleJSONPromises = [];
249         var coreModuleNames = [];
250         for (var i = 0; i < configuration.length; ++i) {
251             var descriptor = configuration[i];
252             if (descriptor["type"] === "worker")
253                 continue;
254             var name = descriptor["name"];
255             var moduleJSON = allDescriptorsByName[name];
256             if (moduleJSON)
257                 moduleJSONPromises.push(Promise.resolve(moduleJSON));
258             else
259                 moduleJSONPromises.push(loadResourcePromise(name + "/module.json").then(JSON.parse.bind(JSON)));
260             if (descriptor["type"] === "autostart")
261                 coreModuleNames.push(name);
262         }
264         Promise.all(moduleJSONPromises).then(instantiateRuntime);
265         /**
266          * @param {!Array.<!Object>} moduleDescriptors
267          */
268         function instantiateRuntime(moduleDescriptors)
269         {
270             for (var i = 0; !Runtime.isReleaseMode() && i < moduleDescriptors.length; ++i) {
271                 moduleDescriptors[i]["name"] = configuration[i]["name"];
272                 moduleDescriptors[i]["condition"] = configuration[i]["condition"];
273             }
274             self.runtime = new Runtime(moduleDescriptors, coreModuleNames);
275         }
276     }
280  * @param {string} name
281  * @return {?string}
282  */
283 Runtime.queryParam = function(name)
285     return Runtime._queryParamsObject[name] || null;
289  * @param {!Array.<string>} banned
290  * @return {string}
291  */
292 Runtime.constructQueryParams = function(banned)
294     var params = [];
295     for (var key in Runtime._queryParamsObject) {
296         if (!key || banned.indexOf(key) !== -1)
297             continue;
298         params.push(key + "=" + Runtime._queryParamsObject[key]);
299     }
300     return params.length ? "?" + params.join("&") : "";
304  * @return {!Object}
305  */
306 Runtime._experimentsSetting = function()
308     try {
309         return /** @type {!Object} */ (JSON.parse(self.localStorage && self.localStorage["experiments"] ? self.localStorage["experiments"] : "{}"));
310     } catch (e) {
311         console.error("Failed to parse localStorage['experiments']");
312         return {};
313     }
317  * @param {!Array.<!Promise.<T, !Error>>} promises
318  * @return {!Promise.<!Array.<T>>}
319  * @template T
320  */
321 Runtime._some = function(promises)
323     var all = [];
324     var wasRejected = [];
325     for (var i = 0; i < promises.length; ++i) {
326         // Workaround closure compiler bug.
327         var handlerFunction = /** @type {function()} */ (handler.bind(promises[i], i));
328         all.push(promises[i].catch(handlerFunction));
329     }
331     return Promise.all(all).then(filterOutFailuresResults);
333     /**
334      * @param {!Array.<T>} results
335      * @return {!Array.<T>}
336      * @template T
337      */
338     function filterOutFailuresResults(results)
339     {
340         var filtered = [];
341         for (var i = 0; i < results.length; ++i) {
342             if (!wasRejected[i])
343                 filtered.push(results[i]);
344         }
345         return filtered;
346     }
348     /**
349      * @this {!Promise}
350      * @param {number} index
351      * @param {!Error} e
352      */
353     function handler(index, e)
354     {
355         wasRejected[index] = true;
356         console.error(e.stack);
357     }
360 Runtime._console = console;
361 Runtime._originalAssert = console.assert;
362 Runtime._assert = function(value, message)
364     if (value)
365         return;
366     Runtime._originalAssert.call(Runtime._console, value, message + " " + new Error().stack);
369 Runtime.prototype = {
370     useTestBase: function()
371     {
372         Runtime._remoteBase = "http://localhost:8000/inspector-sources/";
373     },
375     /**
376      * @param {!Runtime.ModuleDescriptor} descriptor
377      */
378     _registerModule: function(descriptor)
379     {
380         var module = new Runtime.Module(this, descriptor);
381         this._modules.push(module);
382         this._modulesMap[descriptor["name"]] = module;
383     },
385     /**
386      * @param {string} moduleName
387      * @return {!Promise.<undefined>}
388      */
389     loadModulePromise: function(moduleName)
390     {
391         return this._modulesMap[moduleName]._loadPromise();
392     },
394     /**
395      * @param {!Array.<string>} moduleNames
396      * @return {!Promise.<!Array.<*>>}
397      */
398     _loadAutoStartModules: function(moduleNames)
399     {
400         var promises = [];
401         for (var i = 0; i < moduleNames.length; ++i) {
402             if (Runtime.isReleaseMode())
403                 this._modulesMap[moduleNames[i]]._loaded = true;
404             else
405                 promises.push(this.loadModulePromise(moduleNames[i]));
406         }
407         return Promise.all(promises);
408     },
410     /**
411      * @param {!Runtime.Extension} extension
412      * @param {?function(function(new:Object)):boolean} predicate
413      * @return {boolean}
414      */
415     _checkExtensionApplicability: function(extension, predicate)
416     {
417         if (!predicate)
418             return false;
419         var contextTypes = /** @type {!Array.<string>|undefined} */ (extension.descriptor().contextTypes);
420         if (!contextTypes)
421             return true;
422         for (var i = 0; i < contextTypes.length; ++i) {
423             var contextType = this._resolve(contextTypes[i]);
424             var isMatching = !!contextType && predicate(contextType);
425             if (isMatching)
426                 return true;
427         }
428         return false;
429     },
431     /**
432      * @param {!Runtime.Extension} extension
433      * @param {?Object} context
434      * @return {boolean}
435      */
436     isExtensionApplicableToContext: function(extension, context)
437     {
438         if (!context)
439             return true;
440         return this._checkExtensionApplicability(extension, isInstanceOf);
442         /**
443          * @param {!Function} targetType
444          * @return {boolean}
445          */
446         function isInstanceOf(targetType)
447         {
448             return context instanceof targetType;
449         }
450     },
452     /**
453      * @param {!Runtime.Extension} extension
454      * @param {!Set.<!Function>=} currentContextTypes
455      * @return {boolean}
456      */
457     isExtensionApplicableToContextTypes: function(extension, currentContextTypes)
458     {
459         if (!extension.descriptor().contextTypes)
460             return true;
462         return this._checkExtensionApplicability(extension, currentContextTypes ? isContextTypeKnown : null);
464         /**
465          * @param {!Function} targetType
466          * @return {boolean}
467          */
468         function isContextTypeKnown(targetType)
469         {
470             return currentContextTypes.has(targetType);
471         }
472     },
474     /**
475      * @param {*} type
476      * @param {?Object=} context
477      * @return {!Array.<!Runtime.Extension>}
478      */
479     extensions: function(type, context)
480     {
481         return this._extensions.filter(filter).sort(orderComparator);
483         /**
484          * @param {!Runtime.Extension} extension
485          * @return {boolean}
486          */
487         function filter(extension)
488         {
489             if (extension._type !== type && extension._typeClass() !== type)
490                 return false;
491             if (!extension.enabled())
492                 return false;
493             return !context || extension.isApplicable(context);
494         }
496         /**
497          * @param {!Runtime.Extension} extension1
498          * @param {!Runtime.Extension} extension2
499          * @return {number}
500          */
501         function orderComparator(extension1, extension2)
502         {
503             var order1 = extension1.descriptor()["order"] || 0;
504             var order2 = extension2.descriptor()["order"] || 0;
505             return order1 - order2;
506         }
507     },
509     /**
510      * @param {*} type
511      * @param {?Object=} context
512      * @return {?Runtime.Extension}
513      */
514     extension: function(type, context)
515     {
516         return this.extensions(type, context)[0] || null;
517     },
519     /**
520      * @param {*} type
521      * @param {?Object=} context
522      * @return {!Promise.<!Array.<!Object>>}
523      */
524     instancesPromise: function(type, context)
525     {
526         var extensions = this.extensions(type, context);
527         var promises = [];
528         for (var i = 0; i < extensions.length; ++i)
529             promises.push(extensions[i].instancePromise());
530         return Runtime._some(promises);
531     },
533     /**
534      * @param {*} type
535      * @param {?Object=} context
536      * @return {!Promise.<!Object>}
537      */
538     instancePromise: function(type, context)
539     {
540         var extension = this.extension(type, context);
541         if (!extension)
542             return Promise.reject(new Error("No such extension: " + type + " in given context."));
543         return extension.instancePromise();
544     },
546     /**
547      * @return {?function(new:Object)}
548      */
549     _resolve: function(typeName)
550     {
551         if (!this._cachedTypeClasses[typeName]) {
552             var path = typeName.split(".");
553             var object = window;
554             for (var i = 0; object && (i < path.length); ++i)
555                 object = object[path[i]];
556             if (object)
557                 this._cachedTypeClasses[typeName] = /** @type function(new:Object) */(object);
558         }
559         return this._cachedTypeClasses[typeName] || null;
560     }
564  * @constructor
565  */
566 Runtime.ModuleDescriptor = function()
568     /**
569      * @type {string}
570      */
571     this.name;
573     /**
574      * @type {!Array.<!Runtime.ExtensionDescriptor>}
575      */
576     this.extensions;
578     /**
579      * @type {!Array.<string>|undefined}
580      */
581     this.dependencies;
583     /**
584      * @type {!Array.<string>}
585      */
586     this.scripts;
588     /**
589      * @type {boolean|undefined}
590      */
591     this.remote;
595  * @constructor
596  */
597 Runtime.ExtensionDescriptor = function()
599     /**
600      * @type {string}
601      */
602     this.type;
604     /**
605      * @type {string|undefined}
606      */
607     this.className;
609     /**
610      * @type {!Array.<string>|undefined}
611      */
612     this.contextTypes;
616  * @constructor
617  * @param {!Runtime} manager
618  * @param {!Runtime.ModuleDescriptor} descriptor
619  */
620 Runtime.Module = function(manager, descriptor)
622     this._manager = manager;
623     this._descriptor = descriptor;
624     this._name = descriptor.name;
625     /** @type {!Object.<string, ?Object>} */
626     this._instanceMap = {};
627     var extensions = /** @type {?Array.<!Runtime.ExtensionDescriptor>} */ (descriptor.extensions);
628     for (var i = 0; extensions && i < extensions.length; ++i)
629         this._manager._extensions.push(new Runtime.Extension(this, extensions[i]));
630     this._loaded = false;
633 Runtime.Module.prototype = {
634     /**
635      * @return {string}
636      */
637     name: function()
638     {
639         return this._name;
640     },
642     /**
643      * @return {boolean}
644      */
645     enabled: function()
646     {
647         var activatorExperiment = this._descriptor["experiment"];
648         if (activatorExperiment && !Runtime.experiments.isEnabled(activatorExperiment))
649             return false;
650         var condition = this._descriptor["condition"];
651         if (condition && !Runtime.queryParam(condition))
652             return false;
653         return true;
654     },
656     /**
657      * @param {string} name
658      * @return {string}
659      */
660     resource: function(name)
661     {
662         var fullName = this._name + "/" + name;
663         var content = Runtime.cachedResources[fullName];
664         if (!content)
665             throw new Error(fullName + " not preloaded. Check module.json");
666         return content;
667     },
669     /**
670      * @return {!Promise.<undefined>}
671      */
672     _loadPromise: function()
673     {
674         if (this._loaded)
675             return Promise.resolve();
677         if (!this.enabled())
678             return Promise.reject(new Error("Module " + this._name + " is not enabled"));
680         if (this._pendingLoadPromise)
681             return this._pendingLoadPromise;
683         var dependencies = this._descriptor.dependencies;
684         var dependencyPromises = [];
685         for (var i = 0; dependencies && i < dependencies.length; ++i)
686             dependencyPromises.push(this._manager._modulesMap[dependencies[i]]._loadPromise());
688         this._pendingLoadPromise = Promise.all(dependencyPromises)
689             .then(this._loadResources.bind(this))
690             .then(this._loadScripts.bind(this))
691             .then(markAsLoaded.bind(this));
693         return this._pendingLoadPromise;
695         /**
696          * @this {Runtime.Module}
697          */
698         function markAsLoaded()
699         {
700             delete this._pendingLoadPromise;
701             this._loaded = true;
702         }
703     },
705     /**
706      * @return {!Promise.<undefined>}
707      * @this {Runtime.Module}
708      */
709     _loadResources: function()
710     {
711         var resources = this._descriptor["resources"];
712         if (!resources)
713             return Promise.resolve();
714         var promises = [];
715         for (var i = 0; i < resources.length; ++i) {
716             var url = this._modularizeURL(resources[i]);
717             promises.push(loadResourcePromise(url).then(cacheResource.bind(this, url), cacheResource.bind(this, url, undefined)));
718         }
719         return Promise.all(promises).then(undefined);
721         /**
722          * @param {string} path
723          * @param {string=} content
724          */
725         function cacheResource(path, content)
726         {
727             if (!content) {
728                 console.error("Failed to load resource: " + path);
729                 return;
730             }
731             var sourceURL = window.location.href;
732             if (window.location.search)
733                 sourceURL = sourceURL.replace(window.location.search, "");
734             sourceURL = sourceURL.substring(0, sourceURL.lastIndexOf("/") + 1) + path;
735             Runtime.cachedResources[path] = content + "\n/*# sourceURL=" + sourceURL + " */";
736         }
737     },
739     /**
740      * @return {!Promise.<undefined>}
741      */
742     _loadScripts: function()
743     {
744         if (!this._descriptor.scripts)
745             return Promise.resolve();
747         if (Runtime.isReleaseMode())
748             return loadScriptsPromise([this._name + "_module.js"], this._remoteBase());
750         return loadScriptsPromise(this._descriptor.scripts.map(this._modularizeURL, this));
751     },
753     /**
754      * @param {string} resourceName
755      */
756     _modularizeURL: function(resourceName)
757     {
758         return normalizePath(this._name + "/" + resourceName);
759     },
761     /**
762      * @return {string|undefined}
763      */
764     _remoteBase: function()
765     {
766         return this._descriptor.remote && Runtime._remoteBase || undefined;
767     },
769     /**
770      * @param {string} value
771      * @return {string}
772      */
773     substituteURL: function(value)
774     {
775         var base = this._remoteBase() || "";
776         return value.replace(/@url\(([^\)]*?)\)/g, convertURL.bind(this));
778         function convertURL(match, url)
779         {
780             return base + this._modularizeURL(url);
781         }
782     },
784     /**
785      * @param {string} className
786      * @param {!Runtime.Extension} extension
787      * @return {?Object}
788      */
789     _instance: function(className, extension)
790     {
791         if (className in this._instanceMap)
792             return this._instanceMap[className];
794         var constructorFunction = window.eval(className);
795         if (!(constructorFunction instanceof Function)) {
796             this._instanceMap[className] = null;
797             return null;
798         }
800         var instance = new constructorFunction(extension);
801         this._instanceMap[className] = instance;
802         return instance;
803     }
807  * @constructor
808  * @param {!Runtime.Module} module
809  * @param {!Runtime.ExtensionDescriptor} descriptor
810  */
811 Runtime.Extension = function(module, descriptor)
813     this._module = module;
814     this._descriptor = descriptor;
816     this._type = descriptor.type;
817     this._hasTypeClass = this._type.charAt(0) === "@";
819     /**
820      * @type {?string}
821      */
822     this._className = descriptor.className || null;
825 Runtime.Extension.prototype = {
826     /**
827      * @return {!Object}
828      */
829     descriptor: function()
830     {
831         return this._descriptor;
832     },
834     /**
835      * @return {!Runtime.Module}
836      */
837     module: function()
838     {
839         return this._module;
840     },
842     /**
843      * @return {boolean}
844      */
845     enabled: function()
846     {
847         var activatorExperiment = this.descriptor()["experiment"];
848         if (activatorExperiment && activatorExperiment.startsWith("!") && Runtime.experiments.isEnabled(activatorExperiment.substring(1)))
849             return false;
850         if (activatorExperiment && !activatorExperiment.startsWith("!") && !Runtime.experiments.isEnabled(activatorExperiment))
851             return false;
852         var condition = this.descriptor()["condition"];
853         if (condition && !Runtime.queryParam(condition))
854             return false;
855         return this._module.enabled();
856     },
858     /**
859      * @return {?function(new:Object)}
860      */
861     _typeClass: function()
862     {
863         if (!this._hasTypeClass)
864             return null;
865         return this._module._manager._resolve(this._type.substring(1));
866     },
868     /**
869      * @param {?Object} context
870      * @return {boolean}
871      */
872     isApplicable: function(context)
873     {
874         return this._module._manager.isExtensionApplicableToContext(this, context);
875     },
877     /**
878      * @return {!Promise.<!Object>}
879      */
880     instancePromise: function()
881     {
882         if (!this._className)
883             return Promise.reject(new Error("No class name in extension"));
884         var className = this._className;
885         if (this._instance)
886             return Promise.resolve(this._instance);
888         return this._module._loadPromise().then(constructInstance.bind(this));
890         /**
891          * @return {!Object}
892          * @this {Runtime.Extension}
893          */
894         function constructInstance()
895         {
896             var result = this._module._instance(className, this);
897             if (!result)
898                 return Promise.reject("Could not instantiate: " + className);
899             return result;
900         }
901     },
903     /**
904      * @param {string} platform
905      * @return {string}
906      */
907     title: function(platform)
908     {
909         // FIXME: should be WebInspector.UIString() but runtime is not l10n aware yet.
910         return this._descriptor["title-" + platform] || this._descriptor["title"];
911     }
915  * @constructor
916  */
917 Runtime.ExperimentsSupport = function()
919     this._supportEnabled = Runtime.queryParam("experiments") !== null;
920     this._experiments = [];
921     this._experimentNames = {};
922     this._enabledTransiently = {};
925 Runtime.ExperimentsSupport.prototype = {
926     /**
927      * @return {!Array.<!Runtime.Experiment>}
928      */
929     allConfigurableExperiments: function()
930     {
931         var result = [];
932         for (var i = 0; i < this._experiments.length; i++) {
933             var experiment = this._experiments[i];
934             if (!this._enabledTransiently[experiment.name])
935                 result.push(experiment);
936         }
937         return result;
938     },
940     /**
941      * @return {boolean}
942      */
943     supportEnabled: function()
944     {
945         return this._supportEnabled;
946     },
948     /**
949      * @param {!Object} value
950      */
951     _setExperimentsSetting: function(value)
952     {
953         if (!self.localStorage)
954             return;
955         self.localStorage["experiments"] = JSON.stringify(value);
956     },
958     /**
959      * @param {string} experimentName
960      * @param {string} experimentTitle
961      * @param {boolean=} hidden
962      */
963     register: function(experimentName, experimentTitle, hidden)
964     {
965         Runtime._assert(!this._experimentNames[experimentName], "Duplicate registration of experiment " + experimentName);
966         this._experimentNames[experimentName] = true;
967         this._experiments.push(new Runtime.Experiment(this, experimentName, experimentTitle, !!hidden));
968     },
970     /**
971      * @param {string} experimentName
972      * @return {boolean}
973      */
974     isEnabled: function(experimentName)
975     {
976         this._checkExperiment(experimentName);
978         if (this._enabledTransiently[experimentName])
979             return true;
980         if (!this.supportEnabled())
981             return false;
983         return !!Runtime._experimentsSetting()[experimentName];
984     },
986     /**
987      * @param {string} experimentName
988      * @param {boolean} enabled
989      */
990     setEnabled: function(experimentName, enabled)
991     {
992         this._checkExperiment(experimentName);
993         var experimentsSetting = Runtime._experimentsSetting();
994         experimentsSetting[experimentName] = enabled;
995         this._setExperimentsSetting(experimentsSetting);
996     },
998     /**
999      * @param {!Array.<string>} experimentNames
1000      */
1001     setDefaultExperiments: function(experimentNames)
1002     {
1003         for (var i = 0; i < experimentNames.length; ++i) {
1004             this._checkExperiment(experimentNames[i]);
1005             this._enabledTransiently[experimentNames[i]] = true;
1006         }
1007     },
1009     /**
1010      * @param {string} experimentName
1011      */
1012     enableForTest: function(experimentName)
1013     {
1014         this._checkExperiment(experimentName);
1015         this._enabledTransiently[experimentName] = true;
1016     },
1018     clearForTest: function()
1019     {
1020         this._experiments = [];
1021         this._experimentNames = {};
1022         this._enabledTransiently = {};
1023     },
1025     cleanUpStaleExperiments: function()
1026     {
1027         var experimentsSetting = Runtime._experimentsSetting();
1028         var cleanedUpExperimentSetting = {};
1029         for (var i = 0; i < this._experiments.length; ++i) {
1030             var experimentName = this._experiments[i].name;
1031             if (experimentsSetting[experimentName])
1032                 cleanedUpExperimentSetting[experimentName] = true;
1033         }
1034         this._setExperimentsSetting(cleanedUpExperimentSetting);
1035     },
1037     /**
1038      * @param {string} experimentName
1039      */
1040     _checkExperiment: function(experimentName)
1041     {
1042         Runtime._assert(this._experimentNames[experimentName], "Unknown experiment " + experimentName);
1043     }
1047  * @constructor
1048  * @param {!Runtime.ExperimentsSupport} experiments
1049  * @param {string} name
1050  * @param {string} title
1051  * @param {boolean} hidden
1052  */
1053 Runtime.Experiment = function(experiments, name, title, hidden)
1055     this.name = name;
1056     this.title = title;
1057     this.hidden = hidden;
1058     this._experiments = experiments;
1061 Runtime.Experiment.prototype = {
1062     /**
1063      * @return {boolean}
1064      */
1065     isEnabled: function()
1066     {
1067         return this._experiments.isEnabled(this.name);
1068     },
1070     /**
1071      * @param {boolean} enabled
1072      */
1073     setEnabled: function(enabled)
1074     {
1075         this._experiments.setEnabled(this.name, enabled);
1076     }
1079 {(function parseQueryParameters()
1081     var queryParams = location.search;
1082     if (!queryParams)
1083         return;
1084     var params = queryParams.substring(1).split("&");
1085     for (var i = 0; i < params.length; ++i) {
1086         var pair = params[i].split("=");
1087         var name = pair.shift();
1088         Runtime._queryParamsObject[name] = pair.join("=");
1089     }
1090 })();}
1093 // This must be constructed after the query parameters have been parsed.
1094 Runtime.experiments = new Runtime.ExperimentsSupport();
1097  * @type {?string}
1098  */
1099 Runtime._remoteBase = Runtime.queryParam("remoteBase");
1101 /** @type {!Runtime} */
1102 var runtime;