Infrequently Noted

Alex Russell on browsers, standards, and the process of progress.


I'm sitting here in Derek Featherstone's amazing a11y talk at Fronteers and I feel like I need to follow up the last post with a quick primer on the zen of function for (both of) the spec authors who read this blog.

The reason it's offensive to the JS hacker for WebIDL to disallow new against DOM types -- any of them -- is that it means that it's no longer specifying how you'd describe these types in the primitive we use over here in JS for this sort of thing: functions. This might sound nuts to folks who come from C++ or who spend their time in spec-ese, but there's no difference between a plain-old function, a "constructor", and a "partial" or "mixin" in JS semantics. We use functions for all of them. You can say new function(){ ... } and create an instance of an anonymous "class" in JS. You can take the same function and invoke it as a "regular function" -- (function(){ ... })(); -- and you can use that sort of function as a mixin or partial interface too: new function(){ (function(){ ... }).call(this); ... }. The exact same function object can even act in all of these capacities (although it's rare). People use them as they need to, but they all boil down to functions.

What, then, does it mean for something to disallow new against some type for which you can in some otherwise get an instance in JS? The same thing when you can't .call() it: it's alien. It's not a class as we know it, which means that it's not a function, and if it's not a function...well, it doesn't belong. Fundamentally, it's smuggling static semantics into a language that has perfectly good dynamic semantics for the same thing. This strikes at the very heart of what WebIDL is supposed to be for: describing JS types for things implemented somewhere else. By not allowing new and .call() WebIDL is giving JS semantics the bird, asserting that the fact that these things aren't JS types makes them better in some way...and that is a bug, either in the perspective of the spec authors or of the specs themselves.

Luckily, the fix is easy: all WebIDL types should de-sugar to functions. All of them. All the time. No questions asked. That you will be able to use new and .call() and all the rest isn't a bug, and it's not something to guard against. It's just how JavaScript rolls...and how JavaScript's largest, most important library should roll too.

Real Constructors & WebIDL Last Call

For those who haven't been following the progress of WebIDL -- and really, how could you not? An IDL? For the web? I'd like to subscribe to your newsletter... -- the standard is now in last call, which is W3C for "alllllllllllmost done".

Which it is not.

Before I get to why, let me first say some nice, well-earned things about WebIDL: first, it has helped us out of the ad-hoc IDL sludge that used to be how APIs for JavaScript have been exposed in the past. It has shaved off many sharp edges and is giving spec authors a single dialect in which to write their API descriptions. From a browser perspective, this is a Very Good Thing (TM). Next, the draft in question contains some wonderful changes from the status quo, particularly the addition of a sane prototype to all WebIDL-specified objects.

That all sounds good, so what's missing?

In a word, constructors.

Well, a lot more than that, but I'd settle for constructors. Functionally speaking, it boils down to the fact that WebIDL makes spec authors do extra work to make something like this sane:

new HTMLDivElement();

Why doesn't this work today? Funny story...see, HTML defines HTMLDivElement as a regular WebIDL interface. WebIDL doesn't really have the notion of concrete classes, just interfaces with and without constructors. Since the HTML spec is just doing what most specs will do -- adding the smallest IDL you can get away with -- the JS usability of this API is left in limbo; neither clearly HTML5's responsibility nor WebIDL's.

So what should a contentious version of HTML5 do? One answer is to specify a constructor, turning the IDL from this:

interface HTMLDivElement : HTMLElement {};

to this:

interface HTMLDivElement : HTMLElement {};

Repeat ad-infinitum for each and every interface that should be constructable in every single spec that browser vendors ever implement. Don't miss any! And please make sure that all your spec editors are on-board with good JS APIs as a goal! As it stands today, WebIDL doesn't even force most spec authors to consider the question "do I need a constructor here?" -- spoiler: yes -- let alone the obvious follow-ups like "what arguments should one take?".

The obvious better answer here is to flip the default on interfaces, causing them to generate constructors by default unless turned off with [NoConstructor] attributes or specified as partial interfaces (i.e., mixins or traits).

Cameron McCormack who is heading up the WebIDL effort tweeted in response to my exasperation that:

I think a "W3C Web API design guidelines" document would be a perfect place for such a recommendation.

For serious? Such a document might be useful (and I'm working on something that might pass as a first draft), but what's the argument against flipping the default here? This isn't a dissent on the facts of the situation: most WebIDL "interfaces" that are exposed to JS are things that could be easily new'd up to useful ends. Most specs flub this in spectacular style. Most spec authors seem entirely ignorant of the problem and the design language of WebIDL continues to lead down a primrose copy-and-paste path that has little overlap with sanity. So why punt the decision? And why did it take and act of coordination with TC39 to get the prototype thing fixed?

And Why Are We Having This Discussion Anyway?

WebIDL, for all of its virtues, is deeply confused.

If you're reading any of the stuff in the HTML5 spec that's describing its API this way, it's hard to see how it would have any sane relationship to JavaScript. Sure, you could argue that there might be other languages that matter, other languages for which you'd need to be able to generate some API, but none of them rise to anything like the importance of JavaScript. It is the programming language of the web, so if WebIDL has any animating force at all, it's JS. Then there's the "accident of history" aspect. Early DOM was specified as a form of IDL in part because there was some expectation that other languages would need to consume it and IDL was how C++ hackers (who still make up the entire set of people working on browser engines) are/were comfortable in describing their FFIs thanks to the legacy of COM/CORBA. Hilarious examples of multi-language-ism still persist in the WebIDL spec for no apparent reason whatsoever, warping the entire design around the altar of an ideal that is either quixotic or vestigial depending on which argument you give more weight.

Since the debate was re-kindled thanks to a debate at a TC39 meeting in July, I've been on the receiving end of more than one webdev's rant about DOM's JS incoherence, generally taking the form:

Why the *#!*?^@$ isn't DOM just #@!*@ing specified in JavaScript?

To be honest, I have no answer aside from pointing to the IDL history, the fact that browser hackers don't tend to write JS so don't feel the pain, and noting that WebIDL is better in some important ways. Certainly these interfaces could be specified in a subset of JS with annotations for where native behavior is required. But their larger lament has merit too: seamlessness with JS is the bar WebIDL should be judged by. I.e. does it help spec authors do the right thing by JS devs? Or does it lead them down paths that make their generated APIs stick out like sore thumbs, full of anti-social/alien behavior such that you can't think of them as "regular" JS?

Yes, constructors are only one minor step toward reaching this aspiration, but the fact that WebIDL has gotten to last-call without a reasonable solution to them speak volumes. If WebIDL isn't animated by the need of JS developers, it would be good if that could be loudly called out somewhere so that the community can have the spirited debate that this point demands. If it is, can we please get on discussing how best to ensure that most "interfaces" generate constructors and stop punting?

Either way, WebIDL isn't done yet.

Update: It occurred to me, as part of the discussion in the comments, that the provision against new with a class or type of any type is completely non-sensical in JS, as is the lack of call() and apply() methods on them. Idiomatic subclassing requires that the mixin-style be available, which uses This is what you'd do with things that are "virtual" or "partial" interfaces if you're describing them in actual JS. And there's no real problem with folks new-ing them up. Doesn't happen, doesn't matter. Strong restrictions against it are, to quote Andrew DuPont, anti-social. Long story short: there is absolutely no reason whatsoever to disable construction on any WebIDL interface. It's deeply non-sensical from the JavaScript perspective.

Older Posts

Newer Posts