Chromium Code Reviews| Index: extensions/renderer/resources/utils.js |
| diff --git a/extensions/renderer/resources/utils.js b/extensions/renderer/resources/utils.js |
| index eee1356272ed1fe7d182cb6a6343480f3f07b441..7e7f1884b123b9654f7c7a49935bffbdd0854c11 100644 |
| --- a/extensions/renderer/resources/utils.js |
| +++ b/extensions/renderer/resources/utils.js |
| @@ -2,7 +2,6 @@ |
| // 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; |
| @@ -65,15 +64,21 @@ function loadTypeSchema(typeName, defaultSchema) { |
| } |
| /** |
| - * 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 |
| + * 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). |
| - * @param {string} name The name of the exposed wrapper class. |
| - * @param {Object} cls The class implementation. |
| + * |
| + * |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. |
|
Devlin
2016/04/22 18:52:46
nit: I think this should be +4 indentation from th
robwu
2016/04/22 20:33:43
Done.
|
| + * @param {Object} privateClass The class implementation. |
| * @param {{superclass: ?Function, |
| * functions: ?Array<string>, |
| * properties: ?Array<string>, |
| @@ -84,13 +89,47 @@ function loadTypeSchema(typeName, defaultSchema) { |
| * 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); |
| +function expose(publicClass, privateClass, exposed) { |
| + DCHECK(!(privateClass instanceof $Object.self)); |
| + |
| + $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, |
| + }; |
| if ('functions' in exposed) { |
| $Array.forEach(exposed.functions, function(func) { |
| - publicClass.prototype[func] = function() { |
| - var impl = privates(this).impl; |
| + publicClassPrototype[func] = function() { |
| + var impl = getPrivateImpl(this); |
| return $Function.apply(impl[func], impl, arguments); |
| }; |
| }); |
| @@ -98,13 +137,14 @@ function expose(name, cls, exposed) { |
| if ('properties' in exposed) { |
| $Array.forEach(exposed.properties, function(prop) { |
| - $Object.defineProperty(publicClass.prototype, prop, { |
| + $Object.defineProperty(publicClassPrototype, prop, { |
| + __proto__: null, |
| enumerable: true, |
| get: function() { |
| - return privates(this).impl[prop]; |
| + return getPrivateImpl(this)[prop]; |
| }, |
| set: function(value) { |
| - var impl = privates(this).impl; |
| + var impl = getPrivateImpl(this); |
| delete impl[prop]; |
| impl[prop] = value; |
| } |
| @@ -114,15 +154,21 @@ function expose(name, cls, exposed) { |
| if ('readonly' in exposed) { |
| $Array.forEach(exposed.readonly, function(readonly) { |
| - $Object.defineProperty(publicClass.prototype, readonly, { |
| + $Object.defineProperty(publicClassPrototype, readonly, { |
| + __proto__: null, |
| enumerable: true, |
| get: function() { |
| - return privates(this).impl[readonly]; |
| + 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. |
| + publicClassPrototype.__proto__ = exposed.superclass.prototype || $Object.self; |
|
Devlin
2016/04/22 18:52:46
Won't this throw if exposed.superclass is undefine
robwu
2016/04/22 20:33:43
Fixed. And I should also have used $Object.self.pr
|
| + publicClass.prototype = publicClassPrototype; |
| + |
| return publicClass; |
| } |