initial dojo generator. moving to dojo nightly build and checking in the widget libraries as well.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/WCM-DEV2/root@3488 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Ariel Backenroth
2006-08-12 15:37:09 +00:00
parent 0a29ff4f5e
commit 129728f447
715 changed files with 79028 additions and 5781 deletions

View File

@@ -0,0 +1,565 @@
dojo.provide("dojo.io.BrowserIO");
dojo.require("dojo.io");
dojo.require("dojo.lang.array");
dojo.require("dojo.lang.func");
dojo.require("dojo.string.extras");
dojo.require("dojo.dom");
dojo.require("dojo.undo.browser");
dojo.io.checkChildrenForFile = function(node){
var hasFile = false;
var inputs = node.getElementsByTagName("input");
dojo.lang.forEach(inputs, function(input){
if(hasFile){ return; }
if(input.getAttribute("type")=="file"){
hasFile = true;
}
});
return hasFile;
}
dojo.io.formHasFile = function(formNode){
return dojo.io.checkChildrenForFile(formNode);
}
dojo.io.updateNode = function(node, urlOrArgs){
node = dojo.byId(node);
var args = urlOrArgs;
if(dojo.lang.isString(urlOrArgs)){
args = { url: urlOrArgs };
}
args.mimetype = "text/html";
args.load = function(t, d, e){
while(node.firstChild){
if(dojo["event"]){
try{
dojo.event.browser.clean(node.firstChild);
}catch(e){}
}
node.removeChild(node.firstChild);
}
node.innerHTML = d;
};
dojo.io.bind(args);
}
dojo.io.formFilter = function(node) {
var type = (node.type||"").toLowerCase();
return !node.disabled && node.name
&& !dojo.lang.inArray(["file", "submit", "image", "reset", "button"], type);
}
// TODO: Move to htmlUtils
dojo.io.encodeForm = function(formNode, encoding, formFilter){
if((!formNode)||(!formNode.tagName)||(!formNode.tagName.toLowerCase() == "form")){
dojo.raise("Attempted to encode a non-form element.");
}
if(!formFilter) { formFilter = dojo.io.formFilter; }
var enc = /utf/i.test(encoding||"") ? encodeURIComponent : dojo.string.encodeAscii;
var values = [];
for(var i = 0; i < formNode.elements.length; i++){
var elm = formNode.elements[i];
if(!elm || elm.tagName.toLowerCase() == "fieldset" || !formFilter(elm)) { continue; }
var name = enc(elm.name);
var type = elm.type.toLowerCase();
if(type == "select-multiple"){
for(var j = 0; j < elm.options.length; j++){
if(elm.options[j].selected) {
values.push(name + "=" + enc(elm.options[j].value));
}
}
}else if(dojo.lang.inArray(["radio", "checkbox"], type)){
if(elm.checked){
values.push(name + "=" + enc(elm.value));
}
}else{
values.push(name + "=" + enc(elm.value));
}
}
// now collect input type="image", which doesn't show up in the elements array
var inputs = formNode.getElementsByTagName("input");
for(var i = 0; i < inputs.length; i++) {
var input = inputs[i];
if(input.type.toLowerCase() == "image" && input.form == formNode
&& formFilter(input)) {
var name = enc(input.name);
values.push(name + "=" + enc(input.value));
values.push(name + ".x=0");
values.push(name + ".y=0");
}
}
return values.join("&") + "&";
}
dojo.io.FormBind = function(args) {
this.bindArgs = {};
if(args && args.formNode) {
this.init(args);
} else if(args) {
this.init({formNode: args});
}
}
dojo.lang.extend(dojo.io.FormBind, {
form: null,
bindArgs: null,
clickedButton: null,
init: function(args) {
var form = dojo.byId(args.formNode);
if(!form || !form.tagName || form.tagName.toLowerCase() != "form") {
throw new Error("FormBind: Couldn't apply, invalid form");
} else if(this.form == form) {
return;
} else if(this.form) {
throw new Error("FormBind: Already applied to a form");
}
dojo.lang.mixin(this.bindArgs, args);
this.form = form;
this.connect(form, "onsubmit", "submit");
for(var i = 0; i < form.elements.length; i++) {
var node = form.elements[i];
if(node && node.type && dojo.lang.inArray(["submit", "button"], node.type.toLowerCase())) {
this.connect(node, "onclick", "click");
}
}
var inputs = form.getElementsByTagName("input");
for(var i = 0; i < inputs.length; i++) {
var input = inputs[i];
if(input.type.toLowerCase() == "image" && input.form == form) {
this.connect(input, "onclick", "click");
}
}
},
onSubmit: function(form) {
return true;
},
submit: function(e) {
e.preventDefault();
if(this.onSubmit(this.form)) {
dojo.io.bind(dojo.lang.mixin(this.bindArgs, {
formFilter: dojo.lang.hitch(this, "formFilter")
}));
}
},
click: function(e) {
var node = e.currentTarget;
if(node.disabled) { return; }
this.clickedButton = node;
},
formFilter: function(node) {
var type = (node.type||"").toLowerCase();
var accept = false;
if(node.disabled || !node.name) {
accept = false;
} else if(dojo.lang.inArray(["submit", "button", "image"], type)) {
if(!this.clickedButton) { this.clickedButton = node; }
accept = node == this.clickedButton;
} else {
accept = !dojo.lang.inArray(["file", "submit", "reset", "button"], type);
}
return accept;
},
// in case you don't have dojo.event.* pulled in
connect: function(srcObj, srcFcn, targetFcn) {
if(dojo.evalObjPath("dojo.event.connect")) {
dojo.event.connect(srcObj, srcFcn, this, targetFcn);
} else {
var fcn = dojo.lang.hitch(this, targetFcn);
srcObj[srcFcn] = function(e) {
if(!e) { e = window.event; }
if(!e.currentTarget) { e.currentTarget = e.srcElement; }
if(!e.preventDefault) { e.preventDefault = function() { window.event.returnValue = false; } }
fcn(e);
}
}
}
});
dojo.io.XMLHTTPTransport = new function(){
var _this = this;
var _cache = {}; // FIXME: make this public? do we even need to?
this.useCache = false; // if this is true, we'll cache unless kwArgs.useCache = false
this.preventCache = false; // if this is true, we'll always force GET requests to cache
// FIXME: Should this even be a function? or do we just hard code it in the next 2 functions?
function getCacheKey(url, query, method) {
return url + "|" + query + "|" + method.toLowerCase();
}
function addToCache(url, query, method, http) {
_cache[getCacheKey(url, query, method)] = http;
}
function getFromCache(url, query, method) {
return _cache[getCacheKey(url, query, method)];
}
this.clearCache = function() {
_cache = {};
}
// moved successful load stuff here
function doLoad(kwArgs, http, url, query, useCache) {
if( ((http.status>=200)&&(http.status<300))|| // allow any 2XX response code
(http.status==304)|| // get it out of the cache
(location.protocol=="file:" && (http.status==0 || http.status==undefined))||
(location.protocol=="chrome:" && (http.status==0 || http.status==undefined))
){
var ret;
if(kwArgs.method.toLowerCase() == "head"){
var headers = http.getAllResponseHeaders();
ret = {};
ret.toString = function(){ return headers; }
var values = headers.split(/[\r\n]+/g);
for(var i = 0; i < values.length; i++) {
var pair = values[i].match(/^([^:]+)\s*:\s*(.+)$/i);
if(pair) {
ret[pair[1]] = pair[2];
}
}
}else if(kwArgs.mimetype == "text/javascript"){
try{
ret = dj_eval(http.responseText);
}catch(e){
dojo.debug(e);
dojo.debug(http.responseText);
ret = null;
}
}else if(kwArgs.mimetype == "text/json"){
try{
ret = dj_eval("("+http.responseText+")");
}catch(e){
dojo.debug(e);
dojo.debug(http.responseText);
ret = false;
}
}else if((kwArgs.mimetype == "application/xml")||
(kwArgs.mimetype == "text/xml")){
ret = http.responseXML;
if(!ret || typeof ret == "string" || !http.getResponseHeader("Content-Type")) {
ret = dojo.dom.createDocumentFromText(http.responseText);
}
}else{
ret = http.responseText;
}
if(useCache){ // only cache successful responses
addToCache(url, query, kwArgs.method, http);
}
kwArgs[(typeof kwArgs.load == "function") ? "load" : "handle"]("load", ret, http, kwArgs);
}else{
var errObj = new dojo.io.Error("XMLHttpTransport Error: "+http.status+" "+http.statusText);
kwArgs[(typeof kwArgs.error == "function") ? "error" : "handle"]("error", errObj, http, kwArgs);
}
}
// set headers (note: Content-Type will get overriden if kwArgs.contentType is set)
function setHeaders(http, kwArgs){
if(kwArgs["headers"]) {
for(var header in kwArgs["headers"]) {
if(header.toLowerCase() == "content-type" && !kwArgs["contentType"]) {
kwArgs["contentType"] = kwArgs["headers"][header];
} else {
http.setRequestHeader(header, kwArgs["headers"][header]);
}
}
}
}
this.inFlight = [];
this.inFlightTimer = null;
this.startWatchingInFlight = function(){
if(!this.inFlightTimer){
// setInterval broken in mozilla x86_64 in some circumstances, see
// https://bugzilla.mozilla.org/show_bug.cgi?id=344439
// using setTimeout instead
this.inFlightTimer = setTimeout("dojo.io.XMLHTTPTransport.watchInFlight();", 10);
}
}
this.watchInFlight = function(){
var now = null;
// make sure sync calls stay thread safe, if this callback is called during a sync call
// and this results in another sync call before the first sync call ends the browser hangs
if(!dojo.hostenv._blockAsync && !_this._blockAsync){
for(var x=this.inFlight.length-1; x>=0; x--){
var tif = this.inFlight[x];
if(!tif || tif.http._aborted || !tif.http.readyState){
this.inFlight.splice(x, 1); continue;
}
if(4==tif.http.readyState){
// remove it so we can clean refs
this.inFlight.splice(x, 1);
doLoad(tif.req, tif.http, tif.url, tif.query, tif.useCache);
}else if (tif.startTime){
//See if this is a timeout case.
if(!now){
now = (new Date()).getTime();
}
if(tif.startTime + (tif.req.timeoutSeconds * 1000) < now){
//Stop the request.
if(typeof tif.http.abort == "function"){
tif.http.abort();
}
// remove it so we can clean refs
this.inFlight.splice(x, 1);
tif.req[(typeof tif.req.timeout == "function") ? "timeout" : "handle"]("timeout", null, tif.http, tif.req);
}
}
}
}
clearTimeout(this.inFlightTimer);
if(this.inFlight.length == 0){
this.inFlightTimer = null;
return;
}
this.inFlightTimer = setTimeout("dojo.io.XMLHTTPTransport.watchInFlight();", 10);
}
var hasXmlHttp = dojo.hostenv.getXmlhttpObject() ? true : false;
this.canHandle = function(kwArgs){
// canHandle just tells dojo.io.bind() if this is a good transport to
// use for the particular type of request.
// FIXME: we need to determine when form values need to be
// multi-part mime encoded and avoid using this transport for those
// requests.
return hasXmlHttp
&& dojo.lang.inArray(["text/plain", "text/html", "application/xml", "text/xml", "text/javascript", "text/json"], (kwArgs["mimetype"].toLowerCase()||""))
&& !( kwArgs["formNode"] && dojo.io.formHasFile(kwArgs["formNode"]) );
}
this.multipartBoundary = "45309FFF-BD65-4d50-99C9-36986896A96F"; // unique guid as a boundary value for multipart posts
this.bind = function(kwArgs){
if(!kwArgs["url"]){
// are we performing a history action?
if( !kwArgs["formNode"]
&& (kwArgs["backButton"] || kwArgs["back"] || kwArgs["changeUrl"] || kwArgs["watchForURL"])
&& (!djConfig.preventBackButtonFix)) {
dojo.deprecated("Using dojo.io.XMLHTTPTransport.bind() to add to browser history without doing an IO request",
"Use dojo.undo.browser.addToHistory() instead.", "0.4");
dojo.undo.browser.addToHistory(kwArgs);
return true;
}
}
// build this first for cache purposes
var url = kwArgs.url;
var query = "";
if(kwArgs["formNode"]){
var ta = kwArgs.formNode.getAttribute("action");
if((ta)&&(!kwArgs["url"])){ url = ta; }
var tp = kwArgs.formNode.getAttribute("method");
if((tp)&&(!kwArgs["method"])){ kwArgs.method = tp; }
query += dojo.io.encodeForm(kwArgs.formNode, kwArgs.encoding, kwArgs["formFilter"]);
}
if(url.indexOf("#") > -1) {
dojo.debug("Warning: dojo.io.bind: stripping hash values from url:", url);
url = url.split("#")[0];
}
if(kwArgs["file"]){
// force post for file transfer
kwArgs.method = "post";
}
if(!kwArgs["method"]){
kwArgs.method = "get";
}
// guess the multipart value
if(kwArgs.method.toLowerCase() == "get"){
// GET cannot use multipart
kwArgs.multipart = false;
}else{
if(kwArgs["file"]){
// enforce multipart when sending files
kwArgs.multipart = true;
}else if(!kwArgs["multipart"]){
// default
kwArgs.multipart = false;
}
}
if(kwArgs["backButton"] || kwArgs["back"] || kwArgs["changeUrl"]){
dojo.undo.browser.addToHistory(kwArgs);
}
var content = kwArgs["content"] || {};
if(kwArgs.sendTransport) {
content["dojo.transport"] = "xmlhttp";
}
do { // break-block
if(kwArgs.postContent){
query = kwArgs.postContent;
break;
}
if(content) {
query += dojo.io.argsFromMap(content, kwArgs.encoding);
}
if(kwArgs.method.toLowerCase() == "get" || !kwArgs.multipart){
break;
}
var t = [];
if(query.length){
var q = query.split("&");
for(var i = 0; i < q.length; ++i){
if(q[i].length){
var p = q[i].split("=");
t.push( "--" + this.multipartBoundary,
"Content-Disposition: form-data; name=\"" + p[0] + "\"",
"",
p[1]);
}
}
}
if(kwArgs.file){
if(dojo.lang.isArray(kwArgs.file)){
for(var i = 0; i < kwArgs.file.length; ++i){
var o = kwArgs.file[i];
t.push( "--" + this.multipartBoundary,
"Content-Disposition: form-data; name=\"" + o.name + "\"; filename=\"" + ("fileName" in o ? o.fileName : o.name) + "\"",
"Content-Type: " + ("contentType" in o ? o.contentType : "application/octet-stream"),
"",
o.content);
}
}else{
var o = kwArgs.file;
t.push( "--" + this.multipartBoundary,
"Content-Disposition: form-data; name=\"" + o.name + "\"; filename=\"" + ("fileName" in o ? o.fileName : o.name) + "\"",
"Content-Type: " + ("contentType" in o ? o.contentType : "application/octet-stream"),
"",
o.content);
}
}
if(t.length){
t.push("--"+this.multipartBoundary+"--", "");
query = t.join("\r\n");
}
}while(false);
// kwArgs.Connection = "close";
var async = kwArgs["sync"] ? false : true;
var preventCache = kwArgs["preventCache"] ||
(this.preventCache == true && kwArgs["preventCache"] != false);
var useCache = kwArgs["useCache"] == true ||
(this.useCache == true && kwArgs["useCache"] != false );
// preventCache is browser-level (add query string junk), useCache
// is for the local cache. If we say preventCache, then don't attempt
// to look in the cache, but if useCache is true, we still want to cache
// the response
if(!preventCache && useCache){
var cachedHttp = getFromCache(url, query, kwArgs.method);
if(cachedHttp){
doLoad(kwArgs, cachedHttp, url, query, false);
return;
}
}
// much of this is from getText, but reproduced here because we need
// more flexibility
var http = dojo.hostenv.getXmlhttpObject(kwArgs);
var received = false;
// build a handler function that calls back to the handler obj
if(async){
var startTime =
// FIXME: setting up this callback handler leaks on IE!!!
this.inFlight.push({
"req": kwArgs,
"http": http,
"url": url,
"query": query,
"useCache": useCache,
"startTime": kwArgs.timeoutSeconds ? (new Date()).getTime() : 0
});
this.startWatchingInFlight();
}else{
// block async callbacks until sync is in, needed in khtml, others?
_this._blockAsync = true;
}
if(kwArgs.method.toLowerCase() == "post"){
// FIXME: need to hack in more flexible Content-Type setting here!
http.open("POST", url, async);
setHeaders(http, kwArgs);
http.setRequestHeader("Content-Type", kwArgs.multipart ? ("multipart/form-data; boundary=" + this.multipartBoundary) :
(kwArgs.contentType || "application/x-www-form-urlencoded"));
try{
http.send(query);
}catch(e){
if(typeof http.abort == "function"){
http.abort();
}
doLoad(kwArgs, {status: 404}, url, query, useCache);
}
}else{
var tmpUrl = url;
if(query != "") {
tmpUrl += (tmpUrl.indexOf("?") > -1 ? "&" : "?") + query;
}
if(preventCache) {
tmpUrl += (dojo.string.endsWithAny(tmpUrl, "?", "&")
? "" : (tmpUrl.indexOf("?") > -1 ? "&" : "?")) + "dojo.preventCache=" + new Date().valueOf();
}
http.open(kwArgs.method.toUpperCase(), tmpUrl, async);
setHeaders(http, kwArgs);
try {
http.send(null);
}catch(e) {
if(typeof http.abort == "function"){
http.abort();
}
doLoad(kwArgs, {status: 404}, url, query, useCache);
}
}
if( !async ) {
doLoad(kwArgs, http, url, query, useCache);
_this._blockAsync = false;
}
kwArgs.abort = function(){
try{// khtml doesent reset readyState on abort, need this workaround
http._aborted = true;
}catch(e){/*squelsh*/}
return http.abort();
}
return;
}
dojo.io.transports.addTransport("XMLHTTPTransport");
}

