While working on Constructors by Construction in JavaScript, MSHTML and EdgeHTML I left out a few interesting details that might be useful to others. Specifically, I left out the concept of a purely static interface, where you will never provide any instances of said object, have no need of a prototype and simply want to hang constants, methods and properties off of something other than the Global. You also don't want to provide a fully prototyped instance, just to give access to a namespace of methods. Think about the various collections of APIs that exist on the window today, under sub-objects, that could have effectively been static methods and properties in a namespace. A primary example being singletons like the navigator and history objects.
Okay, those aren't GREAT examples, but they are good examples. The instance in that case may make sense if you want to execute in the context a window other than your own. In that case the instance makes sense and you can pass it around as you see fit. But the same can be said for Constructor with static methods on it. For instance, take an existing Constructor like Node and pass it around. People can still access constants on it like ELEMENT_NODE even across window boundaries (assuming same domain) so the Constructor itself behaves like an instance and we do construct one of those per document.
So for the FastDOM type system, we decided it was necessary to make a non-conforming WebIDL construct to support this behavior. We call it the
[msInterfaceObjectOnly]
attribute and it can hang off of an existing interface to give us the behaviors that we want. If we were to propose this into WebIDL it'll probably get a much nicer syntax such as replacing interface with module
or namespace
.msInterfaceObjectOnly support
So the following is the list of things that msInterfaceObjectOnly does. First and foremost it creates a Constructor the interface by name. In our case check out the
So try it out in the toolbar. Bang out NodeFilter and hit enter. You'll get some output that is either an object (IE 9 through IE 11) or you'll find a Function is returned in either Chrome, FireFox or EdgeHTML. Nice, we all agree there. Also, so we can all be on the same page, here is a listing of how our version of NodeFilter is defined.
Our next difference from a standard interface is that we should NOT have a prototype. Why? We can't create instances of this guy. It exists solely to promote the constants into the type system. Note if you try this in Chrome stable it looks, oddly, like NodeFilter does have a prototype and even more strange, there is a native function called acceptNode hanging off of said prototype. I'm a little bit confused by that, since that doesn't match the spec, but maybe there is some a cool use case there. If anyone knows feel free to leave a comment!
The interface also describes criterion for what it takes to supply a custom NodeFilter to APIs. Basically that an acceptNode method must exist or that the function passed in must itself be callable. This is a bit confusing so I'll state it one more way, you can either pass in a function directly to an API taking a NodeFilter and it will be called directly, or you can pass in an object that has property named acceptNode that returns a function that will be called instead.
Since we have some constants, they must be placed on our Constructor instance. This is no different from a normal interface binding, except that since we don't have a prototype, we won't mirror the constants on the prototype as well. Constants are kind of interesting. If you get their property descriptor you'll find that they are writable: false and configurable: false. They can't be reconfigured, shadowed or updated in any way, which is good, since they ARE constants after all.
NodeFilter
interface which is kind of defined to be a callback interface in the spec and there are even rules around the callback interface keywords and how to code generate. Our implementation predates all of those rules and so we've continued to use our existing mechanismsSo try it out in the toolbar. Bang out NodeFilter and hit enter. You'll get some output that is either an object (IE 9 through IE 11) or you'll find a Function is returned in either Chrome, FireFox or EdgeHTML. Nice, we all agree there. Also, so we can all be on the same page, here is a listing of how our version of NodeFilter is defined.
Our next difference from a standard interface is that we should NOT have a prototype. Why? We can't create instances of this guy. It exists solely to promote the constants into the type system. Note if you try this in Chrome stable it looks, oddly, like NodeFilter does have a prototype and even more strange, there is a native function called acceptNode hanging off of said prototype. I'm a little bit confused by that, since that doesn't match the spec, but maybe there is some a cool use case there. If anyone knows feel free to leave a comment!
The interface also describes criterion for what it takes to supply a custom NodeFilter to APIs. Basically that an acceptNode method must exist or that the function passed in must itself be callable. This is a bit confusing so I'll state it one more way, you can either pass in a function directly to an API taking a NodeFilter and it will be called directly, or you can pass in an object that has property named acceptNode that returns a function that will be called instead.
Since we have some constants, they must be placed on our Constructor instance. This is no different from a normal interface binding, except that since we don't have a prototype, we won't mirror the constants on the prototype as well. Constants are kind of interesting. If you get their property descriptor you'll find that they are writable: false and configurable: false. They can't be reconfigured, shadowed or updated in any way, which is good, since they ARE constants after all.
Statics
The last interesting bit is that we like to define things on these special constructors as 'static'. This way you can use the name of the constructor like a namespace. Only methods and properties can be made to be static. Constants are already static by their definition so they don't need to be augmented with the keyword to work.
When we see a static, we define the method or property directly on the Constructor instance just like we do for the constants. This is somewhat interesting in that when the method or property is called your 'this' instance is actually the Constructor object and not an instance. This means you have to do a bit of work to figure out where and how you are being called, but as these are statics, once you know which engine you are in, you only care about the parameters and you can ignore whatever the this pointer is.
Our crowning example of this is an object most web developers won't be familiar with. The MSApp object which we define when you are running inside of a WWA. It allows you to access a scheduler like system on the thread. These types of systems don't need instances to hang off of and so they naturally fall into the model of using statics. Here is a trimmed version of the webIDL for this type.
Hopefully this is enough for you to see the pros and cons of the static model and also why we'd implement this for some of our behaviors. You often see this model in JavaScript in the form of modules, namespaces and singletons. It makes the most sense to web developers when the Browser libraries follow the same patterns that they are used to when writing their own libraries.
Futures
So most of this is already available in WebIDL. The static keywords were added a while back and they even feigned support for static properties before any Browser implemented one (it is actually possible that no shipped Browser has a static property, the EdgeHTML engine doesn't currently have any that I know of).
The closest thing to our msInterfaceObjectOnly attribute is some of the verbage around callback interfaces, but they have additional restrictions that make them not able to be used for our purposes. I'm currently hoping that we can gain support for a module or namespace keyword and remove our custom attribute. Since we have an implementation object in MSApp it would be cool to prototype that up and see it in action. There is also an open question about whether these type system members should be implemented as objects or functions the way other Constructors are. They aren't constructable since they don't have instances, so maybe they shouldn't be typeof function. After all, that does add a lot of extra methods and properties to them by default and it would be handy if they were a much cleaner abstraction.
Hopefully you enjoyed this short addendum to my original Constructor post. Feel free to let me know in comments or ask questions via @JustrogDigiTec.
No comments:
Post a Comment