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

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

d3

File size: 18.7 KB
Line 
1
2/*
3 * Envjs xhr.1.3.pre03
4 * Pure JavaScript Browser Environment
5 * By John Resig <http://ejohn.org/> and the Envjs Team
6 * Copyright 2008-2010 John Resig, under the MIT License
7 */
8
9//CLOSURE_START
10(function(){
11
12
13
14
15/*
16 * Envjs xhr.1.3.pre03
17 * Pure JavaScript Browser Environment
18 * By John Resig <http://ejohn.org/> and the Envjs Team
19 * Copyright 2008-2010 John Resig, under the MIT License
20 *
21 * Parts of the implementation originally written by Yehuda Katz.
22 *
23 * This file simply provides the global definitions we need to
24 * be able to correctly implement to core browser (XML)HTTPRequest
25 * interfaces.
26
27This module leaks the following global definitions.
28
29var Location,
30    XMLHttpRequest;
31
32 */
33
34var Envjs = Envjs || require('./platform/core').Envjs,
35        Document = Document || require('./dom').Document;
36
37/**
38 * @author john resig
39 */
40// Helper method for extending one object with another.
41function __extend__(a,b) {
42    for ( var i in b ) {
43        if(b.hasOwnProperty(i)){
44            var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
45            if ( g || s ) {
46                if ( g ) { a.__defineGetter__(i, g); }
47                if ( s ) { a.__defineSetter__(i, s); }
48            } else {
49                a[i] = b[i];
50            }
51        }
52    }
53    return a;
54}
55
56/**
57 * These functions require network IO provided by XMLHttpRequest
58 */
59(function(){
60       
61var log = Envjs.logger();       
62Envjs.once('tick', function(){
63        log = Envjs.logger('Envjs.DOM.Document').
64                info('doc logger available');
65});
66
67__extend__(Document.prototype,{
68    load: function(url){
69        if(this.documentURI == 'about:html'){
70            this.location.assign(url);
71        }else if(this.documentURI == url){
72            this.location.reload(false);
73        }else{
74            this.location.replace(url);
75        }
76    },
77    get location(){
78        return this.ownerWindow.location;
79    },
80    set location(url){
81        //very important or you will go into an infinite
82        //loop when creating a xml document
83        this.ownerWindow.location = url;
84    }
85});
86
87}(/*Document/Location Mixin*/));
88/**
89 * Location
90 *
91 * Mozilla MDC:
92 * https://developer.mozilla.org/En/DOM/Window.location
93 * https://developer.mozilla.org/en/DOM/document.location
94 *
95 * HTML5: 6.10.4 The Location interface
96 * http://dev.w3.org/html5/spec/Overview.html#location
97 *
98 * HTML5: 2.5.3 Interfaces for URL manipulation
99 * http://dev.w3.org/html5/spec/Overview.html#url-decomposition-idl-attributes
100 * All of section 2.5 is worth reading, but 2.5.3 contains very
101 * detailed information on how getters/setter should work
102 *
103 * NOT IMPLEMENTED:
104 *  HTML5: Section 6.10.4.1 Security -- prevents scripts from another domain
105 *   from accessing most of the 'Location'
106 *  Not sure if anyone implements this in HTML4
107 */
108(function(){
109   
110var log = Envjs.logger();
111Envjs.once('tick', function(){
112    log = Envjs.logger('Envjs.Location').
113        debug('location logger available');
114});
115   
116exports.Location = Location = function(url, doc, history) {
117    log = log||Envjs.logger('Envjs.Location');
118    log.debug('Location url %s', url);
119    var $url = url,
120        $document = doc ? doc : null,
121        $history = history ? history : null;
122
123    var parts = Envjs.urlsplit($url);
124
125    return {
126        get hash() {
127            return parts.fragment ? '#' + parts.fragment : parts.fragment;
128        },
129        set hash(s) {
130            if (s[0] === '#') {
131                parts.fragment = s.substr(1);
132            } else {
133                parts.fragment = s;
134            }
135            $url = Envjs.urlunsplit(parts);
136            if ($history) {
137                $history.add($url, 'hash');
138            }
139        },
140
141        get host() {
142            return parts.netloc;
143        },
144        set host(s) {
145            if (!s || s === '') {
146                return;
147            }
148
149            parts.netloc = s;
150            $url = Envjs.urlunsplit(parts);
151
152            // this regenerates hostname & port
153            parts = Envjs.urlsplit($url);
154
155            if ($history) {
156                $history.add( $url, 'host');
157            }
158            this.assign($url);
159        },
160
161        get hostname() {
162            return parts.hostname;
163        },
164        set hostname(s) {
165            if (!s || s === '') {
166                return;
167            }
168
169            parts.netloc = s;
170            if (parts.port != '') {
171                parts.netloc += ':' + parts.port;
172            }
173            parts.hostname = s;
174            $url = Envjs.urlunsplit(parts);
175            if ($history) {
176                $history.add( $url, 'hostname');
177            }
178            this.assign($url);
179        },
180
181        get href() {
182            return $url;
183        },
184        set href(url) {
185            $url = url;
186            if ($history) {
187                $history.add($url, 'href');
188            }
189            this.assign($url);
190        },
191
192        get pathname() {
193            return parts.path;
194        },
195        set pathname(s) {
196            if (s[0] === '/') {
197                parts.path = s;
198            } else {
199                parts.path = '/' + s;
200            }
201            $url = Envjs.urlunsplit(parts);
202
203            if ($history) {
204                $history.add($url, 'pathname');
205            }
206            this.assign($url);
207        },
208
209        get port() {
210            // make sure it's a string
211            return '' + parts.port;
212        },
213        set port(p) {
214            // make a string
215            var s = '' + p;
216            parts.port = s;
217            parts.netloc = parts.hostname + ':' + parts.port;
218            $url = Envjs.urlunsplit(parts);
219            if ($history) {
220                $history.add( $url, 'port');
221            }
222            this.assign($url);
223        },
224
225        get protocol() {
226            return parts.scheme + ':';
227        },
228        set protocol(s) {
229            var i = s.indexOf(':');
230            if (i != -1) {
231                s = s.substr(0,i);
232            }
233            parts.scheme = s;
234            $url = Envjs.urlunsplit(parts);
235            if ($history) {
236                $history.add($url, 'protocol');
237            }
238            this.assign($url);
239        },
240
241        get search() {
242            return (parts.query) ? '?' + parts.query : parts.query;
243        },
244        set search(s) {
245            if (s[0] == '?') {
246                s = s.substr(1);
247            }
248            parts.query = s;
249            $url = Envjs.urlunsplit(parts);
250            if ($history) {
251                $history.add($url, 'search');
252            }
253            this.assign($url);
254        },
255
256        toString: function() {
257            return $url;
258        },
259
260        assign: function(url, /*non-standard*/ method, data) {
261            var _this = this,
262                xhr,
263                event;
264            method = method||"GET";
265            data = data||null;
266            log.debug('assigning %s',url);
267
268            //we can only assign if this Location is associated with a document
269            if ($document) {
270                log.debug('fetching %s (async? %s)', url, $document.async);
271                xhr = new XMLHttpRequest();
272               
273                xhr.setRequestHeader('Referer', $document.location);
274                log.debug("REFERER: %s", $document.location);
275                // TODO: make async flag a Envjs paramter
276                xhr.open(method, url, false);//$document.async);
277
278                // TODO: is there a better way to test if a node is an HTMLDocument?
279                if ($document.toString() === '[object HTMLDocument]') {
280                    //tell the xhr to not parse the document as XML
281                    log.debug('loading html document');
282                    xhr.onreadystatechange = function() {
283                        log.debug('readyState %s', xhr.readyState);
284                        if (xhr.readyState === 4) {
285                            switch(xhr.status){
286                            case 301:
287                            case 302:
288                            case 303:
289                            case 305:
290                            case 307:
291                                log.debug('status is not good for assignment %s', xhr.status);
292                                break;
293                            default:
294                                log.debug('status is good for assignment %s', xhr.status);
295                                $url = xhr.url;
296                                parts = Envjs.urlsplit($url);
297                                log.debug('new document location %s', xhr.url);
298                                Envjs.exchangeHTMLDocument($document, xhr.responseText, xhr.url);
299                            }
300                        }
301                    };
302                    try{
303                        xhr.send(data, false);//dont parse html
304                    }catch(e){
305                        log.debug('failed to load content %s', e);
306                        Envjs.exchangeHTMLDocument(
307                            $document,
308                            "<html><head><title>Error Loading</title></head><body>"+e+"</body></html>",
309                            xhr.url
310                        );
311                    }
312                } else {
313                    //Treat as an XMLDocument
314                    xhr.onreadystatechange = function() {
315                        if (xhr.readyState === 4) {
316                            log.debug('exchanging xml content %s', e);
317                            $document = xhr.responseXML;
318                            $document.baseURI = xhr.url;
319                            if ($document.createEvent) {
320                                event = $document.createEvent('Event');
321                                event.initEvent('DOMContentLoaded');
322                                $document.dispatchEvent( event, false );
323                            }
324                        }
325                    };
326                    xhr.send();
327                }
328            }//end if($document)
329        },
330        reload: function(forceget) {
331            //for now we have no caching so just proxy to assign
332            log.debug('reloading %s',$url);
333            this.assign($url);
334        },
335        replace: function(url, /*non-standard*/ method, data) {
336            this.assign(url, method, data);
337        }
338    };
339};
340
341}(/*Location*/));
342/**
343 *
344 * @class XMLHttpRequest
345 * @author Originally implemented by Yehuda Katz
346 *
347 */
348   
349(function(){
350   
351// this implementation can be used without requiring a DOMParser
352// assuming you dont try to use it to get xml/html documents
353var domparser,
354    log = Envjs.logger();
355   
356Envjs.once('tick', function(){
357    log = Envjs.logger('Envjs.XMLHttpRequest').
358        debug('xhr logger available');
359});
360
361exports.XMLHttpRequest = XMLHttpRequest = function(){
362    this.headers = {};
363    this.responseHeaders = {};
364    this.aborted = false;//non-standard
365};
366
367// defined by the standard: http://www.w3.org/TR/XMLHttpRequest/#xmlhttprequest
368// but not provided by Firefox.  Safari and others do define it.
369XMLHttpRequest.UNSENT = 0;
370XMLHttpRequest.OPEN = 1;
371XMLHttpRequest.HEADERS_RECEIVED = 2;
372XMLHttpRequest.LOADING = 3;
373XMLHttpRequest.DONE = 4;
374
375XMLHttpRequest.prototype = {
376    open: function(method, url, async, user, password){
377        log.debug('opening xhr %s %s %s', method, url, async);
378        this.readyState = 1;
379        this.async = (async === false)?false:true;
380        this.method = method || "GET";
381        this.url = Envjs.uri(url);
382        this.onreadystatechange();
383    },
384    setRequestHeader: function(header, value){
385        this.headers[header] = value;
386    },
387    send: function(data, parsedoc/*non-standard*/, redirect_count){
388        var _this = this;
389        log.debug('sending request for url %s', this.url);
390        parsedoc = (parsedoc === undefined)?true:!!parsedoc;
391        redirect_count = (redirect_count === undefined) ? 0 : redirect_count;
392        function makeRequest(){
393            var cookie = Envjs.getCookies(_this.url),
394                redirecting = false;
395            if(cookie){
396                _this.setRequestHeader('COOKIE', cookie);
397            }
398            if(window&&window.navigator&&window.navigator.userAgent){
399                _this.setRequestHeader('User-Agent', window.navigator.userAgent);
400            }
401           
402            log.debug('establishing platform native connection %s', _this.url);
403            Envjs.connection(_this, function(){
404                log.debug('callback remove xhr from network queue');
405                Envjs.connections.removeConnection(_this);
406                if (!_this.aborted){
407                    var doc = null,
408                        domparser,
409                        cookie,
410                        contentType,
411                        location;
412                   
413                    try{
414                        cookie = _this.getResponseHeader('SET-COOKIE');
415                        if(cookie){
416                            Envjs.setCookie(_this.url, cookie);
417                        }
418                    }catch(e){
419                        log.warn("Failed to set cookie");
420                    }
421                    //console.log('status : %s', _this.status);
422                    switch(_this.status){
423                        case 301:
424                        case 302:
425                        case 303:
426                        case 305:
427                        case 307:
428                        if(_this.getResponseHeader('Location') && redirect_count < 20){
429                            //follow redirect and copy headers
430                            redirecting = true;
431                            location = _this.getResponseHeader('Location');
432                            log.debug('following %s redirect %s from %s url %s',
433                                redirect_count,
434                                _this.status,
435                                _this.url,
436                                location);
437                            _this.url = Envjs.uri(location);
438                            //remove current cookie headers to allow the redirect to determine
439                            //the currect cookie based on the new location
440                            if('Cookie' in _this.headers ){
441                                delete _this.headers.Cookie;
442                            }
443                            if('Cookie2' in _this.headers ){
444                                delete _this.headers.Cookie2;
445                            }
446                            redirect_count++;
447                            if (_this.async){
448                                //TODO: see TODO notes below
449                                Envjs.runAsync(makeRequest);
450                            }else{
451                                makeRequest();
452                            }
453                            return;
454                        }break;
455                        default:
456                        // try to parse the document if we havent explicitly set a
457                        // flag saying not to and if we can assure the text at least
458                        // starts with valid xml
459                        contentType = _this.getResponseHeader('Content-Type');
460                        log.debug("response content-type : %s", contentType);
461                        if ( parsedoc &&
462                             contentType &&
463                             contentType.indexOf('xml') > -1 &&
464                            _this.responseText.match(/^\s*</) ) {
465   
466                            domparser = domparser||new DOMParser();
467                            try {
468                                log.debug("parsing response text into xml document");
469                                doc = domparser.parseFromString(_this.responseText+"", 'text/xml');
470                            } catch(ee) {
471                                //Envjs.error('response XML does not appear to be well formed xml', e);
472                                log.error('parseerror \n%s', ee);
473                                doc = document.implementation.createDocument('','error',null);
474                                doc.appendChild(doc.createTextNode(ee+''));
475                            }
476
477                        }else{
478                            log.debug('response XML does not appear to be xml');
479                        }
480
481                        _this.__defineGetter__("responseXML", function(){
482                            return doc;
483                        });
484                           
485                    }
486                }
487            }, data);
488
489            if (!_this.aborted  && !redirecting){
490                log.debug('did not abort and not redirecting so calling onreadystatechange');
491                _this.onreadystatechange();
492            }
493
494        }//end makeRequest
495       
496        log.debug('requesting async: %s', this.url);
497        Envjs.connections.addConnection(this);
498        if (this.async){
499            //DONE: what we really need to do here is rejoin the
500            //      current thread and call onreadystatechange via
501            //      setTimeout so the callback is essentially applied
502            //      at the end of the current callstack
503            Envjs.runAsync(makeRequest);
504        }else{
505            log.debug('requesting sync: %s', this.url);
506            makeRequest();
507        }
508    },
509    abort: function(){
510        this.aborted = true;
511    },
512    onreadystatechange: function(){
513        //Instance specific
514    },
515    getResponseHeader: function(header){
516        log.debug('getting response header %s', header);
517        var rHeader, returnedHeaders;
518        if (this.readyState < 3){
519            throw new Error("INVALID_STATE_ERR");
520        } else {
521            returnedHeaders = [];
522            log.debug('searching response headers for %s ', header);
523            for (rHeader in this.responseHeaders) {
524                if ((rHeader+'').match(new RegExp(header, "i"))) {
525                    log.debug('found response header, %s is %s', rHeader, header);
526                    returnedHeaders.push(this.responseHeaders[rHeader]);
527                }
528            }
529
530            if (returnedHeaders.length){
531                returnedHeaders = returnedHeaders.join(", ");
532                log.debug('got response header %s', returnedHeaders);
533                return returnedHeaders;
534            }
535        }   
536        return null;
537    },
538    getAllResponseHeaders: function(){
539        var header, returnedHeaders = [];
540        if (this.readyState < 3){
541            throw new Error("INVALID_STATE_ERR");
542        } else {
543            for (header in this.responseHeaders) {
544                if(this.responseHeader.hasOwnProperty(header)){
545                    returnedHeaders.push( header + ": " + this.responseHeaders[header] );
546                }
547            }
548        }
549        return returnedHeaders.join("\r\n");
550    },
551    async: true,
552    readyState: 0,
553    responseText: "",
554    status: 0,
555    statusText: ""
556};
557
558}(/*XMLHttpREquest*/));
559/**
560 * @author john resig & the envjs team
561 * @uri http://www.envjs.com/
562 * @copyright 2008-2010
563 * @license MIT
564 */
565//CLOSURE_END
566}());
Note: See TracBrowser for help on using the repository browser.