567 строки
19 KiB
JavaScript
567 строки
19 KiB
JavaScript
|
|
/*
|
|
* Envjs xhr.1.3.pre03
|
|
* Pure JavaScript Browser Environment
|
|
* By John Resig <http://ejohn.org/> and the Envjs Team
|
|
* Copyright 2008-2010 John Resig, under the MIT License
|
|
*/
|
|
|
|
//CLOSURE_START
|
|
(function(){
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Envjs xhr.1.3.pre03
|
|
* Pure JavaScript Browser Environment
|
|
* By John Resig <http://ejohn.org/> and the Envjs Team
|
|
* Copyright 2008-2010 John Resig, under the MIT License
|
|
*
|
|
* Parts of the implementation originally written by Yehuda Katz.
|
|
*
|
|
* This file simply provides the global definitions we need to
|
|
* be able to correctly implement to core browser (XML)HTTPRequest
|
|
* interfaces.
|
|
|
|
This module leaks the following global definitions.
|
|
|
|
var Location,
|
|
XMLHttpRequest;
|
|
|
|
*/
|
|
|
|
var Envjs = Envjs || require('./platform/core').Envjs,
|
|
Document = Document || require('./dom').Document;
|
|
|
|
/**
|
|
* @author john resig
|
|
*/
|
|
// Helper method for extending one object with another.
|
|
function __extend__(a,b) {
|
|
for ( var i in b ) {
|
|
if(b.hasOwnProperty(i)){
|
|
var g = b.__lookupGetter__(i), s = b.__lookupSetter__(i);
|
|
if ( g || s ) {
|
|
if ( g ) { a.__defineGetter__(i, g); }
|
|
if ( s ) { a.__defineSetter__(i, s); }
|
|
} else {
|
|
a[i] = b[i];
|
|
}
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
|
|
/**
|
|
* These functions require network IO provided by XMLHttpRequest
|
|
*/
|
|
(function(){
|
|
|
|
var log = Envjs.logger();
|
|
Envjs.once('tick', function(){
|
|
log = Envjs.logger('Envjs.DOM.Document').
|
|
info('doc logger available');
|
|
});
|
|
|
|
__extend__(Document.prototype,{
|
|
load: function(url){
|
|
if(this.documentURI == 'about:html'){
|
|
this.location.assign(url);
|
|
}else if(this.documentURI == url){
|
|
this.location.reload(false);
|
|
}else{
|
|
this.location.replace(url);
|
|
}
|
|
},
|
|
get location(){
|
|
return this.ownerWindow.location;
|
|
},
|
|
set location(url){
|
|
//very important or you will go into an infinite
|
|
//loop when creating a xml document
|
|
this.ownerWindow.location = url;
|
|
}
|
|
});
|
|
|
|
}(/*Document/Location Mixin*/));
|
|
/**
|
|
* Location
|
|
*
|
|
* Mozilla MDC:
|
|
* https://developer.mozilla.org/En/DOM/Window.location
|
|
* https://developer.mozilla.org/en/DOM/document.location
|
|
*
|
|
* HTML5: 6.10.4 The Location interface
|
|
* http://dev.w3.org/html5/spec/Overview.html#location
|
|
*
|
|
* HTML5: 2.5.3 Interfaces for URL manipulation
|
|
* http://dev.w3.org/html5/spec/Overview.html#url-decomposition-idl-attributes
|
|
* All of section 2.5 is worth reading, but 2.5.3 contains very
|
|
* detailed information on how getters/setter should work
|
|
*
|
|
* NOT IMPLEMENTED:
|
|
* HTML5: Section 6.10.4.1 Security -- prevents scripts from another domain
|
|
* from accessing most of the 'Location'
|
|
* Not sure if anyone implements this in HTML4
|
|
*/
|
|
(function(){
|
|
|
|
var log = Envjs.logger();
|
|
Envjs.once('tick', function(){
|
|
log = Envjs.logger('Envjs.Location').
|
|
debug('location logger available');
|
|
});
|
|
|
|
exports.Location = Location = function(url, doc, history) {
|
|
log = log||Envjs.logger('Envjs.Location');
|
|
log.debug('Location url %s', url);
|
|
var $url = url,
|
|
$document = doc ? doc : null,
|
|
$history = history ? history : null;
|
|
|
|
var parts = Envjs.urlsplit($url);
|
|
|
|
return {
|
|
get hash() {
|
|
return parts.fragment ? '#' + parts.fragment : parts.fragment;
|
|
},
|
|
set hash(s) {
|
|
if (s[0] === '#') {
|
|
parts.fragment = s.substr(1);
|
|
} else {
|
|
parts.fragment = s;
|
|
}
|
|
$url = Envjs.urlunsplit(parts);
|
|
if ($history) {
|
|
$history.add($url, 'hash');
|
|
}
|
|
},
|
|
|
|
get host() {
|
|
return parts.netloc;
|
|
},
|
|
set host(s) {
|
|
if (!s || s === '') {
|
|
return;
|
|
}
|
|
|
|
parts.netloc = s;
|
|
$url = Envjs.urlunsplit(parts);
|
|
|
|
// this regenerates hostname & port
|
|
parts = Envjs.urlsplit($url);
|
|
|
|
if ($history) {
|
|
$history.add( $url, 'host');
|
|
}
|
|
this.assign($url);
|
|
},
|
|
|
|
get hostname() {
|
|
return parts.hostname;
|
|
},
|
|
set hostname(s) {
|
|
if (!s || s === '') {
|
|
return;
|
|
}
|
|
|
|
parts.netloc = s;
|
|
if (parts.port != '') {
|
|
parts.netloc += ':' + parts.port;
|
|
}
|
|
parts.hostname = s;
|
|
$url = Envjs.urlunsplit(parts);
|
|
if ($history) {
|
|
$history.add( $url, 'hostname');
|
|
}
|
|
this.assign($url);
|
|
},
|
|
|
|
get href() {
|
|
return $url;
|
|
},
|
|
set href(url) {
|
|
$url = url;
|
|
if ($history) {
|
|
$history.add($url, 'href');
|
|
}
|
|
this.assign($url);
|
|
},
|
|
|
|
get pathname() {
|
|
return parts.path;
|
|
},
|
|
set pathname(s) {
|
|
if (s[0] === '/') {
|
|
parts.path = s;
|
|
} else {
|
|
parts.path = '/' + s;
|
|
}
|
|
$url = Envjs.urlunsplit(parts);
|
|
|
|
if ($history) {
|
|
$history.add($url, 'pathname');
|
|
}
|
|
this.assign($url);
|
|
},
|
|
|
|
get port() {
|
|
// make sure it's a string
|
|
return '' + parts.port;
|
|
},
|
|
set port(p) {
|
|
// make a string
|
|
var s = '' + p;
|
|
parts.port = s;
|
|
parts.netloc = parts.hostname + ':' + parts.port;
|
|
$url = Envjs.urlunsplit(parts);
|
|
if ($history) {
|
|
$history.add( $url, 'port');
|
|
}
|
|
this.assign($url);
|
|
},
|
|
|
|
get protocol() {
|
|
return parts.scheme + ':';
|
|
},
|
|
set protocol(s) {
|
|
var i = s.indexOf(':');
|
|
if (i != -1) {
|
|
s = s.substr(0,i);
|
|
}
|
|
parts.scheme = s;
|
|
$url = Envjs.urlunsplit(parts);
|
|
if ($history) {
|
|
$history.add($url, 'protocol');
|
|
}
|
|
this.assign($url);
|
|
},
|
|
|
|
get search() {
|
|
return (parts.query) ? '?' + parts.query : parts.query;
|
|
},
|
|
set search(s) {
|
|
if (s[0] == '?') {
|
|
s = s.substr(1);
|
|
}
|
|
parts.query = s;
|
|
$url = Envjs.urlunsplit(parts);
|
|
if ($history) {
|
|
$history.add($url, 'search');
|
|
}
|
|
this.assign($url);
|
|
},
|
|
|
|
toString: function() {
|
|
return $url;
|
|
},
|
|
|
|
assign: function(url, /*non-standard*/ method, data) {
|
|
var _this = this,
|
|
xhr,
|
|
event;
|
|
method = method||"GET";
|
|
data = data||null;
|
|
log.debug('assigning %s',url);
|
|
|
|
//we can only assign if this Location is associated with a document
|
|
if ($document) {
|
|
log.debug('fetching %s (async? %s)', url, $document.async);
|
|
xhr = new XMLHttpRequest();
|
|
|
|
xhr.setRequestHeader('Referer', $document.location);
|
|
log.debug("REFERER: %s", $document.location);
|
|
// TODO: make async flag a Envjs paramter
|
|
xhr.open(method, url, false);//$document.async);
|
|
|
|
// TODO: is there a better way to test if a node is an HTMLDocument?
|
|
if ($document.toString() === '[object HTMLDocument]') {
|
|
//tell the xhr to not parse the document as XML
|
|
log.debug('loading html document');
|
|
xhr.onreadystatechange = function() {
|
|
log.debug('readyState %s', xhr.readyState);
|
|
if (xhr.readyState === 4) {
|
|
switch(xhr.status){
|
|
case 301:
|
|
case 302:
|
|
case 303:
|
|
case 305:
|
|
case 307:
|
|
log.debug('status is not good for assignment %s', xhr.status);
|
|
break;
|
|
default:
|
|
log.debug('status is good for assignment %s', xhr.status);
|
|
$url = xhr.url;
|
|
parts = Envjs.urlsplit($url);
|
|
log.debug('new document location %s', xhr.url);
|
|
Envjs.exchangeHTMLDocument($document, xhr.responseText, xhr.url);
|
|
}
|
|
}
|
|
};
|
|
try{
|
|
xhr.send(data, false);//dont parse html
|
|
}catch(e){
|
|
log.debug('failed to load content %s', e);
|
|
Envjs.exchangeHTMLDocument(
|
|
$document,
|
|
"<html><head><title>Error Loading</title></head><body>"+e+"</body></html>",
|
|
xhr.url
|
|
);
|
|
}
|
|
} else {
|
|
//Treat as an XMLDocument
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === 4) {
|
|
log.debug('exchanging xml content %s', e);
|
|
$document = xhr.responseXML;
|
|
$document.baseURI = xhr.url;
|
|
if ($document.createEvent) {
|
|
event = $document.createEvent('Event');
|
|
event.initEvent('DOMContentLoaded');
|
|
$document.dispatchEvent( event, false );
|
|
}
|
|
}
|
|
};
|
|
xhr.send();
|
|
}
|
|
}//end if($document)
|
|
},
|
|
reload: function(forceget) {
|
|
//for now we have no caching so just proxy to assign
|
|
log.debug('reloading %s',$url);
|
|
this.assign($url);
|
|
},
|
|
replace: function(url, /*non-standard*/ method, data) {
|
|
this.assign(url, method, data);
|
|
}
|
|
};
|
|
};
|
|
|
|
}(/*Location*/));
|
|
/**
|
|
*
|
|
* @class XMLHttpRequest
|
|
* @author Originally implemented by Yehuda Katz
|
|
*
|
|
*/
|
|
|
|
(function(){
|
|
|
|
// this implementation can be used without requiring a DOMParser
|
|
// assuming you dont try to use it to get xml/html documents
|
|
var domparser,
|
|
log = Envjs.logger();
|
|
|
|
Envjs.once('tick', function(){
|
|
log = Envjs.logger('Envjs.XMLHttpRequest').
|
|
debug('xhr logger available');
|
|
});
|
|
|
|
exports.XMLHttpRequest = XMLHttpRequest = function(){
|
|
this.headers = {};
|
|
this.responseHeaders = {};
|
|
this.aborted = false;//non-standard
|
|
};
|
|
|
|
// defined by the standard: http://www.w3.org/TR/XMLHttpRequest/#xmlhttprequest
|
|
// but not provided by Firefox. Safari and others do define it.
|
|
XMLHttpRequest.UNSENT = 0;
|
|
XMLHttpRequest.OPEN = 1;
|
|
XMLHttpRequest.HEADERS_RECEIVED = 2;
|
|
XMLHttpRequest.LOADING = 3;
|
|
XMLHttpRequest.DONE = 4;
|
|
|
|
XMLHttpRequest.prototype = {
|
|
open: function(method, url, async, user, password){
|
|
log.debug('opening xhr %s %s %s', method, url, async);
|
|
this.readyState = 1;
|
|
this.async = (async === false)?false:true;
|
|
this.method = method || "GET";
|
|
this.url = Envjs.uri(url);
|
|
this.onreadystatechange();
|
|
},
|
|
setRequestHeader: function(header, value){
|
|
this.headers[header] = value;
|
|
},
|
|
send: function(data, parsedoc/*non-standard*/, redirect_count){
|
|
var _this = this;
|
|
log.debug('sending request for url %s', this.url);
|
|
parsedoc = (parsedoc === undefined)?true:!!parsedoc;
|
|
redirect_count = (redirect_count === undefined) ? 0 : redirect_count;
|
|
function makeRequest(){
|
|
var cookie = Envjs.getCookies(_this.url),
|
|
redirecting = false;
|
|
if(cookie){
|
|
_this.setRequestHeader('COOKIE', cookie);
|
|
}
|
|
if(window&&window.navigator&&window.navigator.userAgent){
|
|
_this.setRequestHeader('User-Agent', window.navigator.userAgent);
|
|
}
|
|
|
|
log.debug('establishing platform native connection %s', _this.url);
|
|
Envjs.connection(_this, function(){
|
|
log.debug('callback remove xhr from network queue');
|
|
Envjs.connections.removeConnection(_this);
|
|
if (!_this.aborted){
|
|
var doc = null,
|
|
domparser,
|
|
cookie,
|
|
contentType,
|
|
location;
|
|
|
|
try{
|
|
cookie = _this.getResponseHeader('SET-COOKIE');
|
|
if(cookie){
|
|
Envjs.setCookie(_this.url, cookie);
|
|
}
|
|
}catch(e){
|
|
log.warn("Failed to set cookie");
|
|
}
|
|
//console.log('status : %s', _this.status);
|
|
switch(_this.status){
|
|
case 301:
|
|
case 302:
|
|
case 303:
|
|
case 305:
|
|
case 307:
|
|
if(_this.getResponseHeader('Location') && redirect_count < 20){
|
|
//follow redirect and copy headers
|
|
redirecting = true;
|
|
location = _this.getResponseHeader('Location');
|
|
log.debug('following %s redirect %s from %s url %s',
|
|
redirect_count,
|
|
_this.status,
|
|
_this.url,
|
|
location);
|
|
_this.url = Envjs.uri(location);
|
|
//remove current cookie headers to allow the redirect to determine
|
|
//the currect cookie based on the new location
|
|
if('Cookie' in _this.headers ){
|
|
delete _this.headers.Cookie;
|
|
}
|
|
if('Cookie2' in _this.headers ){
|
|
delete _this.headers.Cookie2;
|
|
}
|
|
redirect_count++;
|
|
if (_this.async){
|
|
//TODO: see TODO notes below
|
|
Envjs.runAsync(makeRequest);
|
|
}else{
|
|
makeRequest();
|
|
}
|
|
return;
|
|
}break;
|
|
default:
|
|
// try to parse the document if we havent explicitly set a
|
|
// flag saying not to and if we can assure the text at least
|
|
// starts with valid xml
|
|
contentType = _this.getResponseHeader('Content-Type');
|
|
log.debug("response content-type : %s", contentType);
|
|
if ( parsedoc &&
|
|
contentType &&
|
|
contentType.indexOf('xml') > -1 &&
|
|
_this.responseText.match(/^\s*</) ) {
|
|
|
|
domparser = domparser||new DOMParser();
|
|
try {
|
|
log.debug("parsing response text into xml document");
|
|
doc = domparser.parseFromString(_this.responseText+"", 'text/xml');
|
|
} catch(ee) {
|
|
//Envjs.error('response XML does not appear to be well formed xml', e);
|
|
log.error('parseerror \n%s', ee);
|
|
doc = document.implementation.createDocument('','error',null);
|
|
doc.appendChild(doc.createTextNode(ee+''));
|
|
}
|
|
|
|
}else{
|
|
log.debug('response XML does not appear to be xml');
|
|
}
|
|
|
|
_this.__defineGetter__("responseXML", function(){
|
|
return doc;
|
|
});
|
|
|
|
}
|
|
}
|
|
}, data);
|
|
|
|
if (!_this.aborted && !redirecting){
|
|
log.debug('did not abort and not redirecting so calling onreadystatechange');
|
|
_this.onreadystatechange();
|
|
}
|
|
|
|
}//end makeRequest
|
|
|
|
log.debug('requesting async: %s', this.url);
|
|
Envjs.connections.addConnection(this);
|
|
if (this.async){
|
|
//DONE: what we really need to do here is rejoin the
|
|
// current thread and call onreadystatechange via
|
|
// setTimeout so the callback is essentially applied
|
|
// at the end of the current callstack
|
|
Envjs.runAsync(makeRequest);
|
|
}else{
|
|
log.debug('requesting sync: %s', this.url);
|
|
makeRequest();
|
|
}
|
|
},
|
|
abort: function(){
|
|
this.aborted = true;
|
|
},
|
|
onreadystatechange: function(){
|
|
//Instance specific
|
|
},
|
|
getResponseHeader: function(header){
|
|
log.debug('getting response header %s', header);
|
|
var rHeader, returnedHeaders;
|
|
if (this.readyState < 3){
|
|
throw new Error("INVALID_STATE_ERR");
|
|
} else {
|
|
returnedHeaders = [];
|
|
log.debug('searching response headers for %s ', header);
|
|
for (rHeader in this.responseHeaders) {
|
|
if ((rHeader+'').match(new RegExp(header, "i"))) {
|
|
log.debug('found response header, %s is %s', rHeader, header);
|
|
returnedHeaders.push(this.responseHeaders[rHeader]);
|
|
}
|
|
}
|
|
|
|
if (returnedHeaders.length){
|
|
returnedHeaders = returnedHeaders.join(", ");
|
|
log.debug('got response header %s', returnedHeaders);
|
|
return returnedHeaders;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
getAllResponseHeaders: function(){
|
|
var header, returnedHeaders = [];
|
|
if (this.readyState < 3){
|
|
throw new Error("INVALID_STATE_ERR");
|
|
} else {
|
|
for (header in this.responseHeaders) {
|
|
if(this.responseHeader.hasOwnProperty(header)){
|
|
returnedHeaders.push( header + ": " + this.responseHeaders[header] );
|
|
}
|
|
}
|
|
}
|
|
return returnedHeaders.join("\r\n");
|
|
},
|
|
async: true,
|
|
readyState: 0,
|
|
responseText: "",
|
|
status: 0,
|
|
statusText: ""
|
|
};
|
|
|
|
}(/*XMLHttpREquest*/));
|
|
/**
|
|
* @author john resig & the envjs team
|
|
* @uri http://www.envjs.com/
|
|
* @copyright 2008-2010
|
|
* @license MIT
|
|
*/
|
|
//CLOSURE_END
|
|
}());
|