RE: Web IDL attributes and delegation

>> Right now, most script libraries end up doing a shallow copy of properties from the native event object to a surrogate and then fix up the standard event methods (stopPropagation, etc.) to point back to the native object, which is quite inefficient, and can be especially bad for performance when dealing with high-volume events (touchmove, mousemove, etc.).

Agree this is inefficient. These approaches use the “sub-class” pattern. The approach we widely deploy for these scenarios in IE9 and 10 relies on the existing prototypes defined by the browser (and the behavior in WebIDL)—we simply replace the default behavior with our new desired behavior (native behavior replacement). This way we get the CPU efficiency of avoiding the shallow copy for any instances, and the memory savings of having a single entry-point for the behavior fix-up.

WebIDL is built for this style of “fixup”. Using the sub-class pattern exclusively is much harder to rationalize, especially if a method will behave differently depending on the “this” value (and requires platform object behavior to understand the dynamic prototype chains of arbitrary objects in order to attempt to match a correct “this” value for a native method/attribute). If you use a combination of the native behavior replacement designed to work with a sub-class ‘this’ object, then you get the best of both worlds ☺.

// Create instance attributes on native object that derive from platform objects (on-demand)
function augmentAttribute(pd, ob, prop) {
  Object.defineProperty(ob, prop, { get: function() {
      // example: read from ‘this’ (could be native object not platform object)
      if (!this.hasOwnProperty(prop)) // If not available, cache the existing value on the native implementation
          Object.defineProperty(this, prop, { value: pd.get.call(Object.getPrototypeOf(this)) });
    }, set: function(x) {
      if (!this.hasOwnProperty(prop))
         Object.defineProperty(this, prop, { value: x, configurable: true, writable: true, enumerable: true});
      else
         this[prop] = x;
    }
  });
}

// One-time init
augmentAttribute(Object.getOwnPropertyDescriptor(Event.prototype, “type”), Event.prototype, “type”);

// […]
// (Run-time) sub-class event types when they are dispatched
var subEvent = Object.create(eventInstance);

// ‘type’ will now work as read/write by default and additional custom behavior can be applied.
// platform-defined methods on the event will need a similar replacement. These only need to be done once.


From: Colin Snover [mailto:w3.org@zetafleet.com]
Sent: Wednesday, September 12, 2012 8:39 AM
To: public-script-coord@w3.org
Subject: Web IDL attributes and delegation

Hi everyone,

It was recommended to me by Boris Zbarsky at https://bugzilla.mozilla.org/show_bug.cgi?id=790303 that I should bring my concerns to this list regarding using platform objects as prototypes and the current rules for Web IDL attribute getters/setters.

As a JS author, it is important to me to be able to have a way to augment DOM event objects and/or override certain properties (which may be defined as readonly) in a time- and memory-efficient manner. Right now, most script libraries end up doing a shallow copy of properties from the native event object to a surrogate and then fix up the standard event methods (stopPropagation, etc.) to point back to the native object, which is quite inefficient, and can be especially bad for performance when dealing with high-volume events (touchmove, mousemove, etc.). Delegation via e.g. Object.create should be a more reasonable way to accomplish this, but the latest editor’s draft of the Web IDL spec makes it impossible by defining behaviour that requires attribute getters to throw when |this| is not a platform object, which means any attempt to use a platform object as the prototype of a new object fails (http://dev.w3.org/2006/webapi/WebIDL/#es-attributes attribute getters step 2 substep 2.2).

Ultimately I suppose that the idea is ES6 proxies will be “the” solution, but it is burdensome how limited we are now, given that all the pieces seem to exist in ES5 to accomplish this goal. I strongly believe that browser vendors should be making every effort to make host/platform objects indistinguishable from native JavaScript objects as much as possible, but this latest, less ambiguous version of the Web IDL spec seems to go in the opposite direction.

My first thought for an alternative proposal, were one to be entertained, would be to fix the |this| value of platform object getter/setters to the platform object itself, as though all the methods were defined using Function.prototype.bind, if |this| is not a platform object. This would ensure things like var obj = Object.create(event); obj.stopPropagation(); would work correctly (current browser implementations vary wildly in this scenario) whilst still allowing read-only properties to be safely redefined on the delegate. This may not be the best ultimate solution, since it does mean that a platform object would be changing its own properties instead of the properties of the true |this|, but I think it gets closer to the goal of minimising the differences of host/platform objects than the current proposal and enables platform objects to be used as prototypes.

Please let me know your thoughts. Thanks!


--

Colin Snover

http://zetafleet.com

Received on Wednesday, 12 September 2012 16:31:39 UTC