View File

@@ -0,0 +1,236 @@
dojo.provide("dojo.io.IframeIO");
dojo.require("dojo.io.BrowserIO");
dojo.require("dojo.uri.*");
// FIXME: is it possible to use the Google htmlfile hack to prevent the
// background click with this transport?
dojo.io.createIFrame = function(fname, onloadstr, uri){
if(window[fname]){ return window[fname]; }
if(window.frames[fname]){ return window.frames[fname]; }
var r = dojo.render.html;
var cframe = null;
var turi = uri||dojo.uri.dojoUri("iframe_history.html?noInit=true");
var ifrstr = ((r.ie)&&(dojo.render.os.win)) ? '<iframe name="'+fname+'" src="'+turi+'" onload="'+onloadstr+'">' : 'iframe';
cframe = document.createElement(ifrstr);
with(cframe){
name = fname;
setAttribute("name", fname);
id = fname;
}
dojo.body().appendChild(cframe);
window[fname] = cframe;
with(cframe.style){
if(!r.safari){
//We can't change the src in Safari 2.0.3 if absolute position. Bizarro.
position = "absolute";
}
left = top = "0px";
height = width = "1px";
visibility = "hidden";
/*
if(djConfig.isDebug){
position = "relative";
height = "300px";
width = "600px";
visibility = "visible";
}
*/
}
if(!r.ie){
dojo.io.setIFrameSrc(cframe, turi, true);
cframe.onload = new Function(onloadstr);
}
return cframe;
}
dojo.io.IframeTransport = new function(){
var _this = this;
this.currentRequest = null;
this.requestQueue = [];
this.iframeName = "dojoIoIframe";
this.fireNextRequest = function(){
if((this.currentRequest)||(this.requestQueue.length == 0)){ return; }
// dojo.debug("fireNextRequest");
var cr = this.currentRequest = this.requestQueue.shift();
cr._contentToClean = [];
var fn = cr["formNode"];
var content = cr["content"] || {};
if(cr.sendTransport) {
content["dojo.transport"] = "iframe";
}
if(fn){
if(content){
// if we have things in content, we need to add them to the form
// before submission
for(var x in content){
if(!fn[x]){
var tn;
if(dojo.render.html.ie){
tn = document.createElement("<input type='hidden' name='"+x+"' value='"+content[x]+"'>");
fn.appendChild(tn);
}else{
tn = document.createElement("input");
fn.appendChild(tn);
tn.type = "hidden";
tn.name = x;
tn.value = content[x];
}
cr._contentToClean.push(x);
}else{
fn[x].value = content[x];
}
}
}
if(cr["url"]){
cr._originalAction = fn.getAttribute("action");
fn.setAttribute("action", cr.url);
}
if(!fn.getAttribute("method")){
fn.setAttribute("method", (cr["method"]) ? cr["method"] : "post");
}
cr._originalTarget = fn.getAttribute("target");
fn.setAttribute("target", this.iframeName);
fn.target = this.iframeName;
fn.submit();
}else{
// otherwise we post a GET string by changing URL location for the
// iframe
var query = dojo.io.argsFromMap(this.currentRequest.content);
var tmpUrl = (cr.url.indexOf("?") > -1 ? "&" : "?") + query;
dojo.io.setIFrameSrc(this.iframe, tmpUrl, true);
}
}
this.canHandle = function(kwArgs){
return (
(
// FIXME: can we really handle text/plain and
// text/javascript requests?
dojo.lang.inArray([ "text/plain", "text/html", "text/javascript", "text/json"], kwArgs["mimetype"])
)&&(
// make sur we really only get used in file upload cases
(kwArgs["formNode"])&&(dojo.io.checkChildrenForFile(kwArgs["formNode"]))
)&&(
dojo.lang.inArray(["post", "get"], kwArgs["method"].toLowerCase())
)&&(
// never handle a sync request
! ((kwArgs["sync"])&&(kwArgs["sync"] == true))
)
);
}
this.bind = function(kwArgs){
if(!this["iframe"]){ this.setUpIframe(); }
this.requestQueue.push(kwArgs);
this.fireNextRequest();
return;
}
this.setUpIframe = function(){
// NOTE: IE 5.0 and earlier Mozilla's don't support an onload event for
// iframes. OTOH, we don't care.
this.iframe = dojo.io.createIFrame(this.iframeName, "dojo.io.IframeTransport.iframeOnload();");
}
this.iframeOnload = function(){
if(!_this.currentRequest){
_this.fireNextRequest();
return;
}
var req = _this.currentRequest;
// remove all the hidden content inputs
var toClean = req._contentToClean;
for(var i = 0; i < toClean.length; i++) {
var key = toClean[i];
if(dojo.render.html.safari){
//In Safari (at least 2.0.3), can't use formNode[key] syntax to find the node,
//for nodes that were dynamically added.
var fNode = req.formNode;
for(var j = 0; j < fNode.childNodes.length; j++){
var chNode = fNode.childNodes[j];
if(chNode.name == key){
var pNode = chNode.parentNode;
pNode.removeChild(chNode);
break;
}
}
}else if(req.formNode){
var input = req.formNode[key];
req.formNode.removeChild(input);
req.formNode[key] = null;
}
}
// restore original action + target
if(req["_originalAction"]){
req.formNode.setAttribute("action", req._originalAction);
}
req.formNode.setAttribute("target", req._originalTarget);
req.formNode.target = req._originalTarget;
var contentDoc = function(iframe_el){
var doc = iframe_el.contentDocument || // W3
(
(iframe_el.contentWindow)&&(iframe_el.contentWindow.document)
) || // IE
(
(iframe_el.name)&&(document.frames[iframe_el.name])&&
(document.frames[iframe_el.name].document)
) || null;
return doc;
};
var ifd = contentDoc(_this.iframe);
// handle successful returns
// FIXME: how do we determine success for iframes? Is there an equiv of
// the "status" property?
var value;
var success = false;
try{
var cmt = req.mimetype;
if((cmt == "text/javascript")||(cmt == "text/json")){
// FIXME: not sure what to do here? try to pull some evalulable
// text from a textarea or cdata section?
// how should we set up the contract for that?
var js = ifd.getElementsByTagName("textarea")[0].value;
if(cmt == "text/json") { js = "(" + js + ")"; }
value = dj_eval(js);
}else if(cmt == "text/html"){
value = ifd;
}else{ // text/plain
value = ifd.getElementsByTagName("textarea")[0].value;
}
success = true;
}catch(e){
// looks like we didn't get what we wanted!
var errObj = new dojo.io.Error("IframeTransport Error");
if(dojo.lang.isFunction(req["error"])){
req.error("error", errObj, req);
}
}
// don't want to mix load function errors with processing errors, thus
// a separate try..catch
try {
if(success && dojo.lang.isFunction(req["load"])){
req.load("load", value, req);
}
} catch(e) {
throw e;
} finally {
_this.currentRequest = null;
_this.fireNextRequest();
}
}
dojo.io.transports.addTransport("IframeTransport");
}

View File

