Improve output: distinguish between failed assertions (failures) and unexpected exceptions (errors), and print a filtered stack trace for any exception.

This commit is contained in:
jeb228 2010-01-29 22:13:57 +00:00
commit 4f2e303079
1839 changed files with 235630 additions and 0 deletions

View file

@ -0,0 +1,557 @@
/*
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.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(type, ["file", "submit", "image", "reset", "button"]);
}
// 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(type, ["radio", "checkbox"])){
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(node.type.toLowerCase(), ["submit", "button"])) {
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(type, ["submit", "button", "image"])) {
if(!this.clickedButton) { this.clickedButton = node; }
accept = node == this.clickedButton;
} else {
accept = !dojo.lang.inArray(type, ["file", "submit", "reset", "button"]);
}
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){
this.inFlightTimer = setInterval("dojo.io.XMLHTTPTransport.watchInFlight();", 10);
}
}
this.watchInFlight = function(){
var now = null;
for(var x=this.inFlight.length-1; x>=0; x--){
var tif = this.inFlight[x];
if(!tif){ 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);
}
}
}
if(this.inFlight.length == 0){
clearInterval(this.inFlightTimer);
this.inFlightTimer = null;
}
}
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((kwArgs["mimetype"].toLowerCase()||""), ["text/plain", "text/html", "application/xml", "text/xml", "text/javascript", "text/json"])
&& !( 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();
}
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);
}
kwArgs.abort = function(){
return http.abort();
}
return;
}
dojo.io.transports.addTransport("XMLHTTPTransport");
}

View file

@ -0,0 +1,253 @@
/*
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.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){
if(window[fname]){ return window[fname]; }
if(window.frames[fname]){ return window.frames[fname]; }
var r = dojo.render.html;
var cframe = null;
var turi = 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;
}
(document.body||document.getElementsByTagName("body")[0]).appendChild(cframe);
window[fname] = cframe;
with(cframe.style){
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;
}
// thanks burstlib!
dojo.io.iframeContentWindow = function(iframe_el) {
var win = iframe_el.contentWindow || // IE
dojo.io.iframeContentDocument(iframe_el).defaultView || // Moz, opera
// Moz. TODO: is this available when defaultView isn't?
dojo.io.iframeContentDocument(iframe_el).__parent__ ||
(iframe_el.name && document.frames[iframe_el.name]) || null;
return win;
}
dojo.io.iframeContentDocument = 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;
}
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(kwArgs["mimetype"],
[ "text/plain", "text/html",
"text/javascript", "text/json"])
)&&(
// make sur we really only get used in file upload cases
(kwArgs["formNode"])&&(dojo.io.checkChildrenForFile(kwArgs["formNode"]))
)&&(
dojo.lang.inArray(kwArgs["method"].toLowerCase(), ["post", "get"])
)&&(
// 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{
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 ifd = dojo.io.iframeContentDocument(_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.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,22 @@
/*
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.RhinoIO");
// TODO: this doesn't execute
/*dojo.io.SyncHTTPRequest = function(){
dojo.io.SyncRequest.call(this);
this.send = function(URI){
}
}
dojo.inherits(dojo.io.SyncHTTPRequest, dojo.io.SyncRequest);
*/

View file

@ -0,0 +1,452 @@
/*
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.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((kwArgs["mimetype"].toLowerCase()), ["text/javascript", "text/json"])
&& (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"]){
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,17 @@
/*
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.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.*");

108
webapp/web/src/io/cookie.js Normal file
View file

@ -0,0 +1,108 @@
/*
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.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");