-
Notifications
You must be signed in to change notification settings - Fork 39
/
README
359 lines (271 loc) · 13.3 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
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