@@ -0,0 +1,517 @@
// Copyright (c) 2004 Friendster Inc., Licensed under the Academic Free
// License version 2.0 or later
dojo.require("dojo.event");
dojo.require("dojo.event.BrowserEvent");
dojo.require("dojo.io.BrowserIO");
dojo.provide("dojo.io.RepubsubIO");
dojo.provide("dojo.io.repubsub");
dojo.provide("dojo.io.repubsubTransport");
dojo.io.repubsubTranport = new function(){
var rps = dojo.io.repubsub;
this.canHandle = function(kwArgs){
if((kwArgs["mimetype"] == "text/javascript")&&(kwArgs["method"] == "repubsub")){
return true;
}
return false;
}
this.bind = function(kwArgs){
if(!rps.isInitialized){
// open up our tunnel, queue up requests anyway
rps.init();
}
// FIXME: we need to turn this into a topic subscription
// var tgtURL = kwArgs.url+"?"+dojo.io.argsFromMap(kwArgs.content);
// sampleTransport.sendRequest(tgtURL, hdlrFunc);
// a normal "bind()" call in a request-response transport layer is
// something that (usually) encodes most of it's payload with the
// request. Multi-event systems like repubsub are a bit more complex,
// and repubsub in particular distinguishes the publish and subscribe
// portions of thep rocess with different method calls to handle each.
// Therefore, a "bind" in the sense of repubsub must first determine if
// we have an open subscription to a channel provided by the server,
// and then "publish" the request payload if there is any. We therefore
// must take care not to incorrectly or too agressively register or
// file event handlers which are provided with the kwArgs method.
// NOTE: we ONLY pay attention to those event handlers that are
// registered with the bind request that subscribes to the channel. If
// event handlers are provided with subsequent requests, we might in
// the future support some additive or replacement syntax, but for now
// they get dropped on the floor.
// NOTE: in this case, url MUST be the "topic" to which we
// subscribe/publish for this channel
if(!rps.topics[kwArgs.url]){
kwArgs.rpsLoad = function(evt){
kwArgs.load("load", evt);
}
rps.subscribe(kwArgs.url, kwArgs, "rpsLoad");
}
if(kwArgs["content"]){
// what we wanted to send
var cEvt = dojo.io.repubsubEvent.initFromProperties(kwArgs.content);
rps.publish(kwArgs.url, cEvt);
}
}
dojo.io.transports.addTransport("repubsubTranport");
}
dojo.io.repubsub = new function(){
this.initDoc = "init.html";
this.isInitialized = false;
this.subscriptionBacklog = [];
this.debug = true;
this.rcvNodeName = null;
this.sndNodeName = null;
this.rcvNode = null;
this.sndNode = null;
this.canRcv = false;
this.canSnd = false;
this.canLog = false;
this.sndTimer = null;
this.windowRef = window;
this.backlog = [];
this.tunnelInitCount = 0;
this.tunnelFrameKey = "tunnel_frame";
this.serverBaseURL = location.protocol+"//"+location.host+location.pathname;
this.logBacklog = [];
this.getRandStr = function(){
return Math.random().toString().substring(2, 10);
}
this.userid = "guest";
this.tunnelID = this.getRandStr();
this.attachPathList = [];
this.topics = []; // list of topics we have listeners to
// actually, now that I think about it a little bit more, it would sure be
// useful to parse out the <script> src attributes. We're looking for
// something with a "do_method=lib", since that's what would have included
// us in the first place (in the common case).
this.parseGetStr = function(){
var baseUrl = document.location.toString();
var params = baseUrl.split("?", 2);
if(params.length > 1){
var paramStr = params[1];
var pairs = paramStr.split("&");
var opts = [];
for(var x in pairs){
var sp = pairs[x].split("=");
// FIXME: is this eval dangerous?
try{
opts[sp[0]]=eval(sp[1]);
}catch(e){
opts[sp[0]]=sp[1];
}
}
return opts;
}else{
return [];
}
}
// parse URL params and use them as default vals
var getOpts = this.parseGetStr();
for(var x in getOpts){
// FIXME: should I be checking for undefined here before setting? Does
// that buy me anything?
this[x] = getOpts[x];
}
if(!this["tunnelURI"]){
this.tunnelURI = [ "/who/", escape(this.userid), "/s/",
this.getRandStr(), "/kn_journal"].join("");
// this.tunnelURI = this.absoluteTopicURI(this.tunnelURI);
}
/*
if (self.kn_tunnelID) kn.tunnelID = self.kn_tunnelID; // the server says
if (kn._argv.kn_tunnelID) kn.tunnelID = kn._argv.kn_tunnelID; // the url says
*/
// check the options object if it exists and use its properties as an
// over-ride
if(window["repubsubOpts"]||window["rpsOpts"]){
var optObj = window["repubsubOpts"]||window["rpsOpts"];
for(var x in optObj){
this[x] = optObj[x]; // copy the option object properties
}
}
// things that get called directly from our iframe to inform us of events
this.tunnelCloseCallback = function(){
// when we get this callback, we should immediately attempt to re-start
// our tunnel connection
dojo.io.setIFrameSrc(this.rcvNode, this.initDoc+"?callback=repubsub.rcvNodeReady&domain="+document.domain);
}
this.receiveEventFromTunnel = function(evt, srcWindow){
// we should never be getting events from windows we didn't create
// NOTE: events sourced from the local window are also supported for
// debugging purposes
// any event object MUST have a an "elements" property
if(!evt["elements"]){
this.log("bailing! event received without elements!", "error");
return;
}
// if the event passes some minimal sanity tests, we need to attempt to
// dispatch it!
// first, it seems we have to munge the event object a bit
var e = {};
for(var i=0; i<evt.elements.length; i++){
var ee = evt.elements[i];
e[ee.name||ee.nameU] = (ee.value||ee.valueU);
// FIXME: need to enable this only in some extreme debugging mode!
this.log("[event]: "+(ee.name||ee.nameU)+": "+e[ee.name||ee.nameU]);
}
// NOTE: the previous version of this library put a bunch of code here
// to manage state that tried to make sure that we never, ever, lost
// any info about an event. If we unload RIGHT HERE, I don't think it's
// going to make a huge difference one way or another. Time will tell.
// and with THAT out of the way, dispatch it!
this.dispatch(e);
// TODO: remove the script block that created the event obj to save
// memory, etc.
}
this.widenDomain = function(domainStr){
// the purpose of this is to set the most liberal domain policy
// available
var cd = domainStr||document.domain;
if(cd.indexOf(".")==-1){ return; } // probably file:/// or localhost
var dps = cd.split(".");
if(dps.length<=2){ return; } // probably file:/// or an RFC 1918 address
dps = dps.slice(dps.length-2);
document.domain = dps.join(".");
}
// FIXME: parseCookie and setCookie should be methods that are more broadly
// available. Perhaps in htmlUtils?
this.parseCookie = function(){
var cs = document.cookie;
var keypairs = cs.split(";");
for(var x=0; x<keypairs.length; x++){
keypairs[x] = keypairs[x].split("=");
if(x!=keypairs.length-1){ cs+=";"; }
}
return keypairs;
}
this.setCookie = function(keypairs, clobber){
// NOTE: we want to only ever set session cookies, so never provide an
// expires date
if((clobber)&&(clobber==true)){ document.cookie = ""; }
var cs = "";
for(var x=0; x<keypairs.length; x++){
cs += keypairs[x][0]+"="+keypairs[x][1];
if(x!=keypairs.length-1){ cs+=";"; }
}
document.cookie = cs;
}
// FIXME: need to replace w/ dojo.log.*
this.log = function(str, lvl){
if(!this.debug){ return; } // we of course only care if we're in debug mode
while(this.logBacklog.length>0){
if(!this.canLog){ break; }
var blo = this.logBacklog.shift();
this.writeLog("["+blo[0]+"]: "+blo[1], blo[2]);
}
this.writeLog(str, lvl);
}
this.writeLog = function(str, lvl){
dojo.debug(((new Date()).toLocaleTimeString())+": "+str);
}
this.init = function(){
this.widenDomain();
// this.findPeers();
this.openTunnel();
this.isInitialized = true;
// FIXME: this seems like entirely the wrong place to replay the backlog
while(this.subscriptionBacklog.length){
this.subscribe.apply(this, this.subscriptionBacklog.shift());
}
}
this.clobber = function(){
if(this.rcvNode){
this.setCookie( [
[this.tunnelFrameKey,"closed"],
["path","/"]
], false
);
}
}
this.openTunnel = function(){
// We create two iframes here:
// one for getting data
this.rcvNodeName = "rcvIFrame_"+this.getRandStr();
// set cookie that can be used to find the receiving iframe
this.setCookie( [
[this.tunnelFrameKey,this.rcvNodeName],
["path","/"]
], false
);
this.rcvNode = dojo.io.createIFrame(this.rcvNodeName);
// FIXME: set the src attribute here to the initialization URL
dojo.io.setIFrameSrc(this.rcvNode, this.initDoc+"?callback=repubsub.rcvNodeReady&domain="+document.domain);
// the other for posting data in reply
this.sndNodeName = "sndIFrame_"+this.getRandStr();
this.sndNode = dojo.io.createIFrame(this.sndNodeName);
// FIXME: set the src attribute here to the initialization URL
dojo.io.setIFrameSrc(this.sndNode, this.initDoc+"?callback=repubsub.sndNodeReady&domain="+document.domain);
}
this.rcvNodeReady = function(){
// FIXME: why is this sequence number needed? Why isn't the UID gen
// function enough?
var statusURI = [this.tunnelURI, '/kn_status/', this.getRandStr(), '_',
String(this.tunnelInitCount++)].join("");
// (kn._seqNum++); // FIXME: !!!!
// this.canRcv = true;
this.log("rcvNodeReady");
// FIXME: initialize receiver and request the base topic
// dojo.io.setIFrameSrc(this.rcvNode, this.serverBaseURL+"/kn?do_method=blank");
var initURIArr = [ this.serverBaseURL, "/kn?kn_from=", escape(this.tunnelURI),
"&kn_id=", escape(this.tunnelID), "&kn_status_from=",
escape(statusURI)];
// FIXME: does the above really need a kn_response_flush? won't the
// server already know? If not, what good is it anyway?
dojo.io.setIFrameSrc(this.rcvNode, initURIArr.join(""));
// setup a status path listener, but don't tell the server about it,
// since it already knows we're itnerested in our own tunnel status
this.subscribe(statusURI, this, "statusListener", true);
this.log(initURIArr.join(""));
}
this.sndNodeReady = function(){
this.canSnd = true;
this.log("sndNodeReady");
this.log(this.backlog.length);
// FIXME: handle any pent-up send commands
if(this.backlog.length > 0){
this.dequeueEvent();
}
}
this.statusListener = function(evt){
this.log("status listener called");
this.log(evt.status, "info");
}
// this handles local event propigation
this.dispatch = function(evt){
// figure out what topic it came from
if(evt["to"]||evt["kn_routed_from"]){
var rf = evt["to"]||evt["kn_routed_from"];
// split off the base server URL
var topic = rf.split(this.serverBaseURL, 2)[1];
if(!topic){
// FIXME: how do we recover when we don't get a sane "from"? Do
// we try to route to it anyway?
topic = rf;
}
this.log("[topic] "+topic);
if(topic.length>3){
if(topic.slice(0, 3)=="/kn"){
topic = topic.slice(3);
}
}
if(this.attachPathList[topic]){
this.attachPathList[topic](evt);
}
}
}
this.subscribe = function( topic /* kn_from in the old terminilogy */,
toObj, toFunc, dontTellServer){
if(!this.isInitialized){
this.subscriptionBacklog.push([topic, toObj, toFunc, dontTellServer]);
return;
}
if(!this.attachPathList[topic]){
this.attachPathList[topic] = function(){ return true; }
this.log("subscribing to: "+topic);
this.topics.push(topic);
}
var revt = new dojo.io.repubsubEvent(this.tunnelURI, topic, "route");
var rstr = [this.serverBaseURL+"/kn", revt.toGetString()].join("");
dojo.event.kwConnect({
once: true,
srcObj: this.attachPathList,
srcFunc: topic,
adviceObj: toObj,
adviceFunc: toFunc
});
// NOTE: the above is a local mapping, if we're not the leader, we
// should connect our mapping to the topic handler of the peer
// leader, this ensures that not matter what happens to the
// leader, we don't really loose our heads if/when the leader
// goes away.
if(!this.rcvNode){ /* this should be an error! */ }
if(dontTellServer){
return;
}
this.log("sending subscription to: "+topic);
// create a subscription event object and give it all the props we need
// to updates on the specified topic
// FIXME: we should only enqueue if this is our first subscription!
this.sendTopicSubToServer(topic, rstr);
}
this.sendTopicSubToServer = function(topic, str){
if(!this.attachPathList[topic]["subscriptions"]){
this.enqueueEventStr(str);
this.attachPathList[topic].subscriptions = 0;
}
this.attachPathList[topic].subscriptions++;
}
this.unSubscribe = function(topic, toObj, toFunc){
// first, locally disconnect
dojo.event.kwDisconnect({
srcObj: this.attachPathList,
srcFunc: topic,
adviceObj: toObj,
adviceFunc: toFunc
});
// FIXME: figure out if there are any remaining listeners to the topic,
// and if not, inform the server of our desire not to be
// notified of updates to the topic
}
// the "publish" method is really a misnomer, since it really means "take
// this event and send it to the server". Note that the "dispatch" method
// handles local event promigulation, and therefore we emulate both sides
// of a real event router without having to swallow all of the complexity.
this.publish = function(topic, event){
var evt = dojo.io.repubsubEvent.initFromProperties(event);
// FIXME: need to make sure we have from and to set correctly
// before we serialize and send off to the great blue
// younder.
evt.to = topic;
// evt.from = this.tunnelURI;
var evtURLParts = [];
evtURLParts.push(this.serverBaseURL+"/kn");
// serialize the event to a string and then post it to the correct
// topic
evtURLParts.push(evt.toGetString());
this.enqueueEventStr(evtURLParts.join(""));
}
this.enqueueEventStr = function(evtStr){
this.log("enqueueEventStr");
this.backlog.push(evtStr);
this.dequeueEvent();
}
this.dequeueEvent = function(force){
this.log("dequeueEvent");
if(this.backlog.length <= 0){ return; }
if((this.canSnd)||(force)){
dojo.io.setIFrameSrc(this.sndNode, this.backlog.shift()+"&callback=repubsub.sndNodeReady");
this.canSnd = false;
}else{
this.log("sndNode not available yet!", "debug");
}
}
}
dojo.io.repubsubEvent = function(to, from, method, id, routeURI, payload, dispname, uid){
this.to = to;
this.from = from;
this.method = method||"route";
this.id = id||repubsub.getRandStr();
this.uri = routeURI;
this.displayname = dispname||repubsub.displayname;
this.userid = uid||repubsub.userid;
this.payload = payload||"";
this.flushChars = 4096;
this.initFromProperties = function(evt){
if(evt.constructor = dojo.io.repubsubEvent){
for(var x in evt){
this[x] = evt[x];
}
}else{
// we want to copy all the properties of the evt object, and transform
// those that are "stock" properties of dojo.io.repubsubEvent. All others should
// be copied as-is
for(var x in evt){
if(typeof this.forwardPropertiesMap[x] == "string"){
this[this.forwardPropertiesMap[x]] = evt[x];
}else{
this[x] = evt[x];
}
}
}
}
this.toGetString = function(noQmark){
var qs = [ ((noQmark) ? "" : "?") ];
for(var x=0; x<this.properties.length; x++){
var tp = this.properties[x];
if(this[tp[0]]){
qs.push(tp[1]+"="+encodeURIComponent(String(this[tp[0]])));
}
// FIXME: we need to be able to serialize non-stock properties!!!
}
return qs.join("&");
}
}
dojo.io.repubsubEvent.prototype.properties = [["from", "kn_from"], ["to", "kn_to"],
["method", "do_method"], ["id", "kn_id"],
["uri", "kn_uri"],
["displayname", "kn_displayname"],
["userid", "kn_userid"],
["payload", "kn_payload"],
["flushChars", "kn_response_flush"],
["responseFormat", "kn_response_format"] ];
// maps properties from their old names to their new names...
dojo.io.repubsubEvent.prototype.forwardPropertiesMap = {};
// ...and vice versa...
dojo.io.repubsubEvent.prototype.reversePropertiesMap = {};
// and we then populate them both from the properties list
for(var x=0; x<dojo.io.repubsubEvent.prototype.properties.length; x++){
var tp = dojo.io.repubsubEvent.prototype.properties[x];
dojo.io.repubsubEvent.prototype.reversePropertiesMap[tp[0]] = tp[1];
dojo.io.repubsubEvent.prototype.forwardPropertiesMap[tp[1]] = tp[0];
}
// static version of initFromProperties, creates new event and object and
// returns it after init
dojo.io.repubsubEvent.initFromProperties = function(evt){
var eventObj = new dojo.io.repubsubEvent();
eventObj.initFromProperties(evt);
return eventObj;
}

