For Dave and David
Dave Herman jokingly accused me a couple of TC39 meetings ago of being an "advocate for JavaScript as we have it today", and while he meant it in jest, I guess to an extent it's true -- I'm certainly not interested in solutions to problems I can't observe in the wild. That tends to scope my thinking aggressively towards solutions that look like they'll have good adoption characteristics. Fix things that are broken for real people in ways they can understand how to use.
This is why I get so exercised about WebIDL and the way it breaks the mental model of JS's "it's just extensible objects and callable functions". It's also why my discussions with folks at last year's TPAC were so bleakly depressing. I've been meaning to write about TPAC ever since it happened, but the time and context never presented themselves. Now that I got some of my words out about layering in the platform, the time seems right.
Let me start by trying to present the argument I heard from multiple sources, most likely from (in my feeble memory) Anne van Kestern Jonas Sicking(?):
ECMAScript is not fully self-describing. Chapter 8 drives a hole right through the semantics, allowing host objects to whatever they want and more to the point, there's no way in JS to describe e.g. list accessor semantics. You can't subclass an Array in JS meaningfully. JS doesn't follow it's own rules, so why should we? DOM is just host objects and all of DOM, therefore, is Chapter 8 territory.
Brain asploded.
Consider the disconnect: they're not saying "oh, it sure would be nice if our types played better with JS", they're saying "you and what army are gonna make us?" Remember, WebIDL isn't just a shorthand for describing JavaScript classes, it's an entirely parallel type hierarchy.
Many of the Chapter 8 properties and operations are still in the realm of magic from JS today, and we're working to open more of them up over time by giving them API -- in particular I'm hopeful about Allen Wirfs-Brock's work on making array accessors something that we can treat as a protocol -- but it's magic that DOM is appealing to and even specifying itself in terms of. Put this in the back of your brain: DOM's authors have declared that they can and will do magic.
Ok, that's regrettable, but you can sort of understand where it comes from. Browsers are largely C/C++ enterprises and DOM started in most of the successful runtimes as an FFI call from JS to an underlying set of objects which are owned by C/C++. The truth of the document's state was not owned by the JS heap, meaning every API you expose is a conversation with a C++ object, not a call into a fellow JS traveler, and this has profound implications. While we have one type for strings in JS, your C++ side might have bstr
, cstring
, wstring
, std::string
, and/or some variant of string16
.
JS, likewise, has Number
while C++ has char
, short int
, int
, long int
, float
, double
, long double
, long long int
...you get the idea. If you've got storage, C++ has about 12 names for it. Don't even get me started on Array
.
It's natural, then, for DOM to just make up it's own types so long as its raison d'être is to front for C++ and not to be a standard library for JS. Not because it's malicious, but because that's just what one does in C++. Can't count on a particular platform/endianness/compiler/stdlib? Macro that baby into submission. WTF, indeed.
This is the same dynamic that gives rise to the tussle over constructable constructors. To recap, there is no way in JS to create a function which cannot have new
on the left-hand-side. Yes, that might return something other than an instance of the function-object on the right-hand side. It might even throw an exception or do something entirely non-sensical, but because function
is a JavaScript concept and because all JS classes are just functions, the idea of an unconstructable constructor is entirely alien. It's not that you shouldn't do it...the moment to have an opinion about that particular question never arises in JS. That's not true if you're using magic to front for a C/C++ object graph, though. You can have that moment of introspection, and you can choose to say "no, JS is wrong". And they do, over and over.
What we're witnessing here isn't "right" or "wrong"-ness. It's entirely conflicting world views that wind up in tension because from the perspective of some implementations and all spec authors, the world looks like this:
Not to go all Jeff Foxworthy on you, but if this looks reasonable to you, you might be a browser developer. In this worldview, JS is just a growth protruding from the side of an otherwise healthy platform. But that's not how webdevs think of it. True or not, this is the mental model of someone scripting the browser:
The parser, DOM, and rendering system are browser-provided, but they're just JS libraries in some sense. With <canvas>
's 2D and 3D contexts, we're even punching all the way up to the rendering stack with JS, and it gets ever-more awkward the more our implementations look like the first diagram and not the second.
To get from parser to DOM in the layered world, you have to describe your objects as JS objects. This is the disconnect. Today's spec hackers don't think of their task as the work of describing the imperative bits of the platform in the platform's imperative language. Instead, their mental model (when it includes JS at all) pushes it to the side as a mere consumer in an ecosystem that it is not a coherent part of. No wonder they're unwilling to deploy the magic they hold dear to help get to better platform layering; it's just not something that would ever occur to them.
Luckily, at least on the implementation side, this is changing. Mozilla's work on dom.js is but one of several projects looking to move the source of truth for the rendering system out of the C++ heap and into the JS heap. Practice is moving on. It's time for us to get our ritual lined up with the new reality.
Which brings me too David Flanagan who last fall asked to read my manifesto on how the platform should be structured. Here it is, then:
The network is our bottleneck and markup is our lingua-franca. To deny these facts is to design for failure. Because the network is our bottleneck, there is incredible power in growing the platform to cover our common use cases. To the extent possible, we should attempt to grow the platform through markup first, since markup provides the most value to the largest set of people and provides a coherent way to expose APIs via DOM.Markup begets JS objects via a parser. DOM, therefore, is merely the largest built-in JS library.
Any place where you cannot draw a line from browser-provided behavior from a tag to the JS API which describes it is magical. The job of Web API designers is first to introduce new power through markup and second to banish magic, replacing it with understanding. There may continue to be things which exist outside of our understanding, but that is a challenge to be met by cataloging and describing them in our language, not an excuse for why we cannot or should not.
The ground below our feet is moving and alignment throughout the platform, while not inevitable, is clearly desirable and absolutely doable in a portable and interoperable way. Time, then, to start making Chapter 8 excuses in the service of being more idiomatic and better layered. Not less and worse.