source: Dev/trunk/d3/lib/env-js/envjs/window.js @ 76

Last change on this file since 76 was 76, checked in by fpvanagthoven, 14 years ago

d3

File size: 24.3 KB
Line 
1/*
2 * Envjs window.1.3.pre03
3 * Pure JavaScript Browser Environment
4 * By John Resig <http://ejohn.org/> and the Envjs Team
5 * Copyright 2008-2010 John Resig, under the MIT License
6
7This module leaks the following global definitions.
8
9var Window,
10    Screen,
11    History,
12    Navigator;
13
14 */
15
16var Envjs               = Envjs             || require('./platform/core').Envjs,
17    DOMImplementation   = DOMImplementation || require('./dom').DOMImplementation,
18    HTMLDocument        = HTMLDocument      || require('./html').HTMLDocument,
19    HTMLFrameElement    = HTMLFrameElement  || require('./html').HTMLFrameElement,
20    HTMLIFrameElement   = HTMLIFrameElement || require('./html').HTMLIFrameElement,
21    HTMLParser          = HTMLParser        || require('./parser').HTMLParser,
22    Location            = Location          || require('./xhr').Location,
23    CSSRule             = CSSRule           || require('./css').CSSRule;
24
25
26/*
27 * Envjs window.1.3.pre03
28 * Pure JavaScript Browser Environment
29 * By John Resig <http://ejohn.org/> and the Envjs Team
30 * Copyright 2008-2010 John Resig, under the MIT License
31 */
32
33//CLOSURE_START
34(function(){
35
36
37
38
39
40/**
41 * @author john resig
42 */
43// Helper method for extending one object with another.
44function __extend__(a,b) {
45    for ( var i in b ) {
46        if(b.hasOwnProperty(i)){
47            var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
48            if ( g || s ) {
49                if ( g ) { a.__defineGetter__(i, g); }
50                if ( s ) { a.__defineSetter__(i, s); }
51            } else {
52                a[i] = b[i];
53            }
54        }
55    }
56    return a;
57}
58
59/**
60 * Frame/Window Mixin
61 */
62(function(){
63
64var log = Envjs.logger();
65Envjs.once('tick', function(){
66        log = Envjs.logger('Envjs.HTML.Frame').
67                debug('html frame logger available');
68});
69
70__extend__(HTMLFrameElement.prototype,{
71
72    set src(value){
73        var event;
74        this.setAttribute('src', value);
75                //only load if we are already appended to the dom
76        if (this.parentNode && value && value.length > 0){
77            log.debug('loading frame via set src %s', value);
78            Envjs.loadFrame(this, Envjs.uri(value, this.ownerDocument?this.ownerDocument.location+'':null));
79        }
80    }
81
82});
83
84__extend__(HTMLIFrameElement.prototype, HTMLFrameElement.prototype);
85
86}(/*Frame/Window Mixin*/));
87
88/*
89 *  TODO: Document the History object via HTML5 spec
90 *
91 */
92(function(){
93
94var log = Envjs.logger();
95Envjs.once('tick', function(){
96        log = Envjs.logger('Envjs.Window.History').
97                debug('history logger available');
98});
99
100exports.History = History = function(owner) {
101    var $current = 0,
102        $history = [null],
103        $owner = owner;
104
105    return {
106        go : function(target) {
107            if (typeof target === "number") {
108                target = $current + target;
109                if (target > -1 && target < $history.length){
110                    if ($history[target].type === "hash") {
111                        if ($owner.location) {
112                            $owner.location.hash = $history[target].value;
113                        }
114                    } else {
115                        if ($owner.location) {
116                            $owner.location = $history[target].value;
117                        }
118                    }
119                    $current = target;
120                }
121            } else {
122                //TODO: walk through the history and find the 'best match'?
123            }
124        },
125
126        get length() {
127            return $history.length;
128        },
129
130        back : function(count) {
131            if (count) {
132                this.go(-count);
133            } else {
134                this.go(-1);
135            }
136        },
137
138        get current() {
139            return this.item($current);
140        },
141
142        get previous() {
143            return this.item($current-1);
144        },
145
146        forward : function(count) {
147            if (count) {
148                this.go(count);
149            } else {
150                this.go(1);
151            }
152        },
153
154        item: function(idx) {
155            if (idx >= 0 && idx < $history.length) {
156                return $history[idx];
157            } else {
158                return null;
159            }
160        },
161
162        add: function(newLocation, type) {
163            //not a standard interface, we expose it to simplify
164            //history state modifications
165            if (newLocation !== $history[$current]) {
166                $history.slice(0, $current);
167                $history.push({
168                    type: type || 'href',
169                    value: newLocation
170                });
171            }
172        }
173    };
174};
175
176}(/*History*/));
177
178
179/*
180 *      navigator.js
181 *  Browser Navigator
182 */
183
184(function(){
185
186var log = Envjs.logger();
187
188Envjs.once('tick', function(){
189        log = Envjs.logger('Envjs.Window.Navigator').
190                debug('window navigator logger available');
191});
192
193exports.Navigator = Navigator = function(){
194        var $userAgent;
195    return {
196        get appCodeName(){
197            return Envjs.appCodeName;
198        },
199        get appName(){
200            return Envjs.appName;
201        },
202        get appVersion(){
203            return Envjs.version +" ("+
204                this.platform +"; "+
205                "U; "+//?
206                Envjs.os_name+" "+Envjs.os_arch+" "+Envjs.os_version+"; "+
207                (Envjs.lang?Envjs.lang:"en-US")+"; "+
208                "rv:"+Envjs.revision+
209                ")";
210        },
211        get cookieEnabled(){
212            return true;
213        },
214        get mimeTypes(){
215            return [];
216        },
217        get platform(){
218            return Envjs.platform;
219        },
220        get plugins(){
221            return [];
222        },
223        get userAgent(){
224            return $userAgent||(this.appCodeName + "/" + this.appVersion + " Resig/20070309 PilotFish/1.3.pre03");
225        },
226                set userAgent(agent){
227                        if(agent){
228                                $userAgent = agent;
229                        }
230                },
231        javaEnabled : function(){
232            return Envjs.javaEnabled;
233        }
234    };
235};
236
237}());
238/**
239 * Screen
240 * @param {Object} __window__
241 */
242(function(){
243
244var log = Envjs.logger();
245
246Envjs.once('tick', function(){
247        log = Envjs.logger('Envjs.Window.Screen').
248                debug('window screen logger available');
249});
250
251exports.Screen = Screen = function(__window__){
252
253    var $availHeight  = 600,
254        $availWidth   = 800,
255        $colorDepth   = 16,
256        $pixelDepth   = 24,
257        $height       = 600,
258        $width        = 800,
259        $top          = 0,
260        $left         = 0,
261        $availTop     = 0,
262        $availLeft    = 0;
263
264        log.debug('extending window with screen properties');
265    __extend__( __window__, {
266        moveBy : function(dx,dy){
267            //TODO - modify $locals to reflect change
268        },
269        moveTo : function(x,y) {
270            //TODO - modify $locals to reflect change
271        },
272        /*print : function(){
273            //TODO - good global to modify to ensure print is not misused
274        };*/
275        resizeBy : function(dw, dh){
276            __window__.resizeTo($width + dw, $height + dh);
277        },
278        resizeTo : function(width, height){
279            $width = (width <= $availWidth) ? width : $availWidth;
280            $height = (height <= $availHeight) ? height : $availHeight;
281        },
282        scroll : function(x,y){
283            //TODO - modify $locals to reflect change
284        },
285        scrollBy : function(dx, dy){
286            //TODO - modify $locals to reflect change
287        },
288        scrollTo : function(x,y){
289            //TODO - modify $locals to reflect change
290        }
291    });
292
293        log.debug('creating screen');
294    return {
295        get top(){
296            return $top;
297        },
298        get left(){
299            return $left;
300        },
301        get availTop(){
302            return $availTop;
303        },
304        get availLeft(){
305            return $availLeft;
306        },
307        get availHeight(){
308            return $availHeight;
309        },
310        get availWidth(){
311            return $availWidth;
312        },
313        get colorDepth(){
314            return $colorDepth;
315        },
316        get pixelDepth(){
317            return $pixelDepth;
318        },
319        get height(){
320            return $height;
321        },
322        get width(){
323            return $width;
324        }
325    };
326};
327
328}(/*Screen*/));
329/*
330 * Copyright (c) 2010 Nick Galbreath
331 * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
332 *
333 * Permission is hereby granted, free of charge, to any person
334 * obtaining a copy of this software and associated documentation
335 * files (the "Software"), to deal in the Software without
336 * restriction, including without limitation the rights to use,
337 * copy, modify, merge, publish, distribute, sublicense, and/or sell
338 * copies of the Software, and to permit persons to whom the
339 * Software is furnished to do so, subject to the following
340 * conditions:
341 *
342 * The above copyright notice and this permission notice shall be
343 * included in all copies or substantial portions of the Software.
344 *
345 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
346 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
347 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
348 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
349 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
350 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
351 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
352 * OTHER DEALINGS IN THE SOFTWARE.
353 */
354
355/* base64 encode/decode compatible with window.btoa/atob
356 *
357 * window.atob/btoa is a Firefox extension to convert binary data (the "b")
358 * to base64 (ascii, the "a").
359 *
360 * It is also found in Safari and Chrome.  It is not available in IE.
361 *
362 * if (!window.btoa) window.btoa = base64.encode
363 * if (!window.atob) window.atob = base64.decode
364 *
365 * The original spec's for atob/btoa are a bit lacking
366 * https://developer.mozilla.org/en/DOM/window.atob
367 * https://developer.mozilla.org/en/DOM/window.btoa
368 *
369 * window.btoa and base64.encode takes a string where charCodeAt is [0,255]
370 * If any character is not [0,255], then an DOMException(5) is thrown.
371 *
372 * window.atob and base64.decode take a base64-encoded string
373 * If the input length is not a multiple of 4, or contains invalid characters
374 *   then an DOMException(5) is thrown.
375 */
376var base64 = {};
377base64.PADCHAR = '=';
378base64.ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
379
380base64.makeDOMException = function() {
381    // sadly in FF,Safari,Chrome you can't make a DOMException
382    var e;
383
384    try {
385        return new DOMException(DOMException.INVALID_CHARACTER_ERR);
386    } catch (tmp) {
387        // not available, just passback a duck-typed equiv
388        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error
389        // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Error/prototype
390        var ex = new Error("DOM Exception 5");
391
392        // ex.number and ex.description is IE-specific.
393        ex.code = ex.number = 5;
394        ex.name = ex.description = "INVALID_CHARACTER_ERR";
395
396        // Safari/Chrome output format
397        ex.toString = function() { return 'Error: ' + ex.name + ': ' + ex.message; };
398        return ex;
399    }
400};
401
402base64.getbyte64 = function(s,i) {
403    // This is oddly fast, except on Chrome/V8.
404    //  Minimal or no improvement in performance by using a
405    //   object with properties mapping chars to value (eg. 'A': 0)
406    var idx = base64.ALPHA.indexOf(s.charAt(i));
407    if (idx === -1) {
408        throw base64.makeDOMException();
409    }
410    return idx;
411};
412
413base64.decode = function(s) {
414    // convert to string
415    s = '' + s;
416    var getbyte64 = base64.getbyte64;
417    var pads, i, b10;
418    var imax = s.length;
419    if (imax === 0) {
420        return s;
421    }
422
423    if (imax % 4 !== 0) {
424        throw base64.makeDOMException();
425    }
426
427    pads = 0;
428    if (s.charAt(imax - 1) === base64.PADCHAR) {
429        pads = 1;
430        if (s.charAt(imax - 2) === base64.PADCHAR) {
431            pads = 2;
432        }
433        // either way, we want to ignore this last block
434        imax -= 4;
435    }
436
437    var x = [];
438    for (i = 0; i < imax; i += 4) {
439        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) |
440            (getbyte64(s,i+2) << 6) | getbyte64(s,i+3);
441        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff, b10 & 0xff));
442    }
443
444    switch (pads) {
445    case 1:
446        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12) | (getbyte64(s,i+2) << 6);
447        x.push(String.fromCharCode(b10 >> 16, (b10 >> 8) & 0xff));
448        break;
449    case 2:
450        b10 = (getbyte64(s,i) << 18) | (getbyte64(s,i+1) << 12);
451        x.push(String.fromCharCode(b10 >> 16));
452        break;
453    }
454    return x.join('');
455};
456
457base64.getbyte = function(s,i) {
458    var x = s.charCodeAt(i);
459    if (x > 255) {
460        throw base64.makeDOMException();
461    }
462    return x;
463};
464
465base64.encode = function(s) {
466    if (arguments.length !== 1) {
467        throw new SyntaxError("Not enough arguments");
468    }
469    var padchar = base64.PADCHAR;
470    var alpha   = base64.ALPHA;
471    var getbyte = base64.getbyte;
472
473    var i, b10;
474    var x = [];
475
476    // convert to string
477    s = '' + s;
478
479    var imax = s.length - s.length % 3;
480
481    if (s.length === 0) {
482        return s;
483    }
484    for (i = 0; i < imax; i += 3) {
485        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8) | getbyte(s,i+2);
486        x.push(alpha.charAt(b10 >> 18));
487        x.push(alpha.charAt((b10 >> 12) & 0x3F));
488        x.push(alpha.charAt((b10 >> 6) & 0x3f));
489        x.push(alpha.charAt(b10 & 0x3f));
490    }
491    switch (s.length - imax) {
492    case 1:
493        b10 = getbyte(s,i) << 16;
494        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
495               padchar + padchar);
496        break;
497    case 2:
498        b10 = (getbyte(s,i) << 16) | (getbyte(s,i+1) << 8);
499        x.push(alpha.charAt(b10 >> 18) + alpha.charAt((b10 >> 12) & 0x3F) +
500               alpha.charAt((b10 >> 6) & 0x3f) + padchar);
501        break;
502    }
503    return x.join('');
504};
505
506
507(function(){
508
509var log = Envjs.logger('Envjs.Window');
510Envjs.once('tick', function(){
511    log = Envjs.logger('Envjs.Window').
512        debug('window logger available');
513});
514
515//These descriptions of window properties are taken loosely David Flanagan's
516//'JavaScript - The Definitive Guide' (O'Reilly)
517
518var __top__ = function(_scope){
519    var _parent = _scope.parent;
520    while (_scope && _parent && _scope !== _parent) {
521        if (_parent === _parent.parent) {
522            break;
523        }
524        _parent = _parent.parent;
525        //console.log('scope %s _parent %s', scope, _parent);
526    }
527    return _parent || null;
528};
529
530/**
531 * Window
532 * @param {Object} scope
533 * @param {Object} parent
534 * @param {Object} opener
535 */
536exports.Window = Window = function(scope, parent, opener){
537
538    if(!scope){
539        scope = __this__;
540    }
541    if(!parent){
542        parent = scope;
543    }
544    // the window property is identical to the self property and to this obj
545    scope.__defineGetter__('window', function(){
546        return scope;
547    });
548
549    var $uuid = new Date().getTime()+'-'+Math.floor(Math.random()*1000000000000000);
550    Envjs.windows($uuid, scope);
551    //log.debug('opening window %s', $uuid);
552
553    // every window has one-and-only-one .document property which is always
554    // an [object HTMLDocument].  also, only window.document objects are
555    // html documents, all other documents created by the window.document are
556    // [object XMLDocument]
557    var $htmlImplementation =  new DOMImplementation();
558    $htmlImplementation.namespaceAware = true;
559    $htmlImplementation.errorChecking = false;
560
561    // read only reference to the Document object
562    var $document = new HTMLDocument($htmlImplementation, scope);
563
564    // A read-only reference to the Window object that contains this window
565    // or frame.  If the window is a top-level window, parent refers to
566    // the window itself.  If this window is a frame, this property refers
567    // to the window or frame that contains it.
568    var $parent = parent;
569
570    /**> $cookies - see cookie.js <*/
571    // read only boolean specifies whether the window has been closed
572    var $closed = false;
573
574    // a read/write string that specifies the default message that
575    // appears in the status line
576    var $defaultStatus = "Done";
577
578    // IE only, refers to the most recent event object - this maybe be
579    // removed after review
580    var $event = null;
581
582    // a read-only reference to the History object
583    var $history = new History();
584
585    // a read-only reference to the Location object.  the location object does
586    // expose read/write properties
587    var $location = new Location('about:blank', $document, $history);
588
589    // The name of window/frame. Set directly, when using open(), or in frameset.
590    // May be used when specifying the target attribute of links
591    var $name = null;
592
593    // a read-only reference to the Navigator object
594    var $navigator = new Navigator();
595
596    // a read/write reference to the Window object that contained the script
597    // that called open() to open this browser window.  This property is valid
598    // only for top-level window objects.
599    var $opener = opener?opener:null;
600
601    // read-only properties that specify the height and width, in pixels
602    var $innerHeight = 600, $innerWidth = 800;
603
604    // Read-only properties that specify the total height and width, in pixels,
605    // of the browser window. These dimensions include the height and width of
606    // the menu bar, toolbars, scrollbars, window borders and so on.  These
607    // properties are not supported by IE and IE offers no alternative
608    // properties;
609    var $outerHeight = $innerHeight,
610        $outerWidth = $innerWidth;
611
612    // Read-only properties that specify the number of pixels that the current
613    // document has been scrolled to the right and down.  These are not
614    // supported by IE.
615    var $pageXOffset = 0, $pageYOffset = 0;
616
617    // a read-only reference to the Screen object that specifies information
618    // about the screen: the number of available pixels and the number of
619    // available colors.
620    var $screen = new Screen(scope);
621
622    // read only properties that specify the coordinates of the upper-left
623    // corner of the screen.
624    var $screenX = 1,
625        $screenY = 1;
626    var $screenLeft = $screenX,
627        $screenTop = $screenY;
628
629    // a read/write string that specifies the current status line.
630    var $status = '';
631    __extend__(scope, EventTarget.prototype);
632
633    return __extend__( scope, {
634        get closed(){
635            return $closed;
636        },
637        get defaultStatus(){
638            return $defaultStatus;
639        },
640        set defaultStatus(defaultStatus){
641            $defaultStatus = defaultStatus;
642        },
643        get document(){
644            return $document;
645        },
646        set document(doc){
647            $document = doc;
648        },
649        /*
650        deprecated ie specific property probably not good to support
651        get event(){
652            return $event;
653        },
654        */
655        get frames(){
656            return $document.getElementsByTagName('frame');
657        },
658        get length(){
659            // should be frames.length,
660            return this.frames.length;
661        },
662        get history(){
663            return $history;
664        },
665        get innerHeight(){
666            return $innerHeight;
667        },
668        get innerWidth(){
669            return $innerWidth;
670        },
671        get clientHeight(){
672            return $innerHeight;
673        },
674        get clientWidth(){
675            return $innerWidth;
676        },
677        get location(){
678            return $location;
679        },
680        set location(url){
681            //very important or you will go into an infinite
682            //loop when creating a xml document
683            log.debug('setting window location %s', url);
684            if(url) {
685                $location.assign(Envjs.uri(url, $location+''));
686            }
687        },
688        get name(){
689            return $name;
690        },
691        set name(newName){
692            $name = newName;
693        },
694        get navigator(){
695            return $navigator;
696        },
697        get opener(){
698            return $opener;
699        },
700        get outerHeight(){
701            return $outerHeight;
702        },
703        get outerWidth(){
704            return $outerWidth;
705        },
706        get pageXOffest(){
707            return $pageXOffset;
708        },
709        get pageYOffset(){
710            return $pageYOffset;
711        },
712        get parent(){
713            return $parent;
714        },
715        get screen(){
716            return $screen;
717        },
718        get screenLeft(){
719            return $screenLeft;
720        },
721        get screenTop(){
722            return $screenTop;
723        },
724        get screenX(){
725            return $screenX;
726        },
727        get screenY(){
728            return $screenY;
729        },
730        get self(){
731            return scope;
732        },
733        get status(){
734            return $status;
735        },
736        set status(status){
737            $status = status;
738        },
739        // a read-only reference to the top-level window that contains this window.
740        // If this window is a top-level window it is simply a reference to itself.
741        // If this window is a frame, the top property refers to the top-level
742        // window that contains the frame.
743        get top(){
744            return __top__(scope);
745        },
746        get window(){
747            return this;
748        },
749        toString : function(){
750            return '[Window]';
751        },
752
753        /**
754         * getComputedStyle
755         *
756         * Firefox 3.6:
757         *  - Requires both elements to be present else an
758         *    exception is thrown.
759         *  - Returns a 'ComputedCSSStyleDeclaration' object.
760         *    while a raw element.style returns a 'CSSStyleDeclaration' object.
761         *  - Bogus input also throws exception
762         *
763         * Safari 4:
764         *  - Requires one argument (second can be MIA)
765         *  - Returns a CSSStyleDeclaration object
766         *  - if bad imput, returns null
767         *
768         * getComputedStyle should really be an "add on" from the css
769         * modules.  Unfortunately, 'window' comes way after the 'css'
770         * so css can't add it.
771         */
772        getComputedStyle: function(element, pseudoElement) {
773            return element.style;
774        },
775
776        open: function(url, name, features, replace){
777            if (features) {
778                console.log("'features argument not yet implemented");
779            }
780            var _window = Envjs.proxy({}),
781                open;
782            if(replace && name){
783                for(open in Envjs.windows()){
784                    if(open.name === name) {
785                        _window = open;
786                    }
787                }
788            }
789            var w = new Window(_window, _window, this);
790            if(name) {
791                _window.name = name;
792            }
793            _window.document.async = false;
794            _window.document.location.assign(Envjs.uri(url));
795            return _window;
796        },
797        close: function(){
798            log.debug('closing window %s', $uuid);
799            var frames = $document.getElementsByTagName('frame'),
800                iframes = $document.getElementsByTagName('iframe'),
801                i;
802            for(i=0;i<frames.length;i++){
803                Envjs.unloadFrame(frames[i]);
804            }
805            for(i=0;i<iframes.length;i++){
806                Envjs.unloadFrame(iframes[i]);
807            }
808            try{
809                Envjs.windows($uuid, null);
810            }catch(e){
811                log.error('%s',e);
812            }
813            return null;
814        },
815        alert : function(message){
816            Envjs.alert(message);
817        },
818        confirm : function(question){
819            Envjs.confirm(question);
820        },
821        prompt : function(message, defaultMsg){
822            Envjs.prompt(message, defaultMsg);
823        },
824        btoa: function(binary){
825            return base64.encode(binary);
826        },
827        atob: function(ascii){
828            return base64.decode(ascii);
829        },
830        //these should be undefined on instantiation
831        //onload: function(){},
832        //onunload: function(){},
833        focus: function(){},
834        blur: function(){},
835        get guid(){
836            return $uuid;
837        },
838        set guid(_guid){
839            $uuid = _guid;
840        }
841    });
842
843};
844
845//console.log('scheduling default window creation');
846setTimeout(function(){
847    var w = new Window(__this__);
848    log.info('[ %s ]', window.navigator.userAgent);
849},1);
850
851}(/*Window*/));
852
853
854//console.log('starting Envjs.eventLoop');
855Envjs.eventLoop();
856
857/**
858 * @author john resig & the envjs team
859 * @uri http://www.envjs.com/
860 * @copyright 2008-2010
861 * @license MIT
862 */
863//CLOSURE_END
864}());
Note: See TracBrowser for help on using the repository browser.