Proxy handler.has() does not have a receiver argument?

Michael Theriot michael.lee.theriot at gmail.com
Fri Mar 18 23:15:50 UTC 2016


To be clear, I'm not suggesting behavior like `getOwnPropertyNames` be
overridden by anything on the prototype, just a way to use proxies without
having to instantiate identical copies that all use the same handler.

I still believe a proxy on the prototype should always have a `receiver`
sent to each trap, but if I need to return a proxy for each object it
doesn't really matter for the example I made.

On Fri, Mar 18, 2016 at 5:43 PM, Tom Van Cutsem <tomvc.be at gmail.com> wrote:

> I was on board with potentially making `has()` receiver-dependent but you
> lost me when you expected `getOwnPropertyNames` to trigger a trap on a
> proxy-as-prototype. Adding `receiver` to every MOP method is a no-go. It
> fundamentally changes the meaning of these operations and it would destroy
> the pay-only-when-you-use-it performance model of proxies, since operations
> that used to be local to only the 'own' object would now need to search the
> proto-chain for a potential proxy trap.
>
> Using a proxy-as-prototype was never intended as a way to be able to
> intercept arbitrary operations on whatever object happened to inherit from
> the proxy. A proxy-as-prototype still emulates only a single object. It
> just happens to be an object that serves as a prototype for other objects.
>
> Cheers,
> Tom
>
> 2016-03-18 23:18 GMT+01:00 Michael Theriot <michael.lee.theriot at gmail.com>
> :
>
>> I think I figured out how to make inheritance work...
>>
>> ```js
>> var wm1 = new WeakMap(); function A() { let proxy = new Proxy(this, {
>> get: (target, property, receiver) => property === 'bacon' ||
>> target[property] }); wm1.set(proxy, {}); return proxy; } var wm2 = new
>> WeakMap(); function B() { let proxy = A.call(new Proxy(this, { get:
>> (target, property, receiver) => property === 'ham' || target[property] }));
>> wm2.set(proxy, {}); return proxy; } var a = new A(); var b = new B();
>> wm1.has(a); // true wm2.has(a); // false wm1.has(b); // true wm2.has(b); //
>> true
>>
>> a.bacon; // true
>> a.ham; // undefined
>>
>> b.bacon; // true
>> b.ham; // true
>> ```
>>
>> I can't imagine this is good for optimizations though. But I guess it
>> does what I need.
>>
>> On Fri, Mar 18, 2016 at 4:45 PM, Michael Theriot <
>> michael.lee.theriot at gmail.com> wrote:
>>
>>> Michael’s preferred approach also introduces observable irregularity
>>>> into the standard JS inheritance model for ordinary objects.
>>>> Consider an object created using Michael’s preferred approach:
>>>> ```js
>>>> var arr = [0, 1];
>>>> console.log(Reflect.has(arr,”0”));  //true, arr has “0” as an own
>>>> property
>>>> var subArr = Object.create(arr);
>>>> console.log(Reflect.has(subArr,”0”));  //true, all unshadowed
>>>> properties of proto visible from ordinary objects
>>>> var b = new ArrayView2(arr);
>>>> console.log(Reflect.has(b,”0”));  //true, prototype proxy makes array
>>>> elements visible as if properties of b
>>>> var subB= Object.create(b);
>>>> console.log(Reflect.has(subB,”0”));  //false, some unshadowed
>>>> properties of proto is not visible from subB
>>>> ```
>>>
>>>
>>> I think this relates to the original concern; if you could pass the
>>> receiver this could be resolved. That still leaves `getOwnPropertyNames`
>>> reporting wrong values though and I see no foreseeable way to resolve that
>>> without returning an actual proxy itself.
>>>
>>> The reason I'm trying this approach is because I read on the MDN that
>>> when used in the prototype a `receiver` argument is passed that references
>>> the instance, so I assumed this was the intent behind it. The only other
>>> explanation I could think of is that proxies have a receiver to mimic the
>>> `Reflect.set`/`Reflect.get` methods which need a receiver for
>>> getters/setters to work properly, not so you can use them on the prototype
>>> chain.
>>>
>>> The other case I would make is every instance would have an identical
>>> proxy, and it just makes sense to put that on the prototype for the same
>>> reasons you put shared methods/properties there.
>>>
>>> Note that we are not really talking about a new capability here.
>>>> Michael’s first design shows that ES proxies already have the capability to
>>>> implement the object level semantics he desires.
>>>
>>>
>>> To be fair I had several obstacles with inheritance using the first
>>> version.
>>>
>>> ```js
>>> var wm1 = new WeakMap();
>>>
>>> function A() {
>>>   wm1.set(this, {});
>>>   return new Proxy(this, {});
>>> }
>>>
>>> var wm2 = new WeakMap();
>>>
>>> function B() {
>>>   A.call(this);
>>>   wm2.set(this, {});
>>>   return new Proxy(this, {});
>>> }
>>>
>>> var a = new A();
>>> var b = new B();
>>>
>>> wm1.has(a); // true
>>> wm2.has(a); // false
>>>
>>> wm1.has(b); // false
>>> wm2.has(b); // true
>>> ```
>>>
>>> As you can see storing a reference to `this` can't work anymore, since
>>> we actually return a proxy. You can try to work around this...
>>>
>>> ```js
>>> var wm1 = new WeakMap();
>>>
>>> function A() {
>>>   let self = this;
>>>   if(new.target === A) {
>>>     self = new Proxy(this, {});
>>>   }
>>>   wm1.set(self, {});
>>>   return self;
>>> }
>>>
>>> var wm2 = new WeakMap();
>>>
>>> function B() {
>>>   let self = this;
>>>   if(new.target === B) {
>>>     self = new Proxy(this, {});
>>>   }
>>>   A.call(self);
>>>   wm2.set(self, {});
>>>   return self;
>>> }
>>>
>>> var a = new A();
>>> var b = new B();
>>>
>>> wm1.has(a); // true
>>> wm2.has(a); // false
>>>
>>> wm1.has(b); // true
>>> wm2.has(b); // true
>>> ```
>>>
>>> But then problems arise because the new proxy doesn't go through the old
>>> proxy. So anything guaranteed by A()'s proxy is not guaranteed by B()'s
>>> proxy.
>>>
>>> ```js
>>> var wm1 = new WeakMap();
>>>
>>> function A() {
>>>   let self = this;
>>>   if(new.target === A) {
>>>     self = new Proxy(this, {
>>>       get: (target, property, receiver) => property === 'bacon' ||
>>> target[property]
>>>     });
>>>   }
>>>   wm1.set(self, {});
>>>   return self;
>>> }
>>>
>>> var wm2 = new WeakMap();
>>>
>>> function B() {
>>>   let self = this;
>>>   if(new.target === B) {
>>>     self = new Proxy(this, {
>>>       get: (target, property, receiver) => property === 'ham' ||
>>> target[property]
>>>     });
>>>   }
>>>   A.call(self);
>>>   wm2.set(self, {});
>>>   return self;
>>> }
>>>
>>> var a = new A();
>>> var b = new B();
>>>
>>> wm1.has(a); // true
>>> wm2.has(a); // false
>>>
>>> wm1.has(b); // true
>>> wm2.has(b); // true
>>>
>>> a.bacon; // true
>>> a.ham; // undefined
>>>
>>> b.bacon; // undefined
>>> b.ham; // true
>>> ```
>>>
>>> (I'm open to solutions on this particular case... One that doesn't
>>> require me to leak the handler of the A proxy)
>>>
>>> Ultimately I can actually achieve both what I want with the ArrayView
>>> example and inheritance by using a **lot** of `defineProperty` calls on
>>> `this` in the constructor, but performance is a disaster as you might
>>> expect.
>>>
>>> On Fri, Mar 18, 2016 at 2:55 PM, Andrea Giammarchi <
>>> andrea.giammarchi at gmail.com> wrote:
>>>
>>>> AFAIK the reason there is a `receiver` is to deal with prototype cases
>>>> ... if that was a good enough reason to have one, every prototype case
>>>> should be considered for consistency sake.
>>>>
>>>> We've been advocating prototypal inheritance for 20 years and now it's
>>>> an obstacle or "not how JS is"?
>>>>
>>>> ```js
>>>> class Magic extends new Proxy(unbe, lievable) {
>>>>   // please make it happen
>>>>   // as it is now, that won't work at all
>>>> }
>>>> ```
>>>>
>>>> Best Regards
>>>>
>>>>
>>>> On Fri, Mar 18, 2016 at 7:30 PM, Mark S. Miller <erights at google.com>
>>>> wrote:
>>>>
>>>>> I agree with Allen. I am certainly willing -- often eager -- to
>>>>> revisit and revise old design decisions that are considered done, when I
>>>>> think the cost of leaving it alone exceeds the cost of changing it. In this
>>>>> case, the arguments that this extra parameter would be an improvement seem
>>>>> weak. Even without the revising-old-decision costs, I am uncertain which
>>>>> decision I would prefer. Given these costs, it seems clear we should leave
>>>>> this one alone.
>>>>>
>>>>> Unless it turns out that the cost of leaving it alone is much greater
>>>>> than I have understood. If so, please help me see what I'm missing.
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> On Fri, Mar 18, 2016 at 12:17 PM, Allen Wirfs-Brock <
>>>>> allen at wirfs-brock.com> wrote:
>>>>>
>>>>>>
>>>>>> On Mar 18, 2016, at 9:24 AM, Andrea Giammarchi <
>>>>>> andrea.giammarchi at gmail.com> wrote:
>>>>>>
>>>>>> Agreed with everybody else the `receiver` is always needed and
>>>>>> `Proxy` on the prototype makes way more sense than per instance.
>>>>>>
>>>>>>
>>>>>> I don’t agree.  While you certainly can imagine a language where each
>>>>>> object’s “prototype” determines that object’s fundamental behaviors and
>>>>>> provides the MOP intercession hooks(in fact that’s how most class-based
>>>>>> languages work).  But that’s not the JS object model.  Each JS object is
>>>>>> essentially a singleton that defines it’s own fundamental behaviors.
>>>>>> Whether or this model is better or worse than the class-based model isn't
>>>>>> really relevant, but in the context of JS there are advantage to
>>>>>> consistently adhering to that model,
>>>>>>
>>>>>> For example, in Michael’s  desired approach, the instance objects of
>>>>>> his ArrayView abstraction are “ordinary objects”.  One of the fundamental
>>>>>> behavioral characteristics of ordinary objects is that all of there own
>>>>>> properties are defined and available to the implementation in a standard
>>>>>> way. Implementations certainly make use of that characteristic for
>>>>>> optimization purposes. Michael’s approach would make such optimizations
>>>>>> invalid because every time an own property needed to be access a prototype
>>>>>> walk would have to be performed  because there might be an exotic object
>>>>>> somewhere on the prototype chain that was injecting own property into the
>>>>>> original “receiver”.
>>>>>>
>>>>>> Michael’s preferred approach also introduces observable irregularity
>>>>>> into the standard JS inheritance model for ordinary objects.
>>>>>>
>>>>>> Consider an object created using Michael’s preferred approach:
>>>>>>
>>>>>> ```js
>>>>>> var arr = [0, 1];
>>>>>> console.log(Reflect.has(arr,”0”));  //true, arr has “0” as an own
>>>>>> property
>>>>>> var subArr = Object.create(arr);
>>>>>> console.log(Reflect.has(subArr,”0”));  //true, all unshadowed
>>>>>> properties of proto visible from ordinary objects
>>>>>>
>>>>>> var b = new ArrayView2(arr);
>>>>>> console.log(Reflect.has(b,”0”));  //true, prototype proxy makes array
>>>>>> elements visible as if properties of b
>>>>>> var subB= Object.create(b);
>>>>>> console.log(Reflect.has(subB,”0”));  //false, some unshadowed
>>>>>> properties of proto is not visible from subB
>>>>>> ```
>>>>>>
>>>>>> Note the his original Proxy implementation does not have this
>>>>>> undesirable characteristic.
>>>>>>
>>>>>> So what about the use of `receiver` in [[Get]]/[[Set]].  That’s a
>>>>>> different situation.  [[Get]]/[[Set]] are not fundamental, rather they are
>>>>>> derived (they work by applying other more fundamental MOP operations). The
>>>>>> `receiver` argument is not used by them to perform property lookup (they
>>>>>> use [[GetOwnProperty]] and [[GetPrototypeOf]]) for the actual property
>>>>>> lookup).  `receiver` is only used in the semantics of what happens after
>>>>>> the property lookup occurs.  Adding a `receiver` argument to the other MOP
>>>>>> operations for the purpose of changing property lookup semantics seems like
>>>>>> a step too far. The ES MOP design is a balancing act between capability,
>>>>>> implementability, and consistency. I think adding `receiver` to every MOP
>>>>>> operation would throw the design out of balance.
>>>>>>
>>>>>> Finally,
>>>>>>
>>>>>> Note that we are not really talking about a new capability here.
>>>>>> Michael’s first design shows that ES proxies already have the capability to
>>>>>> implement the object level semantics he desires. So, we are only talking
>>>>>> about exactly how he goes about using Proxy to implement that semantics. He
>>>>>> would prefer a different Proxy design than what was actually provided by
>>>>>> ES6. But that isn’t what was specified or what has now been implemented. We
>>>>>> can all imagine how many JS features might be “better” if they worked
>>>>>> somewhat differently. But that generally isn’t an option. The existing
>>>>>> language features and their implementations are what they are and as JS
>>>>>> programmers we need to work within that reality.
>>>>>>
>>>>>> Allen
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> Also the `getPrototypeOf` trap is really pointless right now
>>>>>>
>>>>>> ```js
>>>>>> function Yak() {}
>>>>>> Yak.prototype = new Proxy(Yak.prototype, {
>>>>>>   getPrototypeOf: (target) => console.log('lulz')
>>>>>> });
>>>>>>
>>>>>> var yup = new Yak;
>>>>>> Object.getPrototypeOf(yup);
>>>>>> ```
>>>>>>
>>>>>> The `target` is actually the original `Yak.prototype` which is
>>>>>> already the `yup` prototype: useless trap if used in such way.
>>>>>>
>>>>>> Being also unable to distinguish between `getOwnPropertyNames` vs
>>>>>> `keys` is a bit weird.
>>>>>>
>>>>>> `Proxy` looks so close to be that powerful but these bits make it
>>>>>> kinda useless for most real-world cases I've been recently dealing with.
>>>>>>
>>>>>> Thanks for any sort of improvement.
>>>>>>
>>>>>> Regards
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> On Fri, Mar 18, 2016 at 1:54 PM, Michael Theriot <
>>>>>> michael.lee.theriot at gmail.com> wrote:
>>>>>>
>>>>>>> I'm trying to make the proxy-as-a-prototype pattern work but I've
>>>>>>> just discovered the `ownKeys` trap is never called on traps on the
>>>>>>> prototype. So even if the `has` trap is allowed to see the `receiver`, and
>>>>>>> thus verify the properties "0", "1" exist, this pattern would fail to
>>>>>>> return the properties "0", "1" exist on an `Object.getOwnPropertyNames`
>>>>>>> call. Disappointing! I'd rather use a proxy on the prototype than create
>>>>>>> one for each instance but without a correct `ownKeys` return it just
>>>>>>> doesn't come full circle. Is there a trick to make this work or am I out of
>>>>>>> luck here? I can only think of actually defining the properties to make it
>>>>>>> work, which defeats the idea of using a proxy on the prototype to begin
>>>>>>> with.
>>>>>>>
>>>>>>> Regardless I agree that traps called on a prototype chain should
>>>>>>> always receive the `receiver` as an argument. I think the only trap other
>>>>>>> than `set`, `get`, and `has` that can do this is the `getPrototypeOf` trap
>>>>>>> (currently does not have a `receiver`) when the `instanceof` check needs to
>>>>>>> climb the prototype chain.
>>>>>>>
>>>>>>> On Thu, Mar 17, 2016 at 6:29 PM, Tom Van Cutsem <tomvc.be at gmail.com>
>>>>>>> wrote:
>>>>>>>
>>>>>>>> The rationale for not having a `receiver` argument to `has` is that
>>>>>>>> the value produced by the "in" operator is not normally dependent on the
>>>>>>>> receiver. This is in contrast with `get` and `set` which may find an
>>>>>>>> accessor up the proto chain that needs to run with a `this` bound to the
>>>>>>>> receiver.
>>>>>>>>
>>>>>>>> That said, I follow your line of reasoning and it is true that
>>>>>>>> `has`, `get` and `set` are the three traps that can be called on a
>>>>>>>> proxy-used-as-prototype (now that `enumerate` is considered deprecated), so
>>>>>>>> it would be consistent to allow all of them to  refer back to the original
>>>>>>>> receiver. This enables the general pattern that you illustrate.
>>>>>>>>
>>>>>>>> As you note, the weirdness of this is apparent because it doesn't
>>>>>>>> normally make sense to pass a `receiver` argument to Reflect.has().
>>>>>>>> However, if `receiver` would be made visible in a Proxy handler's `has`
>>>>>>>> trap, then `Reflect.has` should nevertheless be likewise extended so that
>>>>>>>> one can faithfully forward the `receiver` argument.
>>>>>>>>
>>>>>>>> Spec-wise, I think the only significant change is that 7.3.10
>>>>>>>> HasProperty
>>>>>>>> <http://www.ecma-international.org/ecma-262/6.0/#sec-hasproperty>,
>>>>>>>> step 3 must be changed to `O.[[HasProperty]](P, O)` and all [[HasProperty]]
>>>>>>>> internal methods must likewise be extended with an extra argument (which
>>>>>>>> they ignore). Only the Proxy implementation in 9.5.7 would then actually
>>>>>>>> refer to that argument.
>>>>>>>>
>>>>>>>> Cheers,
>>>>>>>> Tom
>>>>>>>>
>>>>>>>> 2016-03-17 11:46 GMT+01:00 Michael Theriot <
>>>>>>>> michael.lee.theriot at gmail.com>:
>>>>>>>>
>>>>>>>>> I feel like it should, or I am misunderstanding something
>>>>>>>>> fundamental. I made a basic scenario to explain:
>>>>>>>>>
>>>>>>>>> ```js
>>>>>>>>> var arrays = new WeakMap();
>>>>>>>>>
>>>>>>>>> function ArrayView(array) {
>>>>>>>>>   arrays.set(this, array);
>>>>>>>>>
>>>>>>>>>   return new Proxy(this, {
>>>>>>>>>     set: (target, property, value) => (arrays.has(this) &&
>>>>>>>>> property in arrays.get(this))  ? arrays.get(this)[property] = value :
>>>>>>>>> target[property] = value,
>>>>>>>>>     get: (target, property)        => (arrays.has(this) &&
>>>>>>>>> property in arrays.get(this))  ? arrays.get(this)[property]         :
>>>>>>>>> target[property],
>>>>>>>>>     has: (target, property)        => (arrays.has(this) &&
>>>>>>>>> property in arrays.get(this)) || property in target
>>>>>>>>>   });
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> ArrayView.prototype = Object.create(Array.prototype, {
>>>>>>>>>   arrayLength: {
>>>>>>>>>     get() {
>>>>>>>>>       return arrays.get(this).length;
>>>>>>>>>     }
>>>>>>>>>   }
>>>>>>>>> });
>>>>>>>>> ```
>>>>>>>>>
>>>>>>>>> When `new ArrayView(somearray)` is called the reference to
>>>>>>>>> `somearray` is stored in the `arrays` weak map and a proxy is returned that
>>>>>>>>> allows you to manipulate indices on it, or fallback to the object for other
>>>>>>>>> properties.
>>>>>>>>>
>>>>>>>>> This could be simplified by putting the proxy on the prototype
>>>>>>>>> chain to reduce overhead and actually return a genuine `ArrayView` object
>>>>>>>>> instead:
>>>>>>>>>
>>>>>>>>> ```js
>>>>>>>>> var arrays = new WeakMap();
>>>>>>>>>
>>>>>>>>> function ArrayView2(array) {
>>>>>>>>>   arrays.set(this, array);
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> var protoLayer = Object.create(Array.prototype, {
>>>>>>>>>   arrayLength: {
>>>>>>>>>     get() {
>>>>>>>>>       return arrays.get(this).length;
>>>>>>>>>     }
>>>>>>>>>   }
>>>>>>>>> });
>>>>>>>>>
>>>>>>>>> ArrayView2.prototype = new Proxy(protoLayer, {
>>>>>>>>>   set: (target, property, value, receiver) =>
>>>>>>>>> (arrays.has(receiver) && property in arrays.get(receiver))  ?
>>>>>>>>> arrays.get(receiver)[property] = value : Reflect.set(target, property,
>>>>>>>>> value, receiver),
>>>>>>>>>   get: (target, property, receiver)        =>
>>>>>>>>> (arrays.has(receiver) && property in arrays.get(receiver))  ?
>>>>>>>>> arrays.get(receiver)[property]         : Reflect.get(target, property,
>>>>>>>>> receiver),
>>>>>>>>>   has: (target, property)                  => (arrays.has(target)
>>>>>>>>>   && property in arrays.get(target))   || Reflect.has(target, property)
>>>>>>>>> });
>>>>>>>>> ```
>>>>>>>>>
>>>>>>>>> Under this setup `target` refers to the protoLayer object which is
>>>>>>>>> useless here, but we can use the `receiver` argument in its place to access
>>>>>>>>> the weak map, and replace our set/get operations with
>>>>>>>>> Reflect.set/Reflect.get calls to the target (protoLayer) using a receiver
>>>>>>>>> (the instance) to pass the correct `this` value to the `arrayLength` getter
>>>>>>>>> and prevent infinite recursion.
>>>>>>>>>
>>>>>>>>> One problem - handler.has() lacks a receiver argument. So in this
>>>>>>>>> scenario when using the `in` operator it will always fail on array
>>>>>>>>> properties because we cannot check the weak map by passing in the instance.
>>>>>>>>>
>>>>>>>>> ```js
>>>>>>>>> var arr = [0, 1];
>>>>>>>>>
>>>>>>>>> var a = new ArrayView(arr);
>>>>>>>>> a.arrayLength; // 2
>>>>>>>>> 'arrayLength' in a; // true
>>>>>>>>> '0' in a; // true
>>>>>>>>> '1' in a; // true
>>>>>>>>> '2' in a; // false
>>>>>>>>>
>>>>>>>>> var b = new ArrayView2(arr);
>>>>>>>>> b.arrayLength; // 2
>>>>>>>>> 'arrayLength' in b; // true
>>>>>>>>> '0' in b; // false
>>>>>>>>> '1' in b; // false
>>>>>>>>> '2' in b; // false
>>>>>>>>> ```
>>>>>>>>>
>>>>>>>>> Without a receiver argument on handler.has(), it is practically
>>>>>>>>> useless for proxies used as a prototype. You can't reference the instance
>>>>>>>>> calling it and your target is simply the parent prototype.
>>>>>>>>>
>>>>>>>>> Is there a reason the handler.has() trap should not obtain the
>>>>>>>>> receiver when used on the prototype chain? I can understand why
>>>>>>>>> Reflect.has() wouldn't have a receiver argument (that wouldn't make sense)
>>>>>>>>> but this seems like a legitimate use for it. Otherwise I don't see a reason
>>>>>>>>> to use the handler.has() trap at all on prototype proxies except for
>>>>>>>>> bizarre behaviors that have nothing to do with the instance. It will always
>>>>>>>>> have the same behavior across all instances since you can't differentiate
>>>>>>>>> them.
>>>>>>>>>
>>>>>>>>> _______________________________________________
>>>>>>>>> es-discuss mailing list
>>>>>>>>> es-discuss at mozilla.org
>>>>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>>>>>
>>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> es-discuss mailing list
>>>>>>> es-discuss at mozilla.org
>>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>>>
>>>>>>>
>>>>>> _______________________________________________
>>>>>> es-discuss mailing list
>>>>>> es-discuss at mozilla.org
>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>>
>>>>>>
>>>>>>
>>>>>> _______________________________________________
>>>>>> es-discuss mailing list
>>>>>> es-discuss at mozilla.org
>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>>
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>>     Cheers,
>>>>>     --MarkM
>>>>>
>>>>
>>>>
>>>> _______________________________________________
>>>> es-discuss mailing list
>>>> es-discuss at mozilla.org
>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>
>>>>
>>>
>>
>> _______________________________________________
>> 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/20160318/a5a6108b/attachment-0001.html>


More information about the es-discuss mailing list