View File

@@ -0,0 +1,133 @@
dojo.provide("dojo.io.RhinoIO");
dojo.require("dojo.io");
dojo.require("dojo.lang.array");
dojo.require("dojo.string.extras");
dojo.io.RhinoHTTPTransport = new function(){
this.canHandle = function(req){
// We have to limit to text types because Rhino doesnt support
// a W3C dom implementation out of the box. In the future we
// should provide some kind of hook to inject your own, because
// in all my projects I use XML for Script to provide a W3C DOM.
if(!dojo.lang.inArray((req.mimetype.toLowerCase() || ""),
["text/plain", "text/html", "text/javascript", "text/json"])){
return false;
}
// Only for sync requests! Async is possible but that would require
// messing about with threads which I am not terribly interested in
// doing at this juncture.
if(!req.sync){
return false;
}
// We only handle http requests! Unfortunately, because the method is
// protected, I can't directly create a java.net.HttpURLConnection, so
// this is the only way to test.
if(req.url.substr(0, 7) != "http://"){
return false;
}
return true;
}
function doLoad(req, conn){
var ret;
if (req.method.toLowerCase() == "head"){
// TODO: return the headers
}else{
var stream = conn.getContent();
var reader = new java.io.BufferedReader(new java.io.InputStreamReader(stream));
// read line-by-line because why not?
var text = "";
var line = null;
while((line = reader.readLine()) != null){
text += line;
}
if(req.mimetype == "text/javascript"){
try{
ret = dj_eval(text);
}catch(e){
dojo.debug(e);
dojo.debug(text);
ret = null;
}
}else if(req.mimetype == "text/json"){
try{
ret = dj_eval("("+text+")");
}catch(e){
dojo.debug(e);
dojo.debug(text);
ret = false;
}
}else{
ret = text;
}
}
req.load("load", ret, req);
}
this.bind = function(req){
var content = req.content || {};
var query;
if (req.sendTransport){
content["dojo.transport"] = "rhinohttp";
}
if(req.postContent){
query = req.postContent;
}else{
query = dojo.io.argsFromMap(content, req.encoding);
}
var url_text = req.url;
if(req.method.toLowerCase() == "get" && query != ""){
url_text = url_text + "?" + query;
}
var url = new java.net.URL(url_text);
var conn = url.openConnection();
//
// configure the connection
//
conn.setRequestMethod(req.method.toUpperCase());
if(req.headers){
for(var header in req.headers){
if(header.toLowerCase() == "content-type" && !req.contentType){
req.contentType = req.headers[header];
}else{
conn.setRequestProperty(header, req.headers[header]);
}
}
}
if(req.contentType){
conn.setRequestProperty("Content-Type", req.contentType);
}
if(req.method.toLowerCase() == "post"){
conn.setDoOutput(true);
// write the post data
var output_stream = conn.getOutputStream();
var byte_array = (new java.lang.String(query)).getBytes();
output_stream.write(byte_array, 0, byte_array.length);
}
// do it to it!
conn.connect();
// perform the load
doLoad(req, conn);
}
dojo.io.transports.addTransport("RhinoHTTPTransport");
}

View File

@@ -0,0 +1,444 @@
dojo.provide("dojo.io.ScriptSrcIO");
dojo.require("dojo.io.BrowserIO");
dojo.require("dojo.undo.browser");
//FIXME: should constantParams be JS object?
//FIXME: check dojo.io calls. Can we move the BrowserIO defined calls somewhere
// else so that we don't depend on BrowserIO at all? The dependent calls
// have to do with dealing with forms and making query params from JS object.
/**
* See test_ScriptSrcIO.html for usage information.
* Notes:
* - The watchInFlight timer is set to 100 ms instead of 10ms (which is what BrowserIO.js uses).
*/
dojo.io.ScriptSrcTransport = new function(){
this.preventCache = false; // if this is true, we'll always force GET requests to not cache
this.maxUrlLength = 1000; //Used to calculate if script request should be multipart.
this.inFlightTimer = null;
this.DsrStatusCodes = {
Continue: 100,
Ok: 200,
Error: 500
};
this.startWatchingInFlight = function(){
if(!this.inFlightTimer){
this.inFlightTimer = setInterval("dojo.io.ScriptSrcTransport.watchInFlight();", 100);
}
}
this.watchInFlight = function(){
var totalCount = 0;
var doneCount = 0;
for(var param in this._state){
totalCount++;
var currentState = this._state[param];
if(currentState.isDone){
doneCount++;
delete this._state[param];
}else{
var listener = currentState.kwArgs;
try{
if(currentState.checkString && eval("typeof(" + currentState.checkString + ") != 'undefined'")){
this._finish(currentState, "load");
doneCount++;
delete this._state[param];
}else if(listener.timeoutSeconds && listener.timeout){
if(currentState.startTime + (listener.timeoutSeconds * 1000) < (new Date()).getTime()){
this._finish(currentState, "timeout");
doneCount++;
delete this._state[param];
}
}else if(!listener.timeoutSeconds){
//Increment the done count if no timeout is specified, so
//that we turn off the timer if all that is left in the state
//list are things we can't clean up because they fail without
//getting a callback.
doneCount++;
}
}catch(e){
this._finish(currentState, "error", {status: this.DsrStatusCodes.Error, response: e});
}
}
}
if(doneCount == totalCount){
clearInterval(this.inFlightTimer);
this.inFlightTimer = null;
}
}
this.canHandle = function(kwArgs){
return dojo.lang.inArray(["text/javascript", "text/json"], (kwArgs["mimetype"].toLowerCase()))
&& (kwArgs["method"].toLowerCase() == "get")
&& !(kwArgs["formNode"] && dojo.io.formHasFile(kwArgs["formNode"]))
&& (!kwArgs["sync"] || kwArgs["sync"] == false)
&& !kwArgs["file"]
&& !kwArgs["multipart"];
}
/**
* Removes any script tags from the DOM that may have been added by ScriptSrcTransport.
* Be careful though, by removing them from the script, you may invalidate some
* script objects that were defined by the js file that was pulled in as the
* src of the script tag. Test carefully if you decide to call this method.
*
* In MSIE 6 (and probably 5.x), if you removed the script element while
* part of the script is still executing, the browser will crash.
*/
this.removeScripts = function(){
var scripts = document.getElementsByTagName("script");
for(var i = 0; scripts && i < scripts.length; i++){
var scriptTag = scripts[i];
if(scriptTag.className == "ScriptSrcTransport"){
var parent = scriptTag.parentNode;
parent.removeChild(scriptTag);
i--; //Set the index back one since we removed an item.
}
}
}
this.bind = function(kwArgs){
//START duplication from BrowserIO.js (some changes made)
var url = kwArgs.url;
var query = "";
if(kwArgs["formNode"]){
var ta = kwArgs.formNode.getAttribute("action");
if((ta)&&(!kwArgs["url"])){ url = ta; }
var tp = kwArgs.formNode.getAttribute("method");
if((tp)&&(!kwArgs["method"])){ kwArgs.method = tp; }
query += dojo.io.encodeForm(kwArgs.formNode, kwArgs.encoding, kwArgs["formFilter"]);
}
if(url.indexOf("#") > -1) {
dojo.debug("Warning: dojo.io.bind: stripping hash values from url:", url);
url = url.split("#")[0];
}
//Break off the domain/path of the URL.
var urlParts = url.split("?");
if(urlParts && urlParts.length == 2){
url = urlParts[0];
query += (query ? "&" : "") + urlParts[1];
}
if(kwArgs["backButton"] || kwArgs["back"] || kwArgs["changeUrl"]){
dojo.undo.browser.addToHistory(kwArgs);
}
//Create an ID for the request.
var id = kwArgs["apiId"] ? kwArgs["apiId"] : "id" + this._counter++;
//Fill out any other content pieces.
var content = kwArgs["content"];
var jsonpName = kwArgs.jsonParamName;
if(kwArgs.sendTransport || jsonpName) {
if (!content){
content = {};
}
if(kwArgs.sendTransport){
content["dojo.transport"] = "scriptsrc";
}
if(jsonpName){
content[jsonpName] = "dojo.io.ScriptSrcTransport._state." + id + ".jsonpCall";
}
}
if(kwArgs.postContent){
query = kwArgs.postContent;
}else if(content){
query += ((query) ? "&" : "") + dojo.io.argsFromMap(content, kwArgs.encoding, jsonpName);
}
//END duplication from BrowserIO.js
//START DSR
//If an apiId is specified, then we want to make sure useRequestId is true.
if(kwArgs["apiId"]){
kwArgs["useRequestId"] = true;
}
//Set up the state for this request.
var state = {
"id": id,
"idParam": "_dsrid=" + id,
"url": url,
"query": query,
"kwArgs": kwArgs,
"startTime": (new Date()).getTime()
};
if(!url){
//Error. An URL is needed.
this._finish(state, "error", {status: this.DsrStatusCodes.Error, statusText: "url.none"});
return;
}
//If this is a jsonp request, intercept the jsonp callback
if(content && content[jsonpName]){
state.jsonp = content[jsonpName];
state.jsonpCall = function(data){
if(data["Error"]||data["error"]){
if(dojo["json"] && dojo["json"]["serialize"]){
dojo.debug(dojo.json.serialize(data));
}
dojo.io.ScriptSrcTransport._finish(this, "error", data);
}else{
dojo.io.ScriptSrcTransport._finish(this, "load", data);
}
};
}
//Only store the request state on the state tracking object if a callback
//is expected or if polling on a checkString will be done.
if(kwArgs["useRequestId"] || kwArgs["checkString"] || state["jsonp"]){
this._state[id] = state;
}
//A checkstring is a string that if evaled will not be undefined once the
//script src loads. Used as an alternative to depending on a callback from
//the script file. If this is set, then multipart is not assumed to be used,
//since multipart requires a specific callback. With checkString we will be doing
//polling.
if(kwArgs["checkString"]){
state.checkString = kwArgs["checkString"];
}
//Constant params are parameters that should always be sent with each
//part of a multipart URL.
state.constantParams = (kwArgs["constantParams"] == null ? "" : kwArgs["constantParams"]);
if(kwArgs["preventCache"] ||
(this.preventCache == true && kwArgs["preventCache"] != false)){
state.nocacheParam = "dojo.preventCache=" + new Date().valueOf();
}else{
state.nocacheParam = "";
}
//Get total length URL, if we were to do it as one URL.
//Add some padding, extra & separators.
var urlLength = state.url.length + state.query.length + state.constantParams.length
+ state.nocacheParam.length + this._extraPaddingLength;
if(kwArgs["useRequestId"]){
urlLength += state.idParam.length;
}
if(!kwArgs["checkString"] && kwArgs["useRequestId"]
&& !state["jsonp"] && !kwArgs["forceSingleRequest"]
&& urlLength > this.maxUrlLength){
if(url > this.maxUrlLength){
//Error. The URL domain and path are too long. We can't
//segment that, so return an error.
this._finish(state, "error", {status: this.DsrStatusCodes.Error, statusText: "url.tooBig"});
return;
}else{
//Start the multiple requests.
this._multiAttach(state, 1);
}
}else{
//Send one URL.
var queryParams = [state.constantParams, state.nocacheParam, state.query];
if(kwArgs["useRequestId"] && !state["jsonp"]){
queryParams.unshift(state.idParam);
}
var finalUrl = this._buildUrl(state.url, queryParams);
//Track the final URL in case we need to use that instead of api ID when receiving
//the load callback.
state.finalUrl = finalUrl;
this._attach(state.id, finalUrl);
}
//END DSR
this.startWatchingInFlight();
}
//Private properties/methods
this._counter = 1;
this._state = {};
this._extraPaddingLength = 16;
//Is there a dojo function for this already?
this._buildUrl = function(url, nameValueArray){
var finalUrl = url;
var joiner = "?";
for(var i = 0; i < nameValueArray.length; i++){
if(nameValueArray[i]){
finalUrl += joiner + nameValueArray[i];
joiner = "&";
}
}
return finalUrl;
}
this._attach = function(id, url){
//Attach the script to the DOM.
var element = document.createElement("script");
element.type = "text/javascript";
element.src = url;
element.id = id;
element.className = "ScriptSrcTransport";
document.getElementsByTagName("head")[0].appendChild(element);
}
this._multiAttach = function(state, part){
//Check to make sure we still have a query to send up. This is mostly
//a protection from a goof on the server side when it sends a part OK
//response instead of a final response.
if(state.query == null){
this._finish(state, "error", {status: this.DsrStatusCodes.Error, statusText: "query.null"});
return;
}
if(!state.constantParams){
state.constantParams = "";
}
//How much of the query can we take?
//Add a padding constant to account for _part and a couple extra amperstands.
//Also add space for id since we'll need it now.
var queryMax = this.maxUrlLength - state.idParam.length
- state.constantParams.length - state.url.length
- state.nocacheParam.length - this._extraPaddingLength;
//Figure out if this is the last part.
var isDone = state.query.length < queryMax;
//Break up the query string if necessary.
var currentQuery;
if(isDone){
currentQuery = state.query;
state.query = null;
}else{
//Find the & or = nearest the max url length.
var ampEnd = state.query.lastIndexOf("&", queryMax - 1);
var eqEnd = state.query.lastIndexOf("=", queryMax - 1);
//See if & is closer, or if = is right at the edge,
//which means we should put it on the next URL.
if(ampEnd > eqEnd || eqEnd == queryMax - 1){
//& is nearer the end. So just chop off from there.
currentQuery = state.query.substring(0, ampEnd);
state.query = state.query.substring(ampEnd + 1, state.query.length) //strip off amperstand with the + 1.
}else{
//= is nearer the end. Take the max amount possible.
currentQuery = state.query.substring(0, queryMax);
//Find the last query name in the currentQuery so we can prepend it to
//ampEnd. Could be -1 (not there), so account for that.
var queryName = currentQuery.substring((ampEnd == -1 ? 0 : ampEnd + 1), eqEnd);
state.query = queryName + "=" + state.query.substring(queryMax, state.query.length);
}
}
//Now send a part of the script
var queryParams = [currentQuery, state.idParam, state.constantParams, state.nocacheParam];
if(!isDone){
queryParams.push("_part=" + part);
}
var url = this._buildUrl(state.url, queryParams);
this._attach(state.id + "_" + part, url);
}
this._finish = function(state, callback, event){
if(callback != "partOk" && !state.kwArgs[callback] && !state.kwArgs["handle"]){
//Ignore "partOk" because that is an internal callback.
if(callback == "error"){
state.isDone = true;
throw event;
}
}else{
switch(callback){
case "load":
var response = event ? event.response : null;
if(!response){
response = event;
}
state.kwArgs[(typeof state.kwArgs.load == "function") ? "load" : "handle"]("load", response, event, state.kwArgs);
state.isDone = true;
break;
case "partOk":
var part = parseInt(event.response.part, 10) + 1;
//Update the constant params, if any.
if(event.response.constantParams){
state.constantParams = event.response.constantParams;
}
this._multiAttach(state, part);
state.isDone = false;
break;
case "error":
state.kwArgs[(typeof state.kwArgs.error == "function") ? "error" : "handle"]("error", event.response, event, state.kwArgs);
state.isDone = true;
break;
default:
state.kwArgs[(typeof state.kwArgs[callback] == "function") ? callback : "handle"](callback, event, event, state.kwArgs);
state.isDone = true;
}
}
}
dojo.io.transports.addTransport("ScriptSrcTransport");
}
//Define callback handler.
window.onscriptload = function(event){
var state = null;
var transport = dojo.io.ScriptSrcTransport;
//Find the matching state object for event ID.
if(transport._state[event.id]){
state = transport._state[event.id];
}else{
//The ID did not match directly to an entry in the state list.
//Try searching the state objects for a matching original URL.
var tempState;
for(var param in transport._state){
tempState = transport._state[param];
if(tempState.finalUrl && tempState.finalUrl == event.id){
state = tempState;
break;
}
}
//If no matching original URL is found, then use the URL that was actually used
//in the SCRIPT SRC attribute.
if(state == null){
var scripts = document.getElementsByTagName("script");
for(var i = 0; scripts && i < scripts.length; i++){
var scriptTag = scripts[i];
if(scriptTag.getAttribute("class") == "ScriptSrcTransport"
&& scriptTag.src == event.id){
state = transport._state[scriptTag.id];
break;
}
}
}
//If state is still null, then throw an error.
if(state == null){
throw "No matching state for onscriptload event.id: " + event.id;
}
}
var callbackName = "error";
switch(event.status){
case dojo.io.ScriptSrcTransport.DsrStatusCodes.Continue:
//A part of a multipart request.
callbackName = "partOk";
break;
case dojo.io.ScriptSrcTransport.DsrStatusCodes.Ok:
//Successful reponse.
callbackName = "load";
break;
}
transport._finish(state, callbackName, event);
};

