Added support for events, made window.location Async (and based upon XHR), added...
[jquery.git] / build / runtest / env.js
1 /*
2  * Simulated browser environment for Rhino
3  *   By John Resig <http://ejohn.org/>
4  * Copyright 2007 John Resig, under the MIT License
5  */
6
7 // The window Object
8 var window = this;
9
10 (function(){
11
12         // Browser Navigator
13
14         window.navigator = {
15                 get userAgent(){
16                         return "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3";
17                 }
18         };
19         
20         var curLocation = (new java.io.File("./")).toURL();
21         
22         window.__defineSetter__("location", function(url){
23                 var xhr = new XMLHttpRequest();
24                 xhr.open("GET", url);
25                 xhr.onreadystatechange = function(){
26                         curLocation = new java.net.URL( curLocation, url );
27                         window.document = xhr.responseXML;
28
29                         var event = document.createEvent();
30                         event.initEvent("load");
31                         window.dispatchEvent( event );
32                 };
33                 xhr.send();
34         });
35         
36         window.__defineGetter__("location", function(url){
37                 return {
38                         get protocol(){
39                                 return curLocation.getProtocol() + ":";
40                         },
41                         get href(){
42                                 return curLocation.toString();
43                         },
44                         toString: function(){
45                                 return this.href;
46                         }
47                 };
48         });
49         
50         // Timers
51
52         var timers = [];
53         
54         window.setTimeout = function(fn, time){
55                 var num;
56                 return num = setInterval(function(){
57                         fn();
58                         clearInterval(num);
59                 }, time);
60         };
61         
62         window.setInterval = function(fn, time){
63                 var num = timers.length;
64                 
65                 timers[num] = new java.lang.Thread(new java.lang.Runnable({
66                         run: function(){
67                                 while (true){
68                                         java.lang.Thread.currentThread().sleep(time);
69                                         fn();
70                                 }
71                         }
72                 }));
73                 
74                 timers[num].start();
75         
76                 return num;
77         };
78         
79         window.clearInterval = function(num){
80                 if ( timers[num] ) {
81                         timers[num].stop();
82                         delete timers[num];
83                 }
84         };
85         
86         // Window Events
87         
88         var events = [{}];
89
90         window.addEventListener = function(type, fn){
91                 if ( !this.uuid || this == window ) {
92                         this.uuid = events.length;
93                         events[this.uuid] = {};
94                 }
95            
96                 if ( !events[this.uuid][type] )
97                         events[this.uuid][type] = [];
98                 
99                 if ( events[this.uuid][type].indexOf( fn ) < 0 )
100                         events[this.uuid][type].push( fn );
101         };
102         
103         window.removeEventListener = function(type, fn){
104            if ( !this.uuid || this == window ) {
105                this.uuid = events.length;
106                events[this.uuid] = {};
107            }
108            
109            if ( !events[this.uuid][type] )
110                         events[this.uuid][type] = [];
111                         
112                 events[this.uuid][type] =
113                         events[this.uuid][type].filter(function(f){
114                                 return f != fn;
115                         });
116         };
117         
118         window.dispatchEvent = function(event){
119                 if ( event.type ) {
120                         if ( this.uuid && events[this.uuid][event.type] ) {
121                                 var self = this;
122                         
123                                 events[this.uuid][event.type].forEach(function(fn){
124                                         fn.call( self, event );
125                                 });
126                         }
127                         
128                         if ( this["on" + event.type] )
129                                 this["on" + event.type].call( self, event );
130                 }
131         };
132         
133         // DOM Document
134         
135         window.DOMDocument = function(file){
136                 this._file = file;
137                 this._dom = Packages.javax.xml.parsers.
138                         DocumentBuilderFactory.newInstance()
139                                 .newDocumentBuilder().parse(file);
140                 
141                 if ( !obj_nodes.containsKey( this._dom ) )
142                         obj_nodes.put( this._dom, this );
143         };
144         
145         DOMDocument.prototype = {
146                 createTextNode: function(text){
147                         return makeNode( this._dom.createTextNode(
148                                 text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")) );
149                 },
150                 createElement: function(name){
151                         return makeNode( this._dom.createElement(name.toLowerCase()) );
152                 },
153                 getElementsByTagName: function(name){
154                         return new DOMNodeList( this._dom.getElementsByTagName(
155                                 name.toLowerCase()) );
156                 },
157                 getElementById: function(id){
158                         var elems = this._dom.getElementsByTagName("*");
159                         
160                         for ( var i = 0; i < elems.length; i++ ) {
161                                 var elem = elems.item(i);
162                                 if ( elem.getAttribute("id") == id )
163                                         return makeNode(elem);
164                         }
165                         
166                         return null;
167                 },
168                 get body(){
169                         return this.getElementsByTagName("body")[0];
170                 },
171                 get documentElement(){
172                         return makeNode( this._dom.getDocumentElement() );
173                 },
174                 get ownerDocument(){
175                         return null;
176                 },
177                 addEventListener: window.addEventListener,
178                 removeEventListener: window.removeEventListener,
179                 dispatchEvent: window.dispatchEvent,
180                 get nodeName() {
181                         return "#document";
182                 },
183                 importNode: function(node, deep){
184                         return makeNode( this._dom.importNode(node._dom, deep) );
185                 },
186                 toString: function(){
187                         return "Document" + (typeof this._file == "string" ?
188                                 ": " + this._file : "");
189                 },
190                 
191                 get defaultView(){
192                         return {
193                                 getComputedStyle: function(elem){
194                                         return {
195                                                 getPropertyValue: function(prop){
196                                                         prop = prop.replace(/\-(\w)/g,function(m,c){
197                                                                 return c.toUpperCase();
198                                                         });
199                                                         var val = elem.style[prop];
200                                                         
201                                                         if ( prop == "opacity" && val == "" )
202                                                                 val = "1";
203                                                                 
204                                                         return val;
205                                                 }
206                                         };
207                                 }
208                         };
209                 },
210                 
211                 createEvent: function(){
212                         return {
213                                 type: "",
214                                 initEvent: function(type){
215                                         this.type = type;
216                                 }
217                         };
218                 }
219         };
220         
221         function getDocument(node){
222                 return obj_nodes.get(node);
223         }
224         
225         // DOM NodeList
226         
227         window.DOMNodeList = function(list){
228                 this._dom = list;
229                 this.length = list.getLength();
230                 
231                 for ( var i = 0; i < this.length; i++ ) {
232                         var node = list.item(i);
233                         this[i] = makeNode( node );
234                 }
235         };
236         
237         DOMNodeList.prototype = {
238                 toString: function(){
239                         return "[ " +
240                                 Array.prototype.join.call( this, ", " ) + " ]";
241                 },
242                 valueOf: function(){
243                         return Array.prototype.map.call(
244                                 this, function(node){return node.valueOf();}).join('');
245                 }
246         };
247         
248         // DOM Node
249         
250         window.DOMNode = function(node){
251                 this._dom = node;
252         };
253         
254         DOMNode.prototype = {
255                 get nodeType(){
256                         return this._dom.getNodeType();
257                 },
258                 get nodeValue(){
259                         return this._dom.getNodeValue();
260                 },
261                 get nodeName() {
262                         return this._dom.getNodeName();
263                 },
264                 cloneNode: function(deep){
265                         return makeNode( this._dom.cloneNode(deep) );
266                 },
267                 get ownerDocument(){
268                         return getDocument( this._dom.ownerDocument );
269                 },
270                 get documentElement(){
271                         return makeNode( this._dom.documentElement );
272                 },
273                 get parentNode() {
274                         return makeNode( this._dom.getParentNode() );
275                 },
276                 get nextSibling() {
277                         return makeNode( this._dom.getNextSibling() );
278                 },
279                 get previousSibling() {
280                         return makeNode( this._dom.getPreviousSibling() );
281                 },
282                 toString: function(){
283                         return '"' + this.nodeValue + '"';
284                 },
285                 valueOf: function(){
286                         return this.nodeValue;
287                 }
288         };
289
290         // DOM Element
291
292         window.DOMElement = function(elem){
293                 this._dom = elem;
294                 this.style = {
295                         get opacity(){ return this._opacity; },
296                         set opacity(val){ this._opacity = val + ""; }
297                 };
298                 
299                 // Load CSS info
300                 var styles = (this.getAttribute("style") || "").split(/\s*;\s*/);
301                 
302                 for ( var i = 0; i < styles.length; i++ ) {
303                         var style = styles[i].split(/\s*:\s*/);
304                         if ( style.length == 2 )
305                                 this.style[ style[0] ] = style[1];
306                 }
307         };
308         
309         DOMElement.prototype = extend( new DOMNode(), {
310                 get nodeName(){
311                         return this.tagName.toUpperCase();
312                 },
313                 get tagName(){
314                         return this._dom.getTagName();
315                 },
316                 toString: function(){
317                         return "<" + this.tagName + (this.id ? "#" + this.id : "" ) + ">";
318                 },
319                 valueOf: function(){
320                         var ret = "<" + this.tagName, attr = this.attributes;
321                         
322                         for ( var i in attr )
323                                 ret += " " + i + "='" + attr[i] + "'";
324                                 
325                         if ( this.childNodes.length || this.nodeName == "SCRIPT" )
326                                 ret += ">" + this.childNodes.valueOf() + 
327                                         "</" + this.tagName + ">";
328                         else
329                                 ret += "/>";
330                         
331                         return ret;
332                 },
333                 
334                 get attributes(){
335                         var attr = {}, attrs = this._dom.getAttributes();
336                         
337                         for ( var i = 0; i < attrs.getLength(); i++ )
338                                 attr[ attrs.item(i).nodeName ] = attrs.item(i).nodeValue;
339                                 
340                         return attr;
341                 },
342                 
343                 get innerHTML(){
344                         return this.childNodes.valueOf();       
345                 },
346                 set innerHTML(html){
347                         html = html.replace(/<\/?([A-Z]+)/g, function(m){
348                                 return m.toLowerCase();
349                         });
350                         
351                         var nodes = this.ownerDocument.importNode(
352                                 new DOMDocument( new java.io.ByteArrayInputStream(
353                                         (new java.lang.String("<wrap>" + html + "</wrap>"))
354                                                 .getBytes("UTF8"))).documentElement, true).childNodes;
355                                 
356                         while (this.firstChild)
357                                 this.removeChild( this.firstChild );
358                         
359                         for ( var i = 0; i < nodes.length; i++ )
360                                 this.appendChild( nodes[i] );
361                 },
362                 
363                 get textContent(){
364                         return nav(this.childNodes);
365                         
366                         function nav(nodes){
367                                 var str = "";
368                                 for ( var i = 0; i < nodes.length; i++ )
369                                         if ( nodes[i].nodeType == 3 )
370                                                 str += nodes[i].nodeValue;
371                                         else if ( nodes[i].nodeType == 1 )
372                                                 str += nav(nodes[i].childNodes);
373                                 return str;
374                         }
375                 },
376                 set textContent(text){
377                         while (this.firstChild)
378                                 this.removeChild( this.firstChild );
379                         this.appendChild( this.ownerDocument.createTextNode(text));
380                 },
381                 
382                 style: {},
383                 clientHeight: 0,
384                 clientWidth: 0,
385                 offsetHeight: 0,
386                 offsetWidth: 0,
387                 
388                 get disabled() {
389                         var val = this.getAttribute("disabled");
390                         return val != "false" && !!val;
391                 },
392                 set disabled(val) { return this.setAttribute("disabled",val); },
393                 
394                 get checked() {
395                         var val = this.getAttribute("checked");
396                         return val != "false" && !!val;
397                 },
398                 set checked(val) { return this.setAttribute("checked",val); },
399                 
400                 get selected() {
401                         if ( !this._selectDone ) {
402                                 this._selectDone = true;
403                                 
404                                 if ( this.nodeName == "OPTION" && !this.parentNode.getAttribute("multiple") ) {
405                                         var opt = this.parentNode.getElementsByTagName("option");
406                                         
407                                         if ( this == opt[0] ) {
408                                                 var select = true;
409                                                 
410                                                 for ( var i = 1; i < opt.length; i++ )
411                                                         if ( opt[i].selected ) {
412                                                                 select = false;
413                                                                 break;
414                                                         }
415                                                         
416                                                 if ( select )
417                                                         this.selected = true;
418                                         }
419                                 }
420                         }
421                         
422                         var val = this.getAttribute("selected");
423                         return val != "false" && !!val;
424                 },
425                 set selected(val) { return this.setAttribute("selected",val); },
426
427                 get className() { return this.getAttribute("class") || ""; },
428                 set className(val) {
429                         return this.setAttribute("class",
430                                 val.replace(/(^\s*|\s*$)/g,""));
431                 },
432                 
433                 get type() { return this.getAttribute("type") || ""; },
434                 set type(val) { return this.setAttribute("type",val); },
435                 
436                 get value() { return this.getAttribute("value") || ""; },
437                 set value(val) { return this.setAttribute("value",val); },
438                 
439                 get src() { return this.getAttribute("src") || ""; },
440                 set src(val) { return this.setAttribute("src",val); },
441                 
442                 get id() { return this.getAttribute("id") || ""; },
443                 set id(val) { return this.setAttribute("id",val); },
444                 
445                 getAttribute: function(name){
446                         return this._dom.hasAttribute(name) ?
447                                 new String( this._dom.getAttribute(name) ) :
448                                 null;
449                 },
450                 setAttribute: function(name,value){
451                         this._dom.setAttribute(name,value);
452                 },
453                 removeAttribute: function(name){
454                         this._dom.removeAttribute(name);
455                 },
456                 
457                 get childNodes(){
458                         return new DOMNodeList( this._dom.getChildNodes() );
459                 },
460                 get firstChild(){
461                         return makeNode( this._dom.getFirstChild() );
462                 },
463                 get lastChild(){
464                         return makeNode( this._dom.getLastChild() );
465                 },
466                 appendChild: function(node){
467                         this._dom.appendChild( node._dom );
468                 },
469                 insertBefore: function(node,before){
470                         this._dom.insertBefore( node._dom, before ? before._dom : before );
471                 },
472                 removeChild: function(node){
473                         this._dom.removeChild( node._dom );
474                 },
475
476                 getElementsByTagName: DOMDocument.prototype.getElementsByTagName,
477                 
478                 addEventListener: window.addEventListener,
479                 removeEventListener: window.removeEventListener,
480                 dispatchEvent: window.dispatchEvent,
481                 
482                 click: function(){
483                         var event = document.createEvent();
484                         event.initEvent("click");
485                         this.dispatchEvent(event);
486                 },
487                 submit: function(){
488                         var event = document.createEvent();
489                         event.initEvent("submit");
490                         this.dispatchEvent(event);
491                 },
492                 focus: function(){
493                         var event = document.createEvent();
494                         event.initEvent("focus");
495                         this.dispatchEvent(event);
496                 },
497                 blur: function(){
498                         var event = document.createEvent();
499                         event.initEvent("blur");
500                         this.dispatchEvent(event);
501                 },
502                 get elements(){
503                         return this.getElementsByTagName("*");
504                 },
505                 get contentWindow(){
506                         return this.nodeName == "IFRAME" ? {
507                                 document: this.contentDocument
508                         } : null;
509                 },
510                 get contentDocument(){
511                         if ( this.nodeName == "IFRAME" ) {
512                                 if ( !this._doc )
513                                         this._doc = new DOMDocument(
514                                                 new java.io.ByteArrayInputStream((new java.lang.String(
515                                                 "<html><head><title></title></head><body></body></html>"))
516                                                 .getBytes("UTF8")));
517                                 return this._doc;
518                         } else
519                                 return null;
520                 }
521         });
522         
523         // Helper method for extending one object with another
524         
525         function extend(a,b) {
526                 for ( var i in b ) {
527                         var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
528                         
529                         if ( g || s ) {
530                                 if ( g )
531                                         a.__defineGetter__(i, g);
532                                 if ( s )
533                                         a.__defineSetter__(i, s);
534                         } else
535                                 a[i] = b[i];
536                 }
537                 return a;
538         }
539         
540         // Helper method for generating the right
541         // DOM objects based upon the type
542         
543         var obj_nodes = new java.util.HashMap();
544         
545         function makeNode(node){
546                 if ( node ) {
547                         if ( !obj_nodes.containsKey( node ) )
548                                 obj_nodes.put( node, node.getNodeType() == 
549                                         Packages.org.w3c.dom.Node.ELEMENT_NODE ?
550                                                 new DOMElement( node ) : new DOMNode( node ) );
551                         
552                         return obj_nodes.get(node);
553                 } else
554                         return null;
555         }
556         
557         // XMLHttpRequest
558         // Originally implemented by Yehuda Katz
559
560         window.XMLHttpRequest = function(){
561                 this.headers = {};
562                 this.responseHeaders = {};
563         };
564         
565         XMLHttpRequest.prototype = {
566                 open: function(method, url, async, user, password){ 
567                         this.readyState = 1;
568                         if (async)
569                                 this.async = true;
570                         this.method = method || "GET";
571                         this.url = url;
572                         this.onreadystatechange();
573                 },
574                 setRequestHeader: function(header, value){
575                         this.headers[header] = value;
576                 },
577                 getResponseHeader: function(header){ },
578                 send: function(data){
579                         var self = this;
580                         
581                         function makeRequest(){
582                                 var url = new java.net.URL(curLocation, self.url),
583                                         connection = url.openConnection();
584                                 
585                                 // Add headers to Java connection
586                                 for (var header in self.headers)
587                                         connection.addRequestProperty(header, self.headers[header]);
588                         
589                                 connection.connect();
590                                 
591                                 // Stick the response headers into responseHeaders
592                                 for (var i=0; ; i++) { 
593                                         var headerName = connection.getHeaderFieldKey(i); 
594                                         var headerValue = connection.getHeaderField(i); 
595                                         if (!headerName && !headerValue) break; 
596                                         if (headerName)
597                                                 self.responseHeaders[headerName] = headerValue;
598                                 }
599                                 
600                                 self.readyState = 4;
601                                 self.status = parseInt(connection.responseCode);
602                                 self.statusText = connection.responseMessage;
603                                 
604                                 var stream = new java.io.InputStreamReader(
605                                                 connection.getInputStream()),
606                                         buffer = new java.io.BufferedReader(stream),
607                                         line;
608                                 
609                                 while ((line = buffer.readLine()) != null)
610                                         self.responseText += line;
611                                         
612                                 self.responseXML = null;
613                                 
614                                 if ( self.responseText.match(/^\s*</) ) {
615                                         try {
616                                                 self.responseXML = new DOMDocument(
617                                                         new java.io.ByteArrayInputStream(
618                                                                 (new java.lang.String(
619                                                                         self.responseText)).getBytes("UTF8")));
620                                         } catch(e) {}
621                                 }
622
623                                 self.onreadystatechange();
624                         }
625
626                         if (this.async)
627                                 (new java.lang.Thread(new java.lang.Runnable({
628                                         run: makeRequest
629                                 }))).start();
630                         else
631                                 makeRequest();
632                 },
633                 abort: function(){},
634                 onreadystatechange: function(){},
635                 getResponseHeader: function(header){
636                         if (this.readyState < 3)
637                                 throw new Error("INVALID_STATE_ERR");
638                         else {
639                                 var returnedHeaders = [];
640                                 for (var rHeader in this.responseHeaders) {
641                                         if (rHeader.match(new Regexp(header, "i")))
642                                                 returnedHeaders.push(this.responseHeaders[rHeader]);
643                                 }
644                         
645                                 if (returnedHeaders.length)
646                                         return returnedHeaders.join(", ");
647                         }
648                         
649                         return null;
650                 },
651                 getAllResponseHeaders: function(header){
652                         if (this.readyState < 3)
653                                 throw new Error("INVALID_STATE_ERR");
654                         else {
655                                 var returnedHeaders = [];
656                                 
657                                 for (var header in this.responseHeaders)
658                                         returnedHeaders.push( header + ": " + this.responseHeaders[header] );
659                                 
660                                 return returnedHeaders.join("\r\n");
661                         }
662                 },
663                 async: true,
664                 readyState: 0,
665                 responseText: "",
666                 status: 0
667         };
668 })();