├── MIT-LICENSE ├── Makefile-gcc ├── NOTES ├── README ├── TODO ├── config-gcc.mk ├── dom ├── core.js ├── events.js ├── graphics.js ├── html.js └── svg.js ├── gezira ├── Makefile-gcc ├── generate-bindings ├── html.js ├── spidermonkey.head.c ├── stubs.js └── svg.js ├── mico.js ├── sdl ├── Makefile-gcc ├── spidermonkey.c ├── stubs.js └── window.js └── system ├── Makefile-gcc ├── browser.js ├── shell.js └── spidermonkey.c /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Dan Amelang (dan@amelang.net) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile-gcc: -------------------------------------------------------------------------------- 1 | include config-gcc.mk 2 | 3 | default: system/mico-$(ENGINE) 4 | @for p in $(PACKAGES_TO_COMPILE); do \ 5 | $(MAKE) -C $$p -f Makefile-gcc $(ENGINE)$(COMPILED_PACKAGE_EXT); \ 6 | done 7 | 8 | system/mico-$(ENGINE): .FORCE 9 | $(MAKE) -C system -f Makefile-gcc mico-$(ENGINE) 10 | 11 | clean: 12 | $(MAKE) -C system -f Makefile-gcc clean 13 | @for p in $(PACKAGES_TO_COMPILE); do \ 14 | $(MAKE) -C $$p -f Makefile-gcc clean; \ 15 | done 16 | 17 | .FORCE: 18 | -------------------------------------------------------------------------------- /NOTES: -------------------------------------------------------------------------------- 1 | LESSONS LEARNED 2 | --- 3 | 4 | - command for making (spider|trace)monkey: 5 | 6 | export BUILD_OPT=1 && export JS_DIST=.. && make -f Makefile.ref all export 7 | 8 | - command for cleaning (spider|trace)monkey: 9 | 10 | export BUILD_OPT=1 && make -f Makefile.ref clean clobber 11 | 12 | (but you still have to manually delete the editline build directory!) 13 | 14 | - to get embedded tracemonkey to work, I had to manually 15 | copy jsutil.h from the src/ to include/ directory 16 | 17 | - (DOM) where should the actual _value reside? In the element, 18 | or in the attr? 19 | - A: in the attr. Because attr can be created outside 20 | without any ownerElement, but must store a _value. 21 | (FF stores a value in both places and keeps them syncronized) 22 | My way means that when an attribute node is replaced 23 | (or even its value is set using the attr API), the 24 | _value is regenerated, meaning that held references 25 | to the old one are now bad. I can live with that. 26 | - (DOM) where should the parsing logic reside? 27 | - A: in the element. Attr can be created independent 28 | of an ownerElement. If/when attached to an element, 29 | the element can then parse the _value and convert 30 | it to a more specific datatype. 31 | When attr have ownerElements, it should use the element's 32 | parser for setting the _value. 33 | - (DOM) what about attributes 34 | - with no accessors? 35 | - handled (I don't know of any that LK uses) 36 | - with accessors? 37 | - handled 38 | - with read-only accessors? 39 | - handled 40 | - that are #IMPLIED? 41 | - use the defaultValue in the attributeSpec 42 | - that are of string type? 43 | - no parser needed 44 | - that are of float type? 45 | - parser in attributeSpec is parseFloat() 46 | - that are of int type? 47 | - parser is parseInt() 48 | - that are of complex type? (e.g., SVGAnimatedLength) 49 | - parser is SVGAnimatedLength.fromString 50 | - (DOM) What about tagName for (HTML) elements? Should they be a 51 | prototype method for known elements? That way we don't have 52 | _tagName defined individually for each element? 53 | Only define it (when elements are created) if the element doesn't 54 | already have it defined (via the prototype) 55 | No, we can't do this. This is becase the tagName may contain 56 | a prefix, of which the corresponding Element prototype is 57 | unaware. 58 | - we must keep local references for parentNode and ownerElement. 59 | This is because nodes that belong to a document aren't necessarily 60 | reachable from the documentElement. 61 | - what about valueAsString? Does that mean that we aren't 62 | expected to take a string as a setter value for value()? 63 | - A: No, it's that valueAsString requires that we parse the 64 | unit out of the string 65 | - why is translate(0) for textselectionmorph showing up 66 | as an unknown transform? 67 | - A: I don't think that it is unknown, I think that they just called 68 | consolidate and we turned a single translation into a matrix transform. 69 | I just have to make consolidate not do anything if there is only one 70 | transform. 71 | - changing a DOM element's gezira fragment will mess up the parent's gezira 72 | fragment's reference to the child's fragment 73 | - A: I won't have to update the parent's child/children references as long 74 | as the child doesn't change his top (or "geziraBegin") gezira object. 75 | - we can't use the built-in JS inheritance very much at all because we 76 | need multiple inheritance to support the DOM specs. This is why we use 77 | the "fake" copy inheritance of mico.extend 78 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Mico is an experimental Javascript programming environment. It provides 2 | structure and functionality on top the Javascript engine of your choice. 3 | To this aim, Mico strives to use Javascript code instead of native, 4 | engine-specific code wherever possible. 5 | 6 | Mico currently provides a fairly complete DOM Level 2 Core implementation 7 | written in pure Javascript, with very limited Level 2 HTML and SVG support. 8 | It also provides incomplete Javascript bindings to SDL and a pure Javascript 9 | bare-bones implementation of the web browser "window" object. Rendering of 10 | DOM nodes happens via an AOP-inspired bridge between DOM elements and the 11 | underlying graphics library of your choice. 12 | 13 | Mico introduces a "system" object for interfacing with the underlying 14 | Javascript engine. This object allows for loading script files 15 | (via system.load()) and compiled bindings to C libraries 16 | (via system.dlload()). 17 | 18 | Scripts in Mico can declare dependencies on other "packages." At runtime, 19 | these dependencies are resolved automatically through a simple package 20 | location mechanism. 21 | 22 | As of this writing, Mico has been used for very little, mostly as a testbed for 23 | novel web browser implementations. 24 | 25 | Initial development of Mico was funded by Viewpoints Research Institute, 26 | NSF Grant No. 0639876 and Sun Microsystems. 27 | 28 | - Dan Amelang (dan@amelang.net) 29 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO 2 | --- 3 | 4 | - fix glyph server hack 5 | - fix window title hack 6 | - get cloning to work 7 | - positioning is wrong 8 | - get rotation to work (alt click) 9 | - rotates wildly, some calculation is wrong 10 | 11 | - get to run again on the n800 12 | - SVG ellipse is not elliptic enough at large sizes. The 13 | radius should be factored into the bezier calculation 14 | (which is trivial to so) 15 | - change gezira real streams contructors to take varargs instead of an array 16 | - create getters for gezira streams attributes (e.g., rotation.angle) 17 | - make shift key work (for events), and change the shift state from 18 | a parameter to a function call on the sdl object 19 | - Fix getTransformToElement 20 | - need to rewrite setAttributeNS to reuse the attr node, not 21 | create a whole new one each time 22 | - I should look at the "fill-opacity" attribute to grab 23 | alpha of fill color. 24 | - fix "copy" inheritance problem (see dom/events.js) by using 25 | true JS inheritance just for the Node clone family? This might also 26 | fix the fragile ordering of loaded files/packages 27 | - make all gezira parents (groups, too?) point to a default 28 | gezira (blank) object upon creation? This "null" node could be 29 | used in several places (instead of just a gezira.Node with no children) 30 | - ideally I'd have a object mapping to and from dom and gezira objects. 31 | This would get rid of the domObject._graphics stuff, and allow for the 32 | hit testing to work. Expose a hash table object? Or a special purpose 33 | function? 34 | - Fix memory leaks of gezira objects. Must install a finalizer gezira 35 | JS objects. 36 | - expose a window.height window.width getters (and setters?) 37 | - an element may be added to the dom render queue multiple times because 38 | multiple attributes of that element are changed in a given phase. Should 39 | we do a uniq() on that queue then? How much of a win does this get 40 | me? Can either == or === get me a fast object id-like comparison? 41 | - write glue code for other JS engines, window/events libraries and 42 | graphics renderers 43 | - v8 44 | - nitro 45 | - rhino 46 | - plug in an (X)HTML parser (see OMeta/JS) 47 | - To make our (X)HTML output the same as FF, we need to: 48 | - support doctype 49 | - the script tags, plus the "1" child text node in each 50 | - don't emit for elements that don't (can't?) have 51 | children (e.g., ) 52 | - don't emit implied args for transforms (e.g., translate(0 0)) 53 | - don't output default implied values (or keep track if they have been 54 | changed (specified)) 55 | - no newlines for text nodes 56 | - spaces/commas for lists 57 | - same order of the attributes 58 | - network functionality. Bindings to libcurl (or something like it) 59 | - CSS 60 | -------------------------------------------------------------------------------- /config-gcc.mk: -------------------------------------------------------------------------------- 1 | ENGINE = spidermonkey 2 | PACKAGES_TO_COMPILE = sdl gezira gezira_glyphserver 3 | COMPILED_PACKAGE_EXT = .jsm 4 | GEZIRA_REAL_TYPE = GEZIRA_REAL_FLOAT 5 | 6 | CFLAGS = \ 7 | -g -Wall -DXP_UNIX \ 8 | -D$(GEZIRA_REAL_TYPE) \ 9 | -DCOMPILED_PACKAGE_EXT=\"$(COMPILED_PACKAGE_EXT)\" \ 10 | -I../../tracemonkey/js/include \ 11 | -L../../tracemonkey/js/lib \ 12 | -I../../gezira \ 13 | -L../../gezira \ 14 | -I/usr/include/freetype2 \ 15 | -I../../gezira_glyphserver \ 16 | -L../../gezira_glyphserver 17 | -------------------------------------------------------------------------------- /dom/core.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | var dom = {}; 7 | 8 | // NodeList 9 | 10 | dom.NodeList = function() { this._nodes = []; }; 11 | 12 | mico.extend(dom.NodeList.prototype, { 13 | item: function(index) { return this._nodes[index]; }, 14 | get length() { return this._nodes.length; } 15 | }); 16 | 17 | // NamedNodeMap 18 | 19 | dom.NamedNodeMap = function() { this._nodes = []; } 20 | 21 | mico.extend(dom.NamedNodeMap.prototype, { 22 | getNamedItem: function(name) { return this.getNamedItemNS(null, name); }, 23 | setNamedItem: function(arg) { return this.setNamedItemNS(null, arg); }, 24 | removeNamedItem: function(name) { return this.removeNamedItemNS(null, name); }, 25 | item: function(index) { return this._nodes[index]; }, 26 | get length() { return this._nodes.length; }, 27 | 28 | getNamedItemNS: function(namespaceURI, localName) { 29 | for (var i = 0; i < this._nodes.length; i++) { 30 | if (this._nodes[i].localName == localName && 31 | this._nodes[i].namespaceURI == namespaceURI) 32 | return this._nodes[i]; 33 | } 34 | return null; 35 | }, 36 | 37 | setNamedItemNS: function(arg) { 38 | var node = this.getNamedItemNS(arg.namespaceURI, arg.localName); 39 | if (node) 40 | this._nodes[this._nodes.indexOf(node)] = arg; 41 | else 42 | this._nodes.push(arg); 43 | return node; 44 | }, 45 | 46 | removeNamedItemNS: function(namespaceURI, localName) { 47 | var node = this.getNamedItemNS(namespaceURI, localName); 48 | if (node) 49 | this._nodes.splice(this._nodes.indexOf(node), 1); 50 | return node; 51 | } 52 | }); 53 | 54 | // Node 55 | 56 | dom.Node = function() {}; 57 | 58 | mico.extend(dom.Node, { 59 | ELEMENT_NODE: 1, 60 | ATTRIBUTE_NODE: 2, 61 | TEXT_NODE: 3, 62 | CDATA_SECTION_NODE: 4, 63 | ENTITY_REFERENCE_NODE: 5, 64 | ENTITY_NODE: 6, 65 | PROCESSING_INSTRUCTION_NODE: 7, 66 | COMMENT_NODE: 8, 67 | DOCUMENT_NODE: 9, 68 | DOCUMENT_TYPE_NODE: 10, 69 | DOCUMENT_FRAGMENT_NODE: 11, 70 | NOTATION_NODE: 12, 71 | 72 | find: function(node, predicate) { 73 | if (predicate(node)) 74 | return node; 75 | for (var i = 0; i < node.childNodes.length; i++) { 76 | result = this.find(node.childNodes.item(i), predicate); 77 | if (result) 78 | return result; 79 | } 80 | return null; 81 | }, 82 | 83 | findAncestor: function(node, predicate) { 84 | if (!node.parentNode) 85 | return null; 86 | else if (predicate(node.parentNode)) 87 | return node.parentNode; 88 | else 89 | return this.findAncestor(node.parentNode, predicate); 90 | }, 91 | 92 | forEach: function(node, action) { 93 | action(node); 94 | for (var i = 0; i < node.childNodes.length; i++) 95 | this.forEach(node.childNodes.item(i), action); 96 | } 97 | }); 98 | 99 | mico.extend(dom.Node.prototype, { 100 | get nodeName() { return null; }, 101 | get nodeValue() { return null; }, 102 | set nodeValue(value) { }, 103 | get nodeType() { return 0; }, 104 | get parentNode() { return this._parentNode; }, 105 | get childNodes() { return this._childNodes ? this._childNodes : 106 | new dom.NodeList; }, 107 | get firstChild() { return this.childNodes.item(0); }, 108 | get lastChild() { return this.childNodes.item(this.childNodes.length - 1); }, 109 | 110 | get previousSibling() { 111 | if (!this.parentNode) 112 | return null; 113 | var siblings = this.parentNode.childNodes; 114 | return siblings.item(siblings._nodes.indexOf(this) - 1); 115 | }, 116 | 117 | get nextSibling() { 118 | if (!this.parentNode) 119 | return null; 120 | var siblings = this.parentNode.childNodes; 121 | return siblings.item(siblings._nodes.indexOf(this) + 1); 122 | }, 123 | 124 | get attributes() { return this._attributes; }, 125 | get ownerDocument() { return this._ownerDocument; }, 126 | 127 | insertBefore: function(newChild, refChild) { 128 | if (!this._childNodes) 129 | return; 130 | this.removeChild(newChild); 131 | newChild._parentNode = this; 132 | if (refChild) { 133 | var index = this.childNodes._nodes.indexOf(refChild); 134 | this.childNodes._nodes.splice(index, 0, newChild); 135 | } 136 | else 137 | this.childNodes._nodes.push(newChild); 138 | return newChild; 139 | }, 140 | 141 | replaceChild: function(newChild, oldChild) { 142 | this.insertBefore(newChild, oldChild); 143 | return this.removeChild(oldChild); 144 | }, 145 | 146 | removeChild: function(oldChild) { 147 | if (!this._childNodes) 148 | return; 149 | var index = this.childNodes._nodes.indexOf(oldChild); 150 | if (index != -1) { 151 | oldChild._parentNode = null; 152 | this.childNodes._nodes.splice(index, 1); 153 | } 154 | return oldChild; 155 | }, 156 | 157 | appendChild: function(newChild) { 158 | if (!this._childNodes) 159 | return; 160 | this.removeChild(newChild); 161 | newChild._parentNode = this; 162 | this.childNodes._nodes.push(newChild); 163 | return newChild; 164 | }, 165 | 166 | hasChildNodes: function() { return this.childNodes.length > 0; }, 167 | 168 | deepClone: function() { 169 | var clone = mico.deepClone(this, 170 | '_ownerDocument', '_parentNode', '_ownerElement'); 171 | if (this._ownerDocument) 172 | clone._ownerDocument = this._ownerDocument; 173 | if (this.childNodes) 174 | for (var i = 0; i < this.childNodes.length; i++) 175 | clone.childNodes.item(i)._parentNode = clone; 176 | if (this.attributes) 177 | for (var i = 0; i < this.attributes.length; i++) 178 | clone.attributes.item(i)._ownerElement = clone; 179 | return clone; 180 | }, 181 | 182 | cloneNode: function(deep) { 183 | var clone; 184 | if (deep) 185 | clone = this.deepClone(); 186 | else { 187 | clone = mico.extend({}, this); 188 | clone._parentNode = null; 189 | if (this._childNodes) 190 | clone._childNodes = new dom.NodeList; 191 | if (this._ownerElement) 192 | clone._ownerElement = null; 193 | // deep clone the attributes 194 | if (this._attributes) { 195 | clone._attributes = mico.deepClone(this._attributes); 196 | for (var i = 0; i < this._attributes.length; i++) 197 | clone._attributes.item(i)._ownerElement = clone; 198 | } 199 | } 200 | return clone; 201 | }, 202 | 203 | normalize: function() { mico.TODO(); }, 204 | isSupported: function(feature, version) { mico.TODO(); }, 205 | get namespaceURI() { return this._namespaceURI; }, 206 | get prefix() { return this._prefix; }, 207 | // TODO according to the spec, this is supposed to have strange side effects 208 | set prefix(value) { this._prefix = value; }, 209 | get localName() { return this._localName; }, 210 | 211 | hasAttributes: function() { 212 | return this.attributes && this.attributes.length > 0; 213 | } 214 | }); 215 | 216 | // Attr 217 | 218 | dom.Attr = function() { dom.Node.call(this); }; 219 | 220 | mico.extend(dom.Attr.prototype, dom.Node.prototype); 221 | mico.extend(dom.Attr.prototype, { 222 | get nodeName() { return this.name; }, 223 | get nodeValue() { return this.value; }, 224 | set nodeValue(value) { this.value = value; }, 225 | get nodeType() { return dom.Node.ATTRIBUTE_NODE; }, 226 | get parentNode() { return null; }, 227 | 228 | get childNodes() { 229 | var nodes = new dom.NodeList; 230 | if (this.value != '') 231 | nodes._nodes = [this.ownerDocument.createTextNode(this.value)]; 232 | return nodes; 233 | }, 234 | 235 | get name() { return this._name; }, 236 | get specified() { return true; }, 237 | get value() { return String(this._value); }, 238 | 239 | set value(value) { 240 | this._value = String(value == null ? '' : value); 241 | var owner = this.ownerElement; 242 | var specs = owner && owner.constructor.attributeSpecs; 243 | var spec = specs && specs[this.name]; 244 | if (this._value == '' && spec && spec.defaultValue) 245 | this._value = spec.defaultValue; 246 | if (spec && spec.parser) 247 | this._value = spec.parser(this._value); 248 | }, 249 | 250 | get ownerElement() { return this._ownerElement; }, 251 | 252 | toString: function() { return this.name + '="' + this.value + '"'; } 253 | }); 254 | 255 | // Element 256 | 257 | dom.Element = function() { 258 | dom.Node.call(this); 259 | this._childNodes = new dom.NodeList; 260 | this._attributes = new dom.NamedNodeMap; 261 | }; 262 | 263 | mico.extend(dom.Element, { 264 | factories: {}, 265 | 266 | // attributeSpec can have name, parser, type(.fromString), defaultValue and xmlName 267 | defineAttribute: function(element, attributeSpec) { 268 | attributeSpec = attributeSpec.name ? attributeSpec : {name:attributeSpec}; 269 | var xmlName = attributeSpec.xmlName || attributeSpec.name; 270 | attributeSpec.parser = attributeSpec.parser || 271 | (attributeSpec.type && attributeSpec.type.fromString) || attributeSpec.type; 272 | element.attributeSpecs = element.attributeSpecs || {}; 273 | element.attributeSpecs[xmlName] = attributeSpec; 274 | 275 | // TODO we should not assume that all attributes have a getter 276 | element.prototype.__defineGetter__(attributeSpec.name, function() { 277 | var node = this.getAttributeNode(xmlName); 278 | if (!node) { 279 | node = this.ownerDocument.createAttribute(xmlName); 280 | this.setAttributeNode(node); 281 | } 282 | return node._value; 283 | }); 284 | 285 | if (!attributeSpec.readonly) { 286 | element.prototype.__defineSetter__(attributeSpec.name, function(value) { 287 | var node = this.getAttributeNode(xmlName); 288 | if (!node) { 289 | node = this.ownerDocument.createAttribute(xmlName); 290 | this.setAttributeNode(node); 291 | } 292 | node.value = value; 293 | }); 294 | } 295 | }, 296 | 297 | defineAttributes: function(element) { 298 | for (var i = 1; i < arguments.length; i++) 299 | this.defineAttribute(element, arguments[i]); 300 | } 301 | }); 302 | 303 | mico.extend(dom.Element.prototype, dom.Node.prototype); 304 | mico.extend(dom.Element.prototype, { 305 | get nodeName() { return this.tagName; }, 306 | get nodeType() { return dom.Node.ELEMENT_NODE; }, 307 | get tagName() { return this._tagName; }, 308 | 309 | getAttribute: function(name) { 310 | return this.getAttributeNS(null, name); 311 | }, 312 | 313 | setAttribute: function(name, value) { 314 | return this.setAttributeNS(null, name, value); 315 | }, 316 | 317 | removeAttribute: function(name) { 318 | return this.removeAttributeNS(null, name); 319 | }, 320 | 321 | getAttributeNode: function(name) { 322 | return this.getAttributeNodeNS(null, name); 323 | }, 324 | 325 | setAttributeNode: function(newAttr) { 326 | return this.setAttributeNodeNS(newAttr); 327 | }, 328 | 329 | removeAttributeNode: function(oldAttr) { 330 | this.attributes.removeNamedItemNS(oldAttr.namespaceURI, oldAttr.localName); 331 | oldAttr._ownerElement = null; 332 | return oldAttr; 333 | }, 334 | 335 | getElementsByTagName: function(name) { 336 | return this.getElementsByTagNameNS('*', name); 337 | }, 338 | 339 | getAttributeNS: function(namespaceURI, localName) { 340 | var node = this.getAttributeNodeNS(namespaceURI, localName); 341 | return node ? node.value : null; 342 | }, 343 | 344 | setAttributeNS: function(namespaceURI, qualifiedName, value) { 345 | // TODO need to reuse the existing attribute node if there already is 346 | // one with this namespaceURI and local name 347 | var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); 348 | this.setAttributeNodeNS(attr); 349 | attr.value = value; 350 | }, 351 | 352 | removeAttributeNS: function(namespaceURI, localName) { 353 | var oldAttr = this.attributes.removeNamedItemNS(namespaceURI, localName); 354 | oldAttr && (oldAttr._ownerElement = null); 355 | }, 356 | 357 | getAttributeNodeNS: function(namespaceURI, localName) { 358 | return this.attributes.getNamedItemNS(namespaceURI, localName); 359 | }, 360 | 361 | setAttributeNodeNS: function(newAttr) { 362 | var oldAttr = this.attributes.setNamedItemNS(newAttr); 363 | oldAttr && (oldAttr._ownerElement = null); 364 | newAttr._ownerElement = this; 365 | newAttr.value = newAttr.value; // Force the value to be parsed 366 | return oldAttr; 367 | }, 368 | 369 | getElementsByTagNameNS: function(namespaceURI, localName) { 370 | var elements = new dom.NodeList; 371 | dom.Node.forEach(this, function(node) { 372 | if (node.nodeType == dom.Node.ELEMENT_NODE && 373 | (localName == '*' || node.localName == localName) && 374 | (namespaceURI == '*' || node.namespaceURI == namespaceURI)) 375 | elements._nodes.push(node); 376 | }); 377 | // TODO this list is supposed to be 'live'. We could use something like 378 | // a version number on the document that increments whenever the structure 379 | // changes (insertBefore called on a node) which has the effect of 380 | // invalidating the contents of the list (forcing a requery when 381 | // item or length is called on the list). 382 | return elements; 383 | }, 384 | 385 | hasAttribute: function(name) { 386 | return this.hasAttributeNS(null, name); 387 | }, 388 | 389 | hasAttributeNS: function(namespaceURI, localName) { 390 | return this.getAttributeNodeNS(namespaceURI, localName) != null; 391 | }, 392 | 393 | // FIXME some elements don't have closing tags 394 | toString: function(deep) { 395 | var result = '<' + this.nodeName; 396 | for (var i = 0; i < this.attributes.length; i++) 397 | result += ' ' + this.attributes.item(i).toString(); 398 | result += '>\n'; 399 | for (var i = 0; deep && i < this.childNodes.length; i++) 400 | result += this.childNodes.item(i).toString(deep) + '\n'; 401 | result += ''; 402 | return result; 403 | } 404 | }); 405 | 406 | // CharacterData 407 | 408 | dom.CharacterData = function() { dom.Node.call(this); }; 409 | 410 | mico.extend(dom.CharacterData.prototype, dom.Node.prototype); 411 | mico.extend(dom.CharacterData.prototype, { 412 | get data() { return this._data; }, 413 | set data(value) { this._data = value; }, 414 | get length() { return this.data ? this.data.length : 0; }, 415 | 416 | substringData: function(offset, count) { 417 | return this.data.substr(offset, count); 418 | }, 419 | 420 | appendData: function(arg) { 421 | this.data = this.data.concat(arg); 422 | }, 423 | 424 | insertData: function(offset, arg) { 425 | this.data = this.data.substr(0, offset).concat(arg). 426 | concat(this.data.substr(offset)); 427 | }, 428 | 429 | deleteData: function(offset, count) { 430 | this.data = this.data.substr(0, offset). 431 | concat(this.data.substr(offset + count)); 432 | }, 433 | 434 | replaceData: function(offset, count, arg) { 435 | this.data = this.data.substr(0, offset).concat(arg). 436 | concat(this.data.substr(offset + count)); 437 | } 438 | }); 439 | 440 | // Text 441 | 442 | dom.Text = function() { dom.CharacterData.call(this); }; 443 | 444 | mico.extend(dom.Text.prototype, dom.CharacterData.prototype); 445 | mico.extend(dom.Text.prototype, { 446 | get nodeName() { return '#text'; }, 447 | get nodeValue() { return this.data; }, 448 | set nodeValue(value) { this.data = value; }, 449 | get nodeType() { return dom.Node.TEXT_NODE; }, 450 | 451 | splitText: function(offset) { 452 | var text = new dom.Text; 453 | text.data = this.data.substr(offset); 454 | this.data = this.data.substr(0, offset); 455 | if (this.parentNode) { 456 | var sibling = this.nextSibling; 457 | if (sibling) 458 | this.parentNode.insertBefore(text, sibling); 459 | else 460 | this.parentNode.appendChild(text); 461 | } 462 | return text; 463 | }, 464 | 465 | toString: function() { return this.data; } 466 | }); 467 | 468 | // CDATASection 469 | 470 | dom.CDATASection = function() { dom.Text.call(this); }; 471 | 472 | mico.extend(dom.CDATASection.prototype, dom.Text.prototype); 473 | dom.CDATASection.prototype.toString = function() { 474 | return ''; 475 | }; 476 | 477 | // Document 478 | 479 | dom.Document = function() { 480 | dom.Node.call(this); 481 | this._childNodes = new dom.NodeList; 482 | }; 483 | 484 | mico.extend(dom.Document.prototype, dom.Node.prototype); 485 | mico.extend(dom.Document.prototype, { 486 | get nodeName() { return '#document;'; }, 487 | get nodeType() { return dom.Node.DOCUMENT_NODE; }, 488 | get doctype() { mico.TODO(); }, 489 | get implementation() { mico.TODO(); }, 490 | 491 | get documentElement() { 492 | return dom.Node.find(this, function(e) { 493 | return e.nodeType == dom.Node.ELEMENT_NODE; 494 | }); 495 | }, 496 | 497 | createElement: function(tagName) { 498 | return this.createElementNS(null, tagName); 499 | }, 500 | 501 | createDocumentFragment: function() { mico.TODO(); }, 502 | 503 | createTextNode: function(data) { 504 | var text = new dom.Text; 505 | text._ownerDocument = this; 506 | text.data = data; 507 | return text; 508 | }, 509 | 510 | createComment: function(data) { mico.TODO(); }, 511 | 512 | createCDATASection: function(data) { 513 | var cdata = new dom.CDATASection; 514 | cdata._ownerDocument = this; 515 | cdata.data = data; 516 | return cdata; 517 | }, 518 | 519 | createProcessingInstruction: function(target, data) { mico.TODO(); }, 520 | 521 | createAttribute: function(name) { 522 | return this.createAttributeNS(null, name); 523 | }, 524 | 525 | createEntityReference: function(name) { mico.TODO(); }, 526 | 527 | getElementsByTagName: function(tagName) { 528 | return this.getElementsByTagNameNS('*', tagName); 529 | }, 530 | 531 | importNode: function(importedNode, deep) { mico.TODO(); }, 532 | 533 | createElementNS: function(namespaceURI, qualifiedName) { 534 | var match = qualifiedName.match(/(\w*):(\w*)/); 535 | var localName = (match && match[2]) || qualifiedName; 536 | var factory = dom.Element.factories[namespaceURI]; 537 | var element = new ((factory && factory[localName]) || dom.Element); 538 | element._ownerDocument = this; 539 | element._tagName = qualifiedName; 540 | element._namespaceURI = namespaceURI; 541 | element._prefix = match && match[1]; 542 | element._localName = localName; 543 | return element; 544 | }, 545 | 546 | createAttributeNS: function(namespaceURI, qualifiedName) { 547 | var attr = new dom.Attr; 548 | var match = qualifiedName.match(/(\w*):(\w*)/); 549 | attr._ownerDocument = this; 550 | attr._name = qualifiedName; 551 | attr._namespaceURI = namespaceURI; 552 | attr._prefix = match && match[1]; 553 | attr._localName = (match && match[2]) || qualifiedName; 554 | attr.value = ''; 555 | return attr; 556 | }, 557 | 558 | getElementsByTagNameNS: function(namespaceURI, localName) { 559 | return this.documentElement.getElementsByTagNameNS(namespaceURI, localName); 560 | }, 561 | 562 | getElementById: function(elementId) { return null; } 563 | }); 564 | -------------------------------------------------------------------------------- /dom/events.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | mico.require('dom.core'); 7 | 8 | // FIXME this is a temporary hack, we need to properly implement 9 | // the event prototypes soon. 10 | dom.MouseEvent = function() {}; 11 | mico.extend(dom.MouseEvent.prototype, { 12 | get currentTarget() { return this._currentTarget; }, 13 | get shiftKey() { return this._shiftKey; }, 14 | get altKey() { return this._altKey; }, 15 | get type() { return this._type; }, 16 | get clientX() { return this._clientX; }, 17 | get clientY() { return this._clientY; }, 18 | stopPropagation: function() { this._propagationStopped = true; } 19 | }); 20 | 21 | dom.EventTarget = { 22 | // TODO go over the spec again 23 | addEventListener: function(type, listener, useCapture) { 24 | this._eventListeners = this._eventListeners || {}; 25 | this._eventListeners[type] = this._eventListeners[type] || []; 26 | this._eventListeners[type].push(listener); 27 | }, 28 | 29 | // TODO go over the spec again 30 | removeEventListener: function(type, listener, useCapture) { 31 | /* 32 | this._eventListeners = this._eventListeners || {}; 33 | if (this._eventListeners[type] && this._eventListeners[type].indexOf(listener) 34 | */ 35 | mico.TODO(); 36 | }, 37 | 38 | dispatchEvent: function(evt) { 39 | var listeners = this._eventListeners && this._eventListeners[evt.type]; 40 | evt._currentTarget = this; 41 | 42 | if (listeners) 43 | listeners.forEach(function(l) { l.handleEvent(evt); }); 44 | 45 | this.childNodes._nodes.forEach(function(c) { 46 | if (!evt._propagationStopped) 47 | c.dispatchEvent(evt); 48 | }); 49 | }, 50 | }; 51 | 52 | /* TODO we really should only have to put this in Node, but because we use 53 | * "copy" inheritance, we have to mix EventTarget into all the children in 54 | * the clone family. 55 | */ 56 | mico.extend(dom.Node.prototype, dom.EventTarget); 57 | mico.extend(dom.Attr.prototype, dom.EventTarget); 58 | mico.extend(dom.Element.prototype, dom.EventTarget); 59 | mico.extend(dom.CharacterData.prototype, dom.EventTarget); 60 | mico.extend(dom.Text.prototype, dom.EventTarget); 61 | mico.extend(dom.CDATASection.prototype, dom.EventTarget); 62 | mico.extend(dom.Document.prototype, dom.EventTarget); 63 | 64 | /* TODO the below needs to be done to all in the Node clone family, 65 | * as we do with mixing in EventTarget above. Perhaps we should use 66 | * "true" JS inheritance for the Node clone family, and copy 67 | * inheritance for the rest (see HTML and SVG elements) ? 68 | */ 69 | mico.around(dom.Element.prototype, 70 | ['deepClone', 'cloneNode'], 71 | function(proceed, args) { 72 | var listeners = this._eventListeners; 73 | delete this._eventListeners; 74 | var clone = proceed.apply(this, args); 75 | listeners && (this._eventListeners = listeners); 76 | return clone; 77 | }); 78 | -------------------------------------------------------------------------------- /dom/graphics.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | mico.require('dom.core'); 7 | 8 | dom.graphics = { 9 | queue: [], 10 | renderers: {}, 11 | 12 | render: function(element) { 13 | var renderer = this.renderers[element.constructor.tagName]; 14 | renderer = renderer || this.renderers[null]; 15 | return renderer && renderer(element); 16 | }, 17 | 18 | update: function() { 19 | // TODO get rid of multiple occurances of the same element in queue? 20 | while (this.queue.length) 21 | this.render(this.queue.pop()); 22 | } 23 | }; 24 | 25 | mico.around(dom.Attr.prototype, 'value=', 26 | function(proceed, args) { 27 | proceed.apply(this, args); 28 | if (this.ownerElement && this.ownerElement._graphics) 29 | dom.graphics.queue.push(this.ownerElement); 30 | }); 31 | 32 | // FIXME I'm uneasy about depending on DOM implementation-specific details here 33 | mico.around(dom.Element.prototype, 34 | ['removeChild', 'removeAttributeNode', 'removeAttributeNS'], 35 | function(proceed, args) { 36 | if (this._graphics) 37 | dom.graphics.queue.push(this); 38 | return proceed.apply(this, args); 39 | }); 40 | 41 | mico.around(dom.Element.prototype, ['deepClone', 'cloneNode'], 42 | function(proceed, args) { 43 | var _graphics = this._graphics; 44 | delete this._graphics; 45 | var clone = proceed.apply(this, args); 46 | _graphics && (this._graphics = _graphics); 47 | return clone; 48 | }); 49 | -------------------------------------------------------------------------------- /dom/html.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | mico.require('dom.core'); 7 | 8 | // TODO deal with case-sensitivity for HTML tagnames and such 9 | 10 | // HTMLDocument 11 | 12 | dom.HTMLDocument = function () { dom.Document.call(this); }; 13 | mico.extend(dom.HTMLDocument.prototype, dom.Document.prototype); 14 | 15 | mico.extend(dom.HTMLDocument.prototype, { 16 | get title() { return this._title; }, 17 | set title(value) { this._title = value; }, 18 | get referrer() { return this._referrer; }, 19 | get domain() { return this._domain; }, 20 | get URL() { return this._URL; }, 21 | get body() { mico.TODO(); }, 22 | set body(value) { mico.TODO(); }, 23 | get images() { mico.TODO(); }, 24 | get applets() { mico.TODO(); }, 25 | get links() { mico.TODO(); }, 26 | get forms() { mico.TODO(); }, 27 | get anchors() { mico.TODO(); }, 28 | get cookie() { return this._cookie; }, 29 | set cookie(value) { this._cookie = value; }, 30 | open: function() { mico.TODO(); }, 31 | close: function() { mico.TODO(); }, 32 | write: function(text) { mico.TODO(); }, 33 | writeln: function(text) { mico.TODO(); }, 34 | getElementsByName: function(elementName) { mico.TODO(); }, 35 | 36 | getElementById: function(id) { 37 | return dom.Node.find(this.documentElement, function(node) { 38 | // TODO case-sensitivity? 39 | return (node.nodeType == dom.Node.ELEMENT_NODE && 40 | node.getAttribute('id') == id); 41 | }); 42 | } 43 | }); 44 | 45 | // HTMLElement 46 | 47 | dom.HTMLElement = function() { 48 | dom.Element.call(this); 49 | // TODO we should actually implement the CSS object, and this should be 50 | // an attribute node, see interface ElementCSSInlineStyle, which HTMLElements 51 | // should mix in. 52 | this.style = {}; 53 | }; 54 | mico.extend(dom.HTMLElement.prototype, dom.Element.prototype); 55 | dom.Element.factories['http://www.w3.org/1999/xhtml'] = 56 | dom.HTMLElement.factory = {}; 57 | 58 | dom.HTMLElement.defineElement = function(name) { 59 | var element = function() { dom.HTMLElement.call(this); }; 60 | mico.extend(element.prototype, dom.HTMLElement.prototype); 61 | element.attributeSpecs = {}; 62 | mico.extend(element.attributeSpecs, dom.HTMLElement.attributeSpecs); 63 | dom.Element.defineAttributes.apply(dom.Element, 64 | [element].concat(Array.prototype.slice.call(arguments, 1))); 65 | this.factory[name] = element; 66 | element.tagName = 'http://www.w3.org/1999/xhtml:' + name; 67 | return element; 68 | }; 69 | 70 | dom.Element.defineAttributes(dom.HTMLElement, 'id', 'title', 'lang', 'dir', 71 | {name:'className', xmlName:'class'}); 72 | 73 | // TODO should define tagName for each (using defineElement) 74 | ['sub', 'sup', 'span', 'bdo', 'tt', 'i', 'b', 'u', 's', 'strike', 'big', 75 | 'small', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 76 | 'acronym', 'abbr', 'dd', 'dt', 'noframes', 'noscript', 'address', 'center']. 77 | forEach(function(name) { dom.HTMLElement.factory[name] = dom.HTMLElement; }); 78 | 79 | dom.HTMLHtmlElement = dom.HTMLElement.defineElement('html', 'version'); 80 | dom.HTMLHeadElement = dom.HTMLElement.defineElement('head', 'profile'); 81 | dom.HTMLBodyElement = dom.HTMLElement.defineElement('body', 82 | 'aLink', 'background', 'bgColor', 'link', 'text', 'vLink'); 83 | dom.HTMLDivElement = dom.HTMLElement.defineElement('div', 'align'); 84 | -------------------------------------------------------------------------------- /dom/svg.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | mico.require('dom.core'); 7 | 8 | // TODO this should go in a separate file dedicated to CSS 9 | dom.CSSStyleDeclaration = function() {}; 10 | dom.CSSStyleDeclaration.fromString = function(s) { mico.TODO(); }; 11 | 12 | // TODO missing interfaces (among many others) 13 | // SVGUnitTypes 14 | // SVGURIReference 15 | // css::ViewCSS 16 | // css::DocumentCSS 17 | // events::DocumentEvent 18 | // SVGZoomAndPan 19 | // SVGFitToViewBox 20 | // SVGTests 21 | // SVGLangSpace 22 | // SVGExternalResourcesRequired 23 | 24 | // SVGNumber 25 | 26 | dom.SVGNumber = function() {}; 27 | 28 | dom.SVGNumber.fromString = function(s) { 29 | var object = new dom.SVGNumber; 30 | object.value = s; 31 | return object; 32 | }; 33 | 34 | mico.extend(dom.SVGNumber.prototype, { 35 | get value() { return this._value; }, 36 | set value(value) { this._value = parseFloat(String(value)) || 0; }, 37 | toString: function() { return String(this.value); } 38 | }); 39 | 40 | // SVGLength 41 | 42 | dom.SVGLength = function() {}; 43 | 44 | mico.extend(dom.SVGLength, { 45 | SVG_LENGTHTYPE_UNKNOWN: 0, 46 | SVG_LENGTHTYPE_NUMBER: 1, 47 | SVG_LENGTHTYPE_PERCENTAGE: 2, 48 | SVG_LENGTHTYPE_EMS: 3, 49 | SVG_LENGTHTYPE_EXS: 4, 50 | SVG_LENGTHTYPE_PX: 5, 51 | SVG_LENGTHTYPE_CM: 6, 52 | SVG_LENGTHTYPE_MM: 7, 53 | SVG_LENGTHTYPE_IN: 8, 54 | SVG_LENGTHTYPE_PT: 9, 55 | SVG_LENGTHTYPE_PC: 10, 56 | unitTypeToString: ['', '', '%', 'em', 'ex', 'px', 'cm', 'mm', 'in', 'pt', 'pc'] 57 | }); 58 | 59 | dom.SVGLength.fromString = function(s) { 60 | var object = new dom.SVGLength; 61 | object.valueAsString = s; 62 | return object; 63 | }; 64 | 65 | mico.extend(dom.SVGLength.prototype, { 66 | get unitType() { return this._unitType; }, 67 | get value() { return this._value; }, 68 | set value(value) { this._value = parseFloat(String(value)) || 0; }, 69 | get valueInSpecifiedUnits() { mico.TODO(); }, 70 | set valueInSpecifiedUnits(value) { mico.TODO(); }, 71 | get valueAsString() { 72 | var value = this._value; 73 | // TODO must denormalize other unit types, too 74 | if (this._unitType == dom.SVGLength.SVG_LENGTHTYPE_PERCENTAGE) 75 | value *= 100; 76 | return value + dom.SVGLength.unitTypeToString[this._unitType]; 77 | }, 78 | set valueAsString(value) { 79 | var match = String(value).match(/(\d*\.?\d*)(%|\w*)/); 80 | this.value = match && match[1]; 81 | this._unitType = dom.SVGLength.unitTypeToString. 82 | lastIndexOf(match && match[2] || ''); 83 | // TODO must normalize other unit types, too 84 | if (this._unitType == dom.SVGLength.SVG_LENGTHTYPE_PERCENTAGE) 85 | this._value /= 100; 86 | }, 87 | newValueSpecifiedUnits: 88 | function(unitType, valueInSpecifiedUnits) { mico.TODO(); }, 89 | convertToSpecifiedUnits: function(unitType) { mico.TODO(); }, 90 | toString: function() { return this.valueAsString; } 91 | }); 92 | 93 | // SVGPoint 94 | 95 | dom.SVGPoint = function() {}; 96 | 97 | dom.SVGPoint.fromString = function(s) { 98 | var point = new dom.SVGPoint; 99 | var coors = s.split(/(?:\s|,)+/); 100 | point.x = parseFloat(coors[0]) || 0; 101 | point.y = parseFloat(coors[1]) || 0; 102 | return point; 103 | }; 104 | 105 | mico.extend(dom.SVGPoint.prototype, { 106 | get x() { return this._x; }, 107 | set x(value) { this._x = value; }, 108 | get y() { return this._y; }, 109 | set y(value) { this._y = value; }, 110 | 111 | matrixTransform: function(matrix) { 112 | var point = new dom.SVGPoint; 113 | point.x = this.x * matrix.a + this.y * matrix.c + matrix.e; 114 | point.y = this.x * matrix.b + this.y * matrix.d + matrix.f; 115 | return point; 116 | }, 117 | 118 | toString: function() { 119 | return this.x + ',' + this.y; 120 | } 121 | }); 122 | 123 | // SVGMatrix 124 | 125 | dom.SVGMatrix = function() { 126 | this._a = 1.0; this._c = 0.0; this._e = 0.0; 127 | this._b = 0.0; this._d = 1.0; this._f = 0.0; 128 | }; 129 | 130 | mico.extend(dom.SVGMatrix.prototype, { 131 | get a() { return this._a; }, 132 | set a(value) { this._a = value; }, 133 | get b() { return this._b; }, 134 | set b(value) { this._b = value; }, 135 | get c() { return this._c; }, 136 | set c(value) { this._c = value; }, 137 | get d() { return this._d; }, 138 | set d(value) { this._d = value; }, 139 | get e() { return this._e; }, 140 | set e(value) { this._e = value; }, 141 | get f() { return this._f; }, 142 | set f(value) { this._f = value; }, 143 | 144 | multiply: function(b) { 145 | var a = this; 146 | var ab = new dom.SVGMatrix; 147 | ab.a = a.a * b.a + a.c * b.b; ab.b = a.b * b.a + a.d * b.b; 148 | ab.c = a.a * b.c + a.c * b.d; ab.d = a.b * b.c + a.d * b.d; 149 | ab.e = a.a * b.e + a.c * b.f + a.e; ab.f = a.b * b.e + a.d * b.f + a.f; 150 | return ab; 151 | }, 152 | 153 | inverse: function() { 154 | var a = this; 155 | var b = new dom.SVGMatrix; 156 | var d = 1 / (a.a * a.d - a.b * a.c); 157 | b.a = a.d * d; b.c = -a.c * d; b.e = -a.e * b.a - a.f * b.c; 158 | b.b = -a.b * d; b.d = a.a * d; b.f = -a.e * b.b - a.f * b.d; 159 | return b; 160 | }, 161 | 162 | translate: function(x, y) { 163 | var matrix = new dom.SVGMatrix; 164 | matrix.e = x; matrix.f = y 165 | return matrix.multiply(this); 166 | }, 167 | 168 | scale: function(scaleFactor) { 169 | return this.scaleNonUniform(scaleFactor, scaleFactor); 170 | }, 171 | 172 | scaleNonUniform: function(scaleFactorX, scaleFactorY) { 173 | var matrix = new dom.SVGMatrix; 174 | matrix.a = scaleFactorX; 175 | matrix.d = scaleFactorY; 176 | return matrix.multiply(this); 177 | }, 178 | 179 | rotate: function(angle) { 180 | var matrix = new dom.SVGMatrix; 181 | matrix.a = Math.cos(angle); 182 | matrix.b = Math.sin(angle); 183 | matrix.c = -matrix.b; 184 | matrix.d = matrix.a; 185 | return matrix.multiply(this); 186 | }, 187 | 188 | rotateFromVector: function(x, y) { mico.TODO(); }, 189 | flipX: function() { mico.TODO(); }, 190 | flipY: function() { mico.TODO(); }, 191 | 192 | skewX: function(angle) { 193 | var matrix = new dom.SVGMatrix; 194 | matrix.c = Math.tan(angle); 195 | return matrix.multiply(this); 196 | }, 197 | 198 | skewY: function(angle) { 199 | var matrix = new dom.SVGMatrix; 200 | matrix.b = Math.tan(angle); 201 | return matrix.multiply(this); 202 | } 203 | }); 204 | 205 | // SVGTransform 206 | 207 | dom.SVGTransform = function() { 208 | this._type = dom.SVGTransform.SVG_TRANSFORM_UNKNOWN; 209 | }; 210 | 211 | mico.extend(dom.SVGTransform, { 212 | SVG_TRANSFORM_UNKNOWN: 0, 213 | SVG_TRANSFORM_MATRIX: 1, 214 | SVG_TRANSFORM_TRANSLATE: 2, 215 | SVG_TRANSFORM_SCALE: 3, 216 | SVG_TRANSFORM_ROTATE: 4, 217 | SVG_TRANSFORM_SKEWX: 5, 218 | SVG_TRANSFORM_SKEWY: 6, 219 | typeToString: 220 | ['unknown', 'matrix', 'translate', 'scale', 'rotate', 'skewX', 'skewY'] 221 | }); 222 | 223 | dom.SVGTransform.fromString = function(s) { 224 | var transform = new dom.SVGTransform; 225 | var match = s.match(/(\w+)\s*\((.*)\)/); 226 | if (match) { 227 | var args = match[2].split(/(?:\s|,)+/). 228 | map(function(n) { return parseFloat(n) || 0; }); 229 | switch (match[1]) { 230 | case 'matrix': 231 | var matrix = new dom.SVGMatrix; 232 | matrix.a = args[0]; matrix.b = args[1]; 233 | matrix.c = args[2]; matrix.d = args[3]; 234 | matrix.e = args[4]; matrix.f = args[5]; 235 | transform.setMatrix(matrix); 236 | break; 237 | case 'translate': 238 | transform.setTranslate(args[0], args[1]); 239 | break; 240 | case 'scale': 241 | transform.setScale(args[0], args[1]); 242 | break; 243 | case 'rotate': 244 | transform.setRotate(args[0], args[1], args[2]); 245 | break; 246 | case 'skewX': 247 | transform.setSkewX(args[0]); 248 | break; 249 | case 'skewY': 250 | transform.setSkewY(args[0]); 251 | break; 252 | } 253 | } 254 | return transform; 255 | }; 256 | 257 | mico.extend(dom.SVGTransform.prototype, { 258 | get type() { return this._type; }, 259 | get matrix() { return this._matrix; }, 260 | get angle() { return this._angle; }, 261 | 262 | setMatrix: function(matrix) { 263 | this._type = dom.SVGTransform.SVG_TRANSFORM_MATRIX; 264 | this._angle = 0; 265 | this._matrix = new dom.SVGMatrix; 266 | this._matrix.a = matrix.a; this._matrix.b = matrix.b; 267 | this._matrix.c = matrix.c; this._matrix.d = matrix.d; 268 | this._matrix.e = matrix.e; this._matrix.f = matrix.f; 269 | }, 270 | 271 | setTranslate: function(tx, ty) { 272 | this._type = dom.SVGTransform.SVG_TRANSFORM_TRANSLATE; 273 | this._angle = 0; 274 | this._matrix = (new dom.SVGMatrix).translate(tx, ty || 0); 275 | }, 276 | 277 | setScale: function(sx, sy) { 278 | this._type = dom.SVGTransform.SVG_TRANSFORM_SCALE; 279 | this._angle = 0; 280 | this._matrix = (new dom.SVGMatrix).scaleNonUniform(sx, sy || sx); 281 | }, 282 | 283 | setRotate: function(angle, cx, cy) { 284 | cx && mico.TODO(); // We don't handle the optional cx cy yet 285 | this._type = dom.SVGTransform.SVG_TRANSFORM_ROTATE; 286 | this._angle = angle; 287 | this._matrix = (new dom.SVGMatrix).rotate(angle); 288 | }, 289 | 290 | setSkewX: function(angle) { 291 | this._type = dom.SVGTransform.SVG_TRANSFORM_SKEWX; 292 | this._angle = angle; 293 | this._matrix = (new dom.SVGMatrix).skewX(angle); 294 | }, 295 | 296 | setSkewY: function(angle) { 297 | this._type = dom.SVGTransform.SVG_TRANSFORM_SKEWY; 298 | this._angle = angle; 299 | this._matrix = (new dom.SVGMatrix).skewY(angle); 300 | }, 301 | 302 | // TODO what about the optional cx cy for rotate? 303 | toString: function() { 304 | var args = []; 305 | with (dom.SVGTransform) 306 | switch (this.type) { 307 | case SVG_TRANSFORM_MATRIX: args = [this.matrix.a, this.matrix.b, 308 | this.matrix.c, this.matrix.d, 309 | this.matrix.e, this.matrix.f]; break; 310 | case SVG_TRANSFORM_TRANSLATE: args = [this.matrix.e, this.matrix.f]; break; 311 | case SVG_TRANSFORM_SCALE: args = [this.matrix.a, this.matrix.d]; break; 312 | case SVG_TRANSFORM_ROTATE: args = [this.angle]; break; 313 | case SVG_TRANSFORM_SKEWX: args = [this.angle]; break; 314 | case SVG_TRANSFORM_SKEWY: args = [this.angle]; break; 315 | } 316 | return dom.SVGTransform.typeToString[this.type] + '(' + args.join(' ') + ')'; 317 | } 318 | }); 319 | 320 | // SVGList (used for SVGStringList, SVGPointList, etc.) 321 | 322 | dom.SVGList = function() { this._items = []; }; 323 | 324 | mico.extend(dom.SVGList.prototype, { 325 | get numberOfItems() { return this._items.length; }, 326 | clear: function() { this._items.length = 0; }, 327 | 328 | initialize: function(newItem) { 329 | this.clear(); 330 | return this.appendItem(newItem); 331 | }, 332 | 333 | getItem: function(index) { return this._items[index]; }, 334 | insertItemBefore: function(newItem, index) { mico.TODO(); }, 335 | replaceItem: function(newItem, index) { mico.TODO(); }, 336 | removeItem: function(index) { mico.TODO(); }, 337 | 338 | appendItem: function(newItem) { 339 | this._items.push(newItem); 340 | return newItem; 341 | }, 342 | 343 | toString: function() { 344 | return this._items.join(' '); 345 | } 346 | }); 347 | 348 | // SVGPointList 349 | 350 | dom.SVGPointList = function() { dom.SVGList.call(this); }; 351 | mico.extend(dom.SVGPointList.prototype, dom.SVGList.prototype); 352 | mico.extend(dom.SVGPointList, { 353 | fromString: function(s) { 354 | var list = new dom.SVGPointList; 355 | var items = s.split(/(?:\s|,)+/); 356 | for (var i = 0; i < items.length - 1; i += 2) 357 | list.appendItem(dom.SVGPoint.fromString(items[i] + ',' + items[i + 1])); 358 | return list; 359 | } 360 | }); 361 | 362 | // SVGNumberList 363 | 364 | dom.SVGNumberList = function() { dom.SVGList.call(this); }; 365 | mico.extend(dom.SVGNumberList.prototype, dom.SVGList.prototype); 366 | mico.extend(dom.SVGNumberList, { 367 | fromString: function(s) { 368 | var list = new dom.SVGNumberList; 369 | var items = s.split(/(?:\s|,)+/); 370 | for (var i = 0; i < items.length; i++) 371 | list.appendItem(dom.SVGNumber.fromString(items[i])); 372 | return list; 373 | } 374 | }); 375 | 376 | // SVGLengthList 377 | 378 | dom.SVGLengthList = function() { dom.SVGList.call(this); }; 379 | mico.extend(dom.SVGLengthList.prototype, dom.SVGList.prototype); 380 | mico.extend(dom.SVGLengthList, { 381 | fromString: function(s) { 382 | var list = new dom.SVGLengthList; 383 | var items = s.split(/(?:\s|,)+/); 384 | for (var i = 0; i < items.length; i++) 385 | list.appendItem(dom.SVGLength.fromString(items[i])); 386 | return list; 387 | } 388 | }); 389 | 390 | // SVGTransformList 391 | 392 | dom.SVGTransformList = function() { dom.SVGList.call(this); }; 393 | mico.extend(dom.SVGTransformList.prototype, dom.SVGList.prototype); 394 | mico.extend(dom.SVGTransformList, { 395 | fromString: function(s) { 396 | var list = new dom.SVGTransformList; 397 | var items = s.split(/\)\s*,*\s*/); 398 | for (var i = 0; i < items.length - 1; i++) 399 | list.appendItem(dom.SVGTransform.fromString(items[i] + ')')); 400 | return list; 401 | } 402 | }); 403 | 404 | mico.extend(dom.SVGTransformList.prototype, { 405 | createSVGTransformFromMatrix: function(matrix) { 406 | var transform = new dom.SVGTransform; 407 | transform.setMatrix(matrix); 408 | return transform; 409 | }, 410 | 411 | consolidate: function() { 412 | if (this.numberOfItems == 0) 413 | return null; 414 | if (this.numberOfItems == 1) 415 | return this.getItem(0); 416 | var matrix = new dom.SVGMatrix; 417 | for (var i = 0; i < this.numberOfItems; i++) 418 | matrix = this.getItem(i).matrix.multiply(matrix); 419 | this.clear(); 420 | return this.appendItem(this.createSVGTransformFromMatrix(matrix)); 421 | } 422 | }); 423 | 424 | // SVGAnimated (used for SVGAnimatedBoolean, etc.) 425 | 426 | dom.SVGAnimated = function() {}; 427 | 428 | dom.SVGAnimated.defineAnimated = function(classToAnimate, readonly) { 429 | var fromString = classToAnimate.fromString || classToAnimate; 430 | var animatedClass = function() {}; 431 | mico.extend(animatedClass.prototype, dom.SVGAnimated.prototype); 432 | if (!readonly) 433 | animatedClass.prototype.__defineSetter__('baseVal', 434 | function(value) { this._baseVal = value; }); 435 | animatedClass.fromString = function(s) { 436 | var object = new animatedClass; 437 | object._baseVal = fromString(s); 438 | return object; 439 | }; 440 | return animatedClass; 441 | }; 442 | 443 | mico.extend(dom.SVGAnimated.prototype, { 444 | get baseVal() { return this._baseVal; }, 445 | // TODO this isn't correct... 446 | get animVal() { return this._baseVal; }, 447 | toString: function() { return this._baseVal.toString(); } 448 | }); 449 | 450 | // TODO will Boolean correctly parse the value strings? Probably not... 451 | dom.SVGAnimatedBoolean = dom.SVGAnimated.defineAnimated(Boolean); 452 | dom.SVGAnimatedNumber = dom.SVGAnimated.defineAnimated(dom.SVGNumber); 453 | dom.SVGAnimatedEnumeration = dom.SVGAnimated.defineAnimated(parseInt); 454 | dom.SVGAnimatedLength = dom.SVGAnimated.defineAnimated(dom.SVGLength, true); 455 | dom.SVGAnimatedString = dom.SVGAnimated.defineAnimated(String); 456 | dom.SVGAnimatedNumberList = 457 | dom.SVGAnimated.defineAnimated(dom.SVGNumberList, true); 458 | dom.SVGAnimatedLengthList = 459 | dom.SVGAnimated.defineAnimated(dom.SVGLengthList, true); 460 | dom.SVGAnimatedTransformList = 461 | dom.SVGAnimated.defineAnimated(dom.SVGTransformList, true); 462 | 463 | // SVGLocatable 464 | 465 | dom.SVGLocatable = function() {}; 466 | mico.extend(dom.SVGLocatable.prototype, { 467 | get nearestViewportElement() { mico.TODO(); }, 468 | get farthestViewportElement() { mico.TODO(); }, 469 | getBBox: function() { mico.TODO(); }, 470 | getCTM: function() { mico.TODO(); }, 471 | getScreenCTM: function() { mico.TODO(); }, 472 | 473 | // FIXME this is broken, I'm pretty sure 474 | getTransformToElement: function(element) { 475 | var matrix; 476 | 477 | if (this === element) 478 | return new dom.SVGMatrix; 479 | if (this.parentNode && this.parentNode.getTransformToElement) 480 | matrix = this.parentNode.getTransformToElement(element); 481 | else 482 | matrix = new dom.SVGMatrix; 483 | 484 | if (this.hasAttribute('transform') && 485 | this.transform.baseVal.numberOfItems) { 486 | var list = new dom.SVGTransformList; 487 | list._items = this.transform.baseVal._items.concat(); 488 | // TODO which is right? 489 | matrix = matrix.multiply(list.consolidate().matrix.inverse()); 490 | //matrix = list.consolidate().matrix.inverse().multiply(matrix); 491 | } 492 | 493 | return matrix; 494 | }, 495 | }); 496 | 497 | // dom.SVGTransformable 498 | 499 | dom.SVGTransformable = function() { dom.SVGLocatable.call(this); }; 500 | mico.extend(dom.SVGTransformable.prototype, dom.SVGLocatable.prototype); 501 | dom.Element.defineAttributes(dom.SVGTransformable, 502 | {name:'transform', type:dom.SVGAnimatedTransformList, readonly:true}); 503 | 504 | // SVGStylable 505 | 506 | dom.SVGStylable = function() {}; 507 | dom.Element.defineAttributes(dom.SVGStylable, 508 | {name:'className', type:dom.SVGAnimatedString, readonly:true, xmlName:'class'}, 509 | {name:'style', type:dom.CSSStyleDeclaration, readonly:true}); 510 | dom.SVGStylable.prototype.getPresentationAttribute = function(name) { mico.TODO(); }; 511 | 512 | // SVGAnimatedPoints 513 | 514 | dom.SVGAnimatedPoints = function() {}; 515 | dom.Element.defineAttributes(dom.SVGAnimatedPoints, 516 | {name:'points', type:dom.SVGPointList, readonly:true}); 517 | 518 | mico.extend(dom.SVGAnimatedPoints.prototype, { 519 | get animatedPoints() { return this.points; } // TODO not correct... 520 | }); 521 | 522 | // SVGElement 523 | 524 | dom.SVGElement = function() { dom.Element.call(this); }; 525 | mico.extend(dom.SVGElement.prototype, dom.Element.prototype); 526 | dom.Element.factories['http://www.w3.org/2000/svg'] = 527 | dom.SVGElement.factory = {}; 528 | 529 | dom.SVGElement.defineElement = function(name, parents) { 530 | (parents = parents || []).unshift(this); 531 | var element = function() { 532 | var _this = this; 533 | parents.forEach(function(parent) { parent.call(_this); }); 534 | }; 535 | element.attributeSpecs = {}; 536 | parents.forEach(function(parent) { 537 | mico.extend(element.attributeSpecs, parent.attributeSpecs); 538 | mico.extend(element.prototype, parent.prototype); 539 | }); 540 | dom.Element.defineAttributes.apply(dom.Element, 541 | [element].concat(Array.prototype.slice.call(arguments, 2))); 542 | if (name) { 543 | this.factory[name] = element; 544 | element.tagName = 'http://www.w3.org/2000/svg:' + name; 545 | } 546 | return element; 547 | }; 548 | 549 | dom.Element.defineAttributes(dom.SVGElement, 'id', 'xmlbase'); 550 | 551 | mico.extend(dom.SVGElement.prototype, { 552 | get ownerSVGElement() { 553 | return dom.Node.findAncestor(this, function(p) { 554 | return p.nodeName == 'svg'; 555 | }); 556 | }, 557 | get viewpointElement() { mico.TODO(); }, 558 | }); 559 | 560 | // SVGSVGElement 561 | 562 | dom.SVGSVGElement = dom.SVGElement.defineElement('svg', 563 | [dom.SVGLocatable, dom.SVGStylable], 564 | {name:'x', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'0'}, 565 | {name:'y', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'0'}, 566 | {name:'width', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'100%'}, 567 | {name:'height', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'100%'}); 568 | 569 | mico.extend(dom.SVGSVGElement.prototype, { 570 | createSVGMatrix: function() { return new dom.SVGMatrix; }, 571 | createSVGTransform: function() { return new dom.SVGTransform; } 572 | }); 573 | 574 | // SVGDefsElement 575 | 576 | dom.SVGDefsElement = dom.SVGElement.defineElement('defs', 577 | [dom.SVGTransformable, dom.SVGStylable]); 578 | 579 | // SVGEllipseElement 580 | 581 | dom.SVGEllipseElement = dom.SVGElement.defineElement('ellipse', 582 | [dom.SVGTransformable, dom.SVGStylable], 583 | {name:'cx', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'0'}, 584 | {name:'cy', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'0'}, 585 | {name:'rx', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'0'}, 586 | {name:'ry', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'0'}); 587 | 588 | // SVGGElement 589 | 590 | dom.SVGGElement = dom.SVGElement.defineElement('g', 591 | [dom.SVGTransformable, dom.SVGStylable]); 592 | 593 | // SVGGradientElement 594 | 595 | dom.SVGGradientElement = dom.SVGElement.defineElement(null, 596 | [dom.SVGStylable], 597 | {name:'gradientUnits', type:dom.SVGAnimatedEnumeration, readonly:true}, 598 | {name:'gradientTransform', type:dom.SVGAnimatedTransformList, readonly:true}, 599 | {name:'spreadMethod', type:dom.SVGAnimatedEnumeration, readonly:true}); 600 | 601 | mico.extend(dom.SVGGradientElement, { 602 | SVG_SPREADMETHOD_UNKNOWN: 0, 603 | SVG_SPREADMETHOD_PAD: 1, 604 | SVG_SPREADMETHOD_REFLECT: 2, 605 | SVG_SPREADMETHOD_REPEAT: 3 606 | }); 607 | 608 | // SVGLinearGradientElement 609 | 610 | dom.SVGLinearGradientElement = dom.SVGElement.defineElement('linearGradient', 611 | [dom.SVGGradientElement], 612 | {name:'x1', type:dom.SVGAnimatedLength, readonly:true, defaultValue: '0%'}, 613 | {name:'y1', type:dom.SVGAnimatedLength, readonly:true, defaultValue: '0%'}, 614 | {name:'x2', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'100%'}, 615 | {name:'y2', type:dom.SVGAnimatedLength, readonly:true, defaultValue: '0%'}); 616 | 617 | // SVGRadialGradientElement 618 | 619 | dom.SVGRadialGradientElement = dom.SVGElement.defineElement('radialGradient', 620 | [dom.SVGGradientElement], 621 | {name:'cx', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'50%'}, 622 | {name:'cy', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'50%'}, 623 | {name: 'r', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'50%'}, 624 | {name:'fx', type:dom.SVGAnimatedLength, readonly:true}, 625 | {name:'fy', type:dom.SVGAnimatedLength, readonly:true}); 626 | 627 | // SVGPolygonElement 628 | 629 | dom.SVGPolygonElement = dom.SVGElement.defineElement('polygon', 630 | [dom.SVGAnimatedPoints, dom.SVGTransformable, dom.SVGStylable]); 631 | 632 | // SVGPolylineElement 633 | 634 | dom.SVGPolylineElement = dom.SVGElement.defineElement('polyline', 635 | [dom.SVGAnimatedPoints, dom.SVGTransformable, dom.SVGStylable]); 636 | 637 | // SVGRectElement 638 | 639 | dom.SVGRectElement = dom.SVGElement.defineElement('rect', 640 | [dom.SVGTransformable, dom.SVGStylable], 641 | {name: 'x', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'0'}, 642 | {name: 'y', type:dom.SVGAnimatedLength, readonly:true, defaultValue:'0'}, 643 | {name: 'width', type:dom.SVGAnimatedLength, readonly:true}, 644 | {name:'height', type:dom.SVGAnimatedLength, readonly:true}, 645 | {name: 'rx', type:dom.SVGAnimatedLength, readonly:true}, 646 | {name: 'ry', type:dom.SVGAnimatedLength, readonly:true}); 647 | 648 | // SVGStopElement 649 | 650 | dom.SVGStopElement = dom.SVGElement.defineElement('stop', 651 | [dom.SVGStylable], 652 | {name:'offset', type:dom.SVGAnimatedNumber, readonly:true}); 653 | 654 | // SVGTextContentElement 655 | 656 | dom.SVGTextContentElement = dom.SVGElement.defineElement(null, 657 | [dom.SVGStylable], 658 | {name:'textLength', type:dom.SVGAnimatedLength, readonly:true}, 659 | {name:'lengthAdjust', type:dom.SVGAnimatedEnumeration, readonly:true}); 660 | 661 | mico.extend(dom.SVGTextContentElement, { 662 | LENGTHADJUST_UNKNOWN: 0, 663 | LENGTHADJUST_SPACING: 1, 664 | LENGTHADJUST_SPACINGANDGLYPHS: 2 665 | }); 666 | 667 | mico.extend(dom.SVGTextContentElement.prototype, { 668 | getNumberOfChars: function () { mico.TODO(); }, 669 | getComputedTextLength: function() { mico.TODO(); }, 670 | getSubStringLength: function(charnum, nchars) { mico.TODO(); }, 671 | getStartPositionOfChar: function(charnum) { mico.TODO(); }, 672 | getEndPositionOfChar: function(charnum) { mico.TODO(); }, 673 | getExtentOfChar: function(charnum) { mico.TODO(); }, 674 | getRotationOfChar: function(charnum) { mico.TODO(); }, 675 | getCharNumAtPosition: function(pot) { mico.TODO(); }, 676 | selectSubString: function(charnum, nchars) { mico.TODO(); }, 677 | }); 678 | 679 | // SVGTextPositioningElement 680 | 681 | dom.SVGTextPositioningElement = dom.SVGElement.defineElement(null, 682 | [dom.SVGTextContentElement], 683 | {name:'x', type:dom.SVGAnimatedLengthList, readonly:true}, 684 | {name:'y', type:dom.SVGAnimatedLengthList, readonly:true}, 685 | {name:'dx', type:dom.SVGAnimatedLengthList, readonly:true}, 686 | {name:'dy', type:dom.SVGAnimatedLengthList, readonly:true}, 687 | {name:'rotate', type:dom.SVGAnimatedNumberList, readonly:true}); 688 | 689 | // SVGTextElement 690 | 691 | dom.SVGTextElement = dom.SVGElement.defineElement('text', 692 | [dom.SVGTextPositioningElement, dom.SVGTransformable]); 693 | 694 | // SVGTSpanElement 695 | 696 | dom.SVGTSpanElement = dom.SVGElement.defineElement('tspan', 697 | [dom.SVGTextPositioningElement]); 698 | -------------------------------------------------------------------------------- /gezira/Makefile-gcc: -------------------------------------------------------------------------------- 1 | include ../config-gcc.mk 2 | 3 | spidermonkey$(COMPILED_PACKAGE_EXT): spidermonkey.generated.c $(MAKEFILE_LIST) 4 | gcc $< $(CFLAGS) -shared -lgezira -o $@ 5 | 6 | spidermonkey.generated.c: generate-bindings spidermonkey.head.c 7 | ./generate-bindings > $@ 8 | 9 | clean: 10 | /bin/rm -f *$(COMPILED_PACKAGE_EXT) spidermonkey.generated.c 11 | -------------------------------------------------------------------------------- /gezira/generate-bindings: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This file is part of the Mico project, available under the MIT license: 3 | # 4 | # http://www.opensource.org/licenses/mit-license.php 5 | # 6 | 7 | class String 8 | def camelize 9 | to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }. 10 | gsub(/(?:^|_)(.)/) { $1.upcase } 11 | end 12 | end 13 | 14 | module Gezira; end 15 | 16 | class Gezira::StreamClass 17 | attr_reader :js_name 18 | 19 | def initialize(name, attributes) 20 | @name = name 21 | @attributes = attributes 22 | @qualified_name = 'gezira_' + name 23 | @js_name = name.camelize 24 | end 25 | 26 | def generate_binding 27 | generate_constructor 28 | generate_setter_function 29 | generate_js_class 30 | generate_property_specs 31 | generate_functions 32 | end 33 | 34 | def generate_constructor 35 | puts <<-END 36 | static JSBool 37 | #{@js_name}_constructor (JSContext *cxt, JSObject *obj, uintN argc, 38 | jsval *argv, jsval *rval) 39 | { 40 | double d; 41 | #{@qualified_name} *stream = 42 | (#{@qualified_name} *) malloc (sizeof (#{@qualified_name})); 43 | stream->type = #{@qualified_name}_type; 44 | stream->n = 0; 45 | stream->elements = 0; 46 | JS_SetPrivate (cxt, obj, stream); 47 | END 48 | 49 | generate_constructor_body 50 | 51 | puts <<-END 52 | *rval = OBJECT_TO_JSVAL (obj); 53 | return JS_TRUE; 54 | } 55 | END 56 | end 57 | 58 | def generate_setter_function 59 | return if @attributes.empty? 60 | 61 | puts <<-END 62 | static JSBool 63 | #{@js_name}_setter (JSContext *cxt, JSObject *obj, 64 | jsval id, jsval *vp) 65 | { 66 | double d; 67 | #{@qualified_name} *stream = 68 | (#{@qualified_name} *) JS_GetPrivate (cxt, obj); 69 | JS_ValueToNumber (cxt, *vp, &d); 70 | switch (JSVAL_TO_INT (id)) { 71 | END 72 | 73 | @attributes.each_with_index do |a, i| 74 | puts "case #{i}: stream->#{a} = GEZIRA_REAL (d); break;" 75 | end 76 | 77 | puts <<-END 78 | } 79 | return JS_TRUE; 80 | } 81 | END 82 | end 83 | 84 | def generate_js_class 85 | puts <<-END 86 | static JSClass #{@js_name}_class = { 87 | "#{@js_name}", JSCLASS_HAS_PRIVATE, 88 | JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, 89 | JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, 90 | JSCLASS_NO_OPTIONAL_MEMBERS 91 | }; 92 | END 93 | end 94 | 95 | def generate_property_specs 96 | puts "static JSPropertySpec #{@js_name}_property_specs[] = {" 97 | @attributes.each_with_index do |a, i| 98 | puts "{\"#{a}\", #{i}, 0, NULL, #{@js_name}_setter}," 99 | end 100 | puts "{0, 0, 0, 0, 0}};" 101 | end 102 | 103 | def generate_functions 104 | puts <<-END 105 | static JSFunctionSpec #{@js_name}_functions[] = { 106 | {NULL, NULL, 0, 0, 0} 107 | }; 108 | END 109 | end 110 | end 111 | 112 | class Gezira::NodeClass < Gezira::StreamClass 113 | def generate_constructor_body 114 | @attributes.each_with_index do |a, i| 115 | puts <<-END 116 | JS_ValueToNumber (cxt, argv[#{i}], &d); 117 | stream->#{a} = GEZIRA_REAL (d); 118 | END 119 | end 120 | end 121 | 122 | def generate_functions 123 | puts <<-END 124 | static JSFunctionSpec #{@js_name}_functions[] = { 125 | {"children", Node_set_children, 0, 0, 0}, 126 | {NULL, NULL, 0, 0, 0} 127 | }; 128 | END 129 | end 130 | def constructor_argc; @attributes.size end 131 | end 132 | 133 | class Gezira::RealStreamClass < Gezira::StreamClass 134 | def constructor_argc; 1 end 135 | def generate_constructor_body 136 | puts "create_real_elements (cxt, argv[0], &stream->elements, &stream->n);" 137 | end 138 | end 139 | 140 | print File::read('spidermonkey.head.c') 141 | 142 | CLASS_DECLS = [ 143 | [Gezira::RealStreamClass, 'line_stream'], 144 | [Gezira::RealStreamClass, 'bezier3_stream'], 145 | [Gezira::NodeClass, 'node'], 146 | [Gezira::NodeClass, 'paint_node'], 147 | [Gezira::NodeClass, 'color_over_node', 148 | 'a', 'r', 'g', 'b'], 149 | [Gezira::NodeClass, 'clip_node', 150 | 'xmin', 'ymin', 'xmax', 'ymax'], 151 | [Gezira::NodeClass, 'color_background_node', 152 | 'r', 'g', 'b'], 153 | [Gezira::NodeClass, 'transformation_node', 154 | 'a', 'b', 'c', 'd', 'e', 'f'], 155 | [Gezira::NodeClass, 'translation_node', 156 | 'x', 'y'], 157 | [Gezira::NodeClass, 'rotation_node', 158 | 'angle'], 159 | [Gezira::NodeClass, 'scale_node', 160 | 'x', 'y'] 161 | ] 162 | 163 | classes = CLASS_DECLS.map do |c| 164 | c[0].new(c[1], c[2..-1]) 165 | end 166 | 167 | classes.each do |c| c.generate_binding end 168 | 169 | # TODO must keep argc in sync! Major bug warning! 170 | classes << Struct.new(:js_name, :constructor_argc).new('Renderer', 5) 171 | 172 | puts <<-END 173 | void 174 | initialize (JSContext *cxt) 175 | { 176 | JSObject *gezira; 177 | 178 | gezira = JS_DefineObject (cxt, JS_GetGlobalObject (cxt), 179 | "gezira", NULL, NULL, 0); 180 | END 181 | 182 | classes.each do |c| 183 | puts <<-END 184 | JS_InitClass (cxt, gezira, NULL, &#{c.js_name}_class, 185 | #{c.js_name}_constructor, #{c.constructor_argc}, 186 | #{c.js_name}_property_specs, #{c.js_name}_functions, 187 | NULL, NULL); 188 | END 189 | end 190 | 191 | puts "\n}" 192 | -------------------------------------------------------------------------------- /gezira/html.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | // TODO move HTML stuff out of svg.js into here 7 | -------------------------------------------------------------------------------- /gezira/spidermonkey.head.c: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | #include 7 | #include "jsapi.h" 8 | #include "gezira.h" 9 | 10 | static JSBool 11 | Renderer_constructor (JSContext *cxt, JSObject *obj, uintN argc, 12 | jsval *argv, jsval *rval) 13 | { 14 | double d; 15 | gezira_renderer *renderer = 16 | (gezira_renderer *) malloc (sizeof (gezira_renderer)); 17 | JS_SetPrivate (cxt, obj, renderer); 18 | renderer->router = gezira_renderer_router; 19 | /* FIXME hackety hack hack */ 20 | JS_ValueToNumber (cxt, argv[0], &d); 21 | renderer->pixmap.pixels = *((char **) (char *) &d); 22 | renderer->pixmap.width = JSVAL_TO_INT (argv[1]); 23 | renderer->pixmap.height = JSVAL_TO_INT (argv[2]); 24 | renderer->pixmap.stride = JSVAL_TO_INT (argv[3]); 25 | renderer->pixmap.bytes_per_pixel = JSVAL_TO_INT (argv[4]); 26 | 27 | *rval = OBJECT_TO_JSVAL (obj); 28 | return JS_TRUE; 29 | } 30 | 31 | static JSBool 32 | Renderer_render (JSContext *cxt, JSObject *obj, uintN argc, 33 | jsval *argv, jsval *rval) 34 | { 35 | gezira_renderer *renderer = 36 | (gezira_renderer *) JS_GetPrivate (cxt, obj); 37 | gezira_stream *stream = 38 | (gezira_stream *) JS_GetPrivate (cxt, JSVAL_TO_OBJECT (argv[0])); 39 | 40 | gezira_render (renderer, stream); 41 | 42 | return JS_TRUE; 43 | } 44 | 45 | static JSClass Renderer_class = { 46 | "Renderer", JSCLASS_HAS_PRIVATE, 47 | JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, 48 | JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, 49 | JSCLASS_NO_OPTIONAL_MEMBERS 50 | }; 51 | 52 | static JSPropertySpec Renderer_property_specs[] = {{0, 0, 0, 0, 0}}; 53 | static JSFunctionSpec Renderer_functions[] = { 54 | {"render", Renderer_render, 1, 0, 0}, 55 | {NULL, NULL, 0, 0, 0} 56 | }; 57 | 58 | static JSBool 59 | Node_set_children (JSContext *cxt, JSObject *obj, uintN argc, 60 | jsval *argv, jsval *rval) 61 | { 62 | int i; 63 | jsval val; 64 | gezira_node *node = (gezira_node *) JS_GetPrivate (cxt, obj); 65 | JSObject *array; 66 | 67 | if (argc && JS_IsArrayObject (cxt, JSVAL_TO_OBJECT (argv[0]))) 68 | array = JSVAL_TO_OBJECT (argv[0]); 69 | else 70 | array = JS_NewArrayObject (cxt, argc, argv); 71 | 72 | JS_GetArrayLength (cxt, array, &node->n); 73 | free (node->elements); 74 | node->elements = (gezira_stream **) 75 | malloc (node->n * sizeof (gezira_stream *)); 76 | 77 | for (i = 0; i < node->n; i++) { 78 | JS_GetElement (cxt, array, i, &val); 79 | node->elements[i] = (gezira_stream *) 80 | JS_GetPrivate (cxt, JSVAL_TO_OBJECT (val)); 81 | } 82 | 83 | /* Need references to children to keep GC happy */ 84 | val = OBJECT_TO_JSVAL (array); 85 | JS_SetProperty (cxt, obj, "_children", &val); 86 | 87 | *rval = OBJECT_TO_JSVAL (obj); 88 | return JS_TRUE; 89 | } 90 | 91 | static void 92 | create_real_elements (JSContext *cxt, jsval val, gezira_real **elements, int *n) 93 | { 94 | int i; 95 | JSObject *array = JSVAL_TO_OBJECT (val); 96 | 97 | JS_GetArrayLength (cxt, array, (jsuint *) n); 98 | *elements = (gezira_real *) malloc (*n * sizeof (gezira_real)); 99 | //printf ("at creation, elements is %p\n", *elements); 100 | 101 | for (i = 0; i < *n; i++) { 102 | double d; 103 | jsval val; 104 | 105 | JS_GetElement (cxt, array, i, &val); 106 | JS_ValueToNumber (cxt, val, &d); 107 | (*elements)[i] = GEZIRA_REAL (d); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /gezira/stubs.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | // FIXME this file is out of date 7 | 8 | var gezira = {}; 9 | 10 | (function(stub) { 11 | ['paint', 'free', 'Object', 'Parent', 'Background', 'ColorOver', 'Composite', 12 | 'Group', 'Lines', 'Bezier3s', 'Translation', 'Scale', 13 | 'Group.prototype.children', 'Parent.prototype.child'].forEach(function(n) { 14 | eval('gezira.' + n + ' = stub;'); 15 | }); 16 | })(function(){}); 17 | -------------------------------------------------------------------------------- /gezira/svg.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | mico.require('dom.html'); 7 | mico.require('dom.svg'); 8 | mico.require('dom.graphics'); 9 | mico.require('gezira'); 10 | mico.require('gezira_glyphserver'); 11 | 12 | dom.graphics.renderers[null] = function(element) { 13 | if (!element._graphics) 14 | element._graphics = new gezira.Node; 15 | return element._graphics; 16 | }; 17 | 18 | dom.graphics.renderers[dom.HTMLHtmlElement.tagName] = 19 | dom.graphics.renderers[dom.HTMLBodyElement.tagName] = 20 | dom.graphics.renderers[dom.SVGSVGElement.tagName] = 21 | dom.graphics.renderers[dom.SVGGElement.tagName] = function(element) { 22 | if (!element._graphics) 23 | element._graphics = new gezira.Node; 24 | var gobj = (new gezira.Node).children( 25 | element.childNodes._nodes.map(function(child) { 26 | return child._graphics || dom.graphics.render(child); 27 | }) 28 | ); 29 | if (element.hasAttribute('transform')) { 30 | var list = element.transform.baseVal; 31 | for (var i = list.numberOfItems - 1; i >= 0; i--) { 32 | var gtransform; 33 | var transform = list.getItem(i); 34 | if (transform.type == dom.SVGTransform.SVG_TRANSFORM_TRANSLATE) 35 | gtransform = new gezira.TranslationNode(transform.matrix.e, transform.matrix.f); 36 | else if (transform.type == dom.SVGTransform.SVG_TRANSFORM_SCALE) 37 | gtransform = new gezira.ScaleNode(transform.matrix.a, transform.matrix.d); 38 | else if (transform.type == dom.SVGTransform.SVG_TRANSFORM_ROTATE) 39 | gtransform = new gezira.RotationNode(transform.angle); 40 | else 41 | continue; 42 | gobj = gtransform.children(gobj); 43 | } 44 | } 45 | return element._graphics.children(gobj); 46 | }; 47 | 48 | dom.graphics.renderers[dom.SVGRectElement.tagName] = function(element) { 49 | if (!element._graphics) 50 | element._graphics = new gezira.Node; 51 | var fill = element.getAttribute('fill'); 52 | var match = fill && fill.match(/rgb\((\d+),(\d+),(\d+)\)/); 53 | if (match) { 54 | var r = Number(match[1]) / 255; 55 | var g = Number(match[2]) / 255; 56 | var b = Number(match[3]) / 255; 57 | var x1 = element.x.baseVal.value; 58 | var y1 = element.y.baseVal.value; 59 | var x2 = x1 + element.width.baseVal.value 60 | var y2 = y1 + element.height.baseVal.value 61 | element._graphics.children( 62 | (new gezira.ColorOverNode(1, r, g, b)).children( 63 | (new gezira.PaintNode).children( 64 | new gezira.LineStream([x1, y1, x1, y2, x1, y2, x2, y2, 65 | x2, y2, x2, y1, x2, y1, x1, y1])))); 66 | } 67 | else 68 | element._graphics.children(new gezira.Node); 69 | 70 | return element._graphics; 71 | }; 72 | 73 | dom.graphics.renderers[dom.SVGPolygonElement.tagName] = function(element) { 74 | if (!element._graphics) 75 | element._graphics = new gezira.Node; 76 | var fill = element.getAttribute('fill'); 77 | var match = fill && fill.match(/rgb\((\d+),(\d+),(\d+)\)/); 78 | if (match) { 79 | var r = Number(match[1]) / 255; 80 | var g = Number(match[2]) / 255; 81 | var b = Number(match[3]) / 255; 82 | var points = element.points; 83 | var lines = new Array(points.numberOfItems * 4); 84 | lines[0] = lines[lines.length - 2] = points.getItem(0).x; 85 | lines[1] = lines[lines.length - 1] = points.getItem(0).y; 86 | for (var p_i = 1, l_i = 2; p_i < points.numberOfItems; p_i++, l_i += 4) { 87 | lines[l_i ] = lines[l_i + 2] = points.getItem(p_i).x; 88 | lines[l_i + 1] = lines[l_i + 3] = points.getItem(p_i).y; 89 | } 90 | element._graphics.children( 91 | (new gezira.ColorOverNode(1, r, g, b)).children( 92 | (new gezira.PaintNode).children( 93 | new gezira.LineStream(lines)))); 94 | } 95 | else 96 | element._graphics.children(new gezira.Node); 97 | 98 | return element._graphics; 99 | }; 100 | 101 | dom.graphics.renderers[dom.SVGEllipseElement.tagName] = function(element) { 102 | if (!element._graphics) 103 | element._graphics = new gezira.Node; 104 | var fill = element.getAttribute('fill'); 105 | var match = fill && fill.match(/rgb\((\d+),(\d+),(\d+)\)/); 106 | if (match) { 107 | var r = Number(match[1]) / 255; 108 | var g = Number(match[2]) / 255; 109 | var b = Number(match[3]) / 255; 110 | var cx = element.cx.baseVal.value; 111 | var cy = element.cy.baseVal.value; 112 | var rx = element.rx.baseVal.value; 113 | var ry = element.ry.baseVal.value; 114 | var a = 0.552284749830794; 115 | // TODO put radius into coords instead of using gezira.ScaleNode 116 | var beziers = [0, 1, a, 1, 1, a, 1, 0, 1, 0, 1, -a, a, -1, 0, -1, 0, -1, 117 | -a, -1, -1, -a, -1, 0, -1, 0, -1, a, -a, 1, 0, 1]; 118 | element._graphics.children( 119 | (new gezira.TranslationNode(cx, cy)).children( 120 | (new gezira.ScaleNode(rx, ry).children( 121 | (new gezira.ColorOverNode(1, r, g, b)).children( 122 | (new gezira.PaintNode).children( 123 | new gezira.Bezier3Stream(beziers))))))); 124 | } 125 | else { 126 | element._graphics.children(new gezira.Node); 127 | } 128 | 129 | return element._graphics; 130 | }; 131 | 132 | dom.graphics.renderers[dom.SVGTextElement.tagName] = function(element) { 133 | if (!element._graphics) 134 | element._graphics = new gezira.Node; 135 | var fill = element.getAttribute('fill'); 136 | var match = fill && fill.match(/rgb\((\d+),(\d+),(\d+)\)/); 137 | if (match) { 138 | var r = Number(match[1]) / 255; 139 | var g = Number(match[2]) / 255; 140 | var b = Number(match[3]) / 255; 141 | element._graphics.children( 142 | (new gezira.ColorOverNode(1, r, g, b)).children( 143 | element.childNodes._nodes.map(function(child) { 144 | return child._graphics || dom.graphics.render(child); 145 | }))); 146 | } 147 | else { 148 | element._graphics.children(new gezira.Node); 149 | } 150 | 151 | return element._graphics; 152 | }; 153 | 154 | gezira._glyphserver = new gezira.Glyphserver; 155 | 156 | dom.graphics.renderers[dom.SVGTSpanElement.tagName] = function(element) { 157 | if (!element._graphics) 158 | element._graphics = new gezira.Node; 159 | var x = Number(element.getAttribute('x')); 160 | var y = Number(element.getAttribute('y')); 161 | element._graphics.children( 162 | (new gezira.TranslationNode(x, y)).children( 163 | (new gezira.ScaleNode(1, -1)).children( 164 | gezira._glyphserver.glyphs(element.firstChild.data)))); 165 | return element._graphics; 166 | }; 167 | -------------------------------------------------------------------------------- /mico.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | var mico = { 7 | TODO: function() { 8 | var e = new Error; 9 | e.message = 'Not implemented yet:\n' + e.stack; 10 | throw e; 11 | }, 12 | 13 | // FIXME In some JS environs (FF 3.0?) we have to delete the getter/setter 14 | // before we overwrite it (I think). 15 | extend: function(destination, source) { 16 | for (var property in source) { 17 | var getter = source.__lookupGetter__(property); 18 | if (getter) 19 | destination.__defineGetter__(property, getter); 20 | var setter = source.__lookupSetter__(property); 21 | if (setter) 22 | destination.__defineSetter__(property, setter); 23 | if (!getter && !setter) 24 | destination[property] = source[property]; 25 | } 26 | return destination; 27 | }, 28 | 29 | // TODO needs testing (and likely some fixing!) 30 | deepClone: function(original /*, blacklist */) { 31 | var clone; 32 | var blacklist = Array.prototype.slice.call(arguments, 1); 33 | 34 | if (original && typeof original == 'object') { 35 | if (original instanceof Array) { 36 | clone = new Array(original.length); 37 | for (var i = 0; i < original.length; i++) 38 | clone[i] = (original[i] && original[i].deepClone) ? 39 | original[i].deepClone() : this.deepClone(original[i]); 40 | } 41 | else { 42 | clone = {}; 43 | clone.constructor = original.constructor; 44 | for (var p in original) { 45 | if (blacklist.indexOf(p) != -1) 46 | continue; 47 | var getter = original.__lookupGetter__(p); 48 | if (getter) 49 | clone.__defineGetter__(p, getter); 50 | var setter = original.__lookupSetter__(p); 51 | if (setter) 52 | clone.__defineSetter__(p, setter); 53 | if (!getter && !setter) 54 | clone[p] = (original[p] && original[p].deepClone) ? 55 | original[p].deepClone() : this.deepClone(original[p]); 56 | } 57 | } 58 | } else 59 | clone = original; 60 | 61 | return clone; 62 | }, 63 | 64 | around: function(prototype, functions, advice) { 65 | [].concat(functions).forEach(function(f) { 66 | if (f[0] === '=') { 67 | f = f.slice(1); 68 | var proceed = prototype.__lookupGetter__(f); 69 | prototype.__defineGetter__(f, function() { 70 | return advice.call(this, proceed, arguments); 71 | }); 72 | } else if (f[f.length - 1] === '=') { 73 | f = f.slice(0, f.length - 1); 74 | var proceed = prototype.__lookupSetter__(f); 75 | prototype.__defineSetter__(f, function() { 76 | return advice.call(this, proceed, arguments); 77 | }); 78 | } else { 79 | var proceed = prototype[f]; 80 | prototype[f] = function() { 81 | return advice.call(this, proceed, arguments); 82 | } 83 | } 84 | }); 85 | }, 86 | 87 | _loadedPackages: {}, 88 | packageLocations: {}, 89 | 90 | require: function() { 91 | Array.prototype.slice.call(arguments).forEach(function(name) { 92 | if (mico._loadedPackages[name]) 93 | return; 94 | 95 | var location = mico.packageLocations[name]; 96 | if (location) { 97 | if (!system.dlload(location) && !system.load(location)) 98 | throw 'Unable to find package: ' + name + ' at location: ' + location; 99 | } 100 | else { 101 | // TODO add the concept of paths (or repositories) here 102 | // FIXME we're not platform independent here 103 | var absName = name.replace('.', '/'); 104 | if (!system.dlload(absName + '/' + system.engine) && 105 | !system.load(absName + '.js')) 106 | throw 'Unable to find package: ' + name; 107 | } 108 | 109 | mico._loadedPackages[name] = true; 110 | }); 111 | } 112 | }; 113 | -------------------------------------------------------------------------------- /sdl/Makefile-gcc: -------------------------------------------------------------------------------- 1 | include ../config-gcc.mk 2 | 3 | spidermonkey$(COMPILED_PACKAGE_EXT): spidermonkey.c $(MAKEFILE_LIST) 4 | gcc $< $(CFLAGS) -shared -lSDL -o $@ 5 | 6 | clean: 7 | /bin/rm -f *$(COMPILED_PACKAGE_EXT) 8 | -------------------------------------------------------------------------------- /sdl/spidermonkey.c: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | #include "jsapi.h" 7 | #include "SDL/SDL.h" 8 | 9 | #define DEFAULT_WIDTH 600 10 | #define DEFAULT_HEIGHT 600 11 | 12 | #define DIE(s, ...) \ 13 | do { \ 14 | fprintf (stderr, "(%s:%4d) " s "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ 15 | exit (1); \ 16 | } while (0) 17 | 18 | #define ERROR_CHECK(f) \ 19 | if (!(f)) DIE ("JS engine error") 20 | 21 | static unsigned int 22 | timer_callback (unsigned int interval, void *param) 23 | { 24 | SDL_Event event; 25 | event.type = SDL_USEREVENT; 26 | event.user.data1 = param; 27 | SDL_PushEvent (&event); 28 | 29 | return 0; 30 | } 31 | 32 | static JSBool 33 | sdl_addTimer (JSContext *context, JSObject *object, uintN argc, 34 | jsval *argv, jsval *rval) 35 | { 36 | int delay; 37 | 38 | if (!JS_ValueToInt32 (context, argv[0], &delay)) 39 | return JS_FALSE; 40 | *rval = INT_TO_JSVAL ( 41 | SDL_AddTimer (delay, timer_callback, (void *) argv[1])); 42 | 43 | return JS_TRUE; 44 | } 45 | 46 | static JSBool 47 | sdl_loop (JSContext *context, JSObject *object, uintN argc, 48 | jsval *argv, jsval *rval) 49 | { 50 | SDL_Event event; 51 | for (;;) { 52 | jsval callback, rval; 53 | 54 | SDL_WaitEvent (&event); 55 | /* TODO change this to a callback too? In that case we have 56 | * to provide a way (in JS) to break out of the loop. 57 | */ 58 | if (event.type == SDL_QUIT) 59 | break; 60 | else if (event.type == SDL_USEREVENT) { 61 | JS_GetProperty (context, object, "ontimer", &callback); 62 | if (!JSVAL_IS_VOID (callback)) { 63 | JS_CallFunctionValue (context, object, callback, 1, 64 | (jsval *) (char *) &event.user.data1, &rval); 65 | } 66 | } 67 | /* TODO I think the shift mode should be queried, using a javascript 68 | * version of SDL_GetModState (with slightly different functionality) 69 | */ 70 | else if (event.type == SDL_MOUSEMOTION) { 71 | void *mark; 72 | jsval *argv; 73 | 74 | JS_GetProperty (context, object, "onmousemove", &callback); 75 | if (!JSVAL_IS_VOID (callback)) { 76 | argv = JS_PushArguments (context, &mark, "cc", 77 | event.motion.x, event.motion.y); 78 | JS_CallFunctionValue (context, object, callback, 2, argv, &rval); 79 | JS_PopArguments (context, mark); 80 | } 81 | } 82 | else if (event.type == SDL_MOUSEBUTTONDOWN) { 83 | void *mark; 84 | jsval *argv; 85 | 86 | JS_GetProperty (context, object, "onmousedown", &callback); 87 | if (!JSVAL_IS_VOID (callback)) { 88 | argv = JS_PushArguments (context, &mark, "cc", 89 | event.button.x, event.button.y); 90 | JS_CallFunctionValue (context, object, callback, 2, argv, &rval); 91 | JS_PopArguments (context, mark); 92 | } 93 | } 94 | else if (event.type == SDL_MOUSEBUTTONUP) { 95 | void *mark; 96 | jsval *argv; 97 | 98 | JS_GetProperty (context, object, "onmouseup", &callback); 99 | if (!JSVAL_IS_VOID (callback)) { 100 | argv = JS_PushArguments (context, &mark, "cc", 101 | event.button.x, event.button.y); 102 | JS_CallFunctionValue (context, object, callback, 2, argv, &rval); 103 | JS_PopArguments (context, mark); 104 | } 105 | } 106 | } 107 | return JS_TRUE; 108 | } 109 | 110 | static JSBool 111 | sdl_altKeyDown (JSContext *context, JSObject *object, 112 | uintN argc, jsval *argv, jsval *rval) 113 | { 114 | *rval = SDL_GetModState () & KMOD_ALT ? JSVAL_TRUE : JSVAL_FALSE; 115 | 116 | return JS_TRUE; 117 | } 118 | 119 | static JSBool 120 | sdl_shiftKeyDown (JSContext *context, JSObject *object, 121 | uintN argc, jsval *argv, jsval *rval) 122 | { 123 | *rval = SDL_GetModState () & KMOD_SHIFT ? JSVAL_TRUE : JSVAL_FALSE; 124 | 125 | return JS_TRUE; 126 | } 127 | 128 | /* TODO this should probably go in a 'finalize' function */ 129 | static JSBool 130 | sdl_quit (JSContext *context, JSObject *object, 131 | uintN argc, jsval *argv, jsval *rval) 132 | { 133 | SDL_Quit (); 134 | 135 | return JS_TRUE; 136 | } 137 | 138 | static JSBool 139 | sdl_videoSurface_get_width(JSContext *context, JSObject *object, 140 | jsval id, jsval *vp) 141 | { 142 | *vp = INT_TO_JSVAL (SDL_GetVideoSurface ()->w); 143 | return JS_TRUE; 144 | } 145 | 146 | static JSBool 147 | sdl_videoSurface_get_height(JSContext *context, JSObject *object, 148 | jsval id, jsval *vp) 149 | { 150 | *vp = INT_TO_JSVAL (SDL_GetVideoSurface ()->h); 151 | return JS_TRUE; 152 | } 153 | 154 | static JSBool 155 | sdl_videoSurface_get_bytesPerPixel(JSContext *context, JSObject *object, 156 | jsval id, jsval *vp) 157 | { 158 | *vp = INT_TO_JSVAL (SDL_GetVideoSurface ()->format->BytesPerPixel); 159 | return JS_TRUE; 160 | } 161 | 162 | static JSBool 163 | sdl_videoSurface_get_pitch(JSContext *context, JSObject *object, 164 | jsval id, jsval *vp) 165 | { 166 | *vp = INT_TO_JSVAL (SDL_GetVideoSurface ()->pitch); 167 | return JS_TRUE; 168 | } 169 | 170 | static JSBool 171 | sdl_videoSurface_get_pixels(JSContext *context, JSObject *object, 172 | jsval id, jsval *vp) 173 | { 174 | void *pixels = SDL_GetVideoSurface ()->pixels; 175 | /* FIXME hackety hack hack */ 176 | JS_NewNumberValue (context, *((double *) (char *) &pixels), vp); 177 | return JS_TRUE; 178 | } 179 | 180 | static JSBool 181 | sdl_videoSurface_update (JSContext *context, JSObject *object, 182 | uintN argc, jsval *argv, jsval *rval) 183 | { 184 | SDL_UpdateRect (SDL_GetVideoSurface (), 0, 0, 0, 0); 185 | return JS_TRUE; 186 | } 187 | 188 | void 189 | initialize (JSContext *context) 190 | { 191 | JSObject *sdl, *videoSurface; 192 | 193 | static JSFunctionSpec sdl_functions[] = { 194 | {"quit", sdl_quit, 0, 0, 0}, 195 | {"addTimer", sdl_addTimer, 2, 0, 0}, 196 | {"loop", sdl_loop, 0, 0, 0}, 197 | {"altKeyDown", sdl_altKeyDown, 0, 0, 0}, 198 | {"shiftKeyDown", sdl_shiftKeyDown, 0, 0, 0}, 199 | {NULL, NULL, 0, 0, 0} 200 | }; 201 | 202 | if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) 203 | DIE ("SDL_Init failed: %s", SDL_GetError ()); 204 | if (!SDL_SetVideoMode (DEFAULT_WIDTH, DEFAULT_HEIGHT, 0, 205 | SDL_HWSURFACE | SDL_ANYFORMAT)) 206 | DIE ("SDL_SetVideoMode failed: %s", SDL_GetError ()); 207 | ERROR_CHECK(sdl = JS_DefineObject (context, JS_GetGlobalObject (context), 208 | "sdl", NULL, NULL, 0)); 209 | ERROR_CHECK (JS_DefineFunctions (context, sdl, sdl_functions)); 210 | ERROR_CHECK (videoSurface = JS_DefineObject (context, sdl, "videoSurface", 211 | NULL, NULL, 0)); 212 | 213 | #define DEFINE_READ_ONLY_PROPERTY(object, name) \ 214 | ERROR_CHECK ( \ 215 | JS_DefineProperty (context, object, #name, JSVAL_NULL, \ 216 | sdl_##object##_get_##name, NULL, \ 217 | JSPROP_READONLY | JSPROP_PERMANENT)) 218 | 219 | DEFINE_READ_ONLY_PROPERTY (videoSurface, width); 220 | DEFINE_READ_ONLY_PROPERTY (videoSurface, height); 221 | DEFINE_READ_ONLY_PROPERTY (videoSurface, bytesPerPixel); 222 | DEFINE_READ_ONLY_PROPERTY (videoSurface, pitch); 223 | DEFINE_READ_ONLY_PROPERTY (videoSurface, pixels); 224 | JS_DefineFunction (context, videoSurface, "update", 225 | sdl_videoSurface_update, 0, 0); 226 | /* FIXME hack for demo! Should pull out of title element... */ 227 | SDL_WM_SetCaption ("Mico - Sun Labs Lively Kernel", "Mico"); 228 | } 229 | -------------------------------------------------------------------------------- /sdl/stubs.js: -------------------------------------------------------------------------------- 1 | var sdl = { 2 | addTimer: function(delay, i) { 3 | /* TODO add some recursion detection and allow for returns 4 | * back to the top level, then trigger timers again...? 5 | */ 6 | sdl.ontimer(i); 7 | }, 8 | loop: function() {} 9 | }; 10 | 11 | sdl.videoSurface = { 12 | update: function() {} 13 | }; 14 | -------------------------------------------------------------------------------- /sdl/window.js: -------------------------------------------------------------------------------- 1 | mico.require('sdl'); 2 | mico.require('dom.graphics'); 3 | mico.require('dom.events'); 4 | 5 | var window = this; 6 | 7 | mico.extend(window, { 8 | parent: window, 9 | alert: system.print, 10 | console: {log: system.print}, 11 | navigator: {userAgent: "Mico"}, 12 | _timers: [], 13 | 14 | setTimeout: function(action, delay) { 15 | var i = 0; 16 | while (window._timers[i]) 17 | i++; 18 | var timer = window._timers[i] = {action: action}; 19 | // NOTE timer may fire before the assignment occurs 20 | timer.id = sdl.addTimer(delay, i); 21 | return i; 22 | }, 23 | 24 | dispatchEvent: function(event) { 25 | window.document && window.document.documentElement.dispatchEvent(event); 26 | window.refresh(); 27 | }, 28 | 29 | refresh: function() { 30 | dom.graphics.update(); 31 | /* FIXME ugly hack */ 32 | if (window._graphics) 33 | window._graphics.render(window.document._graphics); 34 | sdl.videoSurface.update(); 35 | } 36 | }); 37 | 38 | sdl.ontimer = function(i) { 39 | var timer = window._timers[i]; 40 | if (timer) { 41 | window._timers[i] = null; 42 | timer.action.apply(window); 43 | window.refresh(); 44 | } 45 | }; 46 | 47 | sdl.onmousemove = function(x, y) { 48 | var event = new dom.MouseEvent; 49 | event._type = 'mousemove'; 50 | event._shiftKey = sdl.shiftKeyDown (); 51 | event._altKey = sdl.altKeyDown (); 52 | event._clientX = x; 53 | event._clientY = y; 54 | window.dispatchEvent(event); 55 | }; 56 | 57 | sdl.onmousedown = function(x, y) { 58 | var event = new dom.MouseEvent; 59 | event._type = 'mousedown'; 60 | event._shiftKey = sdl.shiftKeyDown(); 61 | event._altKey = sdl.altKeyDown(); 62 | event._clientX = x; 63 | event._clientY = y; 64 | window.dispatchEvent(event); 65 | }; 66 | 67 | sdl.onmouseup = function(x, y) { 68 | var event = new dom.MouseEvent; 69 | event._type = 'mouseup'; 70 | event._shiftKey = sdl.shiftKeyDown(); 71 | event._altKey = sdl.altKeyDown(); 72 | event._clientX = x; 73 | event._clientY = y; 74 | window.dispatchEvent(event); 75 | }; 76 | -------------------------------------------------------------------------------- /system/Makefile-gcc: -------------------------------------------------------------------------------- 1 | include ../config-gcc.mk 2 | 3 | mico-spidermonkey: spidermonkey.c $(MAKEFILE_LIST) 4 | g++ $< $(CFLAGS) -rdynamic -Wl,-Bstatic -ljs -Wl,-Bdynamic -ldl -lm -o $@ 5 | 6 | clean: 7 | /bin/rm -f mico-spidermonkey 8 | -------------------------------------------------------------------------------- /system/browser.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | var system = { 7 | engine: 'browser', 8 | 9 | // TODO this goes away after open() gets implemented 10 | print: function(s) { console.log(s); }, 11 | 12 | open: function(filename, action) { 13 | // TODO maybe do an XHR here? 14 | }, 15 | 16 | dlload: function(filename) {}, 17 | 18 | load: function(filename) { 19 | // TODO use script tag trick? 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /system/shell.js: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | var system = { 7 | engine: 'shell', 8 | 9 | // TODO this goes away after open() gets implemented 10 | print: function(s) { print(s); }, 11 | 12 | open: function(filename, action) { 13 | // TODO readFile() or some common shell API? 14 | }, 15 | 16 | dlload: function(filename) {}, 17 | 18 | load: function() { 19 | Array.prototype.forEach.call(arguments, function (filename) { 20 | load(filename); 21 | }); 22 | return true; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /system/spidermonkey.c: -------------------------------------------------------------------------------- 1 | /* This file is part of the Mico project, available under the MIT license: 2 | * 3 | * http://www.opensource.org/licenses/mit-license.php 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include "jsapi.h" 10 | 11 | #define HEAP_SIZE_LIMIT (8 * 1024 * 1024) 12 | #define STACK_CHUNK_SIZE (8192) 13 | #define NCALLBACKS_BEFORE_WE_MAYBEGC (1000) 14 | 15 | #define DIE(s, ...) \ 16 | do { \ 17 | fprintf (stderr, "(%s:%4d) " s "\n", __FILE__, __LINE__, ##__VA_ARGS__); \ 18 | exit (1); \ 19 | } while (0) 20 | 21 | #define ERROR_CHECK(f) \ 22 | if (!(f)) DIE ("JS engine error") 23 | 24 | static JSBool 25 | branch_callback (JSContext *context, JSScript *script) 26 | { 27 | static int ncallbacks; 28 | 29 | if (ncallbacks == NCALLBACKS_BEFORE_WE_MAYBEGC) { 30 | JS_MaybeGC (context); 31 | ncallbacks = 0; 32 | } 33 | else 34 | ncallbacks++; 35 | 36 | return JS_TRUE; 37 | } 38 | 39 | static void 40 | error_reporter (JSContext *context, const char *message, JSErrorReport *report) 41 | { 42 | fprintf (stderr, "%s:%u:%s\n", 43 | report->filename ? report->filename : "", 44 | (unsigned int) report->lineno, message); 45 | 46 | exit (1); 47 | } 48 | 49 | static JSBool 50 | system_open (JSContext *context, JSObject *object, 51 | uintN argc, jsval *argv, jsval *rval) 52 | { 53 | /* TODO */ 54 | return JS_TRUE; 55 | } 56 | 57 | /* TODO replace this (or implement it in JS) with system.open(1, ) */ 58 | static JSBool 59 | system_print (JSContext *context, JSObject *object, 60 | uintN argc, jsval *argv, jsval *rval) 61 | { 62 | JSString *string; 63 | 64 | if (!(string = JS_ValueToString (context, argv[0]))) 65 | return JS_FALSE; 66 | printf ("%s\n", JS_GetStringBytes (string)); 67 | 68 | return JS_TRUE; 69 | } 70 | 71 | static JSBool 72 | system_dlload (JSContext *context, JSObject *object, 73 | uintN argc, jsval *argv, jsval *rval) 74 | { 75 | void *library; 76 | char *filename, *filename_with_ext; 77 | void (*initialize) (JSContext *context); 78 | JSString *string; 79 | unsigned int i; 80 | 81 | for (i = 0; i < argc; i++) { 82 | string = JS_ValueToString (context, argv[i]); 83 | if (!string) 84 | return JS_FALSE; 85 | filename = JS_GetStringBytes (string); 86 | filename_with_ext = (char *) malloc (strlen (filename) + 87 | strlen (COMPILED_PACKAGE_EXT) + 1); 88 | sprintf (filename_with_ext, "%s%s", filename, COMPILED_PACKAGE_EXT); 89 | 90 | /* TODO don't even try if file doesn't exist */ 91 | library = dlopen (filename_with_ext, RTLD_LAZY | RTLD_GLOBAL); 92 | if (!library) { 93 | //printf ("dlopen error: %s\n", dlerror ()); 94 | free (filename_with_ext); 95 | *rval = JSVAL_NULL; 96 | return JS_TRUE; 97 | } 98 | free (filename_with_ext); 99 | 100 | initialize = (void (*) (JSContext *)) dlsym (library, "initialize"); 101 | if (!initialize) { 102 | *rval = JSVAL_NULL; 103 | return JS_TRUE; 104 | } 105 | 106 | initialize (context); 107 | } 108 | 109 | *rval = JSVAL_TRUE; 110 | return JS_TRUE; 111 | } 112 | 113 | static JSBool 114 | system_load (JSContext *context, JSObject *object, 115 | uintN argc, jsval *argv, jsval *rval) 116 | { 117 | JSScript *script; 118 | jsval val; 119 | JSObject *global; 120 | JSString *string; 121 | char *filename; 122 | FILE *f; 123 | unsigned int i; 124 | 125 | *rval = JSVAL_TRUE; 126 | global = JS_GetGlobalObject (context); 127 | 128 | for (i = 0; i < argc; i++) { 129 | string = JS_ValueToString (context, argv[i]); 130 | if (!string) 131 | return JS_FALSE; 132 | filename = JS_GetStringBytes (string); 133 | 134 | f = fopen (filename, "r"); 135 | if (!f) { 136 | *rval = JSVAL_FALSE; 137 | break; 138 | } 139 | fclose (f); 140 | 141 | if (!(script = JS_CompileFile (context, global, filename))) { 142 | if (JS_IsExceptionPending (context)) { 143 | JS_ReportPendingException (context); 144 | /* 145 | jsval val; 146 | JS_GetPendingException (context, &val); 147 | JS_ThrowReportedError (context, "system.load failed", 148 | JS_ErrorFromException (context, val)); 149 | */ 150 | } 151 | *rval = JSVAL_FALSE; 152 | break; 153 | } 154 | if (!JS_ExecuteScript (context, global, script, &val) && 155 | JS_IsExceptionPending (context)) 156 | JS_ReportPendingException (context); 157 | JS_DestroyScript (context, script); 158 | } 159 | 160 | return JS_TRUE; 161 | } 162 | 163 | int 164 | main (int argc, const char *argv[]) 165 | { 166 | JSRuntime *runtime; 167 | JSContext *context; 168 | JSObject *global, *system; 169 | JSScript *script; 170 | jsval val; 171 | 172 | JSClass global_class = { 173 | "global", JSCLASS_GLOBAL_FLAGS, 174 | JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, 175 | JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, 176 | JSCLASS_NO_OPTIONAL_MEMBERS 177 | }; 178 | 179 | JSFunctionSpec system_functions[] = { 180 | {"print", system_print, 1, 0, 0}, 181 | {"open", system_open, 2, 0, 0}, 182 | {"dlload", system_dlload, 1, 0, 0}, 183 | {"load", system_load, 1, 0, 0}, 184 | {NULL, NULL, 0, 0, 0} 185 | }; 186 | 187 | ERROR_CHECK (runtime = JS_NewRuntime (HEAP_SIZE_LIMIT)); 188 | ERROR_CHECK (context = JS_NewContext (runtime, STACK_CHUNK_SIZE)); 189 | JS_SetOptions (context, JSOPTION_VAROBJFIX); 190 | #ifdef JSOPTION_JIT 191 | JS_SetOptions (context, JSOPTION_JIT); 192 | #endif 193 | ERROR_CHECK (global = JS_NewObject (context, &global_class, NULL, NULL)); 194 | ERROR_CHECK (JS_InitStandardClasses (context, global)); 195 | ERROR_CHECK (system = JS_DefineObject (context, global, "system", 196 | NULL, NULL, 0)); 197 | val = STRING_TO_JSVAL (JS_NewStringCopyZ (context, "spidermonkey")); 198 | ERROR_CHECK (JS_SetProperty (context, system, "engine", &val)); 199 | ERROR_CHECK (JS_DefineFunctions (context, system, system_functions)); 200 | 201 | JS_SetErrorReporter (context, error_reporter); 202 | JS_SetBranchCallback (context, branch_callback); 203 | if (argc > 1) { 204 | ERROR_CHECK (script = JS_CompileFile (context, global, argv[1])); 205 | ERROR_CHECK (JS_ExecuteScript (context, global, script, &val)); 206 | JS_DestroyScript (context, script); 207 | } 208 | 209 | JS_DestroyContext (context); 210 | JS_DestroyRuntime (runtime); 211 | JS_ShutDown (); 212 | 213 | return 0; 214 | } 215 | --------------------------------------------------------------------------------