blob: 9056bd16ad155f40503f0416a86090019c83bd32 [file] [log] [blame]
/*
* Copyright (C) 2014 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// This gets all concatenated module descriptors in the release mode.
var allDescriptors = [];
var applicationDescriptor;
var _loadedScripts = {};
// FIXME: This is a workaround to force Closure compiler provide
// the standard ES6 runtime for all modules. This should be removed
// once Closure provides standard externs for Map et al.
for (var k of []) {};
/**
* @param {string} url
* @return {!Promise.<string>}
*/
function loadResourcePromise(url)
{
return new Promise(load);
/**
* @param {function(?)} fulfill
* @param {function(*)} reject
*/
function load(fulfill, reject)
{
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = onreadystatechange;
/**
* @param {Event} e
*/
function onreadystatechange(e)
{
if (xhr.readyState !== 4)
return;
if ([0, 200, 304].indexOf(xhr.status) === -1) // Testing harness file:/// results in 0.
reject(new Error("While loading from url " + url + " server responded with a status of " + xhr.status));
else
fulfill(e.target.response);
}
xhr.send(null);
}
}
/**
* http://tools.ietf.org/html/rfc3986#section-5.2.4
* @param {string} path
* @return {string}
*/
function normalizePath(path)
{
if (path.indexOf("..") === -1 && path.indexOf('.') === -1)
return path;
var normalizedSegments = [];
var segments = path.split("/");
for (var i = 0; i < segments.length; i++) {
var segment = segments[i];
if (segment === ".")
continue;
else if (segment === "..")
normalizedSegments.pop();
else if (segment)
normalizedSegments.push(segment);
}
var normalizedPath = normalizedSegments.join("/");
if (normalizedPath[normalizedPath.length - 1] === "/")
return normalizedPath;
if (path[0] === "/" && normalizedPath)
normalizedPath = "/" + normalizedPath;
if ((path[path.length - 1] === "/") || (segments[segments.length - 1] === ".") || (segments[segments.length - 1] === ".."))
normalizedPath = normalizedPath + "/";
return normalizedPath;
}
/**
* @param {!Array.<string>} scriptNames
* @param {string=} base
* @return {!Promise.<undefined>}
*/
function loadScriptsPromise(scriptNames, base)
{
/** @type {!Array.<!Promise.<string>>} */
var promises = [];
/** @type {!Array.<string>} */
var urls = [];
var sources = new Array(scriptNames.length);
var scriptToEval = 0;
for (var i = 0; i < scriptNames.length; ++i) {
var scriptName = scriptNames[i];
var sourceURL = (base || self._importScriptPathPrefix) + scriptName;
var schemaIndex = sourceURL.indexOf("://") + 3;
sourceURL = sourceURL.substring(0, schemaIndex) + normalizePath(sourceURL.substring(schemaIndex));
if (_loadedScripts[sourceURL])
continue;
urls.push(sourceURL);
promises.push(loadResourcePromise(sourceURL).then(scriptSourceLoaded.bind(null, i), scriptSourceLoaded.bind(null, i, undefined)));
}
return Promise.all(promises).then(undefined);
/**
* @param {number} scriptNumber
* @param {string=} scriptSource
*/
function scriptSourceLoaded(scriptNumber, scriptSource)
{
sources[scriptNumber] = scriptSource || "";
// Eval scripts as fast as possible.
while (typeof sources[scriptToEval] !== "undefined") {
evaluateScript(urls[scriptToEval], sources[scriptToEval]);
++scriptToEval;
}
}
/**
* @param {string} sourceURL
* @param {string=} scriptSource
*/
function evaluateScript(sourceURL, scriptSource)
{
_loadedScripts[sourceURL] = true;
if (!scriptSource) {
// Do not reject, as this is normal in the hosted mode.
console.error("Empty response arrived for script '" + sourceURL + "'");
return;
}
self.eval(scriptSource + "\n//# sourceURL=" + sourceURL);
}
}
(function() {
var baseUrl = self.location ? self.location.origin + self.location.pathname : "";
self._importScriptPathPrefix = baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1);
})();
/**
* @constructor
* @param {!Array.<!Runtime.ModuleDescriptor>} descriptors
* @param {!Array.<string>=} coreModuleNames
*/
function Runtime(descriptors, coreModuleNames)
{
/**
* @type {!Array.<!Runtime.Module>}
*/
this._modules = [];
/**
* @type {!Object.<string, !Runtime.Module>}
*/
this._modulesMap = {};
/**
* @type {!Array.<!Runtime.Extension>}
*/
this._extensions = [];
/**
* @type {!Object.<string, !function(new:Object)>}
*/
this._cachedTypeClasses = {};
/**
* @type {!Object.<string, !Runtime.ModuleDescriptor>}
*/
this._descriptorsMap = {};
for (var i = 0; i < descriptors.length; ++i)
this._registerModule(descriptors[i]);
if (coreModuleNames)
this._loadAutoStartModules(coreModuleNames);
}
/**
* @type {!Object.<string, string>}
*/
Runtime._queryParamsObject = { __proto__: null };
/**
* @type {!Object.<string, string>}
*/
Runtime.cachedResources = { __proto__: null };
/**
* @return {boolean}
*/
Runtime.isReleaseMode = function()
{
return !!allDescriptors.length;
}
/**
* @param {string} appName
*/
Runtime.startApplication = function(appName)
{
console.timeStamp("Runtime.startApplication");
var allDescriptorsByName = {};
for (var i = 0; Runtime.isReleaseMode() && i < allDescriptors.length; ++i) {
var d = allDescriptors[i];
allDescriptorsByName[d["name"]] = d;
}
var applicationPromise;
if (applicationDescriptor)
applicationPromise = Promise.resolve(applicationDescriptor);
else
applicationPromise = loadResourcePromise(appName + ".json").then(JSON.parse.bind(JSON));
applicationPromise.then(parseModuleDescriptors);
/**
* @param {!Array.<!Object>} configuration
*/
function parseModuleDescriptors(configuration)
{
var moduleJSONPromises = [];
var coreModuleNames = [];
for (var i = 0; i < configuration.length; ++i) {
var descriptor = configuration[i];
if (descriptor["type"] === "worker")
continue;
var name = descriptor["name"];
var moduleJSON = allDescriptorsByName[name];
if (moduleJSON)
moduleJSONPromises.push(Promise.resolve(moduleJSON));
else
moduleJSONPromises.push(loadResourcePromise(name + "/module.json").then(JSON.parse.bind(JSON)));
if (descriptor["type"] === "autostart")
coreModuleNames.push(name);
}
Promise.all(moduleJSONPromises).then(instantiateRuntime);
/**
* @param {!Array.<!Object>} moduleDescriptors
*/
function instantiateRuntime(moduleDescriptors)
{
for (var i = 0; !Runtime.isReleaseMode() && i < moduleDescriptors.length; ++i) {
moduleDescriptors[i]["name"] = configuration[i]["name"];
moduleDescriptors[i]["condition"] = configuration[i]["condition"];
}
self.runtime = new Runtime(moduleDescriptors, coreModuleNames);
}
}
}
/**
* @param {string} name
* @return {?string}
*/
Runtime.queryParam = function(name)
{
return Runtime._queryParamsObject[name] || null;
}
/**
* @param {!Array.<string>} banned
* @return {string}
*/
Runtime.constructQueryParams = function(banned)
{
var params = [];
for (var key in Runtime._queryParamsObject) {
if (!key || banned.indexOf(key) !== -1)
continue;
params.push(key + "=" + Runtime._queryParamsObject[key]);
}
return params.length ? "?" + params.join("&") : "";
}
/**
* @return {!Object}
*/
Runtime._experimentsSetting = function()
{
try {
return /** @type {!Object} */ (JSON.parse(self.localStorage && self.localStorage["experiments"] ? self.localStorage["experiments"] : "{}"));
} catch (e) {
console.error("Failed to parse localStorage['experiments']");
return {};
}
}
/**
* @param {!Array.<!Promise.<T, !Error>>} promises
* @return {!Promise.<!Array.<T>>}
* @template T
*/
Runtime._some = function(promises)
{
var all = [];
var wasRejected = [];
for (var i = 0; i < promises.length; ++i) {
// Workaround closure compiler bug.
var handlerFunction = /** @type {function()} */ (handler.bind(promises[i], i));
all.push(promises[i].catch(handlerFunction));
}
return Promise.all(all).then(filterOutFailuresResults);
/**
* @param {!Array.<T>} results
* @return {!Array.<T>}
* @template T
*/
function filterOutFailuresResults(results)
{
var filtered = [];
for (var i = 0; i < results.length; ++i) {
if (!wasRejected[i])
filtered.push(results[i]);
}
return filtered;
}
/**
* @this {!Promise}
* @param {number} index
* @param {!Error} e
*/
function handler(index, e)
{
wasRejected[index] = true;
console.error(e.stack);
}
}
Runtime._console = console;
Runtime._originalAssert = console.assert;
Runtime._assert = function(value, message)
{
if (value)
return;
Runtime._originalAssert.call(Runtime._console, value, message + " " + new Error().stack);
}
Runtime.prototype = {
useTestBase: function()
{
Runtime._remoteBase = "http://localhost:8000/inspector-sources/";
},
/**
* @param {!Runtime.ModuleDescriptor} descriptor
*/
_registerModule: function(descriptor)
{
var module = new Runtime.Module(this, descriptor);
this._modules.push(module);
this._modulesMap[descriptor["name"]] = module;
},
/**
* @param {string} moduleName
* @return {!Promise.<undefined>}
*/
loadModulePromise: function(moduleName)
{
return this._modulesMap[moduleName]._loadPromise();
},
/**
* @param {!Array.<string>} moduleNames
* @return {!Promise.<!Array.<*>>}
*/
_loadAutoStartModules: function(moduleNames)
{
var promises = [];
for (var i = 0; i < moduleNames.length; ++i) {
if (Runtime.isReleaseMode())
this._modulesMap[moduleNames[i]]._loaded = true;
else
promises.push(this.loadModulePromise(moduleNames[i]));
}
return Promise.all(promises);
},
/**
* @param {!Runtime.Extension} extension
* @param {?function(function(new:Object)):boolean} predicate
* @return {boolean}
*/
_checkExtensionApplicability: function(extension, predicate)
{
if (!predicate)
return false;
var contextTypes = /** @type {!Array.<string>|undefined} */ (extension.descriptor().contextTypes);
if (!contextTypes)
return true;
for (var i = 0; i < contextTypes.length; ++i) {
var contextType = this._resolve(contextTypes[i]);
var isMatching = !!contextType && predicate(contextType);
if (isMatching)
return true;
}
return false;
},
/**
* @param {!Runtime.Extension} extension
* @param {?Object} context
* @return {boolean}
*/
isExtensionApplicableToContext: function(extension, context)
{
if (!context)
return true;
return this._checkExtensionApplicability(extension, isInstanceOf);
/**
* @param {!Function} targetType
* @return {boolean}
*/
function isInstanceOf(targetType)
{
return context instanceof targetType;
}
},
/**
* @param {!Runtime.Extension} extension
* @param {!Set.<!Function>=} currentContextTypes
* @return {boolean}
*/
isExtensionApplicableToContextTypes: function(extension, currentContextTypes)
{
if (!extension.descriptor().contextTypes)
return true;
return this._checkExtensionApplicability(extension, currentContextTypes ? isContextTypeKnown : null);
/**
* @param {!Function} targetType
* @return {boolean}
*/
function isContextTypeKnown(targetType)
{
return currentContextTypes.has(targetType);
}
},
/**
* @param {*} type
* @param {?Object=} context
* @return {!Array.<!Runtime.Extension>}
*/
extensions: function(type, context)
{
return this._extensions.filter(filter).sort(orderComparator);
/**
* @param {!Runtime.Extension} extension
* @return {boolean}
*/
function filter(extension)
{
if (extension._type !== type && extension._typeClass() !== type)
return false;
if (!extension.enabled())
return false;
return !context || extension.isApplicable(context);
}
/**
* @param {!Runtime.Extension} extension1
* @param {!Runtime.Extension} extension2
* @return {number}
*/
function orderComparator(extension1, extension2)
{
var order1 = extension1.descriptor()["order"] || 0;
var order2 = extension2.descriptor()["order"] || 0;
return order1 - order2;
}
},
/**
* @param {*} type
* @param {?Object=} context
* @return {?Runtime.Extension}
*/
extension: function(type, context)
{
return this.extensions(type, context)[0] || null;
},
/**
* @param {*} type
* @param {?Object=} context
* @return {!Promise.<!Array.<!Object>>}
*/
instancesPromise: function(type, context)
{
var extensions = this.extensions(type, context);
var promises = [];
for (var i = 0; i < extensions.length; ++i)
promises.push(extensions[i].instancePromise());
return Runtime._some(promises);
},
/**
* @param {*} type
* @param {?Object=} context
* @return {!Promise.<!Object>}
*/
instancePromise: function(type, context)
{
var extension = this.extension(type, context);
if (!extension)
return Promise.reject(new Error("No such extension: " + type + " in given context."));
return extension.instancePromise();
},
/**
* @return {?function(new:Object)}
*/
_resolve: function(typeName)
{
if (!this._cachedTypeClasses[typeName]) {
var path = typeName.split(".");
var object = window;
for (var i = 0; object && (i < path.length); ++i)
object = object[path[i]];
if (object)
this._cachedTypeClasses[typeName] = /** @type function(new:Object) */(object);
}
return this._cachedTypeClasses[typeName] || null;
}
}
/**
* @constructor
*/
Runtime.ModuleDescriptor = function()
{
/**
* @type {string}
*/
this.name;
/**
* @type {!Array.<!Runtime.ExtensionDescriptor>}
*/
this.extensions;
/**
* @type {!Array.<string>|undefined}
*/
this.dependencies;
/**
* @type {!Array.<string>}
*/
this.scripts;
/**
* @type {boolean|undefined}
*/
this.remote;
}
/**
* @constructor
*/
Runtime.ExtensionDescriptor = function()
{
/**
* @type {string}
*/
this.type;
/**
* @type {string|undefined}
*/
this.className;
/**
* @type {!Array.<string>|undefined}
*/
this.contextTypes;
}
/**
* @constructor
* @param {!Runtime} manager
* @param {!Runtime.ModuleDescriptor} descriptor
*/
Runtime.Module = function(manager, descriptor)
{
this._manager = manager;
this._descriptor = descriptor;
this._name = descriptor.name;
/** @type {!Object.<string, ?Object>} */
this._instanceMap = {};
var extensions = /** @type {?Array.<!Runtime.ExtensionDescriptor>} */ (descriptor.extensions);
for (var i = 0; extensions && i < extensions.length; ++i)
this._manager._extensions.push(new Runtime.Extension(this, extensions[i]));
this._loaded = false;
}
Runtime.Module.prototype = {
/**
* @return {string}
*/
name: function()
{
return this._name;
},
/**
* @return {boolean}
*/
enabled: function()
{
var activatorExperiment = this._descriptor["experiment"];
if (activatorExperiment && !Runtime.experiments.isEnabled(activatorExperiment))
return false;
var condition = this._descriptor["condition"];
if (condition && !Runtime.queryParam(condition))
return false;
return true;
},
/**
* @param {string} name
* @return {string}
*/
resource: function(name)
{
var fullName = this._name + "/" + name;
var content = Runtime.cachedResources[fullName];
if (!content)
throw new Error(fullName + " not preloaded. Check module.json");
return content;
},
/**
* @return {!Promise.<undefined>}
*/
_loadPromise: function()
{
if (this._loaded)
return Promise.resolve();
if (!this.enabled())
return Promise.reject(new Error("Module " + this._name + " is not enabled"));
if (this._pendingLoadPromise)
return this._pendingLoadPromise;
var dependencies = this._descriptor.dependencies;
var dependencyPromises = [];
for (var i = 0; dependencies && i < dependencies.length; ++i)
dependencyPromises.push(this._manager._modulesMap[dependencies[i]]._loadPromise());
this._pendingLoadPromise = Promise.all(dependencyPromises)
.then(this._loadResources.bind(this))
.then(this._loadScripts.bind(this))
.then(markAsLoaded.bind(this));
return this._pendingLoadPromise;
/**
* @this {Runtime.Module}
*/
function markAsLoaded()
{
delete this._pendingLoadPromise;
this._loaded = true;
}
},
/**
* @return {!Promise.<undefined>}
* @this {Runtime.Module}
*/
_loadResources: function()
{
var resources = this._descriptor["resources"];
if (!resources)
return Promise.resolve();
var promises = [];
for (var i = 0; i < resources.length; ++i) {
var url = this._modularizeURL(resources[i]);
promises.push(loadResourcePromise(url).then(cacheResource.bind(this, url), cacheResource.bind(this, url, undefined)));
}
return Promise.all(promises).then(undefined);
/**
* @param {string} path
* @param {string=} content
*/
function cacheResource(path, content)
{
if (!content) {
console.error("Failed to load resource: " + path);
return;
}
var sourceURL = window.location.href;
if (window.location.search)
sourceURL = sourceURL.replace(window.location.search, "");
sourceURL = sourceURL.substring(0, sourceURL.lastIndexOf("/") + 1) + path;
Runtime.cachedResources[path] = content + "\n/*# sourceURL=" + sourceURL + " */";
}
},
/**
* @return {!Promise.<undefined>}
*/
_loadScripts: function()
{
if (!this._descriptor.scripts)
return Promise.resolve();
if (Runtime.isReleaseMode())
return loadScriptsPromise([this._name + "_module.js"], this._remoteBase());
return loadScriptsPromise(this._descriptor.scripts.map(this._modularizeURL, this));
},
/**
* @param {string} resourceName
*/
_modularizeURL: function(resourceName)
{
return normalizePath(this._name + "/" + resourceName);
},
/**
* @return {string|undefined}
*/
_remoteBase: function()
{
return this._descriptor.remote && Runtime._remoteBase || undefined;
},
/**
* @param {string} value
* @return {string}
*/
substituteURL: function(value)
{
var base = this._remoteBase() || "";
return value.replace(/@url\(([^\)]*?)\)/g, convertURL.bind(this));
function convertURL(match, url)
{
return base + this._modularizeURL(url);
}
},
/**
* @param {string} className
* @param {!Runtime.Extension} extension
* @return {?Object}
*/
_instance: function(className, extension)
{
if (className in this._instanceMap)
return this._instanceMap[className];
var constructorFunction = window.eval(className);
if (!(constructorFunction instanceof Function)) {
this._instanceMap[className] = null;
return null;
}
var instance = new constructorFunction(extension);
this._instanceMap[className] = instance;
return instance;
}
}
/**
* @constructor
* @param {!Runtime.Module} module
* @param {!Runtime.ExtensionDescriptor} descriptor
*/
Runtime.Extension = function(module, descriptor)
{
this._module = module;
this._descriptor = descriptor;
this._type = descriptor.type;
this._hasTypeClass = this._type.charAt(0) === "@";
/**
* @type {?string}
*/
this._className = descriptor.className || null;
}
Runtime.Extension.prototype = {
/**
* @return {!Object}
*/
descriptor: function()
{
return this._descriptor;
},
/**
* @return {!Runtime.Module}
*/
module: function()
{
return this._module;
},
/**
* @return {boolean}
*/
enabled: function()
{
var activatorExperiment = this.descriptor()["experiment"];
if (activatorExperiment && activatorExperiment.startsWith("!") && Runtime.experiments.isEnabled(activatorExperiment.substring(1)))
return false;
if (activatorExperiment && !activatorExperiment.startsWith("!") && !Runtime.experiments.isEnabled(activatorExperiment))
return false;
var condition = this.descriptor()["condition"];
if (condition && !Runtime.queryParam(condition))
return false;
return this._module.enabled();
},
/**
* @return {?function(new:Object)}
*/
_typeClass: function()
{
if (!this._hasTypeClass)
return null;
return this._module._manager._resolve(this._type.substring(1));
},
/**
* @param {?Object} context
* @return {boolean}
*/
isApplicable: function(context)
{
return this._module._manager.isExtensionApplicableToContext(this, context);
},
/**
* @return {!Promise.<!Object>}
*/
instancePromise: function()
{
if (!this._className)
return Promise.reject(new Error("No class name in extension"));
var className = this._className;
if (this._instance)
return Promise.resolve(this._instance);
return this._module._loadPromise().then(constructInstance.bind(this));
/**
* @return {!Object}
* @this {Runtime.Extension}
*/
function constructInstance()
{
var result = this._module._instance(className, this);
if (!result)
return Promise.reject("Could not instantiate: " + className);
return result;
}
},
/**
* @param {string} platform
* @return {string}
*/
title: function(platform)
{
// FIXME: should be WebInspector.UIString() but runtime is not l10n aware yet.
return this._descriptor["title-" + platform] || this._descriptor["title"];
}
}
/**
* @constructor
*/
Runtime.ExperimentsSupport = function()
{
this._supportEnabled = Runtime.queryParam("experiments") !== null;
this._experiments = [];
this._experimentNames = {};
this._enabledTransiently = {};
}
Runtime.ExperimentsSupport.prototype = {
/**
* @return {!Array.<!Runtime.Experiment>}
*/
allConfigurableExperiments: function()
{
var result = [];
for (var i = 0; i < this._experiments.length; i++) {
var experiment = this._experiments[i];
if (!this._enabledTransiently[experiment.name])
result.push(experiment);
}
return result;
},
/**
* @return {boolean}
*/
supportEnabled: function()
{
return this._supportEnabled;
},
/**
* @param {!Object} value
*/
_setExperimentsSetting: function(value)
{
if (!self.localStorage)
return;
self.localStorage["experiments"] = JSON.stringify(value);
},
/**
* @param {string} experimentName
* @param {string} experimentTitle
* @param {boolean=} hidden
*/
register: function(experimentName, experimentTitle, hidden)
{
Runtime._assert(!this._experimentNames[experimentName], "Duplicate registration of experiment " + experimentName);
this._experimentNames[experimentName] = true;
this._experiments.push(new Runtime.Experiment(this, experimentName, experimentTitle, !!hidden));
},
/**
* @param {string} experimentName
* @return {boolean}
*/
isEnabled: function(experimentName)
{
this._checkExperiment(experimentName);
if (this._enabledTransiently[experimentName])
return true;
if (!this.supportEnabled())
return false;
return !!Runtime._experimentsSetting()[experimentName];
},
/**
* @param {string} experimentName
* @param {boolean} enabled
*/
setEnabled: function(experimentName, enabled)
{
this._checkExperiment(experimentName);
var experimentsSetting = Runtime._experimentsSetting();
experimentsSetting[experimentName] = enabled;
this._setExperimentsSetting(experimentsSetting);
},
/**
* @param {!Array.<string>} experimentNames
*/
setDefaultExperiments: function(experimentNames)
{
for (var i = 0; i < experimentNames.length; ++i) {
this._checkExperiment(experimentNames[i]);
this._enabledTransiently[experimentNames[i]] = true;
}
},
/**
* @param {string} experimentName
*/
enableForTest: function(experimentName)
{
this._checkExperiment(experimentName);
this._enabledTransiently[experimentName] = true;
},
clearForTest: function()
{
this._experiments = [];
this._experimentNames = {};
this._enabledTransiently = {};
},
cleanUpStaleExperiments: function()
{
var experimentsSetting = Runtime._experimentsSetting();
var cleanedUpExperimentSetting = {};
for (var i = 0; i < this._experiments.length; ++i) {
var experimentName = this._experiments[i].name;
if (experimentsSetting[experimentName])
cleanedUpExperimentSetting[experimentName] = true;
}
this._setExperimentsSetting(cleanedUpExperimentSetting);
},
/**
* @param {string} experimentName
*/
_checkExperiment: function(experimentName)
{
Runtime._assert(this._experimentNames[experimentName], "Unknown experiment " + experimentName);
}
}
/**
* @constructor
* @param {!Runtime.ExperimentsSupport} experiments
* @param {string} name
* @param {string} title
* @param {boolean} hidden
*/
Runtime.Experiment = function(experiments, name, title, hidden)
{
this.name = name;
this.title = title;
this.hidden = hidden;
this._experiments = experiments;
}
Runtime.Experiment.prototype = {
/**
* @return {boolean}
*/
isEnabled: function()
{
return this._experiments.isEnabled(this.name);
},
/**
* @param {boolean} enabled
*/
setEnabled: function(enabled)
{
this._experiments.setEnabled(this.name, enabled);
}
}
{(function parseQueryParameters()
{
var queryParams = location.search;
if (!queryParams)
return;
var params = queryParams.substring(1).split("&");
for (var i = 0; i < params.length; ++i) {
var pair = params[i].split("=");
var name = pair.shift();
Runtime._queryParamsObject[name] = pair.join("=");
}
})();}
// This must be constructed after the query parameters have been parsed.
Runtime.experiments = new Runtime.ExperimentsSupport();
/**
* @type {?string}
*/
Runtime._remoteBase = Runtime.queryParam("remoteBase");
/** @type {!Runtime} */
var runtime;