WebIDL attribute reflection
On 12/28/12 11:52 AM, Brendan Eich wrote:
Sure, but should all those DOM objects whose pre-WebIDL browser-specific bindings used magic data properties have to become proxies?
God, I hope not.
Has anyone checked whether doing so is (a) compatible; (b) performant?
It's not performant. It's very very hard to make performant, since proxies pretty much by definition defeat all the known JIT optimizations like inline caches based on inferred types and whatnot.
You can take a look at bugs.webkit.org/show_bug.cgi?id=104623 to see what happens when you try to make it performant, and at bugs.webkit.org/show_bug.cgi?id=105526 to see what happens when you try to make it correct.
It's possible to optimize this sort of thing in JITs in some cases by special-casing things and tighly coupling your JIT and your DOM implementation (see bugzilla.mozilla.org/show_bug.cgi?id=820846 and bugzilla.mozilla.org/show_bug.cgi?id=769911) but note that the ICs there only work because the properties the script really cares about are in fact on the proto as normal accessor properties in the common case.
All that said, I feel like I'm missing context in this discussion. Why do we need to change anything about how WebIDL attributes are reflected? There were good reasons for the current setup, including the fact that it's somewhat widely deployed already (e.g. implemented in Trident and Gecko) and that it allows authors to usefully interpose DOM getters/setters like they can already interpose methods.
Boris Zbarsky wrote:
All that said, I feel like I'm missing context in this discussion. Why do we need to change anything about how WebIDL attributes are reflected? There were good reasons for the current setup, including the fact that it's somewhat widely deployed already (e.g. implemented in Trident and Gecko) and that it allows authors to usefully interpose DOM getters/setters like they can already interpose methods.
David was questioning the new status quo, which is fine -- we benefit from being skeptical of our theories, as Feynman recommended. He was motivated by the extra complexity of ES5 accessors, which can be reflected on, extracted as get and set functions, called on wrong objects.
Your reply helps a lot, IMHO, in reaffirming why WebIDL, with Gecko, and Trident implementing, chose prototype-homed accessors. JITting proto-accessors is a thing engines already do. JITting through proxy traps (ignoring dummy target overhead) is in the future.
(And let's not ignore the dummy target overhead!)
David, are you convinced?
On 12/28/12 12:24 PM, Brendan Eich wrote:
David was questioning the new status quo, which is fine -- we benefit from being skeptical of our theories, as Feynman recommended. He was motivated by the extra complexity of ES5 accessors, which can be reflected on, extracted as get and set functions, called on wrong objects.
Extra complexity compared to proxies? Seriously?
Which sort of complexity are we worried about? Implementation complexity? Complexity for people trying to use the web platform? Specification complexity? Something else?
I'm happy to consider arguments that things are too complex, but I'd like to understand the arguments. ;)
Note that in practice Gecko's implementation basically treats setters as single-argument methods and getters as 0-argument methods and shares the code for getters/methods/setters under the hood, so since methods already have to handle being called on wrong objects the complexity is pretty low for the DOM code. The complexity of setting up acccessor properties is also pretty low since JS engines already support that. There's a bit of complexity in the JIT to make some of this stuff faster, but nothing even remotely comparable to trying to make proxies fast...
Your reply helps a lot, IMHO, in reaffirming why WebIDL, with Gecko, and Trident implementing, chose prototype-homed accessors. JITting proto-accessors is a thing engines already do. JITting through proxy traps (ignoring dummy target overhead) is in the future.
It's even worse than that. Without certain behavior guarantees on the part of the proxy (which in this case would not be implemented in JS, note, so would not be able to be analyzed by the JIT) it's impossible to JIT through those traps.
Of course one could try to reimplement the entire DOM in JS; that way lie other performance pitfalls.... And even then, safely calling proxy traps has a bunch of overhead unless you can prove they don't do certain things.
When we have gets through a proxy down in the 20-30 cycle range on modern hardware, I'm happy to talk about proxies performance-wise. ;)
On 12/28/12 12:31 PM, Boris Zbarsky wrote:
When we have gets through a proxy down in the 20-30 cycle range on modern hardware, I'm happy to talk about proxies performance-wise. ;)
One other note. I'm somewhat sympathetic to the argument that the spec could describe things as proxies while actual implementations then implement them however the heck they want under the hood. But that does involve each implementation then going through and creating yet another kind of thing internally which is not a proxy so it can be optimized sanely but still has some sort of behavior that isn't describable by normal getters/setters or normal value properties, so it's actually more implementation complexity than what WebIDL has right now, as far as I can tell.
And possibly more specification complexity too. For example, right now WebIDL doesn't have to define what happens when you try to delete DOM properties, because it's obvious, but once you try to use a proxy you have to define that sort of thing.
Boris, what initially triggered my questioning of the inherited accessor setting is in this message: esdiscuss/2012-December/027435 Mostly concerns about a mix of API "securability" and convenience. I "demonstrate" that if every attribute is an inherited accessor, then, it's possible to secure the environment, but it can be at the expense of API usability up to asking people to tweak their code (which is an anti-goal when trying to secure your code from, say, an advertiser's code)
Le 28/12/2012 22:18, Boris Zbarsky a écrit :
On 12/28/12 12:31 PM, Boris Zbarsky wrote:
When we have gets through a proxy down in the 20-30 cycle range on modern hardware, I'm happy to talk about proxies performance-wise. ;)
One other note. I'm somewhat sympathetic to the argument that the spec could describe things as proxies while actual implementations then implement them however the heck they want under the hood.
I was half-clumpsy (I mentioned "An ES6 proxy-based implementation" by mistake), but it was what I was asking for. That's what I tried to express when I wrote "Using ES6 proxies semantics to explain own data properties in WebIDL would..." From the beginning, I wanted to have a spec/semantics discussion. Implementations are free to do whatever they want as long as the observable behavior is conformant.
WebIDL tries to close the gap between DOM semantics and ECMAScript semantics. In an ES5 world, the only thing that can explain the magic behavior is getters/setters (own or inherited). In an ES6 world, proxies can be an explanation too. For some cases like WindowProxy or NodeList, there will be no other way than using proxies to specify these objects in an ECMAScript replicable semantics.
But that does involve each implementation then going through and creating yet another kind of thing internally which is not a proxy so it can be optimized sanely but still has some sort of behavior that isn't describable by normal getters/setters or normal value properties, so it's actually more implementation complexity than what WebIDL has right now, as far as I can tell.
I'm not sure I'm convinced by "it's more work than right now". If you had told me that there is a fundamental issue that implementors can't work around when exposing own data properties, I would have backed out, but you suggested than it's possible to create "yet another kind of thing internally which is not a proxy so it can be optimized sanely". Is there some sort of way to use the getter/setter based implementation, but expose things as data property when asked via Object.getOwnPropertyDescriptor?
And possibly more specification complexity too. For example, right now WebIDL doesn't have to define what happens when you try to delete DOM properties, because it's obvious, but once you try to use a proxy you have to define that sort of thing.
Not with the direct proxy design. I think that the role of a spec using ES6 proxies as a spec tool would only be to define the different traps. Just say "there is no delete trap" and what happens when you try to delete becomes as obvious as how things are currently. Repeat with "keys", "getOwnPropertyNames" and most traps actually. One only needs to define the get/getOwnPropertyDescriptor and set/defineProperty traps which is the work that the spec is already doing at different places. I don't feel it would add complexity; it would just be different.
Out of curiosity, do you have an idea of how much cost the |this| check?
David Bruant wrote:
Boris, what initially triggered my questioning of the inherited accessor setting is in this message: esdiscuss/2012-December/027435 Mostly concerns about a mix of API "securability" and convenience. I "demonstrate" that if every attribute is an inherited accessor, then, it's possible to secure the environment, but it can be at the expense of API usability up to asking people to tweak their code (which is an anti-goal when trying to secure your code from, say, an advertiser's code)
Can you give an example?
Ads loaded via cross-site script src= need defense at a lower depth, without any code changes, by giving them a distinct trust label from the including page's label. I proposed this at AppSecUSA (while jetlagged from travel from London through Austin to NYC on the eve of Hurricane Sandy :-P). More to say when there is time, but the idea is to let the VM distinguish ad code from page code and let CSP enforce finer-grained policies via membranes (in-process, no inversion of control flow -- see Alex Russell's question if the Q&A made the video).
Le 28/12/2012 22:18, Boris Zbarsky a écrit :
On 12/28/12 12:31 PM, Boris Zbarsky wrote:
When we have gets through a proxy down in the 20-30 cycle range on modern hardware, I'm happy to talk about proxies performance-wise. ;)
One other note. I'm somewhat sympathetic to the argument that the spec could describe things as proxies while actual implementations then implement them however the heck they want under the hood. I was half-clumpsy (I mentioned "An ES6 proxy-based implementation" by mistake), but it was what I was asking for. That's what I tried to express when I wrote "Using ES6 proxies semantics to explain own data properties in WebIDL would..." From the beginning, I wanted to have a spec/semantics discussion. Implementations are free to do whatever they want as long as the observable behavior is conformant.
That's all true of any spec: semantics deal in observables, all else is open to radical re-implementation and optimization.
If I understand the rest of your post, you propose that WebIDL-based DOM specs could use a direct proxy with a certain handler sub-spec, unobservably, to create the same observable semantics that native C++ DOM implementations present? That is interesting. We'd need to work out the details. Go! ;-)
On 12/29/12 11:08 AM, David Bruant wrote:
Boris, what initially triggered my questioning of the inherited accessor setting is in this message: esdiscuss/2012-December/027435
OK, but that's a problem methods can have too, yes? Not in this particular case, but in plenty of other cases...
Completely revamping how properties are handled to make this one case easier seems a bit extreme. In my opinion.
That said, thank you for the pointer. I do agree that this particular case would be a bit simpler if .global were an own property.
Mostly concerns about a mix of API "securability" and convenience. I "demonstrate" that if every attribute is an inherited accessor, then, it's possible to secure the environment, but it can be at the expense of API usability up to asking people to tweak their code (which is an anti-goal when trying to secure your code from, say, an advertiser's code)
If you're trying to secure your code from an advertiser's code you want to not run the advertiser's code with your origin. Anything else at best gives you the illusion of security... Again, in my opinion.
Implementations are free to do whatever they want as long as the observable behavior is conformant.
Yes, I understand that. My point is that this makes it too easy to spec overcomplicated things that in fact can't be optimized sanely by implementations unless the specification writers are very careful.
WebIDL tries to close the gap between DOM semantics and ECMAScript semantics.
Yes, while at the same time converging with implementations.
In an ES5 world, the only thing that can explain the magic behavior is getters/setters (own or inherited). In an ES6 world, proxies can be an explanation too.
Yes.
For some cases like WindowProxy or NodeList, there will be no other way than using proxies to specify these objects in an ECMAScript replicable semantics.
Indeed.
I'm not sure I'm convinced by "it's more work than right now". If you had told me that there is a fundamental issue that implementors can't work around when exposing own data properties, I would have backed out, but you suggested than it's possible to create "yet another kind of thing internally which is not a proxy so it can be optimized sanely".
This is empirically true, since that's what JavaScriptCore does today, for example. If I'm not misreading the code, it actually hangs its DOM property getters/setters of the prototype internally, makes them look like own properties on objects when reflected via getOwnPropertyDescriptor, and has magic to make a property lookup return the getter/setter pair internally and have their interpreter and JIT call those.
So in JavaScriptCore there are at least three different kinds of properties (there are more, because they have non-proxy objects with custom getOwnPropertyDescriptor hooks, like Document, but I'm simplifying): accessor properties, value properties, and magic properties that claim to be value properties when reflected but don't have a slot, are actually accessor properties under the hood, and pretend to be on a different object than the object they're really on.
This of course has the interesting side-effect that JavaScriptCore ends up having to claim the properties are non-configurable, because they don't actually live on the objects under the hood, so you have to disallow deleting them. So this approach, in this particular implementation, doesn't address you "delete .global" issue anyway. Try the testcase:
<script> var h = document.documentElement; alert(h.innerHTML); alert(JSON.stringify(Object.getOwnPropertyDescriptor(h, "innerHTML"))); delete h.innerHTML; alert(h.innerHTML); </script>
Is there some sort of way to use the getter/setter based implementation, but expose things as data property when asked via Object.getOwnPropertyDescriptor?
See above.
Again, in computer science pretty much everything is possible. It's all just code. It's doing the thing you want in finite time, both implementation time and run time, is the hard part.
Out of curiosity, do you have an idea of how much cost the |this| check?
Cost in terms of what?
It depends on your implementation, obviously (e.g. it's really expensive in Gecko's XPConnect bindings from 6 years ago), but in Gecko's WebIDL bindings the cost of the "this" check (which we want to minimize no matter what, because methods have to do it!) is about 15-20 cycles on average last I measured (including whatever cache misses are involved every so often, etc). It'll get a bit cheaper as we remove some more edge cases it has to deal with right now. Oh, and in practice we usually only have to do it at JIT-compile time, not at runtime (unless you go out of your way to do .call or .apply on the getter/setter) because the combination of the type inference and guarding the JIT already does means that we'll get the equivalent of an IC miss (it's not quite an IC, but similar concept) if the observed type changes from what it was when the code was compiled.
The upshot is that a DOM getter returning an integer for our WebIDL bindings is a tad faster for me than an equivalent getter in JSC (which does not have to do the |this| check in its current setup), in any sort of testcase that actually gets compiled with IonMonkey.
On 12/29/12 10:42 PM, Boris Zbarsky wrote:
If I'm not misreading the code, it actually hangs its DOM property getters/setters of the prototype internally, makes them look like own properties on objects when reflected via getOwnPropertyDescriptor, and has magic to make a property lookup return the getter/setter pair internally and have their interpreter and JIT call those.
One other note. SpiderMonkey used to have some things sort of like this (though not quite as well-integrated with the JIT); I believe they've been actively trying to remove them precisely because they end up being a pain to reflect in terms of property descriptors. The line I've heard from that direction has been to use proxies for weird stuff, getter/setters or real value properties for non-weird stuff.
Now obviously you can use proxy semantics to describe even things that don't really need to be all that weird. When you have a hammer and all... ;)
On 12/29/12 10:49 PM, Boris Zbarsky wrote:
One other note.
And one last note, promise.
The point is that SpiderMonkey is working on changing its internal model to more closely match the spec. And I agree with that approach: for any somewhat complicated spec, using an internal model that doesn't match the spec model is just a recipe for pain and non-conformance. You can try to fake things with enough epicycles, but like any epicycle setup as soon as you can make more accurate measurements you have to add more epicycles. That means that things meant as "specification devices" become de-facto conformance requirements as specs grow hidden dependencies on those exact algorithms being used.
As a simple example, any time someone adds a DOM getter that calls into page script (assuming there is no such thing already, which I woudln't be on!) calls to [[GetOwnProperty]] become script-detectable if DOM properties are value properties (because you have to call the getter to fill in the descriptor), so if at that point you aren't implementing exactly the spec's algorithms you're detectably non-conformant.
And even if you don't call into page script, there are various getters that have detectable side-effects already (e.g. any of the CSSOM getters have side-effects on starting times of CSS transitions). Thus if you use a specification device in terms of a proxy that implements getOwnPropertyDescriptor only, suddenly sets of offsetWidth (a readonly property, note) start having side-effects which they don't have right now, and implementations that don't have those side-effects become non-conformant. Again, just as an example of how a "specification device" can turn into an unintended conformance requirement.
So I'm very much against using a specification device that doesn't match the intended implementation, which is what you seem to be proposing. In my experience, that leads to implementations which don't actually match the spec in all sorts of edge cases. If we're lucky, they match each other and all that has to happen is the spec has to be rewritten somehow. But more commonly they don't match each other either, and you end up with interoperability problems, which means your standardization effort just failed...
Le 30/12/2012 07:42, Boris Zbarsky a écrit :
On 12/29/12 11:08 AM, David Bruant wrote:
Boris, what initially triggered my questioning of the inherited accessor setting is in this message: esdiscuss/2012-December/027435
OK, but that's a problem methods can have too, yes? Not in this particular case, but in plenty of other cases...
Good point, it's a problem for methods too. Since I've posted the point about inherited accessors, I've realized that the problem is not about accessor, but the prototypal inheritance model. An idea would be to remove the inherited method and add it back to each instance where it's "allowed". I guess the exact same thing can be done with getter/setter pairs. I would say that the difference lies in the fact that properties which usually materialize the object state clearly are a per-instance thing while object methods usually are stateless functions, so can be thought as per-"class". That's a model that's very familiar and widely used by JavaScript developers at least.
Completely revamping how properties are handled to make this one case easier seems a bit extreme. In my opinion.
There is the |this| check removal and Brandon Benvie made a couple of good points on the developer experience at esdiscuss/2012-December/027503 The point about debuggers may be very annoying. It can be solved by devtools. It would be just more work ;-)
Mostly concerns about a mix of API "securability" and convenience. I "demonstrate" that if every attribute is an inherited accessor, then, it's possible to secure the environment, but it can be at the expense of API usability up to asking people to tweak their code (which is an anti-goal when trying to secure your code from, say, an advertiser's code)
If you're trying to secure your code from an advertiser's code you want to not run the advertiser's code with your origin. Anything else at best gives you the illusion of security... Again, in my opinion.
I don't understand your point. I didn't mention anything about origin. Can you develop? Anyway, in a modern browser, the beforescriptexecute event [1] can be of great help to sandbox code from any origin apparently (e.preventDefault() then evaluate with a sandbox of your own recipe).
I'm not sure I'm convinced by "it's more work than right now". If you had told me that there is a fundamental issue that implementors can't work around when exposing own data properties, I would have backed out, but you suggested than it's possible to create "yet another kind of thing internally which is not a proxy so it can be optimized sanely".
This is empirically true, since that's what JavaScriptCore does today, for example. If I'm not misreading the code, it actually hangs its DOM property getters/setters of the prototype internally, makes them look like own properties on objects when reflected via getOwnPropertyDescriptor, and has magic to make a property lookup return the getter/setter pair internally and have their interpreter and JIT call those.
So in JavaScriptCore there are at least three different kinds of properties (there are more, because they have non-proxy objects with custom getOwnPropertyDescriptor hooks, like Document, but I'm simplifying): accessor properties, value properties, and magic properties that claim to be value properties when reflected but don't have a slot, are actually accessor properties under the hood, and pretend to be on a different object than the object they're really on.
This of course has the interesting side-effect that JavaScriptCore ends up having to claim the properties are non-configurable, because they don't actually live on the objects under the hood, so you have to disallow deleting them. So this approach, in this particular implementation, doesn't address you "delete .global" issue anyway.
Try the testcase:<script> var h = document.documentElement; alert(h.innerHTML); alert(JSON.stringify(Object.getOwnPropertyDescriptor(h, "innerHTML"))); delete h.innerHTML; alert(h.innerHTML); </script>
Interesting point. Unrelated, but it's making me realize that innerHTML may stay a getter/setter (not sure about inherited or not). I guess the divide I'd like to put is between object state and what is conveniently put as an accessor
Out of curiosity, do you have an idea of how much cost the |this| check?
Cost in terms of what?
Time mostly, I guess.
It depends on your implementation, obviously (e.g. it's really expensive in Gecko's XPConnect bindings from 6 years ago), but in Gecko's WebIDL bindings the cost of the "this" check (which we want to minimize no matter what, because methods have to do it!) is about 15-20 cycles on average last I measured (including whatever cache misses are involved every so often, etc). It'll get a bit cheaper as we remove some more edge cases it has to deal with right now. Oh, and in practice we usually only have to do it at JIT-compile time, not at runtime (unless you go out of your way to do .call or .apply on the getter/setter) because the combination of the type inference and guarding the JIT already does means that we'll get the equivalent of an IC miss (it's not quite an IC, but similar concept) if the observed type changes from what it was when the code was compiled.
Thanks for this very enriching answer. The JIT-compile time point is the kind of thing I was asking for. If I understand correctly, it means that for code used very often, the |this| check is performed once and never done again (until a .call/apply which is rather rare). It basically mean that the |this| cost has been made almost inexistent. It remains in cold code, but the perf of cold code doesn't matter. It makes my argument to remove the |this| checks still valid for the spec, but it's a very light argument for implementation performance.
On a related thought, I'm always surprised by the tricks in place in current JS engines. It looks like in a lot of cases, empirical runtime type analysis and optimistic JIT compilation allow crazy performance. I'm trying very hard to think of cases where this approach wouldn't be enough for hand-written code and I can't find any.
David
[1] www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#execute
The problem I see with the accessor model is that it's attempting to use what's now being referred to in recent ES6 specs as "Ordinary" object semantics, when the functionality is absolutely of exotic functions. The internal functions are no longer magical, as in completely outside the bounds of what's possible in ECMAScript, but they're trying to be shoehorned to an even lower level: implementation as completely plain objects. Ordinary objects only have accessors to describe "interesting" non-data properties. But exotic objects, even limited to ones entirely specified in ES6, have interesting mechanics that cannot be replicated by ordinary objects and are better described with those mechanics than trying to shoehorn them in.
A comparable thing would be attempting to explain Array instance lengths using a (observable) accessor property with a getter and setter on the prototype. It would be possible, but it would also introduce the same problems I outlined in my earlier response that are currently issues in Firefox and IE.
Le 30/12/2012 17:48, Boris Zbarsky a écrit :
On 12/29/12 10:49 PM, Boris Zbarsky wrote:
One other note.
And one last note, promise.
In my opinion, we get too rarely this kind of feedback from implementors on es-discuss. I'm personally learning a lot.
The point is that SpiderMonkey is working on changing its internal model to more closely match the spec. And I agree with that approach: for any somewhat complicated spec, using an internal model that doesn't match the spec model is just a recipe for pain and non-conformance. You can try to fake things with enough epicycles, but like any epicycle setup as soon as you can make more accurate measurements you have to add more epicycles. That means that things meant as "specification devices" become de-facto conformance requirements as specs grow hidden dependencies on those exact algorithms being used.
True. As far as proxies are concerned, I think it's actually a good thing. I'll explain that in a separate post soon.
As a simple example, any time someone adds a DOM getter that calls into page script (assuming there is no such thing already, which I woudln't be on!) calls to [[GetOwnProperty]] become script-detectable if DOM properties are value properties (because you have to call the getter to fill in the descriptor), so if at that point you aren't implementing exactly the spec's algorithms you're detectably non-conformant.
And even if you don't call into page script, there are various getters that have detectable side-effects already (e.g. any of the CSSOM getters have side-effects on starting times of CSS transitions).
I'm not enough familiar with CSSOM getters and CSS transitions, sorry; is it a spec requirement or an observation shared for all current implementations? As I said in the previous post, some things like innerHTML ought to stay accessors. The criterion could be whether getting or setting has an effect observable outside of the object the property is on. So if the CSSOM spec says some getters have a side-effect on CSS transitions starting time, let these be accessors.
Thus if you use a specification device in terms of a proxy that implements getOwnPropertyDescriptor only, suddenly sets of offsetWidth (a readonly property, note) start having side-effects which they don't have right now, and implementations that don't have those side-effects become non-conformant. Again, just as an example of how a "specification device" can turn into an unintended conformance requirement.
That'd be a spec bug, but one that is easy to make when unfamiliar with proxies I agree. The solution here would be to define the set trap as first performing a check on whether the property is a readonly one. Spec bugs can be avoided if a lot of people read the spec and if interoperability tests are developed. I'm not that worried about unintended conformance requirement.
I feel that for the vast majority of cases in the web platform, WebIDL is the only place that'll need to use proxies (exception for live objects and WindowProxy and probably a handful of other weirdnesses). If the WebIDL part is done right once, everything will follow fine. In the upcoming weeks, I'll take a stab at drafting what WebIDL would look like if defining attributes as own data properties using proxies. We'll see how it goes.
So I'm very much against using a specification device that doesn't match the intended implementation, which is what you seem to be proposing. In my experience, that leads to implementations which don't actually match the spec in all sorts of edge cases. If we're lucky, they match each other and all that has to happen is the spec has to be rewritten somehow. But more commonly they don't match each other either, and you end up with interoperability problems, which means your standardization effort just failed...
I haven't been in the web platform for long enough, but I feel now more people read the specs, catch bugs, discuss, write test cases than before. I wonder if standardization efforts can "just fail" as much as they did in the past. I feel spec errors are caught much much earlier than what I hear from how things were 5+ years ago. To people who've been there for much longer than me, is it a shared feeling?
Le 30 déc. 2012 à 19:20, David Bruant <bruant.d at gmail.com> a écrit :
Interesting point. Unrelated, but it's making me realize that innerHTML may stay a getter/setter (not sure about inherited or not). I guess the divide I'd like to put is between object state and what is conveniently put as an accessor
FWIW, I used the fact that, in IE 8 & 9, innerHTML is conveniently a configurable accessor found at Element.prototype (in IE8) or HTMLElement.prototype (in IE9), for correcting a quirk of its setter, by extracting it and replacing it with a corrected version. I don't know if and how it would be possible if it was an own property on each instance, or a magical property. Here is the code:
if (!window.HTMLElement) window.HTMLElement = window.Element ;(function() { var wrapMarkup = { 'SELECT': [1, '<select multiple>', '</select>'] , 'TABLE' : [1, '<table>', '</table>'] , 'TBODY' : [2, '<table><tbody>', '</tbody></table>'] , 'TFOOT' : [2, '<table><tfoot>', '</tfoot></table>'] , 'THEAD' : [2, '<table><thead>', '</thead><table>'] , 'TR' : [3, '<table><tbody><tr>', '</tr></tbody></table>'] } , setInnerHTML = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'innerHTML').set Object.defineProperty(HTMLElement.prototype, 'innerHTML', {'set': function(content) { var result , i if (wrapMarkup.hasOwnProperty(this.nodeName)) { result = document.createElement('div') setInnerHTML.call(result, wrapMarkup[this.nodeName][1] + content + wrapMarkup[this.nodeName][2]) for (i = 0; i < wrapMarkup[this.nodeName][0]; i++) result = result.firstChild setInnerHTML.call(this, '') while (result.firstChild) this.appendChild(result.firstChild) } else { setInnerHTML.call(this, content) } }}) })()
Claude
On Sun, Dec 30, 2012 at 7:58 PM, David Bruant <bruant.d at gmail.com> wrote:
To people who've been there for much longer than me, is it a shared feeling?
I think we're still quite bad at tests although it's getting better. I agree with bz that if specifications diverge from the conceptual model of implementations or implementations diverge from the conceptual model of the specifications, you'll end up in a world of hurt.
On 12/30/12 10:31 AM, Brandon Benvie wrote:
The problem I see with the accessor model is that it's attempting to use what's now being referred to in recent ES6 specs as "Ordinary" object semantics, when the functionality is absolutely of exotic functions.
Can you please define "exotic functions"? How is the innerHTML getter "exotic", exactly?
The internal functions are no longer magical, as in completely outside the bounds of what's possible in ECMAScript, but they're trying to be shoehorned to an even lower level: implementation as completely plain objects.
Which internal functions are we talking about?
Ordinary objects only have accessors to describe "interesting" non-data properties.
Right, which is many (but not all) properties in the DOM.
But exotic objects, even limited to ones entirely specified in ES6, have interesting mechanics that cannot be replicated by ordinary objects and are better described with those mechanics than trying to shoehorn them in.
I'm just missing the point here entirely, I think. Can you explain?
A comparable thing would be attempting to explain Array instance lengths using a (observable) accessor property with a getter and setter on the prototype.
As opposed to the action-at-a-distance that's in the spec now? It could be, and might be more clear, yeah...
On 12/30/12 1:20 PM, Claude Pache wrote:
FWIW, I used the fact that, in IE 8 & 9, innerHTML is conveniently a configurable accessor found at Element.prototype (in IE8) or HTMLElement.prototype (in IE9), for correcting a quirk of its setter, by extracting it and replacing it with a corrected version.
Indeed, this is one of the major reasons properties are accessors in WebIDL. It allows libraries to polyfill functionality as it's added in specs (e.g. if the set of things that can be assigned to a property changes, that may be possible to polyfill by hooking the setter).
I don't know if and how it would be possible if it was an own property on each instance
It wouldn't be possible, in the proposals I've seen so far in this thread.
On 12/30/12 10:20 AM, David Bruant wrote:
I would say that the difference lies in the fact that properties which usually materialize the object state clearly are a per-instance thing while object methods usually are stateless functions
The line is fuzzy (as usual). Is the innerHTML getter more stateful than querySelector? Why? Is either one more or less stateful than the nodeType getter (which is sorta kinda per instance but in practice per "subclass")?
There is the |this| check removal and Brandon Benvie made a couple of good points on the developer experience at esdiscuss/2012-December/027503 The point about debuggers may be very annoying. It can be solved by devtools. It would be just more work ;-)
Arguably devtools that go through and grab the values of all properties are somewhat broken anyway, because that's an expensive thing to do and can have side-effects....
But in any case, last I checked the devtools I tried in Firefox would show property values on instances and show the getter/setter pair on the prototype.
I don't understand your point. I didn't mention anything about origin. Can you develop?
If you don't want a script to be able to change your runtime environment, don't run it in a place where it can reach your global. On the web, that means don't run it from your origin, in practice.
Anyway, in a modern browser, the beforescriptexecute event [1] can be of great help to sandbox code from any origin apparently (e.preventDefault() then evaluate with a sandbox of your own recipe).
Building that sandbox is ... nontrivial.
Unrelated, but it's making me realize that innerHTML may stay a getter/setter (not sure about inherited or not).
OK, so we're talking about having some attributes work one way and others work a different way, then?
I guess the divide I'd like to put is between object state and what is conveniently put as an accessor
What is the distinction between the two, exactly?
On 12/30/12 10:58 AM, David Bruant wrote:
I'm not enough familiar with CSSOM getters and CSS transitions, sorry; is it a spec requirement or an observation shared for all current implementations?
The spec requirement is that transitions start when computed values change.
The spec does not define when computed values change.
However, determining layout requires that styles be recomputed first, so it would be a tough argument to make that you're returning the value of a layout property like offsetWidth but haven't updated your computed values. So in practice I believe this is in fact a spec requirement.
But further, it's behavior shared by implementations and there is content depending on it.
The criterion could be whether getting or setting has an effect observable outside of the object the property is on. So if the CSSOM spec says some getters have a side-effect on CSS transitions starting time, let these be accessors.
OK. But then what problem(s) are we solving by having some properties still be accessors but having others described via proxy semantics? That seems like a strict increase in complexity over doing one or the other...
That'd be a spec bug, but one that is easy to make when unfamiliar with proxies I agree.
So your proposal here would be for getOwnPropertyDescriptor to return a descriptor with no value (unlike the get trap)?
The solution here would be to define the set trap as first performing a check on whether the property is a readonly one.
There are lots of things that getOwnPropertyDescriptor but are not the set trap.
I feel that for the vast majority of cases in the web platform, WebIDL is the only place that'll need to use proxies (exception for live objects and WindowProxy and probably a handful of other weirdnesses).
I don't understand this statement... Right now live objects are defined in WebIDL in terms of things like [[GetOwnProperty]] and whatnot; they can then be implemented as proxies or equivalent by implementations...
In the upcoming weeks, I'll take a stab at drafting what WebIDL would look like if defining attributes as own data properties using proxies. We'll see how it goes.
OK. I obviously can't tell you what to spend or not spend your time on. ;)
I haven't been in the web platform for long enough, but I feel now more people read the specs, catch bugs, discuss, write test cases than before.
To some extent. The specs are huge, the bugs in them and in UAs are often subtle, and the test cases are woefully incomplete.
One of the great testing success stories of recent years (CSS 2.1) has test coverage that I'd rate somewhere between "middling" and "crappy". It's way better than anything before, but combinatorial explosion makes things really hard to test.
Added to that, far too many implementors either don't think critically about the spec as they implement it or don't bother sending in feedback when they run into spec bugs that they then don't propagate to their code. Far too often I've seen cases in which a spec dozens to hundreds of pages long claims to have an implementation or three with not a single word of feedback sent in the process. How likely that is to happen, you decide (but again in my experience it's trivial to find glaring problems in such specs).
I wonder if standardization efforts can "just fail" as much as they did in the past.
I'm not sure we can get failures (by today's standards, no pun intended) of the magnitude of HTML4 or CSS2, but it's quite possible to fail. Right now the only thing standing between many specs and failure is the hope that some implementor will actually bother to send feedback on the problems, which as I said above they have a tendency to fail to do.
There are several people, maybe as many as a dozen or two, that I can name that send consistent feedback on web specs as they implement them. How many people do you think are working on implementing web specs at Mozilla, Microsoft, Google, Apple, and Opera? I can assure you it's more than two dozen all told.
Boris Zbarsky wrote:
On 12/30/12 10:31 AM, Brandon Benvie wrote:
The problem I see with the accessor model is that it's attempting to use what's now being referred to in recent ES6 specs as "Ordinary" object semantics, when the functionality is absolutely of exotic functions.
Can you please define "exotic functions"?
Think ES1-5 "host objects".
How is the innerHTML getter "exotic", exactly?
It isn't.
Brandon may have assumed more magic in more of the DOM than there is, as you note:
On 12/30/12 10:53 PM, Brendan Eich wrote:
Think ES1-5 "host objects".
Yeah, anything that needs that sort of thing is already not described as accessors in WebIDL. Accessors are for the cases which can be implemented entirely with normal objects plus perhaps WeakMap to store the not-directly-exposed state (though it might be possible to get away with just closures; I haven't thought about it enough).
Le 31/12/2012 07:19, Boris Zbarsky a écrit :
On 12/30/12 1:20 PM, Claude Pache wrote:
FWIW, I used the fact that, in IE 8 & 9, innerHTML is conveniently a configurable accessor found at Element.prototype (in IE8) or HTMLElement.prototype (in IE9), for correcting a quirk of its setter, by extracting it and replacing it with a corrected version.
Indeed, this is one of the major reasons properties are accessors in WebIDL. It allows libraries to polyfill functionality as it's added in specs (e.g. if the set of things that can be assigned to a property changes, that may be possible to polyfill by hooking the setter).
I don't know if and how it would be possible if it was an own property on each instance
It wouldn't be possible, in the proposals I've seen so far in this thread.
Polyfillability is an interesting criteria. Is Object.getOwnPropertyDescriptor polyfillability important? Would a good [[Get]]+[[Set]] polyfill be enough? By that I mean that as long as the [[Get]]+[[Set]] behavior is good, maybe polyfilling a property by an accessor is good enough? I tend to consider data or accessor property as an implementation detail. It's important to specify it in WebIDL for interoperability, but authors should be worried of making the [[Get]]/[[Set]] protocol work and use the rest of the tooling (low-level property descriptor stuff) to make this work.
If necessary, WebIDL is free to add custom property descriptor attribute. It could look like:
Object.defineProperty(webIDLObject, 'bla', {value: 25, webIDLType:
'long'})
And it would create an own property of type long (so subject to all relevant WebIDL logic). I would prefer that innerHTML stay a getter/setter, but it could be possible to describe it as an own property and send out its logic through a property descriptor attribute. Very heavy-weight, but technically possible. I prefer the getter/setter solution.
On 12/31/12 2:24 AM, David Bruant wrote:
Polyfillability is an interesting criteria. Is Object.getOwnPropertyDescriptor polyfillability important? Would a good [[Get]]+[[Set]] polyfill be enough? By that I mean that as long as the [[Get]]+[[Set]] behavior is good, maybe polyfilling a property by an accessor is good enough?
Getting the right [[Get]] and [[Set]] behavior is enough, but how do you propose to implement a polyfill that calls the "original" getter or setter in some cases if those getters and setters aren't ever exposed anywhere?
I tend to consider data or accessor property as an implementation detail.
Not sure what you mean. How is it any more or less an implementation detail than property vs method, say?
It's important to specify it in WebIDL for interoperability, but authors should be worried of making the [[Get]]/[[Set]] protocol work
Yes, agreed.
If necessary, WebIDL is free to add custom property descriptor attribute.
Uh... No. I don't think it is. Nothing else in the language or othe standards would know anything about this attribute; various other specifications that work in terms of property descriptors would suddenly start doing weird stuff, no?
And it would create an own property of type long (so subject to all relevant WebIDL logic).
That requires putting the WebIDL logic in the ECMAScript implementation, no? Which is not how it works in at least SpiderMonkey and V8 (which are meant as standalone ECMAScript implementations).
David Bruant wrote:
Sure, but should all those DOM objects whose pre-WebIDL browser-specific bindings used magic data properties have to become proxies? Has anyone checked whether doing so is (a) compatible; (b) performant?