View File

@@ -0,0 +1,171 @@
/*
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.provide("dojo.io.ShortBusIO");
dojo.require("dojo.io"); // io.js provides setIFrameSrc
// FIXME: determine if we can use XMLHTTP to make x-domain posts despite not
// being able to hear back about the result
dojo.require("dojo.io.IframeIO"); // for posting across domains
dojo.require("dojo.io.cookie"); // for peering
dojo.require("dojo.event.*");
/*
* this file defines a "forever-frame" style Comet client. It passes opaque
* JSON data structures to/from the client. Both styles of request provide a
* topic for the event to be sent to and a payload object to be acted upon.
*
* All outbound events are sent via dojo.io.bind() and all inbound requests are
* processed by Dojo topic dispatch.
*
* ShortBusIO packets have the basic format:
*
* {
* topic: "/destination/topic/name",
* body: {
* // ...
* }
* }
*
* Packets bound for the event router (not one of it's clients) or generated
* from it are prefixed with the special "/meta" topic. Meta-topic events
* either inform the client to take an action or inform the server of a system
* event.
*
* Upon tunnel creation, the server might therefore send the following meta
* topic packet to the client to inform the client of it's assigned identity:
*
* // client <-- server
* {
* topic: "/meta",
* body: {
* action: "setClientId",
* clientId: "fooBar23",
* tunnelId: "fooBarTunnel4",
* tunnelExpiration: "...", // some date in the future
* }
* }
*
* The client may then respond with a confirmation:
*
* // client --> server
* {
* topic: "/meta",
* body: {
* action: "confirmClientId",
* from: "fooBar23"
* }
* }
*
* The client must implement a basic vocabulary of /meta topic verbs in order
* to participate as a ShortBus endpoint. These are TBD.
*
* NOTE: this example elides any authentication or authorization steps the
* client and server may have undertaken prior to tunnel setup.
*/
// TODO: unlike repubsubio we don't handle any sort of connection
// subscription/publishing backlog. Should we?
dojo.io.ShortBusTransport = new function(){
var initialized = false;
var connected = false;
// this class is similar to RepubsubIO save that we don't have the
// externalized protocol handler code. Our messages are simpler so our code
// can be as well.
this.rcvNode = null;
this.rcvNodeName = "";
this.topicRoot = null;
this.getRandStr = function(){
return Math.random().toString().substring(2, 10);
}
this.widenDomain = function(domainStr){
// allow us to make reqests to the TLD
var cd = domainStr||document.domain;
if(cd.indexOf(".")==-1){ return; } // probably file:/// or localhost
var dps = cd.split(".");
if(dps.length<=2){ return; } // probably file:/// or an RFC 1918 address
dps = dps.slice(dps.length-2);
document.domain = dps.join(".");
}
this.canHandle = function(kwArgs){
return (
(connected) &&
(kwArgs["topic"]) &&
(! // async only!
((kwArgs["sync"])&&(kwArgs["sync"] == true))
)
);
}
this.buildConnection = function(){
// NOTE: we require the server to cooperate by hosting
// ShortBusInit.html at the designated endpoint
this.rcvNodeName = "ShortBusRcv_"+this.getRandStr();
// the "forever frame" approach
if(dojo.render.html.ie){
// use the "htmlfile hack" to prevent the background click junk
this.rcvNode = new ActiveXObject("htmlfile");
this.rcvNode.open();
this.rcvNode.write("<html>");
this.rcvNode.write("<script>document.domain = '"+document.domain+"'");
this.rcvNode.write("</html>");
this.rcvNode.close();
var ifrDiv = this.rcvNode.createElement("div");
this.rcvNode.appendChild(ifrDiv);
this.rcvNode.parentWindow.dojo = dojo;
ifrDiv.innerHTML = "<iframe src='"+this.topicRoot+"/?tunntelType=htmlfile'></iframe>"
// and we're ready to go!
connected = true;
}else{
this.rcvNode = dojo.io.createIFrame(this.rcvNodeName);
dojo.io.setIFrameSrc(this.rcvNode, this.topicRoot+"/?tunnelType=iframe");
// we're still waiting on this one to call back up and advertise
// that it's been initialized
}
}
this.iframeConnectionInit = function(){
connected = true;
}
this.dispatchServerEvent = function(eObj){
// FIXME: implement basic /meta topic semantics here!
}
this.init = function(){
if(initialized){
return;
}
initialized = true;
this.widenDomain();
// we want to set up a connection to the designated server. Grab the
// server location out of djConfig.
this.topicRoot = djConfig["ShortBusRoot"];
if(!this.topicRoot){
dojo.debug("no topic root specified in djConfig.ShortBusRoot");
return;
}
}
this.dispatch = function(evt){
// dipatch events along the specified path
}
dojo.io.transports.addTransport("ShortBusTransport");
}

View File

@@ -0,0 +1,75 @@
<html>
<!--
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
-->
<script type="text/javascript">
if(window!=window.parent){
function callByDeRef(fname){
if(!fname){ return null; }
// if someone inadvertently passed in "foo(...)", we make it "foo"
fname = String(fname).split("(")[0];
// get a real array of arguments
var aa = [];
for(var x=1; x<arguments.length; x++){
aa.push(arguments[x]);
}
var parts = String(fname).split(".");
var obj = window;
for(var x=0; x<parts.length-1; x++){
obj = obj[parts[x]];
}
var fn = parts.pop(); // the last element is the function name
// exec the function in the specified namespace
return obj[fn].apply(obj, aa);
}
function widenDomain(domainStr){
// the purpose of this is to set the most liberal domain policy
var cd = domainStr||document.domain;
if(cd.indexOf(".")==-1){
document.domain = cd;
return;
}
var dps = cd.split(".");
if(dps.length>2){
dps = dps.slice(dps.length-2);
}
document.domain = dps.join(".");
}
function doInit(){
widenDomain();
var baseUrl = document.location.toString();
var params = baseUrl.split("?", 2);
if(params.length > 1){
var paramStr = params[1];
var pairs = paramStr.split("&");
var opts = [];
for(var x in pairs){
// alert(pairs[x]);
var sp = pairs[x].split("=");
opts[sp[0]]=sp[1];
if(sp[0]=="true"){
sp[0] = true;
}else if(sp[0]=="false"){
sp[0] = false;
}
}
if(opts["callback"]){
callByDeRef("parent."+opts["callback"]);
}
}
}
doInit();
}
</script>
</html>

View File

