Path: blob/trunk/third_party/closure/goog/net/crossdomainrpc.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Cross domain RPC library using the <a16* href="http://go/xd2_design" target="_top">XD2 approach</a>.17*18* <h5>Protocol</h5>19* Client sends a request across domain via a form submission. Server20* receives these parameters: "xdpe:request-id", "xdpe:dummy-uri" ("xdpe" for21* "cross domain parameter to echo back") and other user parameters prefixed22* with "xdp" (for "cross domain parameter"). Headers are passed as parameters23* prefixed with "xdh" (for "cross domain header"). Only strings are supported24* for parameters and headers. A GET method is mapped to a form GET. All25* other methods are mapped to a POST. Server is expected to produce a26* HTML response such as the following:27* <pre>28* <body>29* <script type="text/javascript"30* src="path-to-crossdomainrpc.js"></script>31* var currentDirectory = location.href.substring(32* 0, location.href.lastIndexOf('/')33* );34*35* // echo all parameters prefixed with "xdpe:"36* var echo = {};37* echo[goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID] =38* <value of parameter "xdpe:request-id">;39* echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI] =40* <value of parameter "xdpe:dummy-uri">;41*42* goog.net.CrossDomainRpc.sendResponse(43* '({"result":"<responseInJSON"})',44* true, // is JSON45* echo, // parameters to echo back46* status, // response status code47* headers // response headers48* );49* </script>50* </body>51* </pre>52*53* <h5>Server Side</h5>54* For an example of the server side, refer to the following files:55* <ul>56* <li>http://go/xdservletfilter.java</li>57* <li>http://go/xdservletrequest.java</li>58* <li>http://go/xdservletresponse.java</li>59* </ul>60*61* <h5>System Requirements</h5>62* Tested on IE6, IE7, Firefox 2.0 and Safari nightly r23841.63*64*/6566goog.provide('goog.net.CrossDomainRpc');6768goog.require('goog.Uri');69goog.require('goog.dom');70goog.require('goog.dom.TagName');71goog.require('goog.dom.safe');72goog.require('goog.events');73goog.require('goog.events.EventTarget');74goog.require('goog.events.EventType');75goog.require('goog.html.SafeHtml');76goog.require('goog.log');77goog.require('goog.net.EventType');78goog.require('goog.net.HttpStatus');79goog.require('goog.string');80goog.require('goog.userAgent');81828384/**85* Creates a new instance of cross domain RPC.86*87* @extends {goog.events.EventTarget}88* @constructor89* @final90*/91goog.net.CrossDomainRpc = function() {92goog.events.EventTarget.call(this);93};94goog.inherits(goog.net.CrossDomainRpc, goog.events.EventTarget);959697/**98* Cross-domain response iframe marker.99* @type {string}100* @private101*/102goog.net.CrossDomainRpc.RESPONSE_MARKER_ = 'xdrp';103104105/**106* Use a fallback dummy resource if none specified or detected.107* @type {boolean}108* @private109*/110goog.net.CrossDomainRpc.useFallBackDummyResource_ = true;111112113/** @type {Object} */114goog.net.CrossDomainRpc.prototype.responseHeaders;115116117/** @type {string} */118goog.net.CrossDomainRpc.prototype.responseText;119120121/** @type {number} */122goog.net.CrossDomainRpc.prototype.status;123124125/** @type {number} */126goog.net.CrossDomainRpc.prototype.timeWaitedAfterResponseReady_;127128129/** @private {boolean} */130goog.net.CrossDomainRpc.prototype.responseTextIsJson_;131132133/** @private {boolean} */134goog.net.CrossDomainRpc.prototype.responseReady_;135136137/** @private {!HTMLIFrameElement} */138goog.net.CrossDomainRpc.prototype.requestFrame_;139140141/** @private {goog.events.Key} */142goog.net.CrossDomainRpc.prototype.loadListenerKey_;143144145/**146* Checks to see if we are executing inside a response iframe. This is the147* case when this page is used as a dummy resource to gain caller's domain.148* @return {*} True if we are executing inside a response iframe; false149* otherwise.150* @private151*/152goog.net.CrossDomainRpc.isInResponseIframe_ = function() {153return window.location &&154(window.location.hash.indexOf(goog.net.CrossDomainRpc.RESPONSE_MARKER_) ==1551 ||156window.location.search.indexOf(157goog.net.CrossDomainRpc.RESPONSE_MARKER_) == 1);158};159160161/**162* Stops execution of the rest of the page if this page is loaded inside a163* response iframe.164*/165if (goog.net.CrossDomainRpc.isInResponseIframe_()) {166if (goog.userAgent.EDGE_OR_IE) {167document.execCommand('Stop');168} else if (goog.userAgent.GECKO) {169window.stop();170} else {171throw Error('stopped');172}173}174175176/**177* Sets the URI for a dummy resource on caller's domain. This function is178* used for specifying a particular resource to use rather than relying on179* auto detection.180* @param {string} dummyResourceUri URI to dummy resource on the same domain181* of caller's page.182*/183goog.net.CrossDomainRpc.setDummyResourceUri = function(dummyResourceUri) {184goog.net.CrossDomainRpc.dummyResourceUri_ = dummyResourceUri;185};186187188/**189* Sets whether a fallback dummy resource ("/robots.txt" on Firefox and Safari190* and current page on IE) should be used when a suitable dummy resource is191* not available.192* @param {boolean} useFallBack Whether to use fallback or not.193*/194goog.net.CrossDomainRpc.setUseFallBackDummyResource = function(useFallBack) {195goog.net.CrossDomainRpc.useFallBackDummyResource_ = useFallBack;196};197198199/**200* Sends a request across domain.201* @param {string} uri Uri to make request to.202* @param {Function=} opt_continuation Continuation function to be called203* when request is completed. Takes one argument of an event object204* whose target has the following properties: "status" is the HTTP205* response status code, "responseText" is the response text,206* and "headers" is an object with all response headers. The event207* target's getResponseJson() method returns a JavaScript object evaluated208* from the JSON response or undefined if response is not JSON.209* @param {string=} opt_method Method of request. Default is POST.210* @param {Object=} opt_params Parameters. Each property is turned into a211* request parameter.212* @param {Object=} opt_headers Map of headers of the request.213*/214goog.net.CrossDomainRpc.send = function(215uri, opt_continuation, opt_method, opt_params, opt_headers) {216var xdrpc = new goog.net.CrossDomainRpc();217if (opt_continuation) {218goog.events.listen(xdrpc, goog.net.EventType.COMPLETE, opt_continuation);219}220goog.events.listen(xdrpc, goog.net.EventType.READY, xdrpc.reset);221xdrpc.sendRequest(uri, opt_method, opt_params, opt_headers);222};223224225/**226* Sets debug mode to true or false. When debug mode is on, response iframes227* are visible and left behind after their use is finished.228* @param {boolean} flag Flag to indicate intention to turn debug model on229* (true) or off (false).230*/231goog.net.CrossDomainRpc.setDebugMode = function(flag) {232goog.net.CrossDomainRpc.debugMode_ = flag;233};234235236/**237* Logger for goog.net.CrossDomainRpc238* @type {goog.log.Logger}239* @private240*/241goog.net.CrossDomainRpc.logger_ = goog.log.getLogger('goog.net.CrossDomainRpc');242243244/**245* Creates the HTML of an input element246* @param {string} name Name of input element.247* @param {*} value Value of input element.248* @return {!goog.html.SafeHtml} HTML of input element with that name and value.249* @private250*/251goog.net.CrossDomainRpc.createInputHtml_ = function(name, value) {252return goog.html.SafeHtml.create('textarea', {'name': name}, String(value));253};254255256/**257* Finds a dummy resource that can be used by response to gain domain of258* requester's page.259* @return {string} URI of the resource to use.260* @private261*/262goog.net.CrossDomainRpc.getDummyResourceUri_ = function() {263if (goog.net.CrossDomainRpc.dummyResourceUri_) {264return goog.net.CrossDomainRpc.dummyResourceUri_;265}266267// find a style sheet if not on IE, which will attempt to save style sheet268if (goog.userAgent.GECKO) {269var links = goog.dom.getElementsByTagName(goog.dom.TagName.LINK);270for (var i = 0; i < links.length; i++) {271var link = links[i];272// find a link which is on the same domain as this page273// cannot use one with '?' or '#' in its URL as it will confuse274// goog.net.CrossDomainRpc.getFramePayload_()275if (link.rel == 'stylesheet' &&276goog.Uri.haveSameDomain(link.href, window.location.href) &&277link.href.indexOf('?') < 0) {278return goog.net.CrossDomainRpc.removeHash_(link.href);279}280}281}282283var images = goog.dom.getElementsByTagName(goog.dom.TagName.IMG);284for (var i = 0; i < images.length; i++) {285var image = images[i];286// find a link which is on the same domain as this page287// cannot use one with '?' or '#' in its URL as it will confuse288// goog.net.CrossDomainRpc.getFramePayload_()289if (goog.Uri.haveSameDomain(image.src, window.location.href) &&290image.src.indexOf('?') < 0) {291return goog.net.CrossDomainRpc.removeHash_(image.src);292}293}294295if (!goog.net.CrossDomainRpc.useFallBackDummyResource_) {296throw Error(297'No suitable dummy resource specified or detected for this page');298}299300if (goog.userAgent.EDGE_OR_IE) {301// use this page as the dummy resource; remove hash from URL if any302return goog.net.CrossDomainRpc.removeHash_(window.location.href);303} else {304/**305* Try to use "http://<this-domain>/robots.txt" which may exist. Even if306* it does not, an error page is returned and is a good dummy resource to307* use on Firefox and Safari. An existing resource is faster because it308* is cached.309*/310var locationHref = window.location.href;311var rootSlash = locationHref.indexOf('/', locationHref.indexOf('//') + 2);312var rootHref = locationHref.substring(0, rootSlash);313return rootHref + '/robots.txt';314}315};316317318/**319* Removes everything at and after hash from URI320* @param {string} uri Uri to to remove hash.321* @return {string} Uri with its hash and all characters after removed.322* @private323*/324goog.net.CrossDomainRpc.removeHash_ = function(uri) {325return uri.split('#')[0];326};327328329// ------------330// request side331332333/**334* next request id used to support multiple XD requests at the same time335* @type {number}336* @private337*/338goog.net.CrossDomainRpc.nextRequestId_ = 0;339340341/**342* Header prefix.343* @type {string}344*/345goog.net.CrossDomainRpc.HEADER = 'xdh:';346347348/**349* Parameter prefix.350* @type {string}351*/352goog.net.CrossDomainRpc.PARAM = 'xdp:';353354355/**356* Parameter to echo prefix.357* @type {string}358*/359goog.net.CrossDomainRpc.PARAM_ECHO = 'xdpe:';360361362/**363* Parameter to echo: request id364* @type {string}365*/366goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID =367goog.net.CrossDomainRpc.PARAM_ECHO + 'request-id';368369370/**371* Parameter to echo: dummy resource URI372* @type {string}373*/374goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI =375goog.net.CrossDomainRpc.PARAM_ECHO + 'dummy-uri';376377378/**379* Cross-domain request marker.380* @type {string}381* @private382*/383goog.net.CrossDomainRpc.REQUEST_MARKER_ = 'xdrq';384385386/**387* Sends a request across domain.388* @param {string} uri Uri to make request to.389* @param {string=} opt_method Method of request, 'GET' or 'POST' (uppercase).390* Default is 'POST'.391* @param {Object=} opt_params Parameters. Each property is turned into a392* request parameter.393* @param {Object=} opt_headers Map of headers of the request.394*/395goog.net.CrossDomainRpc.prototype.sendRequest = function(396uri, opt_method, opt_params, opt_headers) {397// create request frame398var requestFrame = this.requestFrame_ =399goog.dom.createElement(goog.dom.TagName.IFRAME);400var requestId = goog.net.CrossDomainRpc.nextRequestId_++;401requestFrame.id = goog.net.CrossDomainRpc.REQUEST_MARKER_ + '-' + requestId;402if (!goog.net.CrossDomainRpc.debugMode_) {403requestFrame.style.position = 'absolute';404requestFrame.style.top = '-5000px';405requestFrame.style.left = '-5000px';406}407document.body.appendChild(requestFrame);408409// build inputs410var inputs = [];411412// add request id413inputs.push(414goog.net.CrossDomainRpc.createInputHtml_(415goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID, requestId));416417// add dummy resource uri418var dummyUri = goog.net.CrossDomainRpc.getDummyResourceUri_();419goog.log.fine(goog.net.CrossDomainRpc.logger_, 'dummyUri: ' + dummyUri);420inputs.push(421goog.net.CrossDomainRpc.createInputHtml_(422goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI, dummyUri));423424// add parameters425if (opt_params) {426for (var name in opt_params) {427var value = opt_params[name];428inputs.push(429goog.net.CrossDomainRpc.createInputHtml_(430goog.net.CrossDomainRpc.PARAM + name, value));431}432}433434// add headers435if (opt_headers) {436for (var name in opt_headers) {437var value = opt_headers[name];438inputs.push(439goog.net.CrossDomainRpc.createInputHtml_(440goog.net.CrossDomainRpc.HEADER + name, value));441}442}443444var requestFrameContentHtml = goog.html.SafeHtml.create(445'body', {},446goog.html.SafeHtml.create(447'form',448{'method': opt_method == 'GET' ? 'GET' : 'POST', 'action': uri},449inputs));450var requestFrameDoc = goog.dom.getFrameContentDocument(requestFrame);451requestFrameDoc.open();452goog.dom.safe.documentWrite(requestFrameDoc, requestFrameContentHtml);453requestFrameDoc.close();454455requestFrameDoc.forms[0].submit();456requestFrameDoc = null;457458this.loadListenerKey_ =459goog.events.listen(requestFrame, goog.events.EventType.LOAD, function() {460goog.log.fine(goog.net.CrossDomainRpc.logger_, 'response ready');461this.responseReady_ = true;462}, false, this);463464this.receiveResponse_();465};466467468/**469* period of response polling (ms)470* @type {number}471* @private472*/473goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_ = 50;474475476/**477* timeout from response comes back to sendResponse is called (ms)478* @type {number}479* @private480*/481goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_ = 500;482483484/**485* Receives response by polling to check readiness of response and then486* reads response frames and assembles response data487* @private488*/489goog.net.CrossDomainRpc.prototype.receiveResponse_ = function() {490this.timeWaitedAfterResponseReady_ = 0;491var responseDetectorHandle = window.setInterval(goog.bind(function() {492this.detectResponse_(responseDetectorHandle);493}, this), goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_);494};495496497/**498* Detects response inside request frame499* @param {number} responseDetectorHandle Handle of detector.500* @private501*/502goog.net.CrossDomainRpc.prototype.detectResponse_ = function(503responseDetectorHandle) {504var requestFrameWindow = this.requestFrame_.contentWindow;505var grandChildrenLength = requestFrameWindow.frames.length;506var responseInfoFrame = null;507if (grandChildrenLength > 0 &&508goog.net.CrossDomainRpc.isResponseInfoFrame_(509responseInfoFrame =510requestFrameWindow.frames[grandChildrenLength - 1])) {511goog.log.fine(goog.net.CrossDomainRpc.logger_, 'xd response ready');512513var responseInfoPayload =514goog.net.CrossDomainRpc.getFramePayload_(responseInfoFrame)515.substring(1);516var params = new goog.Uri.QueryData(responseInfoPayload);517518var chunks = [];519var numChunks = Number(params.get('n'));520goog.log.fine(521goog.net.CrossDomainRpc.logger_,522'xd response number of chunks: ' + numChunks);523for (var i = 0; i < numChunks; i++) {524var responseFrame = requestFrameWindow.frames[i];525if (!responseFrame || !responseFrame.location ||526!responseFrame.location.href) {527// On Safari 3.0, it is sometimes the case that the528// iframe exists but doesn't have a same domain href yet.529goog.log.fine(530goog.net.CrossDomainRpc.logger_, 'xd response iframe not ready');531return;532}533var responseChunkPayload =534goog.net.CrossDomainRpc.getFramePayload_(responseFrame);535// go past "chunk="536var chunkIndex =537responseChunkPayload.indexOf(goog.net.CrossDomainRpc.PARAM_CHUNK_) +538goog.net.CrossDomainRpc.PARAM_CHUNK_.length + 1;539var chunk = responseChunkPayload.substring(chunkIndex);540chunks.push(chunk);541}542543window.clearInterval(responseDetectorHandle);544545var responseData = chunks.join('');546// Payload is not encoded to begin with on IE. Decode in other cases only.547if (!goog.userAgent.EDGE_OR_IE) {548responseData = decodeURIComponent(responseData);549}550551this.status = Number(params.get('status'));552this.responseText = responseData;553this.responseTextIsJson_ = params.get('isDataJson') == 'true';554this.responseHeaders = /** @type {?Object} */ (JSON.parse(555/** @type {string} */ (params.get('headers'))));556557this.dispatchEvent(goog.net.EventType.READY);558this.dispatchEvent(goog.net.EventType.COMPLETE);559} else {560if (this.responseReady_) {561/* The response has come back. But the first response iframe has not562* been created yet. If this lasts long enough, it is an error.563*/564this.timeWaitedAfterResponseReady_ +=565goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_;566if (this.timeWaitedAfterResponseReady_ >567goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_) {568goog.log.fine(goog.net.CrossDomainRpc.logger_, 'xd response timed out');569window.clearInterval(responseDetectorHandle);570571this.status = goog.net.HttpStatus.INTERNAL_SERVER_ERROR;572this.responseText = 'response timed out';573574this.dispatchEvent(goog.net.EventType.READY);575this.dispatchEvent(goog.net.EventType.ERROR);576this.dispatchEvent(goog.net.EventType.COMPLETE);577}578}579}580};581582583/**584* Checks whether a frame is response info frame.585* @param {Object} frame Frame to check.586* @return {boolean} True if frame is a response info frame; false otherwise.587* @private588*/589goog.net.CrossDomainRpc.isResponseInfoFrame_ = function(frame) {590591try {592return goog.net.CrossDomainRpc.getFramePayload_(frame).indexOf(593goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_) == 1;594} catch (e) {595// frame not ready for same-domain access yet596return false;597}598};599600601/**602* Returns the payload of a frame (value after # or ? on the URL). This value603* is URL encoded except IE, where the value is not encoded to begin with.604* @param {Object} frame Frame.605* @return {string} Payload of that frame.606* @private607*/608goog.net.CrossDomainRpc.getFramePayload_ = function(frame) {609var href = frame.location.href;610var question = href.indexOf('?');611var hash = href.indexOf('#');612// On IE, beucase the URL is not encoded, we can have a case where ?613// is the delimiter before payload and # in payload or # as the delimiter614// and ? in payload. So here we treat whoever is the first as the delimiter.615var delimiter =616question < 0 ? hash : hash < 0 ? question : Math.min(question, hash);617return href.substring(delimiter);618};619620621/**622* If response is JSON, evaluates it to a JavaScript object and623* returns it; otherwise returns undefined.624* @return {Object|undefined} JavaScript object if response is in JSON625* or undefined.626*/627goog.net.CrossDomainRpc.prototype.getResponseJson = function() {628return this.responseTextIsJson_ ?629/** @type {?Object} */ (JSON.parse(this.responseText)) :630undefined;631};632633634/**635* @return {boolean} Whether the request completed with a success.636*/637goog.net.CrossDomainRpc.prototype.isSuccess = function() {638// Definition similar to goog.net.XhrIo.prototype.isSuccess.639switch (this.status) {640case goog.net.HttpStatus.OK:641case goog.net.HttpStatus.NOT_MODIFIED:642return true;643644default:645return false;646}647};648649650/**651* Removes request iframe used.652*/653goog.net.CrossDomainRpc.prototype.reset = function() {654if (!goog.net.CrossDomainRpc.debugMode_) {655goog.log.fine(656goog.net.CrossDomainRpc.logger_,657'request frame removed: ' + this.requestFrame_.id);658goog.events.unlistenByKey(this.loadListenerKey_);659this.requestFrame_.parentNode.removeChild(this.requestFrame_);660}661delete this.requestFrame_;662};663664665// -------------666// response side667668669/**670* Name of response info iframe.671* @type {string}672* @private673*/674goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ =675goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '-info';676677678/**679* Maximal chunk size. IE can only handle 4095 bytes on its URL.680* 16MB has been tested on Firefox. But 1MB is a practical size.681* @type {number}682* @private683*/684goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ =685goog.userAgent.EDGE_OR_IE ? 4095 : 1024 * 1024;686687688/**689* Query parameter 'chunk'.690* @type {string}691* @private692*/693goog.net.CrossDomainRpc.PARAM_CHUNK_ = 'chunk';694695696/**697* Prefix before data chunk for passing other parameters.698* type String699* @private700*/701goog.net.CrossDomainRpc.CHUNK_PREFIX_ =702goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '=1&' +703goog.net.CrossDomainRpc.PARAM_CHUNK_ + '=';704705706/**707* Makes response available for grandparent (requester)'s receiveResponse708* call to pick up by creating a series of iframes pointed to the dummy URI709* with a payload (value after either ? or #) carrying a chunk of response710* data and a response info iframe that tells the grandparent (requester) the711* readiness of response.712* @param {string} data Response data (string or JSON string).713* @param {boolean} isDataJson true if data is a JSON string; false if just a714* string.715* @param {Object} echo Parameters to echo back716* "xdpe:request-id": Server that produces the response needs to717* copy it here to support multiple current XD requests on the same page.718* "xdpe:dummy-uri": URI to a dummy resource that response719* iframes point to to gain the domain of the client. This can be an720* image (IE) or a CSS file (FF) found on the requester's page.721* Server should copy value from request parameter "xdpe:dummy-uri".722* @param {number} status HTTP response status code.723* @param {string} headers Response headers in JSON format.724*/725goog.net.CrossDomainRpc.sendResponse = function(726data, isDataJson, echo, status, headers) {727var dummyUri = echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI];728729// since the dummy-uri can be specified by the user, verify that it doesn't730// use any other protocols. (Specifically we don't want users to use a731// dummy-uri beginning with "javascript:").732if (!goog.string.caseInsensitiveStartsWith(dummyUri, 'http://') &&733!goog.string.caseInsensitiveStartsWith(dummyUri, 'https://')) {734dummyUri = 'http://' + dummyUri;735}736737// usable chunk size is max less dummy URI less chunk prefix length738// TODO(user): Figure out why we need to do "- 1" below739var chunkSize = goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ - dummyUri.length -7401 - // payload delimiter ('#' or '?')741goog.net.CrossDomainRpc.CHUNK_PREFIX_.length - 1;742743/*744* Here we used to do URI encoding of data before we divide it into chunks745* and decode on the receiving end. We don't do this any more on IE for the746* following reasons.747*748* 1) On IE, calling decodeURIComponent on a relatively large string is749* extremely slow (~22s for 160KB). So even a moderate amount of data750* makes this library pretty much useless. Fortunately, we can actually751* put unencoded data on IE's URL and get it back reliably. So we are752* completely skipping encoding and decoding on IE. When we call753* getFrameHash_ to get it back, the value is still intact(*) and unencoded.754* 2) On Firefox, we have to call decodeURIComponent because location.hash755* does decoding by itself. Fortunately, decodeURIComponent is not slow756* on Firefox.757* 3) Safari automatically encodes everything you put on URL and it does not758* automatically decode when you access it via location.hash or759* location.href. So we encode it here and decode it in detectResponse_().760*761* Note(*): IE actually does encode only space to %20 and decodes that762* automatically when you do location.href or location.hash.763*/764if (!goog.userAgent.EDGE_OR_IE) {765data = encodeURIComponent(data);766}767768var numChunksToSend = Math.ceil(data.length / chunkSize);769if (numChunksToSend == 0) {770goog.net.CrossDomainRpc.createResponseInfo_(771dummyUri, numChunksToSend, isDataJson, status, headers);772} else {773var numChunksSent = 0;774var checkToCreateResponseInfo_ = function() {775if (++numChunksSent == numChunksToSend) {776goog.net.CrossDomainRpc.createResponseInfo_(777dummyUri, numChunksToSend, isDataJson, status, headers);778}779};780781for (var i = 0; i < numChunksToSend; i++) {782var chunkStart = i * chunkSize;783var chunkEnd = chunkStart + chunkSize;784var chunk = chunkEnd > data.length ? data.substring(chunkStart) :785data.substring(chunkStart, chunkEnd);786787var responseFrame = goog.dom.createElement(goog.dom.TagName.IFRAME);788responseFrame.src = dummyUri +789goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) +790goog.net.CrossDomainRpc.CHUNK_PREFIX_ + chunk;791document.body.appendChild(responseFrame);792793// We used to call the function below when handling load event of794// responseFrame. But that event does not fire on IE when current795// page is used as the dummy resource (because its loading is stopped?).796// It also does not fire sometimes on Firefox. So now we call it797// directly.798checkToCreateResponseInfo_();799}800}801};802803804/**805* Creates a response info iframe to indicate completion of sendResponse806* @param {string} dummyUri URI to a dummy resource.807* @param {number} numChunks Total number of chunks.808* @param {boolean} isDataJson Whether response is a JSON string or just string.809* @param {number} status HTTP response status code.810* @param {string} headers Response headers in JSON format.811* @private812*/813goog.net.CrossDomainRpc.createResponseInfo_ = function(814dummyUri, numChunks, isDataJson, status, headers) {815var responseInfoFrame = goog.dom.createElement(goog.dom.TagName.IFRAME);816document.body.appendChild(responseInfoFrame);817responseInfoFrame.src = dummyUri +818goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) +819goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ + '=1&n=' + numChunks +820'&isDataJson=' + isDataJson + '&status=' + status + '&headers=' +821encodeURIComponent(headers);822};823824825/**826* Returns payload delimiter, either "#" when caller's page is not used as827* the dummy resource or "?" when it is, in which case caching issues prevent828* response frames to gain the caller's domain.829* @param {string} dummyUri URI to resource being used as dummy resource.830* @return {string} Either "?" when caller's page is used as dummy resource or831* "#" if it is not.832* @private833*/834goog.net.CrossDomainRpc.getPayloadDelimiter_ = function(dummyUri) {835return goog.net.CrossDomainRpc.REFERRER_ == dummyUri ? '?' : '#';836};837838839/**840* Removes all parameters (after ? or #) from URI.841* @param {string} uri URI to remove parameters from.842* @return {string} URI with all parameters removed.843* @private844*/845goog.net.CrossDomainRpc.removeUriParams_ = function(uri) {846// remove everything after question mark847var question = uri.indexOf('?');848if (question > 0) {849uri = uri.substring(0, question);850}851852// remove everything after hash mark853var hash = uri.indexOf('#');854if (hash > 0) {855uri = uri.substring(0, hash);856}857858return uri;859};860861862/**863* Gets a response header.864* @param {string} name Name of response header.865* @return {string|undefined} Value of response header; undefined if not found.866*/867goog.net.CrossDomainRpc.prototype.getResponseHeader = function(name) {868return goog.isObject(this.responseHeaders) ? this.responseHeaders[name] :869undefined;870};871872873/**874* Referrer of current document with all parameters after "?" and "#" stripped.875* @type {string}876* @private877*/878goog.net.CrossDomainRpc.REFERRER_ =879goog.net.CrossDomainRpc.removeUriParams_(document.referrer);880881882