crossdomainrpc.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Cross domain RPC library using the <a
  16. * 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. Server
  20. * receives these parameters: "xdpe:request-id", "xdpe:dummy-uri" ("xdpe" for
  21. * "cross domain parameter to echo back") and other user parameters prefixed
  22. * with "xdp" (for "cross domain parameter"). Headers are passed as parameters
  23. * prefixed with "xdh" (for "cross domain header"). Only strings are supported
  24. * for parameters and headers. A GET method is mapped to a form GET. All
  25. * other methods are mapped to a POST. Server is expected to produce a
  26. * HTML response such as the following:
  27. * <pre>
  28. * &lt;body&gt;
  29. * &lt;script type="text/javascript"
  30. * src="path-to-crossdomainrpc.js"&gt;&lt;/script&gt;
  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. * &lt;value of parameter "xdpe:request-id"&gt;;
  39. * echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI] =
  40. * &lt;value of parameter "xdpe:dummy-uri"&gt;;
  41. *
  42. * goog.net.CrossDomainRpc.sendResponse(
  43. * '({"result":"&lt;responseInJSON"})',
  44. * true, // is JSON
  45. * echo, // parameters to echo back
  46. * status, // response status code
  47. * headers // response headers
  48. * );
  49. * &lt;/script&gt;
  50. * &lt;/body&gt;
  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. */
  65. goog.provide('goog.net.CrossDomainRpc');
  66. goog.require('goog.Uri');
  67. goog.require('goog.dom');
  68. goog.require('goog.dom.TagName');
  69. goog.require('goog.dom.safe');
  70. goog.require('goog.events');
  71. goog.require('goog.events.EventTarget');
  72. goog.require('goog.events.EventType');
  73. goog.require('goog.html.SafeHtml');
  74. goog.require('goog.log');
  75. goog.require('goog.net.EventType');
  76. goog.require('goog.net.HttpStatus');
  77. goog.require('goog.string');
  78. goog.require('goog.userAgent');
  79. /**
  80. * Creates a new instance of cross domain RPC.
  81. *
  82. * @extends {goog.events.EventTarget}
  83. * @constructor
  84. * @final
  85. */
  86. goog.net.CrossDomainRpc = function() {
  87. goog.events.EventTarget.call(this);
  88. };
  89. goog.inherits(goog.net.CrossDomainRpc, goog.events.EventTarget);
  90. /**
  91. * Cross-domain response iframe marker.
  92. * @type {string}
  93. * @private
  94. */
  95. goog.net.CrossDomainRpc.RESPONSE_MARKER_ = 'xdrp';
  96. /**
  97. * Use a fallback dummy resource if none specified or detected.
  98. * @type {boolean}
  99. * @private
  100. */
  101. goog.net.CrossDomainRpc.useFallBackDummyResource_ = true;
  102. /** @type {Object} */
  103. goog.net.CrossDomainRpc.prototype.responseHeaders;
  104. /** @type {string} */
  105. goog.net.CrossDomainRpc.prototype.responseText;
  106. /** @type {number} */
  107. goog.net.CrossDomainRpc.prototype.status;
  108. /** @type {number} */
  109. goog.net.CrossDomainRpc.prototype.timeWaitedAfterResponseReady_;
  110. /** @private {boolean} */
  111. goog.net.CrossDomainRpc.prototype.responseTextIsJson_;
  112. /** @private {boolean} */
  113. goog.net.CrossDomainRpc.prototype.responseReady_;
  114. /** @private {!HTMLIFrameElement} */
  115. goog.net.CrossDomainRpc.prototype.requestFrame_;
  116. /** @private {goog.events.Key} */
  117. goog.net.CrossDomainRpc.prototype.loadListenerKey_;
  118. /**
  119. * Checks to see if we are executing inside a response iframe. This is the
  120. * case when this page is used as a dummy resource to gain caller's domain.
  121. * @return {*} True if we are executing inside a response iframe; false
  122. * otherwise.
  123. * @private
  124. */
  125. goog.net.CrossDomainRpc.isInResponseIframe_ = function() {
  126. return window.location &&
  127. (window.location.hash.indexOf(goog.net.CrossDomainRpc.RESPONSE_MARKER_) ==
  128. 1 ||
  129. window.location.search.indexOf(
  130. goog.net.CrossDomainRpc.RESPONSE_MARKER_) == 1);
  131. };
  132. /**
  133. * Stops execution of the rest of the page if this page is loaded inside a
  134. * response iframe.
  135. */
  136. if (goog.net.CrossDomainRpc.isInResponseIframe_()) {
  137. if (goog.userAgent.EDGE_OR_IE) {
  138. document.execCommand('Stop');
  139. } else if (goog.userAgent.GECKO) {
  140. window.stop();
  141. } else {
  142. throw Error('stopped');
  143. }
  144. }
  145. /**
  146. * Sets the URI for a dummy resource on caller's domain. This function is
  147. * used for specifying a particular resource to use rather than relying on
  148. * auto detection.
  149. * @param {string} dummyResourceUri URI to dummy resource on the same domain
  150. * of caller's page.
  151. */
  152. goog.net.CrossDomainRpc.setDummyResourceUri = function(dummyResourceUri) {
  153. goog.net.CrossDomainRpc.dummyResourceUri_ = dummyResourceUri;
  154. };
  155. /**
  156. * Sets whether a fallback dummy resource ("/robots.txt" on Firefox and Safari
  157. * and current page on IE) should be used when a suitable dummy resource is
  158. * not available.
  159. * @param {boolean} useFallBack Whether to use fallback or not.
  160. */
  161. goog.net.CrossDomainRpc.setUseFallBackDummyResource = function(useFallBack) {
  162. goog.net.CrossDomainRpc.useFallBackDummyResource_ = useFallBack;
  163. };
  164. /**
  165. * Sends a request across domain.
  166. * @param {string} uri Uri to make request to.
  167. * @param {Function=} opt_continuation Continuation function to be called
  168. * when request is completed. Takes one argument of an event object
  169. * whose target has the following properties: "status" is the HTTP
  170. * response status code, "responseText" is the response text,
  171. * and "headers" is an object with all response headers. The event
  172. * target's getResponseJson() method returns a JavaScript object evaluated
  173. * from the JSON response or undefined if response is not JSON.
  174. * @param {string=} opt_method Method of request. Default is POST.
  175. * @param {Object=} opt_params Parameters. Each property is turned into a
  176. * request parameter.
  177. * @param {Object=} opt_headers Map of headers of the request.
  178. */
  179. goog.net.CrossDomainRpc.send = function(
  180. uri, opt_continuation, opt_method, opt_params, opt_headers) {
  181. var xdrpc = new goog.net.CrossDomainRpc();
  182. if (opt_continuation) {
  183. goog.events.listen(xdrpc, goog.net.EventType.COMPLETE, opt_continuation);
  184. }
  185. goog.events.listen(xdrpc, goog.net.EventType.READY, xdrpc.reset);
  186. xdrpc.sendRequest(uri, opt_method, opt_params, opt_headers);
  187. };
  188. /**
  189. * Sets debug mode to true or false. When debug mode is on, response iframes
  190. * are visible and left behind after their use is finished.
  191. * @param {boolean} flag Flag to indicate intention to turn debug model on
  192. * (true) or off (false).
  193. */
  194. goog.net.CrossDomainRpc.setDebugMode = function(flag) {
  195. goog.net.CrossDomainRpc.debugMode_ = flag;
  196. };
  197. /**
  198. * Logger for goog.net.CrossDomainRpc
  199. * @type {goog.log.Logger}
  200. * @private
  201. */
  202. goog.net.CrossDomainRpc.logger_ = goog.log.getLogger('goog.net.CrossDomainRpc');
  203. /**
  204. * Creates the HTML of an input element
  205. * @param {string} name Name of input element.
  206. * @param {*} value Value of input element.
  207. * @return {!goog.html.SafeHtml} HTML of input element with that name and value.
  208. * @private
  209. */
  210. goog.net.CrossDomainRpc.createInputHtml_ = function(name, value) {
  211. return goog.html.SafeHtml.create('textarea', {'name': name}, String(value));
  212. };
  213. /**
  214. * Finds a dummy resource that can be used by response to gain domain of
  215. * requester's page.
  216. * @return {string} URI of the resource to use.
  217. * @private
  218. */
  219. goog.net.CrossDomainRpc.getDummyResourceUri_ = function() {
  220. if (goog.net.CrossDomainRpc.dummyResourceUri_) {
  221. return goog.net.CrossDomainRpc.dummyResourceUri_;
  222. }
  223. // find a style sheet if not on IE, which will attempt to save style sheet
  224. if (goog.userAgent.GECKO) {
  225. var links = goog.dom.getElementsByTagName(goog.dom.TagName.LINK);
  226. for (var i = 0; i < links.length; i++) {
  227. var link = links[i];
  228. // find a link which is on the same domain as this page
  229. // cannot use one with '?' or '#' in its URL as it will confuse
  230. // goog.net.CrossDomainRpc.getFramePayload_()
  231. if (link.rel == 'stylesheet' &&
  232. goog.Uri.haveSameDomain(link.href, window.location.href) &&
  233. link.href.indexOf('?') < 0) {
  234. return goog.net.CrossDomainRpc.removeHash_(link.href);
  235. }
  236. }
  237. }
  238. var images = goog.dom.getElementsByTagName(goog.dom.TagName.IMG);
  239. for (var i = 0; i < images.length; i++) {
  240. var image = images[i];
  241. // find a link which is on the same domain as this page
  242. // cannot use one with '?' or '#' in its URL as it will confuse
  243. // goog.net.CrossDomainRpc.getFramePayload_()
  244. if (goog.Uri.haveSameDomain(image.src, window.location.href) &&
  245. image.src.indexOf('?') < 0) {
  246. return goog.net.CrossDomainRpc.removeHash_(image.src);
  247. }
  248. }
  249. if (!goog.net.CrossDomainRpc.useFallBackDummyResource_) {
  250. throw Error(
  251. 'No suitable dummy resource specified or detected for this page');
  252. }
  253. if (goog.userAgent.EDGE_OR_IE) {
  254. // use this page as the dummy resource; remove hash from URL if any
  255. return goog.net.CrossDomainRpc.removeHash_(window.location.href);
  256. } else {
  257. /**
  258. * Try to use "http://<this-domain>/robots.txt" which may exist. Even if
  259. * it does not, an error page is returned and is a good dummy resource to
  260. * use on Firefox and Safari. An existing resource is faster because it
  261. * is cached.
  262. */
  263. var locationHref = window.location.href;
  264. var rootSlash = locationHref.indexOf('/', locationHref.indexOf('//') + 2);
  265. var rootHref = locationHref.substring(0, rootSlash);
  266. return rootHref + '/robots.txt';
  267. }
  268. };
  269. /**
  270. * Removes everything at and after hash from URI
  271. * @param {string} uri Uri to to remove hash.
  272. * @return {string} Uri with its hash and all characters after removed.
  273. * @private
  274. */
  275. goog.net.CrossDomainRpc.removeHash_ = function(uri) {
  276. return uri.split('#')[0];
  277. };
  278. // ------------
  279. // request side
  280. /**
  281. * next request id used to support multiple XD requests at the same time
  282. * @type {number}
  283. * @private
  284. */
  285. goog.net.CrossDomainRpc.nextRequestId_ = 0;
  286. /**
  287. * Header prefix.
  288. * @type {string}
  289. */
  290. goog.net.CrossDomainRpc.HEADER = 'xdh:';
  291. /**
  292. * Parameter prefix.
  293. * @type {string}
  294. */
  295. goog.net.CrossDomainRpc.PARAM = 'xdp:';
  296. /**
  297. * Parameter to echo prefix.
  298. * @type {string}
  299. */
  300. goog.net.CrossDomainRpc.PARAM_ECHO = 'xdpe:';
  301. /**
  302. * Parameter to echo: request id
  303. * @type {string}
  304. */
  305. goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID =
  306. goog.net.CrossDomainRpc.PARAM_ECHO + 'request-id';
  307. /**
  308. * Parameter to echo: dummy resource URI
  309. * @type {string}
  310. */
  311. goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI =
  312. goog.net.CrossDomainRpc.PARAM_ECHO + 'dummy-uri';
  313. /**
  314. * Cross-domain request marker.
  315. * @type {string}
  316. * @private
  317. */
  318. goog.net.CrossDomainRpc.REQUEST_MARKER_ = 'xdrq';
  319. /**
  320. * Sends a request across domain.
  321. * @param {string} uri Uri to make request to.
  322. * @param {string=} opt_method Method of request, 'GET' or 'POST' (uppercase).
  323. * Default is 'POST'.
  324. * @param {Object=} opt_params Parameters. Each property is turned into a
  325. * request parameter.
  326. * @param {Object=} opt_headers Map of headers of the request.
  327. */
  328. goog.net.CrossDomainRpc.prototype.sendRequest = function(
  329. uri, opt_method, opt_params, opt_headers) {
  330. // create request frame
  331. var requestFrame = this.requestFrame_ =
  332. goog.dom.createElement(goog.dom.TagName.IFRAME);
  333. var requestId = goog.net.CrossDomainRpc.nextRequestId_++;
  334. requestFrame.id = goog.net.CrossDomainRpc.REQUEST_MARKER_ + '-' + requestId;
  335. if (!goog.net.CrossDomainRpc.debugMode_) {
  336. requestFrame.style.position = 'absolute';
  337. requestFrame.style.top = '-5000px';
  338. requestFrame.style.left = '-5000px';
  339. }
  340. document.body.appendChild(requestFrame);
  341. // build inputs
  342. var inputs = [];
  343. // add request id
  344. inputs.push(
  345. goog.net.CrossDomainRpc.createInputHtml_(
  346. goog.net.CrossDomainRpc.PARAM_ECHO_REQUEST_ID, requestId));
  347. // add dummy resource uri
  348. var dummyUri = goog.net.CrossDomainRpc.getDummyResourceUri_();
  349. goog.log.fine(goog.net.CrossDomainRpc.logger_, 'dummyUri: ' + dummyUri);
  350. inputs.push(
  351. goog.net.CrossDomainRpc.createInputHtml_(
  352. goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI, dummyUri));
  353. // add parameters
  354. if (opt_params) {
  355. for (var name in opt_params) {
  356. var value = opt_params[name];
  357. inputs.push(
  358. goog.net.CrossDomainRpc.createInputHtml_(
  359. goog.net.CrossDomainRpc.PARAM + name, value));
  360. }
  361. }
  362. // add headers
  363. if (opt_headers) {
  364. for (var name in opt_headers) {
  365. var value = opt_headers[name];
  366. inputs.push(
  367. goog.net.CrossDomainRpc.createInputHtml_(
  368. goog.net.CrossDomainRpc.HEADER + name, value));
  369. }
  370. }
  371. var requestFrameContentHtml = goog.html.SafeHtml.create(
  372. 'body', {},
  373. goog.html.SafeHtml.create(
  374. 'form',
  375. {'method': opt_method == 'GET' ? 'GET' : 'POST', 'action': uri},
  376. inputs));
  377. var requestFrameDoc = goog.dom.getFrameContentDocument(requestFrame);
  378. requestFrameDoc.open();
  379. goog.dom.safe.documentWrite(requestFrameDoc, requestFrameContentHtml);
  380. requestFrameDoc.close();
  381. requestFrameDoc.forms[0].submit();
  382. requestFrameDoc = null;
  383. this.loadListenerKey_ =
  384. goog.events.listen(requestFrame, goog.events.EventType.LOAD, function() {
  385. goog.log.fine(goog.net.CrossDomainRpc.logger_, 'response ready');
  386. this.responseReady_ = true;
  387. }, false, this);
  388. this.receiveResponse_();
  389. };
  390. /**
  391. * period of response polling (ms)
  392. * @type {number}
  393. * @private
  394. */
  395. goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_ = 50;
  396. /**
  397. * timeout from response comes back to sendResponse is called (ms)
  398. * @type {number}
  399. * @private
  400. */
  401. goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_ = 500;
  402. /**
  403. * Receives response by polling to check readiness of response and then
  404. * reads response frames and assembles response data
  405. * @private
  406. */
  407. goog.net.CrossDomainRpc.prototype.receiveResponse_ = function() {
  408. this.timeWaitedAfterResponseReady_ = 0;
  409. var responseDetectorHandle = window.setInterval(goog.bind(function() {
  410. this.detectResponse_(responseDetectorHandle);
  411. }, this), goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_);
  412. };
  413. /**
  414. * Detects response inside request frame
  415. * @param {number} responseDetectorHandle Handle of detector.
  416. * @private
  417. */
  418. goog.net.CrossDomainRpc.prototype.detectResponse_ = function(
  419. responseDetectorHandle) {
  420. var requestFrameWindow = this.requestFrame_.contentWindow;
  421. var grandChildrenLength = requestFrameWindow.frames.length;
  422. var responseInfoFrame = null;
  423. if (grandChildrenLength > 0 &&
  424. goog.net.CrossDomainRpc.isResponseInfoFrame_(
  425. responseInfoFrame =
  426. requestFrameWindow.frames[grandChildrenLength - 1])) {
  427. goog.log.fine(goog.net.CrossDomainRpc.logger_, 'xd response ready');
  428. var responseInfoPayload =
  429. goog.net.CrossDomainRpc.getFramePayload_(responseInfoFrame)
  430. .substring(1);
  431. var params = new goog.Uri.QueryData(responseInfoPayload);
  432. var chunks = [];
  433. var numChunks = Number(params.get('n'));
  434. goog.log.fine(
  435. goog.net.CrossDomainRpc.logger_,
  436. 'xd response number of chunks: ' + numChunks);
  437. for (var i = 0; i < numChunks; i++) {
  438. var responseFrame = requestFrameWindow.frames[i];
  439. if (!responseFrame || !responseFrame.location ||
  440. !responseFrame.location.href) {
  441. // On Safari 3.0, it is sometimes the case that the
  442. // iframe exists but doesn't have a same domain href yet.
  443. goog.log.fine(
  444. goog.net.CrossDomainRpc.logger_, 'xd response iframe not ready');
  445. return;
  446. }
  447. var responseChunkPayload =
  448. goog.net.CrossDomainRpc.getFramePayload_(responseFrame);
  449. // go past "chunk="
  450. var chunkIndex =
  451. responseChunkPayload.indexOf(goog.net.CrossDomainRpc.PARAM_CHUNK_) +
  452. goog.net.CrossDomainRpc.PARAM_CHUNK_.length + 1;
  453. var chunk = responseChunkPayload.substring(chunkIndex);
  454. chunks.push(chunk);
  455. }
  456. window.clearInterval(responseDetectorHandle);
  457. var responseData = chunks.join('');
  458. // Payload is not encoded to begin with on IE. Decode in other cases only.
  459. if (!goog.userAgent.EDGE_OR_IE) {
  460. responseData = decodeURIComponent(responseData);
  461. }
  462. this.status = Number(params.get('status'));
  463. this.responseText = responseData;
  464. this.responseTextIsJson_ = params.get('isDataJson') == 'true';
  465. this.responseHeaders = /** @type {?Object} */ (JSON.parse(
  466. /** @type {string} */ (params.get('headers'))));
  467. this.dispatchEvent(goog.net.EventType.READY);
  468. this.dispatchEvent(goog.net.EventType.COMPLETE);
  469. } else {
  470. if (this.responseReady_) {
  471. /* The response has come back. But the first response iframe has not
  472. * been created yet. If this lasts long enough, it is an error.
  473. */
  474. this.timeWaitedAfterResponseReady_ +=
  475. goog.net.CrossDomainRpc.RESPONSE_POLLING_PERIOD_;
  476. if (this.timeWaitedAfterResponseReady_ >
  477. goog.net.CrossDomainRpc.SEND_RESPONSE_TIME_OUT_) {
  478. goog.log.fine(goog.net.CrossDomainRpc.logger_, 'xd response timed out');
  479. window.clearInterval(responseDetectorHandle);
  480. this.status = goog.net.HttpStatus.INTERNAL_SERVER_ERROR;
  481. this.responseText = 'response timed out';
  482. this.dispatchEvent(goog.net.EventType.READY);
  483. this.dispatchEvent(goog.net.EventType.ERROR);
  484. this.dispatchEvent(goog.net.EventType.COMPLETE);
  485. }
  486. }
  487. }
  488. };
  489. /**
  490. * Checks whether a frame is response info frame.
  491. * @param {Object} frame Frame to check.
  492. * @return {boolean} True if frame is a response info frame; false otherwise.
  493. * @private
  494. */
  495. goog.net.CrossDomainRpc.isResponseInfoFrame_ = function(frame) {
  496. try {
  497. return goog.net.CrossDomainRpc.getFramePayload_(frame).indexOf(
  498. goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_) == 1;
  499. } catch (e) {
  500. // frame not ready for same-domain access yet
  501. return false;
  502. }
  503. };
  504. /**
  505. * Returns the payload of a frame (value after # or ? on the URL). This value
  506. * is URL encoded except IE, where the value is not encoded to begin with.
  507. * @param {Object} frame Frame.
  508. * @return {string} Payload of that frame.
  509. * @private
  510. */
  511. goog.net.CrossDomainRpc.getFramePayload_ = function(frame) {
  512. var href = frame.location.href;
  513. var question = href.indexOf('?');
  514. var hash = href.indexOf('#');
  515. // On IE, beucase the URL is not encoded, we can have a case where ?
  516. // is the delimiter before payload and # in payload or # as the delimiter
  517. // and ? in payload. So here we treat whoever is the first as the delimiter.
  518. var delimiter =
  519. question < 0 ? hash : hash < 0 ? question : Math.min(question, hash);
  520. return href.substring(delimiter);
  521. };
  522. /**
  523. * If response is JSON, evaluates it to a JavaScript object and
  524. * returns it; otherwise returns undefined.
  525. * @return {Object|undefined} JavaScript object if response is in JSON
  526. * or undefined.
  527. */
  528. goog.net.CrossDomainRpc.prototype.getResponseJson = function() {
  529. return this.responseTextIsJson_ ?
  530. /** @type {?Object} */ (JSON.parse(this.responseText)) :
  531. undefined;
  532. };
  533. /**
  534. * @return {boolean} Whether the request completed with a success.
  535. */
  536. goog.net.CrossDomainRpc.prototype.isSuccess = function() {
  537. // Definition similar to goog.net.XhrIo.prototype.isSuccess.
  538. switch (this.status) {
  539. case goog.net.HttpStatus.OK:
  540. case goog.net.HttpStatus.NOT_MODIFIED:
  541. return true;
  542. default:
  543. return false;
  544. }
  545. };
  546. /**
  547. * Removes request iframe used.
  548. */
  549. goog.net.CrossDomainRpc.prototype.reset = function() {
  550. if (!goog.net.CrossDomainRpc.debugMode_) {
  551. goog.log.fine(
  552. goog.net.CrossDomainRpc.logger_,
  553. 'request frame removed: ' + this.requestFrame_.id);
  554. goog.events.unlistenByKey(this.loadListenerKey_);
  555. this.requestFrame_.parentNode.removeChild(this.requestFrame_);
  556. }
  557. delete this.requestFrame_;
  558. };
  559. // -------------
  560. // response side
  561. /**
  562. * Name of response info iframe.
  563. * @type {string}
  564. * @private
  565. */
  566. goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ =
  567. goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '-info';
  568. /**
  569. * Maximal chunk size. IE can only handle 4095 bytes on its URL.
  570. * 16MB has been tested on Firefox. But 1MB is a practical size.
  571. * @type {number}
  572. * @private
  573. */
  574. goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ =
  575. goog.userAgent.EDGE_OR_IE ? 4095 : 1024 * 1024;
  576. /**
  577. * Query parameter 'chunk'.
  578. * @type {string}
  579. * @private
  580. */
  581. goog.net.CrossDomainRpc.PARAM_CHUNK_ = 'chunk';
  582. /**
  583. * Prefix before data chunk for passing other parameters.
  584. * type String
  585. * @private
  586. */
  587. goog.net.CrossDomainRpc.CHUNK_PREFIX_ =
  588. goog.net.CrossDomainRpc.RESPONSE_MARKER_ + '=1&' +
  589. goog.net.CrossDomainRpc.PARAM_CHUNK_ + '=';
  590. /**
  591. * Makes response available for grandparent (requester)'s receiveResponse
  592. * call to pick up by creating a series of iframes pointed to the dummy URI
  593. * with a payload (value after either ? or #) carrying a chunk of response
  594. * data and a response info iframe that tells the grandparent (requester) the
  595. * readiness of response.
  596. * @param {string} data Response data (string or JSON string).
  597. * @param {boolean} isDataJson true if data is a JSON string; false if just a
  598. * string.
  599. * @param {Object} echo Parameters to echo back
  600. * "xdpe:request-id": Server that produces the response needs to
  601. * copy it here to support multiple current XD requests on the same page.
  602. * "xdpe:dummy-uri": URI to a dummy resource that response
  603. * iframes point to to gain the domain of the client. This can be an
  604. * image (IE) or a CSS file (FF) found on the requester's page.
  605. * Server should copy value from request parameter "xdpe:dummy-uri".
  606. * @param {number} status HTTP response status code.
  607. * @param {string} headers Response headers in JSON format.
  608. */
  609. goog.net.CrossDomainRpc.sendResponse = function(
  610. data, isDataJson, echo, status, headers) {
  611. var dummyUri = echo[goog.net.CrossDomainRpc.PARAM_ECHO_DUMMY_URI];
  612. // since the dummy-uri can be specified by the user, verify that it doesn't
  613. // use any other protocols. (Specifically we don't want users to use a
  614. // dummy-uri beginning with "javascript:").
  615. if (!goog.string.caseInsensitiveStartsWith(dummyUri, 'http://') &&
  616. !goog.string.caseInsensitiveStartsWith(dummyUri, 'https://')) {
  617. dummyUri = 'http://' + dummyUri;
  618. }
  619. // usable chunk size is max less dummy URI less chunk prefix length
  620. // TODO(user): Figure out why we need to do "- 1" below
  621. var chunkSize = goog.net.CrossDomainRpc.MAX_CHUNK_SIZE_ - dummyUri.length -
  622. 1 - // payload delimiter ('#' or '?')
  623. goog.net.CrossDomainRpc.CHUNK_PREFIX_.length - 1;
  624. /*
  625. * Here we used to do URI encoding of data before we divide it into chunks
  626. * and decode on the receiving end. We don't do this any more on IE for the
  627. * following reasons.
  628. *
  629. * 1) On IE, calling decodeURIComponent on a relatively large string is
  630. * extremely slow (~22s for 160KB). So even a moderate amount of data
  631. * makes this library pretty much useless. Fortunately, we can actually
  632. * put unencoded data on IE's URL and get it back reliably. So we are
  633. * completely skipping encoding and decoding on IE. When we call
  634. * getFrameHash_ to get it back, the value is still intact(*) and unencoded.
  635. * 2) On Firefox, we have to call decodeURIComponent because location.hash
  636. * does decoding by itself. Fortunately, decodeURIComponent is not slow
  637. * on Firefox.
  638. * 3) Safari automatically encodes everything you put on URL and it does not
  639. * automatically decode when you access it via location.hash or
  640. * location.href. So we encode it here and decode it in detectResponse_().
  641. *
  642. * Note(*): IE actually does encode only space to %20 and decodes that
  643. * automatically when you do location.href or location.hash.
  644. */
  645. if (!goog.userAgent.EDGE_OR_IE) {
  646. data = encodeURIComponent(data);
  647. }
  648. var numChunksToSend = Math.ceil(data.length / chunkSize);
  649. if (numChunksToSend == 0) {
  650. goog.net.CrossDomainRpc.createResponseInfo_(
  651. dummyUri, numChunksToSend, isDataJson, status, headers);
  652. } else {
  653. var numChunksSent = 0;
  654. var checkToCreateResponseInfo_ = function() {
  655. if (++numChunksSent == numChunksToSend) {
  656. goog.net.CrossDomainRpc.createResponseInfo_(
  657. dummyUri, numChunksToSend, isDataJson, status, headers);
  658. }
  659. };
  660. for (var i = 0; i < numChunksToSend; i++) {
  661. var chunkStart = i * chunkSize;
  662. var chunkEnd = chunkStart + chunkSize;
  663. var chunk = chunkEnd > data.length ? data.substring(chunkStart) :
  664. data.substring(chunkStart, chunkEnd);
  665. var responseFrame = goog.dom.createElement(goog.dom.TagName.IFRAME);
  666. responseFrame.src = dummyUri +
  667. goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) +
  668. goog.net.CrossDomainRpc.CHUNK_PREFIX_ + chunk;
  669. document.body.appendChild(responseFrame);
  670. // We used to call the function below when handling load event of
  671. // responseFrame. But that event does not fire on IE when current
  672. // page is used as the dummy resource (because its loading is stopped?).
  673. // It also does not fire sometimes on Firefox. So now we call it
  674. // directly.
  675. checkToCreateResponseInfo_();
  676. }
  677. }
  678. };
  679. /**
  680. * Creates a response info iframe to indicate completion of sendResponse
  681. * @param {string} dummyUri URI to a dummy resource.
  682. * @param {number} numChunks Total number of chunks.
  683. * @param {boolean} isDataJson Whether response is a JSON string or just string.
  684. * @param {number} status HTTP response status code.
  685. * @param {string} headers Response headers in JSON format.
  686. * @private
  687. */
  688. goog.net.CrossDomainRpc.createResponseInfo_ = function(
  689. dummyUri, numChunks, isDataJson, status, headers) {
  690. var responseInfoFrame = goog.dom.createElement(goog.dom.TagName.IFRAME);
  691. document.body.appendChild(responseInfoFrame);
  692. responseInfoFrame.src = dummyUri +
  693. goog.net.CrossDomainRpc.getPayloadDelimiter_(dummyUri) +
  694. goog.net.CrossDomainRpc.RESPONSE_INFO_MARKER_ + '=1&n=' + numChunks +
  695. '&isDataJson=' + isDataJson + '&status=' + status + '&headers=' +
  696. encodeURIComponent(headers);
  697. };
  698. /**
  699. * Returns payload delimiter, either "#" when caller's page is not used as
  700. * the dummy resource or "?" when it is, in which case caching issues prevent
  701. * response frames to gain the caller's domain.
  702. * @param {string} dummyUri URI to resource being used as dummy resource.
  703. * @return {string} Either "?" when caller's page is used as dummy resource or
  704. * "#" if it is not.
  705. * @private
  706. */
  707. goog.net.CrossDomainRpc.getPayloadDelimiter_ = function(dummyUri) {
  708. return goog.net.CrossDomainRpc.REFERRER_ == dummyUri ? '?' : '#';
  709. };
  710. /**
  711. * Removes all parameters (after ? or #) from URI.
  712. * @param {string} uri URI to remove parameters from.
  713. * @return {string} URI with all parameters removed.
  714. * @private
  715. */
  716. goog.net.CrossDomainRpc.removeUriParams_ = function(uri) {
  717. // remove everything after question mark
  718. var question = uri.indexOf('?');
  719. if (question > 0) {
  720. uri = uri.substring(0, question);
  721. }
  722. // remove everything after hash mark
  723. var hash = uri.indexOf('#');
  724. if (hash > 0) {
  725. uri = uri.substring(0, hash);
  726. }
  727. return uri;
  728. };
  729. /**
  730. * Gets a response header.
  731. * @param {string} name Name of response header.
  732. * @return {string|undefined} Value of response header; undefined if not found.
  733. */
  734. goog.net.CrossDomainRpc.prototype.getResponseHeader = function(name) {
  735. return goog.isObject(this.responseHeaders) ? this.responseHeaders[name] :
  736. undefined;
  737. };
  738. /**
  739. * Referrer of current document with all parameters after "?" and "#" stripped.
  740. * @type {string}
  741. * @private
  742. */
  743. goog.net.CrossDomainRpc.REFERRER_ =
  744. goog.net.CrossDomainRpc.removeUriParams_(document.referrer);