@@ -0,0 +1,199 @@
dojo.provide("dojo.io.XhrIframeProxy");
dojo.provide("dojo.io.XhrIframeFacade");
dojo.require("dojo.experimental");
dojo.experimental("dojo.io.XhrIframeProxy");
dojo.require("dojo.io.IframeIO");
dojo.require("dojo.html.iframe");
dojo.require("dojo.dom");
/*
TODO: This page might generate a "loading unsecure items on a secure page"
popup in browsers if it is served on a https URL, given that we are not
setting a src on the iframe element.
//TODO: Document that it doesn't work from local disk in Safari.
*/
dojo.io.XhrIframeProxy = new function(){
this._state = {};
this._stateIdCounter = 0;
this.send = function(facade){
var stateId = "XhrIframeProxy" + (this._stateIdCounter++);
facade._stateId = stateId;
this._state[stateId] = {
facade: facade,
stateId: stateId,
clientFrame: dojo.io.createIFrame(stateId,
"dojo.io.XhrIframeProxy.clientFrameLoaded('" + stateId + "');",
dojo.uri.dojoUri("src/io/xip_client.html"))
};
}
this.receive = function(stateId, urlEncodedData){
/* urlEncodedData should have the following params:
- responseHeaders
- status
- statusText
- responseText
*/
//Decode response data.
var response = {};
var nvPairs = urlEncodedData.split("&");
for(var i = 0; i < nvPairs.length; i++){
if(nvPairs[i]){
var nameValue = nvPairs[i].split("=");
response[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
//Set data on facade object.
var state = this._state[stateId];
var facade = state.facade;
facade._setResponseHeaders(response.responseHeaders);
if(response.status == 0 || response.status){
facade.status = parseInt(response.status, 10);
}
if(response.statusText){
facade.statusText = response.statusText;
}
if(response.responseText){
facade.responseText = response.responseText;
//Fix responseXML.
var contentType = facade.getResponseHeader("Content-Type");
if(contentType && (contentType == "application/xml" || contentType == "text/xml")){
facade.responseXML = dojo.dom.createDocumentFromText(response.responseText, contentType);
}
}
facade.readyState = 4;
this.destroyState(stateId);
}
this.clientFrameLoaded = function(stateId){
var state = this._state[stateId];
var facade = state.facade;
var clientWindow = dojo.html.iframeContentWindow(state.clientFrame);
var reqHeaders = [];
for(var param in facade._requestHeaders){
reqHeaders.push(param + ": " + facade._requestHeaders[param]);
}
var requestData = {
uri: facade._uri
};
if(reqHeaders.length > 0){
requestData.requestHeaders = reqHeaders.join("\r\n");
}
if(facade._method){
requestData.method = facade._method;
}
if(facade._bodyData){
requestData.data = facade._bodyData;
}
clientWindow.send(stateId, facade._ifpServerUrl, dojo.io.argsFromMap(requestData, "utf8"));
}
this.destroyState = function(stateId){
var state = this._state[stateId];
if(state){
delete this._state[stateId];
var parentNode = state.clientFrame.parentNode;
parentNode.removeChild(state.clientFrame);
state.clientFrame = null;
state = null;
}
}
this.createFacade = function(){
if(arguments && arguments[0] && arguments[0]["iframeProxyUrl"]){
return new dojo.io.XhrIframeFacade(arguments[0]["iframeProxyUrl"]);
}else{
return dojo.io.XhrIframeProxy.oldGetXmlhttpObject.apply(dojo.hostenv, arguments);
}
}
}
//Replace the normal XHR factory with the proxy one.
dojo.io.XhrIframeProxy.oldGetXmlhttpObject = dojo.hostenv.getXmlhttpObject;
dojo.hostenv.getXmlhttpObject = dojo.io.XhrIframeProxy.createFacade;
/**
Using this a reference: http://www.w3.org/TR/XMLHttpRequest/
Does not implement the onreadystate callback since dojo.io.BrowserIO does
not use it.
*/
dojo.io.XhrIframeFacade = function(ifpServerUrl){
this._requestHeaders = {};
this._allResponseHeaders = null;
this._responseHeaders = {};
this._method = null;
this._uri = null;
this._bodyData = null;
this.responseText = null;
this.responseXML = null;
this.status = null;
this.statusText = null;
this.readyState = 0;
this._ifpServerUrl = ifpServerUrl;
this._stateId = null;
}
dojo.lang.extend(dojo.io.XhrIframeFacade, {
//The open method does not properly reset since Dojo does not reuse XHR objects.
open: function(method, uri){
this._method = method;
this._uri = uri;
this.readyState = 1;
},
setRequestHeader: function(header, value){
this._requestHeaders[header] = value;
},
send: function(stringData){
this._bodyData = stringData;
dojo.io.XhrIframeProxy.send(this);
this.readyState = 2;
},
abort: function(){
dojo.io.XhrIframeProxy.destroyState(this._stateId);
},
getAllResponseHeaders: function(){
return this._allResponseHeaders;
},
getResponseHeader: function(header){
return this._responseHeaders[header];
},
_setResponseHeaders: function(allHeaders){
if(allHeaders){
this._allResponseHeaders = allHeaders;
//Make sure ther are now CR characters in the headers.
allHeaders = allHeaders.replace(/\r/g, "");
var nvPairs = allHeaders.split("\n");
for(var i = 0; i < nvPairs.length; i++){
if(nvPairs[i]){
var nameValue = nvPairs[i].split(": ");
this._responseHeaders[nameValue[0]] = nameValue[1];
}
}
}
}
});

View File

@@ -0,0 +1,7 @@
dojo.kwCompoundRequire({
common: ["dojo.io"],
rhino: ["dojo.io.RhinoIO"],
browser: ["dojo.io.BrowserIO", "dojo.io.cookie"],
dashboard: ["dojo.io.BrowserIO", "dojo.io.cookie"]
});
dojo.provide("dojo.io.*");

View File

@@ -0,0 +1,916 @@
dojo.require("dojo.io"); // io.js provides setIFrameSrc and the IO namespace
dojo.provide("dojo.io.cometd");
dojo.provide("cometd");
dojo.require("dojo.AdapterRegistry");
dojo.require("dojo.json");
dojo.require("dojo.io.BrowserIO"); // we need XHR for the handshake, etc.
// FIXME: determine if we can use XMLHTTP to make x-domain posts despite not
// being able to hear back about the result
dojo.require("dojo.io.IframeIO");
dojo.require("dojo.io.ScriptSrcIO"); // for x-domain long polling
dojo.require("dojo.io.cookie"); // for peering
dojo.require("dojo.event.*");
dojo.require("dojo.lang.common");
dojo.require("dojo.lang.func");
/*
* this file defines Comet protocol client. Actual message transport is
* deferred to one of several connection type implementations. The default is a
* forever-frame implementation. A single global object named "cometd" is
* used to mediate for these connection types in order to provide a stable
* interface.
*/
// TODO: the auth handling in this file is a *mess*. It should probably live in
// the cometd object with the ability to mix in or call down to an auth-handler
// object, the prototypical variant of which is a no-op
cometd = new function(){
this.initialized = false;
this.connected = false;
this.connectionTypes = new dojo.AdapterRegistry(true);
this.version = 0.1;
this.minimumVersion = 0.1;
this.clientId = null;
this.isXD = false;
this.handshakeReturn = null;
this.currentTransport = null;
this.url = null;
this.lastMessage = null;
this.globalTopicChannels = {};
this.backlog = [];
this.tunnelInit = function(childLocation, childDomain){
// placeholder
}
this.tunnelCollapse = function(){
dojo.debug("tunnel collapsed!");
// placeholder
}
this.init = function(props, root, bargs){
// FIXME: if the root isn't from the same host, we should automatically
// try to select an XD-capable transport
props = props||{};
// go ask the short bus server what we can support
props.version = this.version;
props.minimumVersion = this.minimumVersion;
props.channel = "/meta/handshake";
// FIXME: do we just assume that the props knows
// everything we care about WRT to auth? Should we be trying to
// call back into it for subsequent auth actions? Should we fire
// local auth functions to ask for/get auth data?
// FIXME: what about ScriptSrcIO for x-domain comet?
this.url = root||djConfig["cometdRoot"];
if(!this.url){
dojo.debug("no cometd root specified in djConfig and no root passed");
return;
}
// FIXME: we need to select a way to handle JSONP-style stuff
// generically here. We already know if the server is gonna be on
// another domain (or can know it), so we should select appropriate
// negotiation methods here as well as in final transport type
// selection.
var bindArgs = {
url: this.url,
method: "POST",
mimetype: "text/json",
load: dojo.lang.hitch(this, "finishInit"),
content: { "message": dojo.json.serialize([props]) }
};
// borrowed from dojo.uri.Uri in lieu of fixed host and port properties
var regexp = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?$";
var r = (""+window.location).match(new RegExp(regexp));
if(r[4]){
var tmp = r[4].split(":");
var thisHost = tmp[0];
var thisPort = tmp[1]||"80"; // FIXME: match 443
r = this.url.match(new RegExp(regexp));
if(r[4]){
tmp = r[4].split(":");
var urlHost = tmp[0];
var urlPort = tmp[1]||"80";
if( (urlHost != thisHost)||
(urlPort != thisPort) ){
dojo.debug(thisHost, urlHost);
dojo.debug(thisPort, urlPort);
this.isXD = true;
bindArgs.transport = "ScriptSrcTransport";
bindArgs.jsonParamName = "jsonp";
}
}
}
if(bargs){
dojo.lang.mixin(bindArgs, bargs);
}
return dojo.io.bind(bindArgs);
}
this.finishInit = function(type, data, evt, request){
this.handshakeReturn = data;
// pick a transport
if(data["authSuccessful"] == false){
dojo.debug("cometd authentication failed");
return;
}
if(data.version < this.minimumVersion){
dojo.debug("cometd protocol version mismatch. We wanted", this.minimumVersion, "but got", data.version);
return;
}
this.currentTransport = this.connectionTypes.match(
data.supportedConnectionTypes,
data.version,
this.isXD
);
this.currentTransport.version = data.version;
this.clientId = data.clientId;
this.tunnelInit = dojo.lang.hitch(this.currentTransport, "tunnelInit");
this.tunnelCollapse = dojo.lang.hitch(this.currentTransport, "tunnelCollapse");
this.initialized = true;
this.currentTransport.startup(data);
while(this.backlog.length != 0){
var cur = this.backlog.shift();
var fn = cur.shift();
this[fn].apply(this, cur);
}
}
this.getRandStr = function(){
return Math.random().toString().substring(2, 10);
}
// public API functions called by cometd or by the transport classes
this.deliver = function(messages){
dojo.lang.forEach(messages, this._deliver, this);
}
this._deliver = function(message){
// dipatch events along the specified path
if(!message["channel"]){
dojo.debug("cometd error: no channel for message!");
return;
}
if(!this.currentTransport){
this.backlog.push(["deliver", message]);
return;
}
this.lastMessage = message;
// check to see if we got a /meta channel message that we care about
if( (message.channel.length > 5)&&
(message.channel.substr(0, 5) == "/meta")){
// check for various meta topic actions that we need to respond to
switch(message.channel){
case "/meta/subscribe":
if(!message.successful){
dojo.debug("cometd subscription error for channel", message.channel, ":", message.error);
return;
}
this.subscribed(message.subscription, message);
break;
case "/meta/unsubscribe":
if(!message.successful){
dojo.debug("cometd unsubscription error for channel", message.channel, ":", message.error);
return;
}
this.unsubscribed(message.subscription, message);
break;
}
}
// send the message down for processing by the transport
this.currentTransport.deliver(message);
// dispatch the message to any locally subscribed listeners
var tname = (this.globalTopicChannels[message.channel]) ? message.channel : "/cometd"+message.channel;
dojo.event.topic.publish(tname, message);
}
this.disconnect = function(){
if(!this.currentTransport){
dojo.debug("no current transport to disconnect from");
return;
}
this.currentTransport.disconnect();
}
// public API functions called by end users
this.publish = function(/*string*/channel, /*object*/data, /*object*/properties){
// summary:
// publishes the passed message to the cometd server for delivery
// on the specified topic
// channel:
// the destination channel for the message
// data:
// a JSON object containing the message "payload"
// properties:
// Optional. Other meta-data to be mixed into the top-level of the
// message
if(!this.currentTransport){
this.backlog.push(["publish", channel, data, properties]);
return;
}
var message = {
data: data,
channel: channel
};
if(properties){
dojo.lang.mixin(message, properties);
}
return this.currentTransport.sendMessage(message);
}
this.subscribe = function( /*string*/ channel,
/*boolean, optional*/ useLocalTopics,
/*object, optional*/ objOrFunc,
/*string, optional*/ funcName){ // return: boolean
// summary:
// inform the server of this client's interest in channel
// channel:
// name of the cometd channel to subscribe to
// useLocalTopics:
// Determines if up a local event topic subscription to the passed
// function using the channel name that was passed is constructed,
// or if the topic name will be prefixed with some other
// identifier for local message distribution. Setting this to
// "true" is a good way to hook up server-sent message delivery to
// pre-existing local topics.
// objOrFunc:
// an object scope for funcName or the name or reference to a
// function to be called when messages are delivered to the
// channel
// funcName:
// the second half of the objOrFunc/funcName pair for identifying
// a callback function to notifiy upon channel message delivery
if(!this.currentTransport){
this.backlog.push(["subscribe", channel, useLocalTopics, objOrFunc, funcName]);
return;
}
if(objOrFunc){
var tname = (useLocalTopics) ? channel : "/cometd"+channel;
if(useLocalTopics){
this.globalTopicChannels[channel] = true;
}
dojo.event.topic.subscribe(tname, objOrFunc, funcName);
}
// FIXME: would we handle queuing of the subscription if not connected?
// Or should the transport object?
return this.currentTransport.sendMessage({
channel: "/meta/subscribe",
subscription: channel
});
}
this.subscribed = function( /*string*/ channel,
/*obj*/ message){
dojo.debug(channel);
dojo.debugShallow(message);
}
this.unsubscribe = function(/*string*/ channel,
/*boolean, optional*/ useLocalTopics,
/*object, optional*/ objOrFunc,
/*string, optional*/ funcName){ // return: boolean
// summary:
// inform the server of this client's disinterest in channel
// channel:
// name of the cometd channel to subscribe to
// useLocalTopics:
// Determines if up a local event topic subscription to the passed
// function using the channel name that was passed is destroyed,
// or if the topic name will be prefixed with some other
// identifier for stopping message distribution.
// objOrFunc:
// an object scope for funcName or the name or reference to a
// function to be called when messages are delivered to the
// channel
// funcName:
// the second half of the objOrFunc/funcName pair for identifying
if(!this.currentTransport){
this.backlog.push(["unsubscribe", channel, useLocalTopics, objOrFunc, funcName]);
return;
}
// a callback function to notifiy upon channel message delivery
if(objOrFunc){
// FIXME: should actual local topic unsubscription be delayed for
// successful unsubcribe notices from the other end? (guessing "no")
// FIXME: if useLocalTopics is false, should we go ahead and
// destroy the local topic?
var tname = (useLocalTopics) ? channel : "/cometd"+channel;
dojo.event.topic.unsubscribe(tname, objOrFunc, funcName);
}
return this.currentTransport.sendMessage({
channel: "/meta/unsubscribe",
subscription: channel
});
}
this.unsubscribed = function(/*string*/ channel,
/*obj*/ message){
dojo.debug(channel);
dojo.debugShallow(message);
}
// FIXME: add an "addPublisher" function
}
/*
transport objects MUST expose the following methods:
- check
- startup
- sendMessage
- deliver
- disconnect
optional, standard but transport dependent methods are:
- tunnelCollapse
- tunnelInit
Transports SHOULD be namespaced under the cometd object and transports MUST
register themselves with cometd.connectionTypes
here's a stub transport defintion:
cometd.blahTransport = new function(){
this.connected = false;
this.connectionId = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.check = function(types, version, xdomain){
// summary:
// determines whether or not this transport is suitable given a
// list of transport types that the server supports
return dojo.lang.inArray(types, "blah");
}
this.startup = function(){
if(this.connected){ return; }
// FIXME: fill in startup routine here
this.connected = true;
}
this.sendMessage = function(message){
// FIXME: fill in message sending logic
}
this.deliver = function(message){
if(message["timestamp"]){
this.lastTimestamp = message.timestamp;
}
if(message["id"]){
this.lastId = message.id;
}
if( (message.channel.length > 5)&&
(message.channel.substr(0, 5) == "/meta")){
// check for various meta topic actions that we need to respond to
// switch(message.channel){
// case "/meta/connect":
// // FIXME: fill in logic here
// break;
// // case ...: ...
// }
}
}
this.disconnect = function(){
if(!this.connected){ return; }
// FIXME: fill in shutdown routine here
this.connected = false;
}
}
cometd.connectionTypes.register("blah", cometd.blahTransport.check, cometd.blahTransport);
*/
cometd.iframeTransport = new function(){
this.connected = false;
this.connectionId = null;
this.rcvNode = null;
this.rcvNodeName = "";
this.phonyForm = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.backlog = [];
this.check = function(types, version, xdomain){
return ((!xdomain)&&
(!dojo.render.html.safari)&&
(dojo.lang.inArray(types, "iframe")));
}
this.tunnelInit = function(){
// we've gotten our initialization document back in the iframe, so
// now open up a connection and start passing data!
this.postToIframe({
message: dojo.json.serialize([
{
channel: "/meta/connect",
clientId: cometd.clientId,
connectionType: "iframe"
// FIXME: auth not passed here!
// "authToken": this.authToken
}
])
});
}
this.tunnelCollapse = function(){
if(this.connected){
// try to restart the tunnel
this.connected = false;
this.postToIframe({
message: dojo.json.serialize([
{
channel: "/meta/reconnect",
clientId: cometd.clientId,
connectionId: this.connectionId,
timestamp: this.lastTimestamp,
id: this.lastId
// FIXME: no authToken provision!
}
])
});
}
}
this.deliver = function(message){
// handle delivery details that this transport particularly cares
// about. Most functions of should be handled by the main cometd object
// with only transport-specific details and state being tracked here.
if(message["timestamp"]){
this.lastTimestamp = message.timestamp;
}
if(message["id"]){
this.lastId = message.id;
}
// check to see if we got a /meta channel message that we care about
if( (message.channel.length > 5)&&
(message.channel.substr(0, 5) == "/meta")){
// check for various meta topic actions that we need to respond to
switch(message.channel){
case "/meta/connect":
if(!message.successful){
dojo.debug("cometd connection error:", message.error);
return;
}
this.connectionId = message.connectionId;
this.connected = true;
this.processBacklog();
break;
case "/meta/reconnect":
if(!message.successful){
dojo.debug("cometd reconnection error:", message.error);
return;
}
this.connected = true;
break;
case "/meta/subscribe":
if(!message.successful){
dojo.debug("cometd subscription error for channel", message.channel, ":", message.error);
return;
}
// this.subscribed(message.channel);
dojo.debug(message.channel);
break;
}
}
}
this.widenDomain = function(domainStr){
// allow us to make reqests to the TLD
var cd = domainStr||document.domain;
if(cd.indexOf(".")==-1){ return; } // probably file:/// or localhost
var dps = cd.split(".");
if(dps.length<=2){ return; } // probably file:/// or an RFC 1918 address
dps = dps.slice(dps.length-2);
document.domain = dps.join(".");
return document.domain;
}
this.postToIframe = function(content, url){
if(!this.phonyForm){
if(dojo.render.html.ie){
this.phonyForm = document.createElement("<form enctype='application/x-www-form-urlencoded' method='POST' style='display: none;'>");
dojo.body().appendChild(this.phonyForm);
}else{
this.phonyForm = document.createElement("form");
this.phonyForm.style.display = "none"; // FIXME: will this still work?
dojo.body().appendChild(this.phonyForm);
this.phonyForm.enctype = "application/x-www-form-urlencoded";
this.phonyForm.method = "POST";
}
}
this.phonyForm.action = url||cometd.url;
this.phonyForm.target = this.rcvNodeName;
this.phonyForm.setAttribute("target", this.rcvNodeName);
while(this.phonyForm.firstChild){
this.phonyForm.removeChild(this.phonyForm.firstChild);
}
for(var x in content){
var tn;
if(dojo.render.html.ie){
tn = document.createElement("<input type='hidden' name='"+x+"' value='"+content[x]+"'>");
this.phonyForm.appendChild(tn);
}else{
tn = document.createElement("input");
this.phonyForm.appendChild(tn);
tn.type = "hidden";
tn.name = x;
tn.value = content[x];
}
}
this.phonyForm.submit();
}
this.processBacklog = function(){
while(this.backlog.length > 0){
this.sendMessage(this.backlog.shift(), true);
}
}
this.sendMessage = function(message, bypassBacklog){
// FIXME: what about auth fields?
if((bypassBacklog)||(this.connected)){
message.connectionId = this.connectionId;
message.clientId = cometd.clientId;
var bindArgs = {
url: cometd.url||djConfig["cometdRoot"],
method: "POST",
mimetype: "text/json",
// FIXME: we should be able to do better than this given that we're sending an array!
content: { message: dojo.json.serialize([ message ]) }
};
return dojo.io.bind(bindArgs);
}else{
this.backlog.push(message);
}
}
this.startup = function(handshakeData){
dojo.debug("startup!");
dojo.debug(dojo.json.serialize(handshakeData));
if(this.connected){ return; }
// this.widenDomain();
// NOTE: we require the server to cooperate by hosting
// cometdInit.html at the designated endpoint
this.rcvNodeName = "cometdRcv_"+cometd.getRandStr();
// the "forever frame" approach
var initUrl = cometd.url+"/?tunnelInit=iframe"; // &domain="+document.domain;
if(false && dojo.render.html.ie){ // FIXME: DISALBED FOR NOW
// use the "htmlfile hack" to prevent the background click junk
this.rcvNode = new ActiveXObject("htmlfile");
this.rcvNode.open();
this.rcvNode.write("<html>");
this.rcvNode.write("<script>document.domain = '"+document.domain+"'");
this.rcvNode.write("</html>");
this.rcvNode.close();
var ifrDiv = this.rcvNode.createElement("div");
this.rcvNode.appendChild(ifrDiv);
this.rcvNode.parentWindow.dojo = dojo;
ifrDiv.innerHTML = "<iframe src='"+initUrl+"'></iframe>"
}else{
this.rcvNode = dojo.io.createIFrame(this.rcvNodeName, "", initUrl);
// dojo.io.setIFrameSrc(this.rcvNode, initUrl);
// we're still waiting on the iframe to call back up to use and
// advertise that it's been initialized via tunnelInit
}
}
}
cometd.mimeReplaceTransport = new function(){
this.connected = false;
this.connectionId = null;
this.xhr = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.backlog = [];
this.check = function(types, version, xdomain){
return ((!xdomain)&&
(dojo.render.html.mozilla)&& // seems only Moz really supports this right now = (
(dojo.lang.inArray(types, "mime-message-block")));
}
this.tunnelInit = function(){
if(this.connected){ return; }
// FIXME: open up the connection here
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/connect",
clientId: cometd.clientId,
connectionType: "mime-message-block"
// FIXME: auth not passed here!
// "authToken": this.authToken
}
])
});
this.connected = true;
}
this.tunnelCollapse = function(){
if(this.connected){
// try to restart the tunnel
this.connected = false;
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/reconnect",
clientId: cometd.clientId,
connectionId: this.connectionId,
timestamp: this.lastTimestamp,
id: this.lastId
// FIXME: no authToken provision!
}
])
});
}
}
this.deliver = cometd.iframeTransport.deliver;
// the logic appears to be the same
this.handleOnLoad = function(resp){
cometd.deliver(dojo.json.evalJson(this.xhr.responseText));
}
this.openTunnelWith = function(content, url){
// set up the XHR object and register the multipart callbacks
this.xhr = dojo.hostenv.getXmlhttpObject();
this.xhr.multipart = true; // FIXME: do Opera and Safari support this flag?
if(dojo.render.html.mozilla){
this.xhr.addEventListener("load", dojo.lang.hitch(this, "handleOnLoad"), false);
}else if(dojo.render.html.safari){
// Blah. WebKit doesn't actually populate responseText and/or responseXML. Useless.
dojo.debug("Webkit is broken with multipart responses over XHR = (");
this.xhr.onreadystatechange = dojo.lang.hitch(this, "handleOnLoad");
}else{
this.xhr.onload = dojo.lang.hitch(this, "handleOnLoad");
}
this.xhr.open("POST", (url||cometd.url), true); // async post
this.xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
dojo.debug(dojo.json.serialize(content));
this.xhr.send(dojo.io.argsFromMap(content, "utf8"));
}
this.processBacklog = function(){
while(this.backlog.length > 0){
this.sendMessage(this.backlog.shift(), true);
}
}
this.sendMessage = function(message, bypassBacklog){
// FIXME: what about auth fields?
if((bypassBacklog)||(this.connected)){
message.connectionId = this.connectionId;
message.clientId = cometd.clientId;
var bindArgs = {
url: cometd.url||djConfig["cometdRoot"],
method: "POST",
mimetype: "text/json",
content: { message: dojo.json.serialize([ message ]) }
};
return dojo.io.bind(bindArgs);
}else{
this.backlog.push(message);
}
}
this.startup = function(handshakeData){
dojo.debugShallow(handshakeData);
if(this.connected){ return; }
this.tunnelInit();
}
}
cometd.longPollTransport = new function(){
this.connected = false;
this.connectionId = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.backlog = [];
this.check = function(types, version, xdomain){
return ((!xdomain)&&(dojo.lang.inArray(types, "long-polling")));
}
this.tunnelInit = function(){
if(this.connected){ return; }
// FIXME: open up the connection here
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/connect",
clientId: cometd.clientId,
connectionType: "long-polling"
// FIXME: auth not passed here!
// "authToken": this.authToken
}
])
});
this.connected = true;
}
this.tunnelCollapse = function(){
if(!this.connected){
// try to restart the tunnel
this.connected = false;
dojo.debug("clientId:", cometd.clientId);
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/reconnect",
connectionType: "long-polling",
clientId: cometd.clientId,
connectionId: this.connectionId,
timestamp: this.lastTimestamp,
id: this.lastId
// FIXME: no authToken provision!
}
])
});
}
}
this.deliver = cometd.iframeTransport.deliver;
// the logic appears to be the same
this.openTunnelWith = function(content, url){
dojo.io.bind({
url: (url||cometd.url),
method: "post",
content: content,
mimetype: "text/json",
load: dojo.lang.hitch(this, function(type, data, evt, args){
// dojo.debug(evt.responseText);
cometd.deliver(data);
this.connected = false;
this.tunnelCollapse();
}),
error: function(){ dojo.debug("tunnel opening failed"); }
});
this.connected = true;
}
this.processBacklog = function(){
while(this.backlog.length > 0){
this.sendMessage(this.backlog.shift(), true);
}
}
this.sendMessage = function(message, bypassBacklog){
// FIXME: what about auth fields?
if((bypassBacklog)||(this.connected)){
message.connectionId = this.connectionId;
message.clientId = cometd.clientId;
var bindArgs = {
url: cometd.url||djConfig["cometdRoot"],
method: "post",
mimetype: "text/json",
content: { message: dojo.json.serialize([ message ]) }
};
return dojo.io.bind(bindArgs);
}else{
this.backlog.push(message);
}
}
this.startup = function(handshakeData){
if(this.connected){ return; }
this.tunnelInit();
}
}
cometd.callbackPollTransport = new function(){
this.connected = false;
this.connectionId = null;
this.authToken = null;
this.lastTimestamp = null;
this.lastId = null;
this.backlog = [];
this.check = function(types, version, xdomain){
// we handle x-domain!
return dojo.lang.inArray(types, "callback-polling");
}
this.tunnelInit = function(){
if(this.connected){ return; }
// FIXME: open up the connection here
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/connect",
clientId: cometd.clientId,
connectionType: "callback-polling"
// FIXME: auth not passed here!
// "authToken": this.authToken
}
])
});
this.connected = true;
}
this.tunnelCollapse = function(){
if(!this.connected){
// try to restart the tunnel
this.connected = false;
this.openTunnelWith({
message: dojo.json.serialize([
{
channel: "/meta/reconnect",
connectionType: "long-polling",
clientId: cometd.clientId,
connectionId: this.connectionId,
timestamp: this.lastTimestamp,
id: this.lastId
// FIXME: no authToken provision!
}
])
});
}
}
this.deliver = cometd.iframeTransport.deliver;
// the logic appears to be the same
this.openTunnelWith = function(content, url){
// create a <script> element to generate the request
dojo.io.bind({
url: (url||cometd.url),
content: content,
transport: "ScriptSrcTransport",
jsonParamName: "jsonp",
load: dojo.lang.hitch(this, function(type, data, evt, args){
dojo.debug(dojo.json.serialize(data));
cometd.deliver(data);
this.connected = false;
this.tunnelCollapse();
}),
error: function(){ dojo.debug("tunnel opening failed"); }
});
this.connected = true;
}
this.processBacklog = function(){
while(this.backlog.length > 0){
this.sendMessage(this.backlog.shift(), true);
}
}
this.sendMessage = function(message, bypassBacklog){
// FIXME: what about auth fields?
if((bypassBacklog)||(this.connected)){
message.connectionId = this.connectionId;
message.clientId = cometd.clientId;
var bindArgs = {
url: cometd.url||djConfig["cometdRoot"],
transport: "ScriptSrcTransport",
jsonParamName: "jsonp",
content: { message: dojo.json.serialize([ message ]) }
};
return dojo.io.bind(bindArgs);
}else{
this.backlog.push(message);
}
}
this.startup = function(handshakeData){
if(this.connected){ return; }
this.tunnelInit();
}
}
cometd.connectionTypes.register("mime-message-block", cometd.mimeReplaceTransport.check, cometd.mimeReplaceTransport);
cometd.connectionTypes.register("long-polling", cometd.longPollTransport.check, cometd.longPollTransport);
cometd.connectionTypes.register("callback-polling", cometd.callbackPollTransport.check, cometd.callbackPollTransport);
cometd.connectionTypes.register("iframe", cometd.iframeTransport.check, cometd.iframeTransport);
// FIXME: need to implement fallback-polling, IE XML block
dojo.io.cometd = cometd;

