Non-generic traps for non-generic objects (was: Overriding Map/etc with get/set hooks?)

Tom Van Cutsem tomvc.be at gmail.com
Fri May 24 07:02:42 PDT 2013


As I mentioned upstream, I don't think extending the Proxy API with hooks
for accessing the private state of every possible built-in is going to fly.

Here's a constructive counter-proposal:

The difficulty of your use case is that you ideally would want to use
inheritance to inherit all of Map's utility methods, but you are forced to
use composition (i.e. wrapping) to avoid users applying the superclass
methods directly onto your Map subclass.

You could actually combine both techniques (inheritance and composition) to
achieve the best of both worlds:

// use inheritance to inherit Map's utilities
class StringMap extends Map {
  get(key) {
    return super(String(key));
  },
  set(key, value) {
    return super(String(key), String(value));
  },
  // similar for has, delete
}

// use composition via explicit forwarding to achieve protection against
tampering with the StringMap instance directly:
function createProtectedStringMap() {
  var wrappedMap = new StringMap();
  return new Proxy(wrappedMap, {
    invoke: function(wrappedMap, name, args, thisBinding) {
      return wrappedMap[name](...args);
    }
  });
}

var m = createProtectedStringMap();
m.set(k,v) // wrappedMap.set(k,v)
m.get(k) // wrappedMap.get(k)
m.keys() // wrappedMap.keys()

Map.prototype.set.call(m,k,v) // error: m is not a Map
// even:
StringMap.prototype.set.call(m,k,v) // error: m is not a Map

It's not strictly necessary to use a Proxy here, since you could easily
manually implement a forwarder object that defines all of the methods in
the Map API. Using a Proxy has the advantage that if the Map API is
extended later, the protected StringMap will support the extended API. Note
though, that if in some later edition, the Map object suddenly defines a
new method that allows alternative ways of getting/setting keys in the Map,
the StringMap could be corrupted. You can't have it both ways: either you
restrict the set of operations on your StringMap, which increases
integrity, or you inherit an open-ended set of operations, which increases
forward-compatibility.

If you're going to spec the protected string map, think of it as an exotic
object (aka host object) whose [[invoke]] operation explicitly forwards the
request to a wrapped StringMap.

(Note: the "invoke" trap used above was only just added to the Proxy API at
this week's TC39 meeting, but you could implement this abstraction today by
having the "get" trap returning a bound method)

Cheers,
Tom



2013/5/24 Tab Atkins Jr. <jackalmage at gmail.com>

> On Tue, May 21, 2013 at 4:07 AM, David Bruant <bruant.d at gmail.com> wrote:
> > David Bruant wrote:
> >> Le 21/05/2013 04:06, Tab Atkins Jr. a écrit :
> >> > (One way to do this today is to subclass Map and provide my own
> >> > get/set/etc. functions, but I need to override a potentially-open set
> >> > (anything that doesn't directly lean on my overridden functions), and
> >> > it doesn't prevent people from directly twiddling my [[MapData]] by
> >> > calling Map.prototype.set.call() on my object.)
> >>
> >> If you want to keep control over how people interact with your key/value
> >> interface, a proxy seems more appropriate. It's been designed so that
> >> you have full control and can't be bypassed.
> >>
> >> Although the behavior Map.prototype.set.call(someProxy) isn't very clear
> >> yet spec-wise [1] you can be sure that it won't be possible to freely
> >> mess around an internal [[MapData]] (because it would open an undesired
> >> communication channel)
> >
> > Would it make sense to add specific traps for specific objects (Date
> would
> > have some specific traps, so would objects with [[MapData]], so would
> > objects with [[SetData]], etc.)?
> > Very much like functions currently have some traps that only apply to
> them.
>
> With more thought, it seems like this, or something roughly equivalent
> to it, is indeed the correct solution.
>
> It's impossible for me to reliably proxy Maps today.  If I subclass
> Map and provide my own implementations of set() and delete(), authors
> can (accidentally?) work around them by calling
> `Map.prototype.set.call(style.vars, obj1, obj2)` and corrupt my map.
> Even if the C++ side stringifies things (which, spec-wise, still
> requires the kind of hooking that's been frowned upon in these
> threads), I've got a confusing Map where two different keys both map
> to the same CSS property, and I have to define that behavior somehow,
> which is dumb.
>
> (And of course, if I *dont'* subclass Map, then I'm in the unenviable
> position of reimplementing the entire Map API myself, including things
> that have nothing to do with my extra constraints like Map#keys(), and
> then having a static snapshot of the Map API stuck in my spec, which
> won't be automatically upgraded when the Map API grows.  I don't
> consider this an acceptable outcome.)
>
> So, yeah, I need a way to hook the specific internal Map operations
> somehow.  I could do it with nothing more than Map-equivalents to the
> four special operations that WebIDL allows me to define.
>
> I don't care if this is exposed as additions to the set of Proxy
> traps, or via special Symbols on the Map that are used for those
> operations, or something else.
>
> ~TJ
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130524/17e13cab/attachment-0001.html>


More information about the es-discuss mailing list