123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880 |
- // 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.TagName');
- goog.require('goog.dom.safe');
- goog.require('goog.events');
- goog.require('goog.events.EventTarget');
- goog.require('goog.events.EventType');
- goog.require('goog.html.SafeHtml');
- 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.
- *
- * @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.EDGE_OR_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 {!goog.html.SafeHtml} HTML of input element with that name and value.
- * @private
- */
- goog.net.CrossDomainRpc.createInputHtml_ = function(name, value) {
- return goog.html.SafeHtml.create('textarea', {'name': name}, String(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 = goog.dom.getElementsByTagName(goog.dom.TagName.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 = goog.dom.getElementsByTagName(goog.dom.TagName.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.EDGE_OR_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, 'GET' or 'POST' (uppercase).
- * 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_ =
- goog.dom.createElement(goog.dom.TagName.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 requestFrameContentHtml = goog.html.SafeHtml.create(
- 'body', {},
- goog.html.SafeHtml.create(
- 'form',
- {'method': opt_method == 'GET' ? 'GET' : 'POST', 'action': uri},
- inputs));
- 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.EDGE_OR_IE) {
- responseData = decodeURIComponent(responseData);
- }
- this.status = Number(params.get('status'));
- this.responseText = responseData;
- this.responseTextIsJson_ = params.get('isDataJson') == 'true';
- this.responseHeaders = /** @type {?Object} */ (JSON.parse(
- /** @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) {
- 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_ ?
- /** @type {?Object} */ (JSON.parse(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.EDGE_OR_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.EDGE_OR_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 = goog.dom.createElement(goog.dom.TagName.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 = goog.dom.createElement(goog.dom.TagName.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);
|