| // Copyright 2006 The Closure Library Authors. All Rights Reserved. |
| // |
| // 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. |
| |
| /** |
| * @fileoverview Cross domain RPC library using the <a |
| * href="http://go/xd2_design" target="_top">XD2 approach</a>. |
| * |
| * <h5>Protocol</h5> |
| * Client sends a request across domain via a form submission. Server |
| * receives these parameters: "xdpe:request-id", "xdpe:dummy-uri" ("xdpe" for |
| * "cross domain parameter to echo back") and other user parameters prefixed |
| * with "xdp" (for "cross domain parameter"). Headers are passed as parameters |
| * prefixed with "xdh" (for "cross domain header"). Only strings are supported |
| * for parameters and headers. A GET method is mapped to a form GET. All |
| * other methods are mapped to a POST. Server is expected to produce a |
| * HTML response such as the following: |
| * <pre> |
| * <body> |
| * <script type="text/javascript" |
| * src="path-to-crossdomainrpc.js"></script> |
| * var currentDirectory = location.href.substring( |
| * 0, location.href.lastIndexOf('/') |
| * ); |
| * |
| * // echo all parameters prefixed with "xdpe:" |
| * var echo = {}; |
| * echo[goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID] = |
| * <value of parameter "xdpe:request-id">; |
| * echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI] = |
| * <value of parameter "xdpe:dummy-uri">; |
| * |
| * goog.net.CrossDomainRpc.sendResponse( |
| * '({"result":"<responseInJSON"})', |
| * true, // is JSON |
| * echo, // parameters to echo back |
| * status, // response status code |
| * headers // response headers |
| * ); |
| * </script> |
| * </body> |
| * </pre> |
| * |
| * <h5>Server Side</h5> |
| * For an example of the server side, refer to the following files: |
| * <ul> |
| * <li>http://go/xdservletfilter.java</li> |
| * <li>http://go/xdservletrequest.java</li> |
| * <li>http://go/xdservletresponse.java</li> |
| * </ul> |
| * |
| * <h5>System Requirements</h5> |
| * Tested on IE6, IE7, Firefox 2.0 and Safari nightly r23841. |
| * |
| */ |
| |
| goog.provide('goog.net.CrossDomainRpc'); |
| |
| goog.require('goog.Uri'); |
| goog.require('goog.dom'); |
| goog.require('goog.dom.safe'); |
| goog.require('goog.events'); |
| goog.require('goog.events.EventTarget'); |
| goog.require('goog.events.EventType'); |
| goog.require('goog.html.legacyconversions'); |
| goog.require('goog.json'); |
| goog.require('goog.log'); |
| goog.require('goog.net.EventType'); |
| goog.require('goog.net.HttpStatus'); |
| goog.require('goog.string'); |
| goog.require('goog.userAgent'); |
| |
| |
| |
| /** |
| * Creates a new instance of cross domain RPC. |
| * |
| * This class makes use of goog.html.legacyconversions and provides no |
| * HTML-type-safe alternative. As such, it is not compatible with |
| * code that sets goog.html.legacyconversions.ALLOW_LEGACY_CONVERSIONS to |
| * false. |
| * |
| * @extends {goog.events.EventTarget} |
| * @constructor |
| * @final |
| */ |
| goog.net.CrossDomainRpc = function() { |
| goog.events.EventTarget.call(this); |
| }; |
| goog.inherits(goog.net.CrossDomainRpc, goog.events.EventTarget); |
| |
| |
| /** |
| * Cross-domain response iframe marker. |
| * @type {string} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.RESPONSE_MARKER_ = 'xdrp'; |
| |
| |
| /** |
| * Use a fallback dummy resource if none specified or detected. |
| * @type {boolean} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.useFallBackDummyResource_ = true; |
| |
| |
| /** @type {Object} */ |
| goog.net.CrossDomainRpc.prototype.responseHeaders; |
| |
| |
| /** @type {string} */ |
| goog.net.CrossDomainRpc.prototype.responseText; |
| |
| |
| /** @type {number} */ |
| goog.net.CrossDomainRpc.prototype.status; |
| |
| |
| /** @type {number} */ |
| goog.net.CrossDomainRpc.prototype.timeWaitedAfterResponseReady_; |
| |
| |
| /** @private {boolean} */ |
| goog.net.CrossDomainRpc.prototype.responseTextIsJson_; |
| |
| |
| /** @private {boolean} */ |
| goog.net.CrossDomainRpc.prototype.responseReady_; |
| |
| |
| /** @private {!HTMLIFrameElement} */ |
| goog.net.CrossDomainRpc.prototype.requestFrame_; |
| |
| |
| /** @private {goog.events.Key} */ |
| goog.net.CrossDomainRpc.prototype.loadListenerKey_; |
| |
| |
| /** |
| * Checks to see if we are executing inside a response iframe. This is the |
| * case when this page is used as a dummy resource to gain caller's domain. |
| * @return {*} True if we are executing inside a response iframe; false |
| * otherwise. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.isInResponseIframe_ = function() { |
| return window.location && (window.location.hash.indexOf( |
| goog.net.CrossDomainRpc.RESPONSE_MARKER_) == 1 || |
| window.location.search.indexOf( |
| goog.net.CrossDomainRpc.RESPONSE_MARKER_) == 1); |
| }; |
| |
| |
| /** |
| * Stops execution of the rest of the page if this page is loaded inside a |
| * response iframe. |
| */ |
| if (goog.net.CrossDomainRpc.isInResponseIframe_()) { |
| if (goog.userAgent.IE) { |
| document.execCommand('Stop'); |
| } else if (goog.userAgent.GECKO) { |
| window.stop(); |
| } else { |
| throw Error('stopped'); |
| } |
| } |
| |
| |
| /** |
| * Sets the URI for a dummy resource on caller's domain. This function is |
| * used for specifying a particular resource to use rather than relying on |
| * auto detection. |
| * @param {string} dummyResourceUri URI to dummy resource on the same domain |
| * of caller's page. |
| */ |
| goog.net.CrossDomainRpc.setDummyResourceUri = function(dummyResourceUri) { |
| goog.net.CrossDomainRpc.dummyResourceUri_ = dummyResourceUri; |
| }; |
| |
| |
| /** |
| * Sets whether a fallback dummy resource ("/robots.txt" on Firefox and Safari |
| * and current page on IE) should be used when a suitable dummy resource is |
| * not available. |
| * @param {boolean} useFallBack Whether to use fallback or not. |
| */ |
| goog.net.CrossDomainRpc.setUseFallBackDummyResource = function(useFallBack) { |
| goog.net.CrossDomainRpc.useFallBackDummyResource_ = useFallBack; |
| }; |
| |
| |
| /** |
| * Sends a request across domain. |
| * @param {string} uri Uri to make request to. |
| * @param {Function=} opt_continuation Continuation function to be called |
| * when request is completed. Takes one argument of an event object |
| * whose target has the following properties: "status" is the HTTP |
| * response status code, "responseText" is the response text, |
| * and "headers" is an object with all response headers. The event |
| * target's getResponseJson() method returns a JavaScript object evaluated |
| * from the JSON response or undefined if response is not JSON. |
| * @param {string=} opt_method Method of request. Default is POST. |
| * @param {Object=} opt_params Parameters. Each property is turned into a |
| * request parameter. |
| * @param {Object=} opt_headers Map of headers of the request. |
| */ |
| goog.net.CrossDomainRpc.send = |
| function(uri, opt_continuation, opt_method, opt_params, opt_headers) { |
| var xdrpc = new goog.net.CrossDomainRpc(); |
| if (opt_continuation) { |
| goog.events.listen(xdrpc, goog.net.EventType.COMPLETE, opt_continuation); |
| } |
| goog.events.listen(xdrpc, goog.net.EventType.READY, xdrpc.reset); |
| xdrpc.sendRequest(uri, opt_method, opt_params, opt_headers); |
| }; |
| |
| |
| /** |
| * Sets debug mode to true or false. When debug mode is on, response iframes |
| * are visible and left behind after their use is finished. |
| * @param {boolean} flag Flag to indicate intention to turn debug model on |
| * (true) or off (false). |
| */ |
| goog.net.CrossDomainRpc.setDebugMode = function(flag) { |
| goog.net.CrossDomainRpc.debugMode_ = flag; |
| }; |
| |
| |
| /** |
| * Logger for goog.net.CrossDomainRpc |
| * @type {goog.log.Logger} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.logger_ = |
| goog.log.getLogger('goog.net.CrossDomainRpc'); |
| |
| |
| /** |
| * Creates the HTML of an input element |
| * @param {string} name Name of input element. |
| * @param {*} value Value of input element. |
| * @return {string} HTML of input element with that name and value. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.createInputHtml_ = function(name, value) { |
| return '<textarea name="' + name + '">' + |
| goog.net.CrossDomainRpc.escapeAmpersand_(value) + '</textarea>'; |
| }; |
| |
| |
| /** |
| * Escapes ampersand so that XML/HTML entities are submitted as is because |
| * browser unescapes them when they are put into a text area. |
| * @param {*} value Value to escape. |
| * @return {*} Value with ampersand escaped, if value is a string; |
| * otherwise the value itself is returned. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.escapeAmpersand_ = function(value) { |
| return value && (goog.isString(value) || value.constructor == String) ? |
| value.replace(/&/g, '&') : value; |
| }; |
| |
| |
| /** |
| * Finds a dummy resource that can be used by response to gain domain of |
| * requester's page. |
| * @return {string} URI of the resource to use. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.getDummyResourceUri_ = function() { |
| if (goog.net.CrossDomainRpc.dummyResourceUri_) { |
| return goog.net.CrossDomainRpc.dummyResourceUri_; |
| } |
| |
| // find a style sheet if not on IE, which will attempt to save style sheet |
| if (goog.userAgent.GECKO) { |
| var links = document.getElementsByTagName('link'); |
| for (var i = 0; i < links.length; i++) { |
| var link = links[i]; |
| // find a link which is on the same domain as this page |
| // cannot use one with '?' or '#' in its URL as it will confuse |
| // goog.net.CrossDomainRpc.getFramePayload_() |
| if (link.rel == 'stylesheet' && |
| goog.Uri.haveSameDomain(link.href, window.location.href) && |
| link.href.indexOf('?') < 0) { |
| return goog.net.CrossDomainRpc.removeHash_(link.href); |
| } |
| } |
| } |
| |
| var images = document.getElementsByTagName('img'); |
| for (var i = 0; i < images.length; i++) { |
| var image = images[i]; |
| // find a link which is on the same domain as this page |
| // cannot use one with '?' or '#' in its URL as it will confuse |
| // goog.net.CrossDomainRpc.getFramePayload_() |
| if (goog.Uri.haveSameDomain(image.src, window.location.href) && |
| image.src.indexOf('?') < 0) { |
| return goog.net.CrossDomainRpc.removeHash_(image.src); |
| } |
| } |
| |
| if (!goog.net.CrossDomainRpc.useFallBackDummyResource_) { |
| throw Error( |
| 'No suitable dummy resource specified or detected for this page'); |
| } |
| |
| if (goog.userAgent.IE) { |
| // use this page as the dummy resource; remove hash from URL if any |
| return goog.net.CrossDomainRpc.removeHash_(window.location.href); |
| } else { |
| /** |
| * Try to use "http://<this-domain>/robots.txt" which may exist. Even if |
| * it does not, an error page is returned and is a good dummy resource to |
| * use on Firefox and Safari. An existing resource is faster because it |
| * is cached. |
| */ |
| var locationHref = window.location.href; |
| var rootSlash = locationHref.indexOf('/', locationHref.indexOf('//') + 2); |
| var rootHref = locationHref.substring(0, rootSlash); |
| return rootHref + '/robots.txt'; |
| } |
| }; |
| |
| |
| /** |
| * Removes everything at and after hash from URI |
| * @param {string} uri Uri to to remove hash. |
| * @return {string} Uri with its hash and all characters after removed. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.removeHash_ = function(uri) { |
| return uri.split('#')[0]; |
| }; |
| |
| |
| // ------------ |
| // request side |
| |
| |
| /** |
| * next request id used to support multiple XD requests at the same time |
| * @type {number} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.nextRequestId_ = 0; |
| |
| |
| /** |
| * Header prefix. |
| * @type {string} |
| */ |
| goog.net.CrossDomainRpc.HEADER = 'xdh:'; |
| |
| |
| /** |
| * Parameter prefix. |
| * @type {string} |
| */ |
| goog.net.CrossDomainRpc.PARAM = 'xdp:'; |
| |
| |
| /** |
| * Parameter to echo prefix. |
| * @type {string} |
| */ |
| goog.net.CrossDomainRpc.PARAM_ECHO = 'xdpe:'; |
| |
| |
| /** |
| * Parameter to echo: request id |
| * @type {string} |
| */ |
| goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID = |
| goog.net.CrossDomainRpc.PARAM_ECHO + 'request-id'; |
| |
| |
| /** |
| * Parameter to echo: dummy resource URI |
| * @type {string} |
| */ |
| goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI = |
| goog.net.CrossDomainRpc.PARAM_ECHO + 'dummy-uri'; |
| |
| |
| /** |
| * Cross-domain request marker. |
| * @type {string} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.REQUEST_MARKER_ = 'xdrq'; |
| |
| |
| /** |
| * Sends a request across domain. |
| * @param {string} uri Uri to make request to. |
| * @param {string=} opt_method Method of request. Default is POST. |
| * @param {Object=} opt_params Parameters. Each property is turned into a |
| * request parameter. |
| * @param {Object=} opt_headers Map of headers of the request. |
| */ |
| goog.net.CrossDomainRpc.prototype.sendRequest = |
| function(uri, opt_method, opt_params, opt_headers) { |
| // create request frame |
| var requestFrame = this.requestFrame_ = /** @type {!HTMLIFrameElement} */ ( |
| document.createElement('iframe')); |
| var requestId = goog.net.CrossDomainRpc.nextRequestId_++; |
| requestFrame.id = goog.net.CrossDomainRpc.REQUEST_MARKER_ + '-' + requestId; |
| if (!goog.net.CrossDomainRpc.debugMode_) { |
| requestFrame.style.position = 'absolute'; |
| requestFrame.style.top = '-5000px'; |
| requestFrame.style.left = '-5000px'; |
| } |
| document.body.appendChild(requestFrame); |
| |
| // build inputs |
| var inputs = []; |
| |
| // add request id |
| inputs.push(goog.net.CrossDomainRpc.createInputHtml_( |
| goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID, requestId)); |
| |
| // add dummy resource uri |
| var dummyUri = goog.net.CrossDomainRpc.getDummyResourceUri_(); |
| goog.log.fine(goog.net.CrossDomainRpc.logger_, |
| 'dummyUri: ' + dummyUri); |
| inputs.push(goog.net.CrossDomainRpc.createInputHtml_( |
| goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI, dummyUri)); |
| |
| // add parameters |
| if (opt_params) { |
| for (var name in opt_params) { |
| var value = opt_params[name]; |
| inputs.push(goog.net.CrossDomainRpc.createInputHtml_( |
| goog.net.CrossDomainRpc.PARAM + name, value)); |
| } |
| } |
| |
| // add headers |
| if (opt_headers) { |
| for (var name in opt_headers) { |
| var value = opt_headers[name]; |
| inputs.push(goog.net.CrossDomainRpc.createInputHtml_( |
| goog.net.CrossDomainRpc.HEADER + name, value)); |
| } |
| } |
| |
| var requestFrameContent = '<body><form method="' + |
| (opt_method == 'GET' ? 'GET' : 'POST') + '" action="' + |
| uri + '">' + inputs.join('') + '</form></body>'; |
| var requestFrameContentHtml = goog.html.legacyconversions.safeHtmlFromString( |
| requestFrameContent); |
| var requestFrameDoc = goog.dom.getFrameContentDocument(requestFrame); |
| requestFrameDoc.open(); |
| goog.dom.safe.documentWrite(requestFrameDoc, requestFrameContentHtml); |
| requestFrameDoc.close(); |
| |
| requestFrameDoc.forms[0].submit(); |
| requestFrameDoc = null; |
| |
| this.loadListenerKey_ = goog.events.listen( |
| requestFrame, goog.events.EventType.LOAD, function() { |
| goog.log.fine(goog.net.CrossDomainRpc.logger_, 'response ready'); |
| this.responseReady_ = true; |
| }, false, this); |
| |
| this.receiveResponse_(); |
| }; |
| |
| |
| /** |
| * period of response polling (ms) |
| * @type {number} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_ = 50; |
| |
| |
| /** |
| * timeout from response comes back to sendResponse is called (ms) |
| * @type {number} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_ = 500; |
| |
| |
| /** |
| * Receives response by polling to check readiness of response and then |
| * reads response frames and assembles response data |
| * @private |
| */ |
| goog.net.CrossDomainRpc.prototype.receiveResponse_ = function() { |
| this.timeWaitedAfterResponseReady_ = 0; |
| var responseDetectorHandle = window.setInterval(goog.bind(function() { |
| this.detectResponse_(responseDetectorHandle); |
| }, this), goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_); |
| }; |
| |
| |
| /** |
| * Detects response inside request frame |
| * @param {number} responseDetectorHandle Handle of detector. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.prototype.detectResponse_ = |
| function(responseDetectorHandle) { |
| var requestFrameWindow = this.requestFrame_.contentWindow; |
| var grandChildrenLength = requestFrameWindow.frames.length; |
| var responseInfoFrame = null; |
| if (grandChildrenLength > 0 && |
| goog.net.CrossDomainRpc.isResponseInfoFrame_(responseInfoFrame = |
| requestFrameWindow.frames[grandChildrenLength - 1])) { |
| goog.log.fine(goog.net.CrossDomainRpc.logger_, |
| 'xd response ready'); |
| |
| var responseInfoPayload = goog.net.CrossDomainRpc.getFramePayload_( |
| responseInfoFrame).substring(1); |
| var params = new goog.Uri.QueryData(responseInfoPayload); |
| |
| var chunks = []; |
| var numChunks = Number(params.get('n')); |
| goog.log.fine(goog.net.CrossDomainRpc.logger_, |
| 'xd response number of chunks: ' + numChunks); |
| for (var i = 0; i < numChunks; i++) { |
| var responseFrame = requestFrameWindow.frames[i]; |
| if (!responseFrame || !responseFrame.location || |
| !responseFrame.location.href) { |
| // On Safari 3.0, it is sometimes the case that the |
| // iframe exists but doesn't have a same domain href yet. |
| goog.log.fine(goog.net.CrossDomainRpc.logger_, |
| 'xd response iframe not ready'); |
| return; |
| } |
| var responseChunkPayload = |
| goog.net.CrossDomainRpc.getFramePayload_(responseFrame); |
| // go past "chunk=" |
| var chunkIndex = responseChunkPayload.indexOf( |
| goog.net.CrossDomainRpc.PARAM_CHUNK_) + |
| goog.net.CrossDomainRpc.PARAM_CHUNK_.length + 1; |
| var chunk = responseChunkPayload.substring(chunkIndex); |
| chunks.push(chunk); |
| } |
| |
| window.clearInterval(responseDetectorHandle); |
| |
| var responseData = chunks.join(''); |
| // Payload is not encoded to begin with on IE. Decode in other cases only. |
| if (!goog.userAgent.IE) { |
| responseData = decodeURIComponent(responseData); |
| } |
| |
| this.status = Number(params.get('status')); |
| this.responseText = responseData; |
| this.responseTextIsJson_ = params.get('isDataJson') == 'true'; |
| this.responseHeaders = goog.json.unsafeParse( |
| /** @type {string} */ (params.get('headers'))); |
| |
| this.dispatchEvent(goog.net.EventType.READY); |
| this.dispatchEvent(goog.net.EventType.COMPLETE); |
| } else { |
| if (this.responseReady_) { |
| /* The response has come back. But the first response iframe has not |
| * been created yet. If this lasts long enough, it is an error. |
| */ |
| this.timeWaitedAfterResponseReady_ += |
| goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_; |
| if (this.timeWaitedAfterResponseReady_ > |
| goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_) { |
| goog.log.fine(goog.net.CrossDomainRpc.logger_, |
| 'xd response timed out'); |
| window.clearInterval(responseDetectorHandle); |
| |
| this.status = goog.net.HttpStatus.INTERNAL_SERVER_ERROR; |
| this.responseText = 'response timed out'; |
| |
| this.dispatchEvent(goog.net.EventType.READY); |
| this.dispatchEvent(goog.net.EventType.ERROR); |
| this.dispatchEvent(goog.net.EventType.COMPLETE); |
| } |
| } |
| } |
| }; |
| |
| |
| /** |
| * Checks whether a frame is response info frame. |
| * @param {Object} frame Frame to check. |
| * @return {boolean} True if frame is a response info frame; false otherwise. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.isResponseInfoFrame_ = function(frame) { |
| /** @preserveTry */ |
| try { |
| return goog.net.CrossDomainRpc.getFramePayload_(frame).indexOf( |
| goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_) == 1; |
| } catch (e) { |
| // frame not ready for same-domain access yet |
| return false; |
| } |
| }; |
| |
| |
| /** |
| * Returns the payload of a frame (value after # or ? on the URL). This value |
| * is URL encoded except IE, where the value is not encoded to begin with. |
| * @param {Object} frame Frame. |
| * @return {string} Payload of that frame. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.getFramePayload_ = function(frame) { |
| var href = frame.location.href; |
| var question = href.indexOf('?'); |
| var hash = href.indexOf('#'); |
| // On IE, beucase the URL is not encoded, we can have a case where ? |
| // is the delimiter before payload and # in payload or # as the delimiter |
| // and ? in payload. So here we treat whoever is the first as the delimiter. |
| var delimiter = question < 0 ? hash : |
| hash < 0 ? question : Math.min(question, hash); |
| return href.substring(delimiter); |
| }; |
| |
| |
| /** |
| * If response is JSON, evaluates it to a JavaScript object and |
| * returns it; otherwise returns undefined. |
| * @return {Object|undefined} JavaScript object if response is in JSON |
| * or undefined. |
| */ |
| goog.net.CrossDomainRpc.prototype.getResponseJson = function() { |
| return this.responseTextIsJson_ ? |
| goog.json.unsafeParse(this.responseText) : undefined; |
| }; |
| |
| |
| /** |
| * @return {boolean} Whether the request completed with a success. |
| */ |
| goog.net.CrossDomainRpc.prototype.isSuccess = function() { |
| // Definition similar to goog.net.XhrIo.prototype.isSuccess. |
| switch (this.status) { |
| case goog.net.HttpStatus.OK: |
| case goog.net.HttpStatus.NOT_MODIFIED: |
| return true; |
| |
| default: |
| return false; |
| } |
| }; |
| |
| |
| /** |
| * Removes request iframe used. |
| */ |
| goog.net.CrossDomainRpc.prototype.reset = function() { |
| if (!goog.net.CrossDomainRpc.debugMode_) { |
| goog.log.fine(goog.net.CrossDomainRpc.logger_, |
| 'request frame removed: ' + this.requestFrame_.id); |
| goog.events.unlistenByKey(this.loadListenerKey_); |
| this.requestFrame_.parentNode.removeChild(this.requestFrame_); |
| } |
| delete this.requestFrame_; |
| }; |
| |
| |
| // ------------- |
| // response side |
| |
| |
| /** |
| * Name of response info iframe. |
| * @type {string} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ = |
| goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '-info'; |
| |
| |
| /** |
| * Maximal chunk size. IE can only handle 4095 bytes on its URL. |
| * 16MB has been tested on Firefox. But 1MB is a practical size. |
| * @type {number} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ = |
| goog.userAgent.IE ? 4095 : 1024 * 1024; |
| |
| |
| /** |
| * Query parameter 'chunk'. |
| * @type {string} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.PARAM_CHUNK_ = 'chunk'; |
| |
| |
| /** |
| * Prefix before data chunk for passing other parameters. |
| * type String |
| * @private |
| */ |
| goog.net.CrossDomainRpc.CHUNK_PREFIX_ = |
| goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '=1&' + |
| goog.net.CrossDomainRpc.PARAM_CHUNK_ + '='; |
| |
| |
| /** |
| * Makes response available for grandparent (requester)'s receiveResponse |
| * call to pick up by creating a series of iframes pointed to the dummy URI |
| * with a payload (value after either ? or #) carrying a chunk of response |
| * data and a response info iframe that tells the grandparent (requester) the |
| * readiness of response. |
| * @param {string} data Response data (string or JSON string). |
| * @param {boolean} isDataJson true if data is a JSON string; false if just a |
| * string. |
| * @param {Object} echo Parameters to echo back |
| * "xdpe:request-id": Server that produces the response needs to |
| * copy it here to support multiple current XD requests on the same page. |
| * "xdpe:dummy-uri": URI to a dummy resource that response |
| * iframes point to to gain the domain of the client. This can be an |
| * image (IE) or a CSS file (FF) found on the requester's page. |
| * Server should copy value from request parameter "xdpe:dummy-uri". |
| * @param {number} status HTTP response status code. |
| * @param {string} headers Response headers in JSON format. |
| */ |
| goog.net.CrossDomainRpc.sendResponse = |
| function(data, isDataJson, echo, status, headers) { |
| var dummyUri = echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI]; |
| |
| // since the dummy-uri can be specified by the user, verify that it doesn't |
| // use any other protocols. (Specifically we don't want users to use a |
| // dummy-uri beginning with "javascript:"). |
| if (!goog.string.caseInsensitiveStartsWith(dummyUri, 'http://') && |
| !goog.string.caseInsensitiveStartsWith(dummyUri, 'https://')) { |
| dummyUri = 'http://' + dummyUri; |
| } |
| |
| // usable chunk size is max less dummy URI less chunk prefix length |
| // TODO(user): Figure out why we need to do "- 1" below |
| var chunkSize = goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ - dummyUri.length - |
| 1 - // payload delimiter ('#' or '?') |
| goog.net.CrossDomainRpc.CHUNK_PREFIX_.length - 1; |
| |
| /* |
| * Here we used to do URI encoding of data before we divide it into chunks |
| * and decode on the receiving end. We don't do this any more on IE for the |
| * following reasons. |
| * |
| * 1) On IE, calling decodeURIComponent on a relatively large string is |
| * extremely slow (~22s for 160KB). So even a moderate amount of data |
| * makes this library pretty much useless. Fortunately, we can actually |
| * put unencoded data on IE's URL and get it back reliably. So we are |
| * completely skipping encoding and decoding on IE. When we call |
| * getFrameHash_ to get it back, the value is still intact(*) and unencoded. |
| * 2) On Firefox, we have to call decodeURIComponent because location.hash |
| * does decoding by itself. Fortunately, decodeURIComponent is not slow |
| * on Firefox. |
| * 3) Safari automatically encodes everything you put on URL and it does not |
| * automatically decode when you access it via location.hash or |
| * location.href. So we encode it here and decode it in detectResponse_(). |
| * |
| * Note(*): IE actually does encode only space to %20 and decodes that |
| * automatically when you do location.href or location.hash. |
| */ |
| if (!goog.userAgent.IE) { |
| data = encodeURIComponent(data); |
| } |
| |
| var numChunksToSend = Math.ceil(data.length / chunkSize); |
| if (numChunksToSend == 0) { |
| goog.net.CrossDomainRpc.createResponseInfo_( |
| dummyUri, numChunksToSend, isDataJson, status, headers); |
| } else { |
| var numChunksSent = 0; |
| var checkToCreateResponseInfo_ = function() { |
| if (++numChunksSent == numChunksToSend) { |
| goog.net.CrossDomainRpc.createResponseInfo_( |
| dummyUri, numChunksToSend, isDataJson, status, headers); |
| } |
| }; |
| |
| for (var i = 0; i < numChunksToSend; i++) { |
| var chunkStart = i * chunkSize; |
| var chunkEnd = chunkStart + chunkSize; |
| var chunk = chunkEnd > data.length ? |
| data.substring(chunkStart) : |
| data.substring(chunkStart, chunkEnd); |
| |
| var responseFrame = document.createElement('iframe'); |
| responseFrame.src = dummyUri + |
| goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) + |
| goog.net.CrossDomainRpc.CHUNK_PREFIX_ + chunk; |
| document.body.appendChild(responseFrame); |
| |
| // We used to call the function below when handling load event of |
| // responseFrame. But that event does not fire on IE when current |
| // page is used as the dummy resource (because its loading is stopped?). |
| // It also does not fire sometimes on Firefox. So now we call it |
| // directly. |
| checkToCreateResponseInfo_(); |
| } |
| } |
| }; |
| |
| |
| /** |
| * Creates a response info iframe to indicate completion of sendResponse |
| * @param {string} dummyUri URI to a dummy resource. |
| * @param {number} numChunks Total number of chunks. |
| * @param {boolean} isDataJson Whether response is a JSON string or just string. |
| * @param {number} status HTTP response status code. |
| * @param {string} headers Response headers in JSON format. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.createResponseInfo_ = |
| function(dummyUri, numChunks, isDataJson, status, headers) { |
| var responseInfoFrame = document.createElement('iframe'); |
| document.body.appendChild(responseInfoFrame); |
| responseInfoFrame.src = dummyUri + |
| goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) + |
| goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ + |
| '=1&n=' + numChunks + '&isDataJson=' + isDataJson + '&status=' + status + |
| '&headers=' + encodeURIComponent(headers); |
| }; |
| |
| |
| /** |
| * Returns payload delimiter, either "#" when caller's page is not used as |
| * the dummy resource or "?" when it is, in which case caching issues prevent |
| * response frames to gain the caller's domain. |
| * @param {string} dummyUri URI to resource being used as dummy resource. |
| * @return {string} Either "?" when caller's page is used as dummy resource or |
| * "#" if it is not. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.getPayloadDelimiter_ = function(dummyUri) { |
| return goog.net.CrossDomainRpc.REFERRER_ == dummyUri ? '?' : '#'; |
| }; |
| |
| |
| /** |
| * Removes all parameters (after ? or #) from URI. |
| * @param {string} uri URI to remove parameters from. |
| * @return {string} URI with all parameters removed. |
| * @private |
| */ |
| goog.net.CrossDomainRpc.removeUriParams_ = function(uri) { |
| // remove everything after question mark |
| var question = uri.indexOf('?'); |
| if (question > 0) { |
| uri = uri.substring(0, question); |
| } |
| |
| // remove everything after hash mark |
| var hash = uri.indexOf('#'); |
| if (hash > 0) { |
| uri = uri.substring(0, hash); |
| } |
| |
| return uri; |
| }; |
| |
| |
| /** |
| * Gets a response header. |
| * @param {string} name Name of response header. |
| * @return {string|undefined} Value of response header; undefined if not found. |
| */ |
| goog.net.CrossDomainRpc.prototype.getResponseHeader = function(name) { |
| return goog.isObject(this.responseHeaders) ? |
| this.responseHeaders[name] : undefined; |
| }; |
| |
| |
| /** |
| * Referrer of current document with all parameters after "?" and "#" stripped. |
| * @type {string} |
| * @private |
| */ |
| goog.net.CrossDomainRpc.REFERRER_ = |
| goog.net.CrossDomainRpc.removeUriParams_(document.referrer); |