-
Notifications
You must be signed in to change notification settings - Fork 39
Self-hosted JavaScript implementation of a WebIDL-compliant HTML5 DOM.
License
andreasgal/dom.js
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
The goal of this project is to evaluate whether it is feasible to implement a WebIDL-compliant HTML5 DOM in JavaScript. While a couple other self-hosted JavaScript DOM implementations exist, none of them are WebIDL-compliant (i.e. properties don't live on the prototype but directly on each node, for example). Also, all other implementations do not provide proper encapsulation. The details of the implementation leak through the API. The code uses ES5 strict mode, and ES6 Proxies and WeakMaps and the 'const' keyword. The Makefile generates dom.js from many smaller files in the src/ directory. dom.js includes all of the smaller files within one large closure so the variables and constants that appear to be global do not, in fact, leak into the global scope. The src/impl/ directory contains an implementation of the DOM using plain JavaScript objects. By itself, this is not a conforming implementation because its internal properties are visible. Publicly-visible objects are wrappers around this internal implementation and we use a WeakMap to create a private mapping from each public DOM object to its the internal implementation object. Many more details of the architecture and code organization are below. Installing ====== Get a copy of the dom.js source: git clone git://github.com/andreasgal/dom.js.git Install node.js: http://nodejs.org/#download Install spidermonkey: Look for a jsshell download at http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-mozilla-aurora/ Or, build from source following these directions: https://developer.mozilla.org/En/SpiderMonkey/Build_Documentation Building ====== 1. Run "git submodule init". 2. Run "git submodule update". This clones the repos that dom.js depends on 3. Run "make". This assembles the dom.js, src/domcore.js, and src/htmlelts.js files from the other source files in the project. Testing ====== Run 'make coverage'. If you've got a debug build of Spidermonkey, it will run slowly but display code coverage information. If you're using a non-debug build, the tests will run quickly but display some error messages about not being able to display coverage information. Directory Structure =================== This repo includes the following important files and directories: LICENSE dom.js is open-source, licensed under the BSD license Makefile Type 'make' to build dom.js from the files in src/ deps/ Other git repos that dom.js depends on. In addition to these, it also requires node (for tools) and spidermonkey (to run standalone and to run tests) deps/CoverMonkey/ A code-coverage tool for SpiderMonkey. Required for the 'make coverage' target in the Makefile deps/webidl.js/ A parser for WebIDL files. This is required to convert the .idl files in src/idl/ (which describe public DOM APIs) into .js files in src/ deps/parser-lib/ A css parser that dom.js uses to implement the HTMLElement.style property. src/ The source code for dom.js src/idl/ WebIDL files that describe public DOM interfaces. The files in this directory are automatically converted into JavaScript code in src/ src/impl/ An internal DOM implementation with implementation details hanging out everywhere. tools/ Programs to convert idl files to js files and to run tests. These tools require Node tests/ An incomplete set of DOM tests. The most complete set is in tests/newtests, and these are the ones that are run by the 'make coverage' target in the Makefile servo/ dom.js is part of a larger Mozilla experimental browser engine called Project Servo. This directory contains code that runs dom.js in a Web Worker, and then uses the browser's native DOM to fake a layout engine for dom.js nodeservo/ A Node-based version of servo, using web sockets to communicate between dom.js running in node and a native DOM fake layout engine running in a browser. Currently unstable. Architecture =============== What follows is an overview of the dom.js architecture, organized by going through the source code files one at a time. Makefile This file defines a list of all the source files. The default target assembles dom.js from a lot of separate sources. It is worth taking a look at what the Makefile does to create dom.js. Most importantly, it wraps all the code from all the files inside a single function, so that none of the variables they define are global variables. To export a global variable, dom.js code must explicitly define a property of 'global'. ('global' is the argument to the containing function and its value is the global object, of course). The other trick that the Makefile does when building dom.js is to put almost all of the code within another big function, this one in strict mode. This leaves only one file of code in non-strict mode. src/loose.js This file contains any code that dom.js needs that must be in non-strict mode. In particular, event handlers registered with HTML attributes must execute with an augmented scope chain, and doing this correctly requires the with statement which is not allowed in strict mode. So this file contains that code. It also contains (for now, at least) an evalScript function used by the HTML parser to execute scripts. That will probably go away eventually. src/snapshot.js This file makes private copies of all the JavaScript primitives, with the goal of making dom.js immune to monkeypatching that occurs after dom.js has loaded. Once we have a proper embedding, with dom.js running in a different excution context than <script> tags do, this may no longer be a goal we need to pursue. For now, though, if we don't want external code to be able to observe or intercept any of the calls that dom.js makes to built-in methods, all dom.js code must use the copies defined in this file. In particular, this means that OO style must be abandoned for all built-in types. If you have a string s and call its s.indexOf(c) method, that call can be intercepted. Instead, you must always call indexOf(s,c) instead. This can be hard to remember to do, and I suspect that there are parts of the codebase that do not do it consistently. src/globals.js This file contains a number of constants. They're not really global variables, but are visible throughout dom.js. The constants use the 'const' keyword. They include namespace URLs, and also the impl and idl objects that hold the internal and external constructors for various DOM interface types. src/utils.js Various utilities including an assert() function and a nyi() function that just throws a "Not Yet Implemented" error for parts of the DOM that aren't ready yet. The key function to understand in this file is defineLazyProperty(). It takes an object o, a property name p and a function f and defines the property o.p with a getter method. When the property is first queried, the function f is evaluated to compute the property value, and the property is then converted to an ordinary data property with that value. This is here because the DOM defines a lot of interfaces like HTMLTableRowElement that are not often used. The global object must have an HTMLTableRowElement property on it, but with this trick we don't actually have to initialize that interface unless it is actually needed. defineLazyProperty() is often used with the IDLInterface() function in src/idl.js src/wrapmap.js As explained earlier in this README file, dom.js creates two separate objects for each node in the document tree. One object is the implementation, and it exposes many implementation details. The second object is the public wrapper that defines only the public methods and property accessors defined by the DOM and HTML specifications and hides all implementation details. The public wrappers are created lazily as needed. If you parse a file of HTML, dom.js will build a document tree with one implementation object for each node in the tree. But it doesn't create the corresponding public objects until you actually access individual nodes with .firstChild or getElementById() or whatever. This file defines two key functions: wrap() and unwrap(). Wrap takes an implementation object as its argument and returns the public wrapper object. Unwrap does the opposite: it takes a public object and returns its implementation object. You'll see these two functions in use in files like src/domcore.js, which is automatically generated from src/idl/domcore.idl. They aren't often used outside of the generated files, but are occasionally when it is necessary to wrap an implementation object before passing it to external code (such as when triggering an event handler). The unwrap() function relies on a WeakMap object (new in ES6) to maintain a mapping between public wrappers and their associated implemenation objects. The wrap() function doesn't need to use a weakmap. If a public object has already been created for a given implementation object, it is stored in the _idl property of the implemenation object and wrap() just returns the value of that property. If not, then wrap() looks at the _idlName property of the implementation object. All wrappable objects are required to have one of these, and it is typically defined on their prototype. The value of _idlName should be a string that names the public interface of the wrapper object. That name must be a property of the idl object (defined in src/globals.js), and the value of that property will be an object with a factory method for creating new public wrapper objects. (See also IDLInterface() in src/idl.js). When a new public wrapper object is created, it is stored in the _idl property of the wrapped object, and a mapping is added to the WeakMap so that the public object can be unwrapped. src/idl.js This file defines a number of type conversion methods that are required by the WebIDL spec and are used in the automatically generated wrapper code in files like src/domcore.js. This file also defines the IDLInterface constructor that is used (often in conjunction with defineLazyProperty()) to create public interface objects like Document, Element and HTMLTableElement as well as their prototype objects. The IDLInterface() code is well-commented; read those comments to see how it works and find examples in src/domcore.js and the other generated files. Note that IDLInterface() is used in a two step process, to first define the a lazy property of the 'idl' object (used by the wrap() method in src/wrapmap.js) and then to define the public interface as a lazy property of the global object. For IDL interfaces that have array-like or other somewhat magical behavior, the IDLInterface() constructor can take a reference to a function that returns a Proxy (new in ES6) object to handle the special behavior. Several types of proxies are described in individual files below. src/xmlnames.js This file defines two utility functions for determining whether a string is a valid XML name or valid XML qualified name. src/domcore.js src/events.js src/htmlelts.js src/windowobjs.js These four files are automatically generated from the corresponding src/idl/*.idl with the program tools/idl2domjs. These files define the public wrapper objects defined by WebIDL interfaces. src/AttrArrayProxy.js This file defines an AttrArrayProxy() factory method that returns a Proxy object that makes the Element.attributes object behave like an array. See also AttrArray in src/impl/Element.js src/NodeListProxy.js This file defines a NodeListProxy() factory function that returns a Proxy object that makes objects with item() methods and length properties behave like arrays. src/HTMLCollectionProxy.js This file defines an HTMLCollectionProxy() factory function that returns a Proxy object used for the HTMLCollection interface. The object passed in should have item() and namedItem() methods and a length property. The returned proxy acts like an array with added named properties. src/DOMException.js This is an implementation of the public DOMException type plus a bunch of utility functions for throwing exceptions of various types. Unlike other interfaces, there is not a dichotomy between implementation object and public wrapper object for DOMException. This implementation is out-of-date. Both WebIDL and DOM4 have made changes to the way exceptions are to be implemented and dom.js has not yet tracked those changes. src/impl/EventTarget.js src/impl/Node.js src/impl/Leaf.js src/impl/CharacterData.js src/impl/Text.js src/impl/Comment.js src/impl/ProcessingInstruction.js src/impl/Element.js src/impl/MutationConstants.js src/impl/domstr.js src/impl/Document.js src/impl/DocumentFragment.js src/impl/DocumentType.js src/impl/DOMImplementation.js src/impl/FilteredElementList.js src/impl/Event.js src/impl/CustomEvent.js src/impl/UIEvent.js src/impl/MouseEvent.js src/impl/HTMLElement.js src/impl/HTMLScriptElement.js src/impl/HTMLParser.js src/impl/CSSStyleDeclaration.js src/impl/cssparser.js src/impl/URL.js src/impl/URLDecompositionAttributes.js src/impl/Location.js src/impl/Window.js src/main.js
About
Self-hosted JavaScript implementation of a WebIDL-compliant HTML5 DOM.
Resources
License
Stars
Watchers
Forks
Releases
No releases published
Packages 0
No packages published