2 * Copyright (C) 2014 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
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.
43 * @return {!Promise.<string>}
45 function loadResourcePromise(url)
47 return new Promise(load);
50 * @param {function(?)} fulfill
51 * @param {function(*)} reject
53 function load(fulfill, reject)
55 var xhr = new XMLHttpRequest();
56 xhr.open("GET", url, true);
57 xhr.onreadystatechange = onreadystatechange;
62 function onreadystatechange(e)
64 if (xhr.readyState !== 4)
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));
70 fulfill(e.target.response);
77 * http://tools.ietf.org/html/rfc3986#section-5.2.4
78 * @param {string} path
81 function normalizePath(path)
83 if (path.indexOf("..") === -1 && path.indexOf('.') === -1)
86 var normalizedSegments = [];
87 var segments = path.split("/");
88 for (var i = 0; i < segments.length; i++) {
89 var segment = segments[i];
92 else if (segment === "..")
93 normalizedSegments.pop();
95 normalizedSegments.push(segment);
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>}
113 function loadScriptsPromise(scriptNames, base)
115 /** @type {!Array.<!Promise.<string>>} */
117 /** @type {!Array.<string>} */
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])
128 urls.push(sourceURL);
129 promises.push(loadResourcePromise(sourceURL).then(scriptSourceLoaded.bind(null, i), scriptSourceLoaded.bind(null, i, undefined)));
131 return Promise.all(promises).then(undefined);
134 * @param {number} scriptNumber
135 * @param {string=} scriptSource
137 function scriptSourceLoaded(scriptNumber, scriptSource)
139 sources[scriptNumber] = scriptSource || "";
140 // Eval scripts as fast as possible.
141 while (typeof sources[scriptToEval] !== "undefined") {
142 evaluateScript(urls[scriptToEval], sources[scriptToEval]);
148 * @param {string} sourceURL
149 * @param {string=} scriptSource
151 function evaluateScript(sourceURL, scriptSource)
153 _loadedScripts[sourceURL] = true;
155 // Do not reject, as this is normal in the hosted mode.
156 console.error("Empty response arrived for script '" + sourceURL + "'");
159 self.eval(scriptSource + "\n//# sourceURL=" + sourceURL);
164 var baseUrl = self.location ? self.location.origin + self.location.pathname : "";
165 self._importScriptPathPrefix = baseUrl.substring(0, baseUrl.lastIndexOf("/") + 1);
170 * @param {!Array.<!Runtime.ModuleDescriptor>} descriptors
171 * @param {!Array.<string>=} coreModuleNames
173 function Runtime(descriptors, coreModuleNames)
176 * @type {!Array.<!Runtime.Module>}
180 * @type {!Object.<string, !Runtime.Module>}
182 this._modulesMap = {};
184 * @type {!Array.<!Runtime.Extension>}
186 this._extensions = [];
189 * @type {!Object.<string, !function(new:Object)>}
191 this._cachedTypeClasses = {};
194 * @type {!Object.<string, !Runtime.ModuleDescriptor>}
196 this._descriptorsMap = {};
198 for (var i = 0; i < descriptors.length; ++i)
199 this._registerModule(descriptors[i]);
201 this._loadAutoStartModules(coreModuleNames);
205 * @type {!Object.<string, string>}
207 Runtime._queryParamsObject = { __proto__: null };
210 * @type {!Object.<string, string>}
212 Runtime.cachedResources = { __proto__: null };
217 Runtime.isReleaseMode = function()
219 return !!allDescriptors.length;
223 * @param {string} appName
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;
235 var applicationPromise;
236 if (applicationDescriptor)
237 applicationPromise = Promise.resolve(applicationDescriptor);
239 applicationPromise = loadResourcePromise(appName + ".json").then(JSON.parse.bind(JSON));
241 applicationPromise.then(parseModuleDescriptors);
244 * @param {!Array.<!Object>} configuration
246 function parseModuleDescriptors(configuration)
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")
254 var name = descriptor["name"];
255 var moduleJSON = allDescriptorsByName[name];
257 moduleJSONPromises.push(Promise.resolve(moduleJSON));
259 moduleJSONPromises.push(loadResourcePromise(name + "/module.json").then(JSON.parse.bind(JSON)));
260 if (descriptor["type"] === "autostart")
261 coreModuleNames.push(name);
264 Promise.all(moduleJSONPromises).then(instantiateRuntime);
266 * @param {!Array.<!Object>} moduleDescriptors
268 function instantiateRuntime(moduleDescriptors)
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"];
274 self.runtime = new Runtime(moduleDescriptors, coreModuleNames);
280 * @param {string} name
283 Runtime.queryParam = function(name)
285 return Runtime._queryParamsObject[name] || null;
289 * @param {!Array.<string>} banned
292 Runtime.constructQueryParams = function(banned)
295 for (var key in Runtime._queryParamsObject) {
296 if (!key || banned.indexOf(key) !== -1)
298 params.push(key + "=" + Runtime._queryParamsObject[key]);
300 return params.length ? "?" + params.join("&") : "";
306 Runtime._experimentsSetting = function()
309 return /** @type {!Object} */ (JSON.parse(self.localStorage && self.localStorage["experiments"] ? self.localStorage["experiments"] : "{}"));
311 console.error("Failed to parse localStorage['experiments']");
317 * @param {!Array.<!Promise.<T, !Error>>} promises
318 * @return {!Promise.<!Array.<T>>}
321 Runtime._some = function(promises)
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));
331 return Promise.all(all).then(filterOutFailuresResults);
334 * @param {!Array.<T>} results
335 * @return {!Array.<T>}
338 function filterOutFailuresResults(results)
341 for (var i = 0; i < results.length; ++i) {
343 filtered.push(results[i]);
350 * @param {number} index
353 function handler(index, e)
355 wasRejected[index] = true;
356 console.error(e.stack);
360 Runtime._console = console;
361 Runtime._originalAssert = console.assert;
362 Runtime._assert = function(value, message)
366 Runtime._originalAssert.call(Runtime._console, value, message + " " + new Error().stack);
369 Runtime.prototype = {
370 useTestBase: function()
372 Runtime._remoteBase = "http://localhost:8000/inspector-sources/";
376 * @param {!Runtime.ModuleDescriptor} descriptor
378 _registerModule: function(descriptor)
380 var module = new Runtime.Module(this, descriptor);
381 this._modules.push(module);
382 this._modulesMap[descriptor["name"]] = module;
386 * @param {string} moduleName
387 * @return {!Promise.<undefined>}
389 loadModulePromise: function(moduleName)
391 return this._modulesMap[moduleName]._loadPromise();
395 * @param {!Array.<string>} moduleNames
396 * @return {!Promise.<!Array.<*>>}
398 _loadAutoStartModules: function(moduleNames)
401 for (var i = 0; i < moduleNames.length; ++i) {
402 if (Runtime.isReleaseMode())
403 this._modulesMap[moduleNames[i]]._loaded = true;
405 promises.push(this.loadModulePromise(moduleNames[i]));
407 return Promise.all(promises);
411 * @param {!Runtime.Extension} extension
412 * @param {?function(function(new:Object)):boolean} predicate
415 _checkExtensionApplicability: function(extension, predicate)
419 var contextTypes = /** @type {!Array.<string>|undefined} */ (extension.descriptor().contextTypes);
422 for (var i = 0; i < contextTypes.length; ++i) {
423 var contextType = this._resolve(contextTypes[i]);
424 var isMatching = !!contextType && predicate(contextType);
432 * @param {!Runtime.Extension} extension
433 * @param {?Object} context
436 isExtensionApplicableToContext: function(extension, context)
440 return this._checkExtensionApplicability(extension, isInstanceOf);
443 * @param {!Function} targetType
446 function isInstanceOf(targetType)
448 return context instanceof targetType;
453 * @param {!Runtime.Extension} extension
454 * @param {!Set.<!Function>=} currentContextTypes
457 isExtensionApplicableToContextTypes: function(extension, currentContextTypes)
459 if (!extension.descriptor().contextTypes)
462 return this._checkExtensionApplicability(extension, currentContextTypes ? isContextTypeKnown : null);
465 * @param {!Function} targetType
468 function isContextTypeKnown(targetType)
470 return currentContextTypes.has(targetType);
476 * @param {?Object=} context
477 * @return {!Array.<!Runtime.Extension>}
479 extensions: function(type, context)
481 return this._extensions.filter(filter).sort(orderComparator);
484 * @param {!Runtime.Extension} extension
487 function filter(extension)
489 if (extension._type !== type && extension._typeClass() !== type)
491 if (!extension.enabled())
493 return !context || extension.isApplicable(context);
497 * @param {!Runtime.Extension} extension1
498 * @param {!Runtime.Extension} extension2
501 function orderComparator(extension1, extension2)
503 var order1 = extension1.descriptor()["order"] || 0;
504 var order2 = extension2.descriptor()["order"] || 0;
505 return order1 - order2;
511 * @param {?Object=} context
512 * @return {?Runtime.Extension}
514 extension: function(type, context)
516 return this.extensions(type, context)[0] || null;
521 * @param {?Object=} context
522 * @return {!Promise.<!Array.<!Object>>}
524 instancesPromise: function(type, context)
526 var extensions = this.extensions(type, context);
528 for (var i = 0; i < extensions.length; ++i)
529 promises.push(extensions[i].instancePromise());
530 return Runtime._some(promises);
535 * @param {?Object=} context
536 * @return {!Promise.<!Object>}
538 instancePromise: function(type, context)
540 var extension = this.extension(type, context);
542 return Promise.reject(new Error("No such extension: " + type + " in given context."));
543 return extension.instancePromise();
547 * @return {?function(new:Object)}
549 _resolve: function(typeName)
551 if (!this._cachedTypeClasses[typeName]) {
552 var path = typeName.split(".");
554 for (var i = 0; object && (i < path.length); ++i)
555 object = object[path[i]];
557 this._cachedTypeClasses[typeName] = /** @type function(new:Object) */(object);
559 return this._cachedTypeClasses[typeName] || null;
566 Runtime.ModuleDescriptor = function()
574 * @type {!Array.<!Runtime.ExtensionDescriptor>}
579 * @type {!Array.<string>|undefined}
584 * @type {!Array.<string>}
589 * @type {boolean|undefined}
597 Runtime.ExtensionDescriptor = function()
605 * @type {string|undefined}
610 * @type {!Array.<string>|undefined}
617 * @param {!Runtime} manager
618 * @param {!Runtime.ModuleDescriptor} descriptor
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 = {
647 var activatorExperiment = this._descriptor["experiment"];
648 if (activatorExperiment && !Runtime.experiments.isEnabled(activatorExperiment))
650 var condition = this._descriptor["condition"];
651 if (condition && !Runtime.queryParam(condition))
657 * @param {string} name
660 resource: function(name)
662 var fullName = this._name + "/" + name;
663 var content = Runtime.cachedResources[fullName];
665 throw new Error(fullName + " not preloaded. Check module.json");
670 * @return {!Promise.<undefined>}
672 _loadPromise: function()
675 return Promise.resolve();
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;
696 * @this {Runtime.Module}
698 function markAsLoaded()
700 delete this._pendingLoadPromise;
706 * @return {!Promise.<undefined>}
707 * @this {Runtime.Module}
709 _loadResources: function()
711 var resources = this._descriptor["resources"];
713 return Promise.resolve();
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)));
719 return Promise.all(promises).then(undefined);
722 * @param {string} path
723 * @param {string=} content
725 function cacheResource(path, content)
728 console.error("Failed to load resource: " + path);
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 + " */";
740 * @return {!Promise.<undefined>}
742 _loadScripts: function()
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));
754 * @param {string} resourceName
756 _modularizeURL: function(resourceName)
758 return normalizePath(this._name + "/" + resourceName);
762 * @return {string|undefined}
764 _remoteBase: function()
766 return this._descriptor.remote && Runtime._remoteBase || undefined;
770 * @param {string} value
773 substituteURL: function(value)
775 var base = this._remoteBase() || "";
776 return value.replace(/@url\(([^\)]*?)\)/g, convertURL.bind(this));
778 function convertURL(match, url)
780 return base + this._modularizeURL(url);
785 * @param {string} className
786 * @param {!Runtime.Extension} extension
789 _instance: function(className, extension)
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;
800 var instance = new constructorFunction(extension);
801 this._instanceMap[className] = instance;
808 * @param {!Runtime.Module} module
809 * @param {!Runtime.ExtensionDescriptor} descriptor
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) === "@";
822 this._className = descriptor.className || null;
825 Runtime.Extension.prototype = {
829 descriptor: function()
831 return this._descriptor;
835 * @return {!Runtime.Module}
847 var activatorExperiment = this.descriptor()["experiment"];
848 if (activatorExperiment && activatorExperiment.startsWith("!") && Runtime.experiments.isEnabled(activatorExperiment.substring(1)))
850 if (activatorExperiment && !activatorExperiment.startsWith("!") && !Runtime.experiments.isEnabled(activatorExperiment))
852 var condition = this.descriptor()["condition"];
853 if (condition && !Runtime.queryParam(condition))
855 return this._module.enabled();
859 * @return {?function(new:Object)}
861 _typeClass: function()
863 if (!this._hasTypeClass)
865 return this._module._manager._resolve(this._type.substring(1));
869 * @param {?Object} context
872 isApplicable: function(context)
874 return this._module._manager.isExtensionApplicableToContext(this, context);
878 * @return {!Promise.<!Object>}
880 instancePromise: function()
882 if (!this._className)
883 return Promise.reject(new Error("No class name in extension"));
884 var className = this._className;
886 return Promise.resolve(this._instance);
888 return this._module._loadPromise().then(constructInstance.bind(this));
892 * @this {Runtime.Extension}
894 function constructInstance()
896 var result = this._module._instance(className, this);
898 return Promise.reject("Could not instantiate: " + className);
904 * @param {string} platform
907 title: function(platform)
909 // FIXME: should be WebInspector.UIString() but runtime is not l10n aware yet.
910 return this._descriptor["title-" + platform] || this._descriptor["title"];
917 Runtime.ExperimentsSupport = function()
919 this._supportEnabled = Runtime.queryParam("experiments") !== null;
920 this._experiments = [];
921 this._experimentNames = {};
922 this._enabledTransiently = {};
925 Runtime.ExperimentsSupport.prototype = {
927 * @return {!Array.<!Runtime.Experiment>}
929 allConfigurableExperiments: function()
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);
943 supportEnabled: function()
945 return this._supportEnabled;
949 * @param {!Object} value
951 _setExperimentsSetting: function(value)
953 if (!self.localStorage)
955 self.localStorage["experiments"] = JSON.stringify(value);
959 * @param {string} experimentName
960 * @param {string} experimentTitle
961 * @param {boolean=} hidden
963 register: function(experimentName, experimentTitle, hidden)
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));
971 * @param {string} experimentName
974 isEnabled: function(experimentName)
976 this._checkExperiment(experimentName);
978 if (this._enabledTransiently[experimentName])
980 if (!this.supportEnabled())
983 return !!Runtime._experimentsSetting()[experimentName];
987 * @param {string} experimentName
988 * @param {boolean} enabled
990 setEnabled: function(experimentName, enabled)
992 this._checkExperiment(experimentName);
993 var experimentsSetting = Runtime._experimentsSetting();
994 experimentsSetting[experimentName] = enabled;
995 this._setExperimentsSetting(experimentsSetting);
999 * @param {!Array.<string>} experimentNames
1001 setDefaultExperiments: function(experimentNames)
1003 for (var i = 0; i < experimentNames.length; ++i) {
1004 this._checkExperiment(experimentNames[i]);
1005 this._enabledTransiently[experimentNames[i]] = true;
1010 * @param {string} experimentName
1012 enableForTest: function(experimentName)
1014 this._checkExperiment(experimentName);
1015 this._enabledTransiently[experimentName] = true;
1018 clearForTest: function()
1020 this._experiments = [];
1021 this._experimentNames = {};
1022 this._enabledTransiently = {};
1025 cleanUpStaleExperiments: function()
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;
1034 this._setExperimentsSetting(cleanedUpExperimentSetting);
1038 * @param {string} experimentName
1040 _checkExperiment: function(experimentName)
1042 Runtime._assert(this._experimentNames[experimentName], "Unknown experiment " + experimentName);
1048 * @param {!Runtime.ExperimentsSupport} experiments
1049 * @param {string} name
1050 * @param {string} title
1051 * @param {boolean} hidden
1053 Runtime.Experiment = function(experiments, name, title, hidden)
1057 this.hidden = hidden;
1058 this._experiments = experiments;
1061 Runtime.Experiment.prototype = {
1065 isEnabled: function()
1067 return this._experiments.isEnabled(this.name);
1071 * @param {boolean} enabled
1073 setEnabled: function(enabled)
1075 this._experiments.setEnabled(this.name, enabled);
1079 {(function parseQueryParameters()
1081 var queryParams = location.search;
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("=");
1093 // This must be constructed after the query parameters have been parsed.
1094 Runtime.experiments = new Runtime.ExperimentsSupport();
1099 Runtime._remoteBase = Runtime.queryParam("remoteBase");
1101 /** @type {!Runtime} */