// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var createClassWrapper = requireNative('utils').createClassWrapper; var nativeDeepCopy = requireNative('utils').deepCopy; var schemaRegistry = requireNative('schema_registry'); var CHECK = requireNative('logging').CHECK; var DCHECK = requireNative('logging').DCHECK; var WARNING = requireNative('logging').WARNING; /** * An object forEach. Calls |f| with each (key, value) pair of |obj|, using * |self| as the target. * @param {Object} obj The object to iterate over. * @param {function} f The function to call in each iteration. * @param {Object} self The object to use as |this| in each function call. */ function forEach(obj, f, self) { for (var key in obj) { if ($Object.hasOwnProperty(obj, key)) $Function.call(f, self, key, obj[key]); } } /** * Assuming |array_of_dictionaries| is structured like this: * [{id: 1, ... }, {id: 2, ...}, ...], you can use * lookup(array_of_dictionaries, 'id', 2) to get the dictionary with id == 2. * @param {Array>} array_of_dictionaries * @param {string} field * @param {?} value */ function lookup(array_of_dictionaries, field, value) { var filter = function (dict) {return dict[field] == value;}; var matches = array_of_dictionaries.filter(filter); if (matches.length == 0) { return undefined; } else if (matches.length == 1) { return matches[0] } else { throw new Error("Failed lookup of field '" + field + "' with value '" + value + "'"); } } function loadTypeSchema(typeName, defaultSchema) { var parts = $String.split(typeName, '.'); if (parts.length == 1) { if (defaultSchema == null) { WARNING('Trying to reference "' + typeName + '" ' + 'with neither namespace nor default schema.'); return null; } var types = defaultSchema.types; } else { var schemaName = $Array.join($Array.slice(parts, 0, parts.length - 1), '.'); var types = schemaRegistry.GetSchema(schemaName).types; } for (var i = 0; i < types.length; ++i) { if (types[i].id == typeName) return types[i]; } return null; } /** * Takes a private class implementation |cls| and exposes a subset of its * methods |functions| and properties |properties| and |readonly| in a public * wrapper class that it returns. Within bindings code, you can access the * implementation from an instance of the wrapper class using * privates(instance).impl, and from the implementation class you can access * the wrapper using this.wrapper (or implInstance.wrapper if you have another * instance of the implementation class). * @param {string} name The name of the exposed wrapper class. * @param {Object} cls The class implementation. * @param {{superclass: ?Function, * functions: ?Array, * properties: ?Array, * readonly: ?Array}} exposed The names of properties on the * implementation class to be exposed. |superclass| represents the * constructor of the class to be used as the superclass of the exposed * class; |functions| represents the names of functions which should be * delegated to the implementation; |properties| are gettable/settable * properties and |readonly| are read-only properties. */ function expose(name, cls, exposed) { var publicClass = createClassWrapper(name, cls, exposed.superclass); if ('functions' in exposed) { $Array.forEach(exposed.functions, function(func) { publicClass.prototype[func] = function() { var impl = privates(this).impl; return $Function.apply(impl[func], impl, arguments); }; }); } if ('properties' in exposed) { $Array.forEach(exposed.properties, function(prop) { $Object.defineProperty(publicClass.prototype, prop, { enumerable: true, get: function() { return privates(this).impl[prop]; }, set: function(value) { var impl = privates(this).impl; delete impl[prop]; impl[prop] = value; } }); }); } if ('readonly' in exposed) { $Array.forEach(exposed.readonly, function(readonly) { $Object.defineProperty(publicClass.prototype, readonly, { enumerable: true, get: function() { return privates(this).impl[readonly]; }, }); }); } return publicClass; } /** * Returns a deep copy of |value|. The copy will have no references to nested * values of |value|. */ function deepCopy(value) { return nativeDeepCopy(value); } /** * Wrap an asynchronous API call to a function |func| in a promise. The * remaining arguments will be passed to |func|. Returns a promise that will be * resolved to the result passed to the callback or rejected if an error occurs * (if chrome.runtime.lastError is set). If there are multiple results, the * promise will be resolved with an array containing those results. * * For example, * promise(chrome.storage.get, 'a').then(function(result) { * // Use result. * }).catch(function(error) { * // Report error.message. * }); */ function promise(func) { var args = $Array.slice(arguments, 1); DCHECK(typeof func == 'function'); return new Promise(function(resolve, reject) { args.push(function() { if (chrome.runtime.lastError) { reject(new Error(chrome.runtime.lastError)); return; } if (arguments.length <= 1) resolve(arguments[0]); else resolve($Array.slice(arguments)); }); $Function.apply(func, null, args); }); } exports.forEach = forEach; exports.loadTypeSchema = loadTypeSchema; exports.lookup = lookup; exports.expose = expose; exports.deepCopy = deepCopy; exports.promise = promise;