View File

@@ -0,0 +1,98 @@
dojo.provide("dojo.io.cookie");
dojo.io.cookie.setCookie = function(name, value, days, path, domain, secure) {
var expires = -1;
if(typeof days == "number" && days >= 0) {
var d = new Date();
d.setTime(d.getTime()+(days*24*60*60*1000));
expires = d.toGMTString();
}
value = escape(value);
document.cookie = name + "=" + value + ";"
+ (expires != -1 ? " expires=" + expires + ";" : "")
+ (path ? "path=" + path : "")
+ (domain ? "; domain=" + domain : "")
+ (secure ? "; secure" : "");
}
dojo.io.cookie.set = dojo.io.cookie.setCookie;
dojo.io.cookie.getCookie = function(name) {
// FIXME: Which cookie should we return?
// If there are cookies set for different sub domains in the current
// scope there could be more than one cookie with the same name.
// I think taking the last one in the list takes the one from the
// deepest subdomain, which is what we're doing here.
var idx = document.cookie.lastIndexOf(name+'=');
if(idx == -1) { return null; }
var value = document.cookie.substring(idx+name.length+1);
var end = value.indexOf(';');
if(end == -1) { end = value.length; }
value = value.substring(0, end);
value = unescape(value);
return value;
}
dojo.io.cookie.get = dojo.io.cookie.getCookie;
dojo.io.cookie.deleteCookie = function(name) {
dojo.io.cookie.setCookie(name, "-", 0);
}
dojo.io.cookie.setObjectCookie = function(name, obj, days, path, domain, secure, clearCurrent) {
if(arguments.length == 5) { // for backwards compat
clearCurrent = domain;
domain = null;
secure = null;
}
var pairs = [], cookie, value = "";
if(!clearCurrent) { cookie = dojo.io.cookie.getObjectCookie(name); }
if(days >= 0) {
if(!cookie) { cookie = {}; }
for(var prop in obj) {
if(prop == null) {
delete cookie[prop];
} else if(typeof obj[prop] == "string" || typeof obj[prop] == "number") {
cookie[prop] = obj[prop];
}
}
prop = null;
for(var prop in cookie) {
pairs.push(escape(prop) + "=" + escape(cookie[prop]));
}
value = pairs.join("&");
}
dojo.io.cookie.setCookie(name, value, days, path, domain, secure);
}
dojo.io.cookie.getObjectCookie = function(name) {
var values = null, cookie = dojo.io.cookie.getCookie(name);
if(cookie) {
values = {};
var pairs = cookie.split("&");
for(var i = 0; i < pairs.length; i++) {
var pair = pairs[i].split("=");
var value = pair[1];
if( isNaN(value) ) { value = unescape(pair[1]); }
values[ unescape(pair[0]) ] = value;
}
}
return values;
}
dojo.io.cookie.isSupported = function() {
if(typeof navigator.cookieEnabled != "boolean") {
dojo.io.cookie.setCookie("__TestingYourBrowserForCookieSupport__",
"CookiesAllowed", 90, null);
var cookieVal = dojo.io.cookie.getCookie("__TestingYourBrowserForCookieSupport__");
navigator.cookieEnabled = (cookieVal == "CookiesAllowed");
if(navigator.cookieEnabled) {
// FIXME: should we leave this around?
this.deleteCookie("__TestingYourBrowserForCookieSupport__");
}
}
return navigator.cookieEnabled;
}
// need to leave this in for backwards-compat from 0.1 for when it gets pulled in by dojo.io.*
if(!dojo.io.cookies) { dojo.io.cookies = dojo.io.cookie; }

