// 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 nativeDeepCopy = requireNative('utils').deepCopy; var logActivity = requireNative('activityLogger'); var exceptionHandler = require('uncaught_exception_handler'); /** * 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.filter(array_of_dictionaries, 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 + "'"); } } /** * Sets a property |value| on |obj| with property name |key|. Like * * obj[key] = value; * * but without triggering setters. */ function defineProperty(obj, key, value) { $Object.defineProperty(obj, key, { __proto__: null, configurable: true, enumerable: true, writable: true, value: value, }); } /** * Takes a private class implementation |privateClass| and exposes a subset of * its methods |functions| and properties |properties| and |readonly| to a * public wrapper class that should be passed in. 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). * * |publicClass| should be a constructor that calls constructPrivate() like so: * * privates(publicClass).constructPrivate(this, arguments); * * @param {function} publicClass The publicly exposed wrapper class. This must * be a named function, and the name appears in stack traces. * @param {Object} privateClass 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(publicClass, privateClass, exposed) { $Object.setPrototypeOf(exposed, null); // This should be called by publicClass. privates(publicClass).constructPrivate = function(self, args) { if (!(self instanceof publicClass)) { throw new Error('Please use "new ' + publicClass.name + '"'); } // The "instanceof publicClass" check can easily be spoofed, so we check // whether the private impl is already set before continuing. var privateSelf = privates(self); if ('impl' in privateSelf) { throw new Error('Object ' + publicClass.name + ' is already constructed'); } var privateObj = $Object.create(privateClass.prototype); $Function.apply(privateClass, privateObj, args); privateObj.wrapper = self; privateSelf.impl = privateObj; }; function getPrivateImpl(self) { var impl = privates(self).impl; if (!(impl instanceof privateClass)) { // Either the object is not constructed, or the property descriptor is // used on a target that is not an instance of publicClass. throw new Error('impl is not an instance of ' + privateClass.name); } return impl; } var publicClassPrototype = { // The final prototype will be assigned at the end of this method. __proto__: null, constructor: publicClass, }; if ('functions' in exposed) { $Array.forEach(exposed.functions, function(func) { publicClassPrototype[func] = function() { var impl = getPrivateImpl(this); return $Function.apply(impl[func], impl, arguments); }; }); } if ('properties' in exposed) { $Array.forEach(exposed.properties, function(prop) { $Object.defineProperty(publicClassPrototype, prop, { __proto__: null, enumerable: true, get: function() { return getPrivateImpl(this)[prop]; }, set: function(value) { var impl = getPrivateImpl(this); delete impl[prop]; impl[prop] = value; } }); }); } if ('readonly' in exposed) { $Array.forEach(exposed.readonly, function(readonly) { $Object.defineProperty(publicClassPrototype, readonly, { __proto__: null, enumerable: true, get: function() { return getPrivateImpl(this)[readonly]; }, }); }); } // The prototype properties have been installed. Now we can safely assign an // unsafe prototype and export the class to the public. var superclass = exposed.superclass || $Object.self; $Object.setPrototypeOf(publicClassPrototype, superclass.prototype); publicClass.prototype = publicClassPrototype; 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); } // DO NOT USE. This causes problems with safe builtins, and makes migration to // native bindings more difficult. function handleRequestWithPromiseDoNotUse( binding, apiName, methodName, customizedFunction) { var fullName = apiName + '.' + methodName; var extensionId = requireNative('process').GetExtensionId(); binding.setHandleRequest(methodName, function() { logActivity.LogAPICall(extensionId, fullName, $Array.slice(arguments)); var stack = exceptionHandler.getExtensionStackTrace(); var callback = arguments[arguments.length - 1]; var args = $Array.slice(arguments, 0, arguments.length - 1); var keepAlive = require('keep_alive').createKeepAlive(); $Function.apply(customizedFunction, this, args).then(function(result) { if (callback) { exceptionHandler.safeCallbackApply( fullName, {__proto__: null, stack: stack}, callback, [result]); } }).catch(function(error) { if (callback) { var message = exceptionHandler.safeErrorToString(error, true); bindingUtil.runCallbackWithLastError(message, callback); } }).then(function() { keepAlive.close(); }); }); }; exports.$set('forEach', forEach); exports.$set('lookup', lookup); exports.$set('defineProperty', defineProperty); exports.$set('expose', expose); exports.$set('deepCopy', deepCopy); exports.$set('handleRequestWithPromiseDoNotUse', handleRequestWithPromiseDoNotUse);