/* * copyright 2005 joe walker * * licensed under the apache license, version 2.0 (the "license"); * you may not use this file except in compliance with the license. * you may obtain a copy of the license at * * http://www.apache.org/licenses/license-2.0 * * unless required by applicable law or agreed to in writing, software * distributed under the license is distributed on an "as is" basis, * without warranties or conditions of any kind, either express or implied. * see the license for the specific language governing permissions and * limitations under the license. */ /** * declare an object to which we can add real functions. */ if (dwr == null) var dwr = {}; if (dwr.engine == null) dwr.engine = {}; if (dwrengine == null) var dwrengine = dwr.engine; /** * set an alternative error handler from the default alert box. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.seterrorhandler = function(handler) { dwr.engine._errorhandler = handler; }; /** * set an alternative warning handler from the default alert box. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.setwarninghandler = function(handler) { dwr.engine._warninghandler = handler; }; /** * setter for the text/html handler - what happens if a dwr request gets an html * reply rather than the expected javascript. often due to login timeout */ dwr.engine.settexthtmlhandler = function(handler) { dwr.engine._texthtmlhandler = handler; }; /** * set a default timeout value for all calls. 0 (the default) turns timeouts off. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.settimeout = function(timeout) { dwr.engine._timeout = timeout; }; /** * the pre-hook is called before any dwr remoting is done. * @see getahead.org/dwr/browser/engine/hooks */ dwr.engine.setprehook = function(handler) { dwr.engine._prehook = handler; }; /** * the post-hook is called after any dwr remoting is done. * @see getahead.org/dwr/browser/engine/hooks */ dwr.engine.setposthook = function(handler) { dwr.engine._posthook = handler; }; /** * custom headers for all dwr calls * @see getahead.org/dwr/???? */ dwr.engine.setheaders = function(headers) { dwr.engine._headers = headers; }; /** * custom parameters for all dwr calls * @see getahead.org/dwr/???? */ dwr.engine.setparameters = function(parameters) { dwr.engine._parameters = parameters; }; /** xhr remoting type constant. see dwr.engine.set[rpc|poll]type() */ dwr.engine.xmlhttprequest = 1; /** xhr remoting type constant. see dwr.engine.set[rpc|poll]type() */ dwr.engine.iframe = 2; /** xhr remoting type constant. see dwr.engine.setrpctype() */ dwr.engine.scripttag = 3; /** * set the preferred remoting type. * @param newtype one of dwr.engine.xmlhttprequest or dwr.engine.iframe or dwr.engine.scripttag * @see getahead.org/dwr/browser/engine/options */ dwr.engine.setrpctype = function(newtype) { if (newtype != dwr.engine.xmlhttprequest && newtype != dwr.engine.iframe && newtype != dwr.engine.scripttag) { dwr.engine._handleerror(null, { name:"dwr.engine.invalidrpctype", message:"rpctype must be one of dwr.engine.xmlhttprequest or dwr.engine.iframe or dwr.engine.scripttag" }); return; } dwr.engine._rpctype = newtype; }; /** * which http method do we use to send results? must be one of "get" or "post". * @see getahead.org/dwr/browser/engine/options */ dwr.engine.sethttpmethod = function(httpmethod) { if (httpmethod != "get" && httpmethod != "post") { dwr.engine._handleerror(null, { name:"dwr.engine.invalidhttpmethod", message:"remoting method must be one of get or post" }); return; } dwr.engine._httpmethod = httpmethod; }; /** * ensure that remote calls happen in the order in which they were sent? (default: false) * @see getahead.org/dwr/browser/engine/ordering */ dwr.engine.setordered = function(ordered) { dwr.engine._ordered = ordered; }; /** * do we ask the xhr object to be asynchronous? (default: true) * @see getahead.org/dwr/browser/engine/options */ dwr.engine.setasync = function(async) { dwr.engine._async = async; }; /** * does dwr poll the server for updates? (default: false) * @see getahead.org/dwr/browser/engine/options */ dwr.engine.setactivereverseajax = function(activereverseajax) { if (activereverseajax) { // bail if we are already started if (dwr.engine._activereverseajax) return; dwr.engine._activereverseajax = true; dwr.engine._poll(); } else { // can we cancel an existing request? if (dwr.engine._activereverseajax && dwr.engine._pollreq) dwr.engine._pollreq.abort(); dwr.engine._activereverseajax = false; } // todo: in iframe mode, if we start, stop, start then the second start may // well kick off a second iframe while the first is still about to return // we should cope with this but we don't }; /** * the default message handler. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.defaulterrorhandler = function(message, ex) { dwr.engine._debug("error: " + ex.name + ", " + ex.message, true); /*if (message == null || message == "") alert("a server error has occured."); // ignore ns_error_not_available if mozilla is being narky else if (message.indexof("0x80040111") != -1) dwr.engine._debug(message); else if (message.indexof("failed to read input") == -1 && message.indexof("not implemented") == -1 && message.indexof("service unavailable") == -1 ) alert(message); */ dwr.engine._debug(message); }; /** * the default warning handler. * @see getahead.org/dwr/browser/engine/errors */ dwr.engine.defaultwarninghandler = function(message, ex) { dwr.engine._debug(message); }; /** * for reduced latency you can group several remote calls together using a batch. * @see getahead.org/dwr/browser/engine/batch */ dwr.engine.beginbatch = function() { if (dwr.engine._batch) { dwr.engine._handleerror(null, { name:"dwr.engine.batchbegun", message:"batch already begun" }); return; } dwr.engine._batch = dwr.engine._createbatch(); }; /** * finished grouping a set of remote calls together. go and execute them all. * @see getahead.org/dwr/browser/engine/batch */ dwr.engine.endbatch = function(options) { var batch = dwr.engine._batch; if (batch == null) { dwr.engine._handleerror(null, { name:"dwr.engine.batchnotbegun", message:"no batch in progress" }); return; } dwr.engine._batch = null; if (batch.map.callcount == 0) return; // the hooks need to be merged carefully to preserve ordering if (options) dwr.engine._mergebatch(batch, options); // in ordered mode, we don't send unless the list of sent items is empty if (dwr.engine._ordered && dwr.engine._batcheslength != 0) { dwr.engine._batchqueue[dwr.engine._batchqueue.length] = batch; } else { dwr.engine._senddata(batch); } }; /** @deprecated */ dwr.engine.setpollmethod = function(type) { dwr.engine.setpolltype(type); }; dwr.engine.setmethod = function(type) { dwr.engine.setrpctype(type); }; dwr.engine.setverb = function(verb) { dwr.engine.sethttpmethod(verb); }; dwr.engine.setpolltype = function() { dwr.engine._debug("manually setting the poll type is not supported"); }; //============================================================================== // only private stuff below here //============================================================================== /** the original page id sent from the server */ dwr.engine._origscriptsessionid = "defbbecb6243b50f4b3e47731eda07d8"; /** the session cookie name */ dwr.engine._sessioncookiename = "jsessionid"; // jsessionid /** is get enabled for the benefit of safari? */ dwr.engine._allowgetforsafaributmakeforgeryeasier = "false"; /** the script prefix to strip in the case of scripttagprotection. */ dwr.engine._scripttagprotection = "throw 'allowscripttagremoting is false.';"; /** the default path to the dwr servlet */ dwr.engine._defaultpath = "/system/dwr"; /** do we use xhr for reverse ajax because we are not streaming? */ dwr.engine._pollwithxhr = "false"; /** the read page id that we calculate */ dwr.engine._scriptsessionid = null; /** the function that we use to fetch/calculate a session id */ dwr.engine._getscriptsessionid = function() { if (dwr.engine._scriptsessionid == null) { dwr.engine._scriptsessionid = dwr.engine._origscriptsessionid + math.floor(math.random() * 1000); } return dwr.engine._scriptsessionid; }; /** a function to call if something fails. */ dwr.engine._errorhandler = dwr.engine.defaulterrorhandler; /** for debugging when something unexplained happens. */ dwr.engine._warninghandler = dwr.engine.defaultwarninghandler; /** a function to be called before requests are marshalled. can be null. */ dwr.engine._prehook = null; /** a function to be called after replies are received. can be null. */ dwr.engine._posthook = null; /** an map of the batches that we have sent and are awaiting a reply on. */ dwr.engine._batches = {}; /** a count of the number of outstanding batches. should be == to _batches.length unless prototype has messed things up */ dwr.engine._batcheslength = 0; /** in ordered mode, the array of batches waiting to be sent */ dwr.engine._batchqueue = []; /** what is the default rpc type */ dwr.engine._rpctype = dwr.engine.xmlhttprequest; /** what is the default remoting method (ie get or post) */ dwr.engine._httpmethod = "post"; /** do we attempt to ensure that calls happen in the order in which they were sent? */ dwr.engine._ordered = false; /** do we make the calls async? */ dwr.engine._async = true; /** the current batch (if we are in batch mode) */ dwr.engine._batch = null; /** the global timeout */ dwr.engine._timeout = 0; /** activex objects to use when we want to convert an xml string into a dom object. */ dwr.engine._domdocument = ["msxml2.domdocument.6.0", "msxml2.domdocument.5.0", "msxml2.domdocument.4.0", "msxml2.domdocument.3.0", "msxml2.domdocument", "msxml.domdocument", "microsoft.xmldom"]; /** the activex objects to use when we want to do an xmlhttprequest call. */ dwr.engine._xmlhttp = ["msxml2.xmlhttp.6.0", "msxml2.xmlhttp.5.0", "msxml2.xmlhttp.4.0", "msxml2.xmlhttp.3.0", "msxml2.xmlhttp", "microsoft.xmlhttp"]; /** are we doing comet or polling? */ dwr.engine._activereverseajax = false; /** the iframe that we are using to poll */ dwr.engine._outstandingiframes = []; /** the xhr object that we are using to poll */ dwr.engine._pollreq = null; /** how many milliseconds between internal comet polls */ dwr.engine._pollcometinterval = 200; /** how many times have we re-tried to poll? */ dwr.engine._pollretries = 0; dwr.engine._maxpollretries = 0; /** do we do a document.reload if we get a text/html reply? */ dwr.engine._texthtmlhandler = null; /** if you wish to send custom headers with every request */ dwr.engine._headers = null; /** if you wish to send extra custom request parameters with each request */ dwr.engine._parameters = null; /** undocumented interceptors - do not use */ dwr.engine._postseperator = "&"; dwr.engine._defaultinterceptor = function(data) { return data; }; dwr.engine._urlrewritehandler = dwr.engine._defaultinterceptor; dwr.engine._contentrewritehandler = dwr.engine._defaultinterceptor; dwr.engine._replyrewritehandler = dwr.engine._defaultinterceptor; /** batch ids allow us to know which batch the server is answering */ dwr.engine._nextbatchid = 0; /** a list of the properties that need merging from calls to a batch */ dwr.engine._propnames = [ "rpctype", "httpmethod", "async", "timeout", "errorhandler", "warninghandler", "texthtmlhandler" ]; /** do we stream, or can be hacked to do so? */ dwr.engine._partialresponseno = 0; dwr.engine._partialresponseyes = 1; dwr.engine._partialresponseflush = 2; /** is this page in the process of unloading? */ dwr.engine._unloading = false; /** * @private send a request. called by the javascript interface stub * @param path part of url after the host and before the exec bit without leading or trailing /s * @param scriptname the class to execute * @param methodname the method on said class to execute * @param func the callback function to which any returned data should be passed * if this is null, any returned data will be ignored * @param vararg_params the parameters to pass to the above class */ dwr.engine._execute = function(path, scriptname, methodname, vararg_params) { var singleshot = false; if (dwr.engine._batch == null) { dwr.engine.beginbatch(); singleshot = true; } var batch = dwr.engine._batch; // to make them easy to manipulate we copy the arguments into an args array var args = []; for (var i = 0; i < arguments.length - 3; i++) { args[i] = arguments[i + 3]; } // all the paths must be to the same servlet if (batch.path == null) { batch.path = path; } else { if (batch.path != path) { dwr.engine._handleerror(batch, { name:"dwr.engine.multipleservlets", message:"can't batch requests to multiple dwr servlets." }); return; } } // from the other params, work out which is the function (or object with // call meta-data) and which is the call parameters var calldata; var lastarg = args[args.length - 1]; if (typeof lastarg == "function" || lastarg == null) calldata = { callback:args.pop() }; else calldata = args.pop(); // merge from the calldata into the batch dwr.engine._mergebatch(batch, calldata); batch.handlers[batch.map.callcount] = { exceptionhandler:calldata.exceptionhandler, callback:calldata.callback }; // copy to the map the things that need serializing var prefix = "c" + batch.map.callcount + "-"; batch.map[prefix + "scriptname"] = scriptname; batch.map[prefix + "methodname"] = methodname; batch.map[prefix + "id"] = batch.map.callcount; var refctx = []; for (i = 0; i < args.length; i++) { dwr.engine._serializeall(batch, refctx, args[i], prefix + "param" + i); } // now we have finished remembering the call, we incr the call count batch.map.callcount++; if (singleshot) dwr.engine.endbatch(); }; /** @private poll the server to see if there is any data waiting */ dwr.engine._poll = function() { if (!dwr.engine._activereverseajax) return; var batch = dwr.engine._createbatch(); batch.map.id = 0; // todo: do we need this?? batch.map.callcount = 1; batch.ispoll = true; if (dwr.engine._pollwithxhr == "true") { batch.rpctype = dwr.engine.xmlhttprequest; batch.map.partialresponse = dwr.engine._partialresponseno; } else { if (navigator.useragent.indexof("gecko/") != -1) { batch.rpctype = dwr.engine.xmlhttprequest; batch.map.partialresponse = dwr.engine._partialresponseyes; } else { batch.rpctype = dwr.engine.xmlhttprequest; batch.map.partialresponse = dwr.engine._partialresponseno; } } batch.httpmethod = "post"; batch.async = true; batch.timeout = 0; batch.path = dwr.engine._defaultpath; batch.prehooks = []; batch.posthooks = []; batch.errorhandler = dwr.engine._pollerrorhandler; batch.warninghandler = dwr.engine._pollerrorhandler; batch.handlers[0] = { callback:function(pause) { dwr.engine._pollretries = 0; settimeout(dwr.engine._poll, pause); } }; // send the data dwr.engine._senddata(batch); if (batch.rpctype == dwr.engine.xmlhttprequest && batch.map.partialresponse == dwr.engine._partialresponseyes) { dwr.engine._checkcometpoll(); } }; /** try to recover from polling errors */ dwr.engine._pollerrorhandler = function(msg, ex) { // if anything goes wrong then just silently try again (up to 3x) after 10s dwr.engine._pollretries++; dwr.engine._debug("reverse ajax poll failed (pollretries=" + dwr.engine._pollretries + "): " + ex.name + " : " + ex.message); if (dwr.engine._pollretries < dwr.engine._maxpollretries) { settimeout(dwr.engine._poll, 10000); } else { dwr.engine._activereverseajax = false; dwr.engine._debug("giving up."); } }; /** @private generate a new standard batch */ dwr.engine._createbatch = function() { var batch = { map:{ callcount:0, page:window.location.pathname + window.location.search, httpsessionid:dwr.engine._getjsessionid(), scriptsessionid:dwr.engine._getscriptsessionid() }, charsprocessed:0, paramcount:0, parameters:{}, headers:{}, ispoll:false, handlers:{}, prehooks:[], posthooks:[], rpctype:dwr.engine._rpctype, httpmethod:dwr.engine._httpmethod, async:dwr.engine._async, timeout:dwr.engine._timeout, errorhandler:dwr.engine._errorhandler, warninghandler:dwr.engine._warninghandler, texthtmlhandler:dwr.engine._texthtmlhandler }; if (dwr.engine._prehook) batch.prehooks.push(dwr.engine._prehook); if (dwr.engine._posthook) batch.posthooks.push(dwr.engine._posthook); var propname, data; if (dwr.engine._headers) { for (propname in dwr.engine._headers) { data = dwr.engine._headers[propname]; if (typeof data != "function") batch.headers[propname] = data; } } if (dwr.engine._parameters) { for (propname in dwr.engine._parameters) { data = dwr.engine._parameters[propname]; if (typeof data != "function") batch.parameters[propname] = data; } } return batch; }; /** @private take further options and merge them into */ dwr.engine._mergebatch = function(batch, overrides) { var propname, data; for (var i = 0; i < dwr.engine._propnames.length; i++) { propname = dwr.engine._propnames[i]; if (overrides[propname] != null) batch[propname] = overrides[propname]; } if (overrides.prehook != null) batch.prehooks.unshift(overrides.prehook); if (overrides.posthook != null) batch.posthooks.push(overrides.posthook); if (overrides.headers) { for (propname in overrides.headers) { data = overrides.headers[propname]; if (typeof data != "function") batch.headers[propname] = data; } } if (overrides.parameters) { for (propname in overrides.parameters) { data = overrides.parameters[propname]; if (typeof data != "function") batch.map["p-" + propname] = "" + data; } } }; /** @private what is our session id? */ dwr.engine._getjsessionid = function() { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i]; while (cookie.charat(0) == ' ') cookie = cookie.substring(1, cookie.length); if (cookie.indexof(dwr.engine._sessioncookiename + "=") == 0) { return cookie.substring(dwr.engine._sessioncookiename.length + 1, cookie.length); } } return ""; }; /** @private check for reverse ajax activity */ dwr.engine._checkcometpoll = function() { for (var i = 0; i < dwr.engine._outstandingiframes.length; i++) { var text = ""; var iframe = dwr.engine._outstandingiframes[i]; try { text = dwr.engine._gettextfromcometiframe(iframe); } catch (ex) { dwr.engine._handlewarning(iframe.batch, ex); } if (text != "") dwr.engine._processcometresponse(text, iframe.batch); } if (dwr.engine._pollreq) { var req = dwr.engine._pollreq; var text = req.responsetext; if (text != null) dwr.engine._processcometresponse(text, req.batch); } // if the poll resources are still there, come back again if (dwr.engine._outstandingiframes.length > 0 || dwr.engine._pollreq) { settimeout(dwr.engine._checkcometpoll, dwr.engine._pollcometinterval); } }; /** @private extract the whole (executed an all) text from the current iframe */ dwr.engine._gettextfromcometiframe = function(frameele) { var body = frameele.contentwindow.document.body; if (body == null) return ""; var text = body.innerhtml; // we need to prevent ie from stripping line feeds if (text.indexof("
") == 0 || text.indexof("") == 0) { text = text.substring(5, text.length - 7); } return text; }; /** @private some more text might have come in, test and execute the new stuff */ dwr.engine._processcometresponse = function(response, batch) { if (batch.charsprocessed == response.length) return; if (response.length == 0) { batch.charsprocessed = 0; return; } var firststarttag = response.indexof("//#dwr-start#", batch.charsprocessed); if (firststarttag == -1) { // dwr.engine._debug("no start tag (search from " + batch.charsprocessed + "). skipping '" + response.substring(batch.charsprocessed) + "'"); batch.charsprocessed = response.length; return; } // if (firststarttag > 0) { // dwr.engine._debug("start tag not at start (search from " + batch.charsprocessed + "). skipping '" + response.substring(batch.charsprocessed, firststarttag) + "'"); // } var lastendtag = response.lastindexof("//#dwr-end#"); if (lastendtag == -1) { // dwr.engine._debug("no end tag. unchanged charsprocessed=" + batch.charsprocessed); return; } // skip the end tag too for next time, remembering cr and lf if (response.charcodeat(lastendtag + 11) == 13 && response.charcodeat(lastendtag + 12) == 10) { batch.charsprocessed = lastendtag + 13; } else { batch.charsprocessed = lastendtag + 11; } var exec = response.substring(firststarttag + 13, lastendtag); dwr.engine._receivedbatch = batch; dwr.engine._eval(exec); dwr.engine._receivedbatch = null; }; /** @private actually send the block of data in the batch object. */ dwr.engine._senddata = function(batch) { batch.map.batchid = dwr.engine._nextbatchid; dwr.engine._nextbatchid++; dwr.engine._batches[batch.map.batchid] = batch; dwr.engine._batcheslength++; batch.completed = false; for (var i = 0; i < batch.prehooks.length; i++) { batch.prehooks[i](); } batch.prehooks = null; // set a timeout if (batch.timeout && batch.timeout != 0) { batch.timeoutid = settimeout(function() { dwr.engine._abortrequest(batch); }, batch.timeout); } // get setup for xmlhttprequest if possible if (batch.rpctype == dwr.engine.xmlhttprequest) { if (window.xmlhttprequest) { batch.req = new xmlhttprequest(); } // ie5 for the mac claims to support window.activexobject, but throws an error when it's used else if (window.activexobject && !(navigator.useragent.indexof("mac") >= 0 && navigator.useragent.indexof("msie") >= 0)) { batch.req = dwr.engine._newactivexobject(dwr.engine._xmlhttp); } } var prop, request; if (batch.req) { // proceed using xmlhttprequest if (batch.async) { batch.req.onreadystatechange = function() { if (typeof dwr != 'undefined') dwr.engine._statechange(batch); }; } // if we're polling, record this for monitoring if (batch.ispoll) { dwr.engine._pollreq = batch.req; // in ie xhr is an activex control so you can't augment it like this if (!(document.all && !window.opera)) batch.req.batch = batch; } // workaround for safari 1.x post bug var indexsafari = navigator.useragent.indexof("safari/"); if (indexsafari >= 0) { var version = navigator.useragent.substring(indexsafari + 7); if (parseint(version, 10) < 400) { if (dwr.engine._allowgetforsafaributmakeforgeryeasier == "true") batch.httpmethod = "get"; else dwr.engine._handlewarning(batch, { name:"dwr.engine.oldsafari", message:"safari get support disabled. see getahead.org/dwr/server/servlet and allowgetforsafaributmakeforgeryeasier." }); } } batch.mode = batch.ispoll ? dwr.engine._modeplainpoll : dwr.engine._modeplaincall; request = dwr.engine._constructrequest(batch); try { batch.req.open(batch.httpmethod, request.url, batch.async); try { for (prop in batch.headers) { var value = batch.headers[prop]; if (typeof value == "string") batch.req.setrequestheader(prop, value); } //if (!batch.headers["content-type"]) batch.req.setrequestheader("content-type", "text/plain"); batch.req.setrequestheader("content-type", "application/x-www-form-urlencoded;charset=utf-8"); } catch (ex) { dwr.engine._handlewarning(batch, ex); } batch.req.send(request.body); if (!batch.async) dwr.engine._statechange(batch); } catch (ex) { dwr.engine._handleerror(batch, ex); } } else if (batch.rpctype != dwr.engine.scripttag) { var idname = batch.ispoll ? "dwr-if-poll-" + batch.map.batchid : "dwr-if-" + batch.map.batchid; // removed htmlfile implementation. don't expect it to return before v3 batch.div = document.createelement("div"); // add the div to the document first, otherwise ie 6 will ignore onload handler. document.body.appendchild(batch.div); batch.div.innerhtml = ""; batch.document = document; batch.iframe = batch.document.getelementbyid(idname); batch.iframe.batch = batch; batch.mode = batch.ispoll ? dwr.engine._modehtmlpoll : dwr.engine._modehtmlcall; if (batch.ispoll) dwr.engine._outstandingiframes.push(batch.iframe); request = dwr.engine._constructrequest(batch); if (batch.httpmethod == "get") { batch.iframe.setattribute("src", request.url); } else { batch.form = batch.document.createelement("form"); batch.form.setattribute("id", "dwr-form"); batch.form.setattribute("action", request.url); batch.form.setattribute("style", "display:none;"); batch.form.setattribute("target", idname); batch.form.target = idname; batch.form.setattribute("method", batch.httpmethod); for (prop in batch.map) { var value = batch.map[prop]; if (typeof value != "function") { var forminput = batch.document.createelement("input"); forminput.setattribute("type", "hidden"); forminput.setattribute("name", prop); forminput.setattribute("value", value); batch.form.appendchild(forminput); } } batch.document.body.appendchild(batch.form); batch.form.submit(); } } else { batch.httpmethod = "get"; // there's no such thing as scripttag using post batch.mode = batch.ispoll ? dwr.engine._modeplainpoll : dwr.engine._modeplaincall; request = dwr.engine._constructrequest(batch); batch.script = document.createelement("script"); batch.script.id = "dwr-st-" + batch.map["c0-id"]; batch.script.src = request.url; document.body.appendchild(batch.script); } }; dwr.engine._modeplaincall = "/call/plaincall/"; dwr.engine._modehtmlcall = "/call/htmlcall/"; dwr.engine._modeplainpoll = "/call/plainpoll/"; dwr.engine._modehtmlpoll = "/call/htmlpoll/"; /** @private work out what the url should look like */ dwr.engine._constructrequest = function(batch) { // a quick string to help people that use web log analysers var request = { url:batch.path + batch.mode, body:null }; if (batch.ispoll == true) { request.url += "reverseajax.dwr"; } else if (batch.map.callcount == 1) { request.url += batch.map["c0-scriptname"] + "." + batch.map["c0-methodname"] + ".dwr"; } else { request.url += "multiple." + batch.map.callcount + ".dwr"; } // play nice with url re-writing var sessionmatch = location.href.match(/jsessionid=([^?]+)/); if (sessionmatch != null) { request.url += ";jsessionid=" + sessionmatch[1]; } var prop; if (batch.httpmethod == "get") { // some browsers (opera/safari2) seem to fail to convert the callcount value // to a string in the loop below so we do it manually here. batch.map.callcount = "" + batch.map.callcount; request.url += "?"; for (prop in batch.map) { if (typeof batch.map[prop] != "function") { request.url += encodeuricomponent(prop) + "=" + encodeuricomponent(batch.map[prop]) + "&"; } } request.url = request.url.substring(0, request.url.length - 1); } else { // performance: for iframe mode this is thrown away. request.body = ""; if (document.all && !window.opera) { // use array joining on ie (fastest) var buf = []; for (prop in batch.map) { if (typeof batch.map[prop] != "function") { buf.push(prop + "=" + encodeuricomponent(batch.map[prop]) + dwr.engine._postseperator); } } request.body = buf.join(""); } else { // use string concat on other browsers (fastest) for (prop in batch.map) { if (typeof batch.map[prop] != "function") { request.body += prop + "=" + encodeuricomponent(batch.map[prop]) + dwr.engine._postseperator; } } } request.body = dwr.engine._contentrewritehandler(request.body); } request.url = dwr.engine._urlrewritehandler(request.url); return request; }; /** @private called by xmlhttprequest to indicate that something has happened */ dwr.engine._statechange = function(batch) { var toeval; if (batch.completed) { dwr.engine._debug("error: _statechange() with batch.completed"); return; } var req = batch.req; try { if (req.readystate != 4) return; } catch (ex) { dwr.engine._handlewarning(batch, ex); // it's broken - clear up and forget this call dwr.engine._clearup(batch); return; } if (dwr.engine._unloading) { dwr.engine._debug("ignoring reply from server as page is unloading."); return; } try { var reply = req.responsetext; reply = dwr.engine._replyrewritehandler(reply); var status = req.status; // causes mozilla to except on page moves if (reply == null || reply == "") { dwr.engine._handlewarning(batch, { name:"dwr.engine.missingdata", message:"no data received from server" }); } else if (status != 200) { dwr.engine._handleerror(batch, { name:"dwr.engine.http." + status, message:req.statustext }); } else { var contenttype = req.getresponseheader("content-type"); if (!contenttype.match(/^text\/plain/) && !contenttype.match(/^text\/javascript/)) { if (contenttype.match(/^text\/html/) && typeof batch.texthtmlhandler == "function") { batch.texthtmlhandler({ status:status, responsetext:reply, contenttype:contenttype }); } else { dwr.engine._handlewarning(batch, { name:"dwr.engine.invalidmimetype", message:"invalid content type: '" + contenttype + "'" }); } } else { // comet replies might have already partially executed if (batch.ispoll && batch.map.partialresponse == dwr.engine._partialresponseyes) { dwr.engine._processcometresponse(reply, batch); } else { if (reply.search("//#dwr") == -1) { dwr.engine._handlewarning(batch, { name:"dwr.engine.invalidreply", message:"invalid reply from server" }); } else { toeval = reply; } } } } } catch (ex) { dwr.engine._handlewarning(batch, ex); } dwr.engine._callposthooks(batch); // outside of the try/catch so errors propogate normally: dwr.engine._receivedbatch = batch; if (toeval != null) toeval = toeval.replace(dwr.engine._scripttagprotection, ""); dwr.engine._eval(toeval); dwr.engine._receivedbatch = null; dwr.engine._validatebatch(batch); if (!batch.completed) dwr.engine._clearup(batch); }; /** * @private this function is invoked when a batch reply is received. * it checks that there is a response for every call in the batch. otherwise, * an error will be signaled (a call without a response indicates that the * server failed to send complete batch response). */ dwr.engine._validatebatch = function(batch) { // if some call left unreplied, report an error. if (!batch.completed) { for (var i = 0; i < batch.map.callcount; i++) { if (batch.handlers[i] != null) { dwr.engine._handlewarning(batch, { name:"dwr.engine.incompletereply", message:"incomplete reply from server" }); break; } } } } /** @private called from iframe onload, check batch using batch-id */ dwr.engine._iframeloadingcomplete = function(batchid) { // dwr.engine._checkcometpoll(); var batch = dwr.engine._batches[batchid]; if (batch) dwr.engine._validatebatch(batch); } /** @private called by the server: execute a callback */ dwr.engine._remotehandlecallback = function(batchid, callid, reply) { var batch = dwr.engine._batches[batchid]; if (batch == null) { dwr.engine._debug("warning: batch == null in remotehandlecallback for batchid=" + batchid, true); return; } // error handlers inside here indicate an error that is nothing to do // with dwr so we handle them differently. try { var handlers = batch.handlers[callid]; batch.handlers[callid] = null; if (!handlers) { dwr.engine._debug("warning: missing handlers. callid=" + callid, true); } else if (typeof handlers.callback == "function") handlers.callback(reply); } catch (ex) { dwr.engine._handleerror(batch, ex); } }; /** @private called by the server: handle an exception for a call */ dwr.engine._remotehandleexception = function(batchid, callid, ex) { var batch = dwr.engine._batches[batchid]; if (batch == null) { dwr.engine._debug("warning: null batch in remotehandleexception", true); return; } var handlers = batch.handlers[callid]; batch.handlers[callid] = null; if (handlers == null) { dwr.engine._debug("warning: null handlers in remotehandleexception", true); return; } if (ex.message == undefined) ex.message = ""; if (typeof handlers.exceptionhandler == "function") handlers.exceptionhandler(ex.message, ex); else if (typeof batch.errorhandler == "function") batch.errorhandler(ex.message, ex); }; /** @private called by the server: the whole batch is broken */ dwr.engine._remotehandlebatchexception = function(ex, batchid) { var searchbatch = (dwr.engine._receivedbatch == null && batchid != null); if (searchbatch) { dwr.engine._receivedbatch = dwr.engine._batches[batchid]; } if (ex.message == undefined) ex.message = ""; dwr.engine._handleerror(dwr.engine._receivedbatch, ex); if (searchbatch) { dwr.engine._receivedbatch = null; dwr.engine._clearup(dwr.engine._batches[batchid]); } }; /** @private called by the server: reverse ajax should not be used */ dwr.engine._remotepollcometdisabled = function(ex, batchid) { dwr.engine.setactivereverseajax(false); var searchbatch = (dwr.engine._receivedbatch == null && batchid != null); if (searchbatch) { dwr.engine._receivedbatch = dwr.engine._batches[batchid]; } if (ex.message == undefined) ex.message = ""; dwr.engine._handleerror(dwr.engine._receivedbatch, ex); if (searchbatch) { dwr.engine._receivedbatch = null; dwr.engine._clearup(dwr.engine._batches[batchid]); } }; /** @private called by the server: an iframe reply is about to start */ dwr.engine._remotebeginiframeresponse = function(iframe, batchid) { if (iframe != null) dwr.engine._receivedbatch = iframe.batch; dwr.engine._callposthooks(dwr.engine._receivedbatch); }; /** @private called by the server: an iframe reply is just completing */ dwr.engine._remoteendiframeresponse = function(batchid) { dwr.engine._clearup(dwr.engine._receivedbatch); dwr.engine._receivedbatch = null; }; /** @private this is a hack to make the context be this window */ dwr.engine._eval = function(script) { if (script == null) return null; if (script == "") { dwr.engine._debug("warning: blank script", true); return null; } // dwr.engine._debug("exec: [" + script + "]", true); return eval(script); }; /** @private called as a result of a request timeout */ dwr.engine._abortrequest = function(batch) { if (batch && !batch.completed) { dwr.engine._clearup(batch); if (batch.req) batch.req.abort(); dwr.engine._handleerror(batch, { name:"dwr.engine.timeout", message:"timeout" }); } }; /** @private call all the post hooks for a batch */ dwr.engine._callposthooks = function(batch) { if (batch.posthooks) { for (var i = 0; i < batch.posthooks.length; i++) { batch.posthooks[i](); } batch.posthooks = null; } }; /** @private a call has finished by whatever means and we need to shut it all down. */ dwr.engine._clearup = function(batch) { if (!batch) { dwr.engine._debug("warning: null batch in dwr.engine._clearup()", true); return; } if (batch.completed) { dwr.engine._debug("warning: double complete", true); return; } // iframe tidyup if (batch.div) batch.div.parentnode.removechild(batch.div); if (batch.iframe) { // if this is a poll frame then stop comet polling for (var i = 0; i < dwr.engine._outstandingiframes.length; i++) { if (dwr.engine._outstandingiframes[i] == batch.iframe) { dwr.engine._outstandingiframes.splice(i, 1); } } batch.iframe.parentnode.removechild(batch.iframe); } if (batch.form) batch.form.parentnode.removechild(batch.form); // xhr tidyup: avoid ie handles increase if (batch.req) { // if this is a poll frame then stop comet polling if (batch.req == dwr.engine._pollreq) dwr.engine._pollreq = null; delete batch.req; } // timeout tidyup if (batch.timeoutid != null) { cleartimeout(batch.timeoutid); delete batch.timeoutid; } if (batch.map && (batch.map.batchid || batch.map.batchid == 0)) { delete dwr.engine._batches[batch.map.batchid]; dwr.engine._batcheslength--; } batch.completed = true; // if there is anything on the queue waiting to go out, then send it. // we don't need to check for ordered mode, here because when ordered mode // gets turned off, we still process *waiting* batches in an ordered way. if (dwr.engine._batchqueue.length != 0) { var sendbatch = dwr.engine._batchqueue.shift(); dwr.engine._senddata(sendbatch); } }; /** @private abort any xhrs in progress at page unload (solves zombie socket problems in ie). */ dwr.engine._unloader = function() { dwr.engine._unloading = true; // empty queue of waiting ordered requests dwr.engine._batchqueue.length = 0; // abort any ongoing xhrs and clear their batches for (var batchid in dwr.engine._batches) { var batch = dwr.engine._batches[batchid]; // only process objects that look like batches (avoid prototype additions!) if (batch && batch.map) { if (batch.req) { batch.req.abort(); } dwr.engine._clearup(batch); } } }; // now register the unload handler if (window.addeventlistener) window.addeventlistener('unload', dwr.engine._unloader, false); else if (window.attachevent) window.attachevent('onunload', dwr.engine._unloader); /** @private generic error handling routing to save having null checks everywhere */ dwr.engine._handleerror = function(batch, ex) { if (typeof ex == "string") ex = { name:"unknown", message:ex }; if (ex.message == null) ex.message = ""; if (ex.name == null) ex.name = "unknown"; if (batch && typeof batch.errorhandler == "function") batch.errorhandler(ex.message, ex); else if (dwr.engine._errorhandler) dwr.engine._errorhandler(ex.message, ex); if (batch) dwr.engine._clearup(batch); }; /** @private generic error handling routing to save having null checks everywhere */ dwr.engine._handlewarning = function(batch, ex) { if (typeof ex == "string") ex = { name:"unknown", message:ex }; if (ex.message == null) ex.message = ""; if (ex.name == null) ex.name = "unknown"; if (batch && typeof batch.warninghandler == "function") batch.warninghandler(ex.message, ex); else if (dwr.engine._warninghandler) dwr.engine._warninghandler(ex.message, ex); if (batch) dwr.engine._clearup(batch); }; /** * @private marshall a data item * @param batch a map of variables to how they have been marshalled * @param referto an array of already marshalled variables to prevent recurrsion * @param data the data to be marshalled * @param name the name of the data being marshalled */ dwr.engine._serializeall = function(batch, referto, data, name) { if (data == null) { batch.map[name] = "null:null"; return; } switch (typeof data) { case "boolean": batch.map[name] = "boolean:" + data; break; case "number": batch.map[name] = "number:" + data; break; case "string": batch.map[name] = "string:" + encodeuricomponent(data); break; case "object": var objstr = object.prototype.tostring.call(data); if (objstr == "[object string]") batch.map[name] = "string:" + encodeuricomponent(data); else if (objstr == "[object boolean]") batch.map[name] = "boolean:" + data; else if (objstr == "[object number]") batch.map[name] = "number:" + data; else if (objstr == "[object date]") batch.map[name] = "date:" + data.gettime(); else if (objstr == "[object array]") batch.map[name] = dwr.engine._serializearray(batch, referto, data, name); else batch.map[name] = dwr.engine._serializeobject(batch, referto, data, name); break; case "function": // we just ignore functions. break; default: dwr.engine._handlewarning(null, { name:"dwr.engine.unexpectedtype", message:"unexpected type: " + typeof data + ", attempting default converter." }); batch.map[name] = "default:" + data; break; } }; /** @private have we already converted this object? */ dwr.engine._lookup = function(referto, data, name) { var lookup; // can't use a map: getahead.org/ajax/javascript-gotchas for (var i = 0; i < referto.length; i++) { if (referto[i].data == data) { lookup = referto[i]; break; } } if (lookup) return "reference:" + lookup.name; referto.push({ data:data, name:name }); return null; }; /** @private marshall an object */ dwr.engine._serializeobject = function(batch, referto, data, name) { var ref = dwr.engine._lookup(referto, data, name); if (ref) return ref; // this check for an html is not complete, but is there a better way? // maybe we should add: data.haschildnodes typeof "function" == true if (data.nodename && data.nodetype) { return dwr.engine._serializexml(batch, referto, data, name); } // treat objects as an associative arrays var reply = "object_" + dwr.engine._getobjectclassname(data) + ":{"; var element; for (element in data) { if (typeof data[element] != "function") { batch.paramcount++; var childname = "c" + dwr.engine._batch.map.callcount + "-e" + batch.paramcount; dwr.engine._serializeall(batch, referto, data[element], childname); reply += encodeuricomponent(element) + ":reference:" + childname + ", "; } } if (reply.substring(reply.length - 2) == ", ") { reply = reply.substring(0, reply.length - 2); } reply += "}"; return reply; }; /** @private returns the classname of supplied argument obj */ dwr.engine._errorclasses = { "error":error, "evalerror":evalerror, "rangeerror":rangeerror, "referenceerror":referenceerror, "syntaxerror":syntaxerror, "typeerror":typeerror, "urierror":urierror }; dwr.engine._getobjectclassname = function(obj) { // try to find the classname by stringifying the object's constructor // and extractfrom "function ". if (obj && obj.constructor && obj.constructor.tostring) { var str = obj.constructor.tostring(); var regexpmatch = str.match(/function\s+(\w+)/); if (regexpmatch && regexpmatch.length == 2) { return regexpmatch[1]; } } // now manually test against the core error classes, as these in some // browsers successfully match to the wrong class in the // object.tostring() test we will do later if (obj && obj.constructor) { for (var errorname in dwr.engine._errorclasses) { if (obj.constructor == dwr.engine._errorclasses[errorname]) return errorname; } } // try to find the classname by calling object.tostring() on the object // and extracting from "[object ]" if (obj) { var str = object.prototype.tostring.call(obj); var regexpmatch = str.match(/\[object\s+(\w+)/); if (regexpmatch && regexpmatch.length==2) { return regexpmatch[1]; } } // supplied argument was probably not an object, but what is better? return "object"; }; /** @private marshall an object */ dwr.engine._serializexml = function(batch, referto, data, name) { var ref = dwr.engine._lookup(referto, data, name); if (ref) return ref; var output; if (window.xmlserializer) output = new xmlserializer().serializetostring(data); else if (data.toxml) output = data.toxml; else output = data.innerhtml; return "xml:" + encodeuricomponent(output); }; /** @private marshall an array */ dwr.engine._serializearray = function(batch, referto, data, name) { var ref = dwr.engine._lookup(referto, data, name); if (ref) return ref; if (document.all && !window.opera) { // use array joining on ie (fastest) var buf = ["array:["]; for (var i = 0; i < data.length; i++) { if (i != 0) buf.push(","); batch.paramcount++; var childname = "c" + dwr.engine._batch.map.callcount + "-e" + batch.paramcount; dwr.engine._serializeall(batch, referto, data[i], childname); buf.push("reference:"); buf.push(childname); } buf.push("]"); reply = buf.join(""); } else { // use string concat on other browsers (fastest) var reply = "array:["; for (var i = 0; i < data.length; i++) { if (i != 0) reply += ","; batch.paramcount++; var childname = "c" + dwr.engine._batch.map.callcount + "-e" + batch.paramcount; dwr.engine._serializeall(batch, referto, data[i], childname); reply += "reference:"; reply += childname; } reply += "]"; } return reply; }; /** @private convert an xml string into a dom object. */ dwr.engine._unserializedocument = function(xml) { var dom; if (window.domparser) { var parser = new domparser(); dom = parser.parsefromstring(xml, "text/xml"); if (!dom.documentelement || dom.documentelement.tagname == "parsererror") { var message = dom.documentelement.firstchild.data; message += "\n" + dom.documentelement.firstchild.nextsibling.firstchild.data; throw message; } return dom; } else if (window.activexobject) { dom = dwr.engine._newactivexobject(dwr.engine._domdocument); dom.loadxml(xml); // what happens on parse fail with ie? return dom; } else { var div = document.createelement("div"); div.innerhtml = xml; return div; } }; /** @param axarray an array of strings to attempt to create activex objects from */ dwr.engine._newactivexobject = function(axarray) { var returnvalue; for (var i = 0; i < axarray.length; i++) { try { returnvalue = new activexobject(axarray[i]); break; } catch (ex) { /* ignore */ } } return returnvalue; }; /** @private used internally when some message needs to get to the programmer */ dwr.engine._debug = function(message, stacktrace) { var written = false; try { if (window.console) { if (stacktrace && window.console.trace) window.console.trace(); window.console.log(message); written = true; } else if (window.opera && window.opera.posterror) { window.opera.posterror(message); written = true; } } catch (ex) { /* ignore */ } if (!written) { var debug = document.getelementbyid("dwr-debug"); if (debug) { var contents = message + "
" + debug.innerhtml; if (contents.length > 2048) contents = contents.substring(0, 2048); debug.innerhtml = contents; } } };