View File

@@ -0,0 +1,14 @@
/*
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.deprecated("dojo.io.cookies", "replaced by dojo.io.cookie", "0.4");
dojo.require("dojo.io.cookie");
if(!dojo.io.cookies) { dojo.io.cookies = dojo.io.cookie; }
dojo.provide("dojo.io.cookies");

View File

@@ -0,0 +1,200 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
<script type="text/javascript">
// <!--
/*
This file is really focused on just sending one message to the server, and
receiving one response. The code does not expect to be re-used for multiple messages.
This might be reworked later if performance indicates a need for it.
xip fragment identifier/hash values have the form:
#id:cmd:realUrlEncodedMessage
id: some ID that should be unique among messages. No inherent meaning,
just something to make sure the hash value is unique so the message
receiver knows a new message is available.
cmd: command to the receiver. Valid values are:
- loaded: the remote frame is loaded. Only sent from server to client.
- ok: the message that this page sent was received OK. The next message may
now be sent.
- start: the start message of a block of messages (a complete message may
need to be segmented into many messages to get around the limitiations
of the size of an URL that a browser accepts.
- part: indicates this is a part of a message.
- end: the end message of a block of messages. The message can now be acted upon.
If the message is small enough that it doesn't need to be segmented, then
just one hash value message can be sent with "end" as the command.
To reassemble a segmented message, the realUrlEncodedMessage parts just have to be concatenated
together.
*/
//Choosing 1024 as an arbitrary limit for the URL sizes.
//Anecdotal info seems to indicate this is safe to use in all
//modern browsers.
xipUrlLimit = 1024;
xipIdCounter = 1;
function xipInit(){
xipIsSending = false;
xipServerUrl = null;
xipStateId = null;
xipRequestData = null;
xipCurrentHash = "";
xipResponseMessage = "";
xipRequestParts = [];
xipPartIndex = 0;
xipServerWindow = null;
}
xipInit();
function send(stateId, ifpServerUrl, urlEncodedData){
if(!xipIsSending){
xipIsSending = true;
xipStateId = stateId;
xipRequestData = urlEncodedData || "";
//Modify the server URL if it is a local path and
//This is done for local/same domain testing.
xipServerUrl = ifpServerUrl;
if(ifpServerUrl.indexOf("..") == 0){
var parts = ifpServerUrl.split("/");
xipServerUrl = parts[parts.length - 1];
}
//Fix server URL to tell it about this page's URL. So that it can call us
//back correctly. Use the fragment identifier to allow for caching of the server
//page, and hey, we're using the fragment identifier for everything else.
ifpServerUrl += "#" + encodeURIComponent(window.location);
//Start counter to inspect hash value.
setInterval(pollHash, 10);
//Loader server iframe, then wait for the server page to call us back.
xipServerWindow = frames["xipServerFrame"];
if (!xipServerWindow){
xipServerWindow = document.getElementById("xipServerFrame").contentWindow;
}
xipServerWindow.location.replace(ifpServerUrl);
}
}
function pollHash(){
//Can't use location.hash because at least Firefox does a decodeURIComponent on it.
var urlParts = window.location.href.split("#");
if(urlParts.length == 2){
var newHash = urlParts[1];
if(newHash != xipCurrentHash){
try{
messageReceived(newHash);
}catch(e){
//Make sure to not keep processing the error hash value.
xipCurrentHash = newHash;
throw e;
}
xipCurrentHash = newHash;
}
}
}
function messageReceived(urlEncodedMessage){
//Split off xip header.
var parts = urlEncodedMessage.split(":");
var command = parts[1];
urlEncodedMessage = parts[2] || "";
switch(command){
case "loaded":
sendRequestStart();
break;
case "ok":
sendRequestPart();
break;
case "start":
xipResponseMessage = "";
xipResponseMessage += urlEncodedMessage;
setServerUrl("ok");
break;
case "part":
xipResponseMessage += urlEncodedMessage;
setServerUrl("ok");
break;
case "end":
setServerUrl("ok");
xipResponseMessage += urlEncodedMessage;
parent.dojo.io.XhrIframeProxy.receive(xipStateId, xipResponseMessage);
break;
}
}
function sendRequestStart(){
//Break the message into parts, if necessary.
xipRequestParts = [];
var reqData = xipRequestData;
var urlLength = xipServerUrl.length;
var partLength = xipUrlLimit - urlLength;
var reqIndex = 0;
while((reqData.length - reqIndex) + urlLength > xipUrlLimit){
xipRequestParts.push(reqData.substring(reqIndex, reqIndex + partLength));
reqIndex += partLength;
}
xipRequestParts.push(reqData.substring(reqIndex, reqData.length));
xipPartIndex = 0;
sendRequestPart();
}
function sendRequestPart(){
if(xipPartIndex < xipRequestParts.length){
//Get the message part.
var partData = xipRequestParts[xipPartIndex];
//Get the command.
var cmd = "part";
if(xipPartIndex + 1 == xipRequestParts.length){
cmd = "end";
}else if (xipPartIndex == 0){
cmd = "start";
}
setServerUrl(cmd, partData);
xipPartIndex++;
}
}
function setServerUrl(cmd, message){
var serverUrl = xipServerUrl + "#" + (xipIdCounter++) + ":" + cmd;
if(message){
serverUrl += ":" + message;
}
//Safari won't let us replace across domains.
if(navigator.userAgent.indexOf("Safari") == -1){
xipServerWindow.location.replace(serverUrl);
}else{
xipServerWindow.location = serverUrl;
}
}
// -->
</script>
</head>
<body>
<h4>The Dojo Toolkit -- xip_client.html</h4>
<p>This file is used for Dojo's XMLHttpRequest Iframe Proxy. This is the "client" file used
internally by dojo.io.XhrIframeProxy.</p>
<iframe id="xipServerFrame"></iframe>
</body>
</html>

View File

@@ -0,0 +1,267 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
<script type="text/javascript" src="isAllowed.js"></script>
<!--
BY DEFAULT THIS FILE DOES NOT WORK SO THAT YOU DON'T ACCIDENTALLY EXPOSE
ALL OF YOUR XHR-ENABLED SERVICES ON YOUR SITE.
In order for this file to work, you should define a function with the following signature:
function isAllowedRequest(request){
return false;
}
Return true out of the function if you want to allow the cross-domain request.
DON'T DEFINE THIS FUNCTION IN THIS FILE! Define it in a separate file called isAllowed.js
and include it in this page with a script tag that has a src attribute pointing to the file.
See the very first script tag in this file for an example. You do not have to place the
script file in the same directory as this file, just update the path above if you move it
somewhere else.
Customize the isAllowedRequest function to restrict what types of requests are allowed
for this server. The request object has the following properties:
- requestHeaders: an object with the request headers that are to be added to
the XHR request.
- method: the HTTP method (GET, POST, etc...)
- uri: The URI for the request.
- data: The URL-encoded data for the request. For a GET request, this would
be the querystring parameters. For a POST request, it wll be the
body data.
-->
<script type="text/javascript">
// <!--
/*
See xip_client.html for more info on the xip fragment identifier protocol.
This page uses Dojo to do the actual XMLHttpRequest (XHR) to the server, but you could
replace the Dojo references with your own XHR code if you like. But keep the other xip
code to communicate back to the xip client frame.
*/
djConfig = {
parseWidgets: false
}
// -->
</script>
<script type="text/javascript" src="../../dojo.js"></script>
<script type="text/javascript">
// <!--
dojo.require("dojo.io.*");
//Choosing 1024 as an arbitrary limit for the URL sizes.
//Anecdotal info seems to indicate this is safe to use in all
//modern browsers.
xipUrlLimit = 1024;
xipIdCounter = 1;
function xipServerInit(){
xipCurrentHash = "";
xipRequestMessage = "";
xipResponseParts = [];
xipPartIndex = 0;
}
function xipServerLoaded(){
xipServerInit();
xipClientUrl = decodeURIComponent(window.location.hash.substring(1, window.location.hash.length));
setInterval(pollHash, 10);
setClientUrl("loaded");
}
function pollHash(){
//Can't use location.hash because at least Firefox does a decodeURIComponent on it.
var urlParts = window.location.href.split("#");
if(urlParts.length == 2){
var newHash = urlParts[1];
if(newHash != xipCurrentHash){
try{
messageReceived(newHash);
}catch(e){
//Make sure to not keep processing the error hash value.
xipCurrentHash = newHash;
throw e;
}
xipCurrentHash = newHash;
}
}
}
function messageReceived(urlEncodedMessage){
//Split off xip header.
var parts = urlEncodedMessage.split(":");
var command = parts[1];
urlEncodedMessage = parts[2] || "";
switch(command){
case "ok":
sendResponsePart();
break;
case "start":
xipRequestMessage = "";
xipRequestMessage += urlEncodedMessage;
setClientUrl("ok");
break;
case "part":
xipRequestMessage += urlEncodedMessage;
setClientUrl("ok");
break;
case "end":
setClientUrl("ok");
xipRequestMessage += urlEncodedMessage;
sendXhr();
break;
}
}
function sendResponse(urlEncodedData){
//Break the message into parts, if necessary.
xipResponseParts = [];
var resData = urlEncodedData;
var urlLength = xipClientUrl.length;
var partLength = xipUrlLimit - urlLength;
var resIndex = 0;
while((resData.length - resIndex) + urlLength > xipUrlLimit){
xipResponseParts.push(resData.substring(resIndex, resIndex + partLength));
resIndex += partLength;
}
xipResponseParts.push(resData.substring(resIndex, resData.length));
xipPartIndex = 0;
sendResponsePart();
}
function sendResponsePart(){
if(xipPartIndex < xipResponseParts.length){
//Get the message part.
var partData = xipResponseParts[xipPartIndex];
//Get the command.
var cmd = "part";
if(xipPartIndex + 1 == xipResponseParts.length){
cmd = "end";
}else if (xipPartIndex == 0){
cmd = "start";
}
setClientUrl(cmd, partData);
xipPartIndex++;
}else{
xipServerInit();
}
}
function setClientUrl(cmd, message){
var clientUrl = xipClientUrl + "#" + (xipIdCounter++) + ":" + cmd;
if(message){
clientUrl += ":" + message;
}
//Safari won't let us replace across domains.
if(navigator.userAgent.indexOf("Safari") == -1){
parent.location.replace(clientUrl);
}else{
parent.location = clientUrl;
}
}
function sendXhr(){
//Decode data and pull off the fields that are sent by
//dojo.io.XhrIframeProxy.clientFrameLoaded()
/*
requestHeaders: reqHeaders.join("\r\n"),
method: facade.method,
uri: facade.uri,
data: facade._bodyData
*/
var request = {};
var nvPairs = xipRequestMessage.split("&");
var i = 0;
var nameValue = null;
for(i = 0; i < nvPairs.length; i++){
if(nvPairs[i]){
var nameValue = nvPairs[i].split("=");
request[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
//Split up the request headers, if any.
var headers = {};
if(request.requestHeaders){
nvPairs = request.requestHeaders.split("\r\n");
for(i = 0; i < nvPairs.length; i++){
if(nvPairs[i]){
nameValue = nvPairs[i].split(": ");
headers[decodeURIComponent(nameValue[0])] = decodeURIComponent(nameValue[1]);
}
}
request.requestHeaders = headers;
}
if(isAllowedRequest(request)){
var kwArgs = {
url: request.uri,
handle: function(type, data, xhr, args){
/* Need to pull off and return the following data:
- responseHeaders
- status
- statusText
- responseText
*/
var response = {};
if(typeof(xhr.getAllResponseHeaders) != "undefined"){
var allHeaders = xhr.getAllResponseHeaders();
if(allHeaders){
response.responseHeaders = allHeaders;
}
}
if(xhr.status == 0 || xhr.status){
response.status = xhr.status;
}
if(xhr.statusText){
response.statusText = xhr.statusText;
}
if(data){
response.responseText = data;
}
sendResponse(dojo.io.argsFromMap(response, "utf8"));
}
};
if(request.method){
kwArgs.method = request.method;
}
if(request.requestHeaders){
kwArgs.headers = request.requestHeaders;
}
if(request.data){
kwArgs.postContent = request.data;
}
dojo.io.bind(kwArgs);
}
}
dojo.addOnLoad(xipServerLoaded);
// -->
</script>
</head>
<body>
<h4>The Dojo Toolkit -- xip_server.html</h4>
<p>This file is used for Dojo's XMLHttpRequest Iframe Proxy. This is the the file
that should go on the server that will actually be doing the XHR request.</p>
</body>
</html>