iframerelaytransport.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. // Copyright 2007 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 Contains the iframe relay tranport.
  16. */
  17. goog.provide('goog.net.xpc.IframeRelayTransport');
  18. goog.require('goog.dom');
  19. goog.require('goog.dom.TagName');
  20. goog.require('goog.dom.safe');
  21. goog.require('goog.events');
  22. goog.require('goog.html.SafeHtml');
  23. goog.require('goog.log');
  24. goog.require('goog.log.Level');
  25. goog.require('goog.net.xpc');
  26. goog.require('goog.net.xpc.CfgFields');
  27. goog.require('goog.net.xpc.Transport');
  28. goog.require('goog.net.xpc.TransportTypes');
  29. goog.require('goog.string');
  30. goog.require('goog.string.Const');
  31. goog.require('goog.userAgent');
  32. /**
  33. * Iframe relay transport. Creates hidden iframes containing a document
  34. * from the peer's origin. Data is transferred in the fragment identifier.
  35. * Therefore the document loaded in the iframes can be served from the
  36. * browser's cache.
  37. *
  38. * @param {goog.net.xpc.CrossPageChannel} channel The channel this
  39. * transport belongs to.
  40. * @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
  41. * the correct window.
  42. * @constructor
  43. * @extends {goog.net.xpc.Transport}
  44. * @final
  45. */
  46. goog.net.xpc.IframeRelayTransport = function(channel, opt_domHelper) {
  47. goog.net.xpc.IframeRelayTransport.base(this, 'constructor', opt_domHelper);
  48. /**
  49. * The channel this transport belongs to.
  50. * @type {goog.net.xpc.CrossPageChannel}
  51. * @private
  52. */
  53. this.channel_ = channel;
  54. /**
  55. * The URI used to relay data to the peer.
  56. * @type {string}
  57. * @private
  58. */
  59. this.peerRelayUri_ =
  60. this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_RELAY_URI];
  61. /**
  62. * The id of the iframe the peer page lives in.
  63. * @type {string}
  64. * @private
  65. */
  66. this.peerIframeId_ =
  67. this.channel_.getConfig()[goog.net.xpc.CfgFields.IFRAME_ID];
  68. if (goog.userAgent.WEBKIT) {
  69. goog.net.xpc.IframeRelayTransport.startCleanupTimer_();
  70. }
  71. };
  72. goog.inherits(goog.net.xpc.IframeRelayTransport, goog.net.xpc.Transport);
  73. if (goog.userAgent.WEBKIT) {
  74. /**
  75. * Array to keep references to the relay-iframes. Used only if
  76. * there is no way to detect when the iframes are loaded. In that
  77. * case the relay-iframes are removed after a timeout.
  78. * @type {Array<Object>}
  79. * @private
  80. */
  81. goog.net.xpc.IframeRelayTransport.iframeRefs_ = [];
  82. /**
  83. * Interval at which iframes are destroyed.
  84. * @type {number}
  85. * @private
  86. */
  87. goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_ = 1000;
  88. /**
  89. * Time after which a relay-iframe is destroyed.
  90. * @type {number}
  91. * @private
  92. */
  93. goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_ = 3000;
  94. /**
  95. * The cleanup timer id.
  96. * @type {number}
  97. * @private
  98. */
  99. goog.net.xpc.IframeRelayTransport.cleanupTimer_ = 0;
  100. /**
  101. * Starts the cleanup timer.
  102. * @private
  103. */
  104. goog.net.xpc.IframeRelayTransport.startCleanupTimer_ = function() {
  105. if (!goog.net.xpc.IframeRelayTransport.cleanupTimer_) {
  106. goog.net.xpc.IframeRelayTransport.cleanupTimer_ =
  107. window.setTimeout(function() {
  108. goog.net.xpc.IframeRelayTransport.cleanup_();
  109. }, goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);
  110. }
  111. };
  112. /**
  113. * Remove all relay-iframes which are older than the maximal age.
  114. * @param {number=} opt_maxAge The maximal age in milliseconds.
  115. * @private
  116. */
  117. goog.net.xpc.IframeRelayTransport.cleanup_ = function(opt_maxAge) {
  118. var now = goog.now();
  119. var maxAge =
  120. opt_maxAge || goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_;
  121. while (goog.net.xpc.IframeRelayTransport.iframeRefs_.length &&
  122. now - goog.net.xpc.IframeRelayTransport.iframeRefs_[0].timestamp >=
  123. maxAge) {
  124. var ifr =
  125. goog.net.xpc.IframeRelayTransport.iframeRefs_.shift().iframeElement;
  126. goog.dom.removeNode(ifr);
  127. goog.log.log(
  128. goog.net.xpc.logger, goog.log.Level.FINEST, 'iframe removed');
  129. }
  130. goog.net.xpc.IframeRelayTransport.cleanupTimer_ = window.setTimeout(
  131. goog.net.xpc.IframeRelayTransport.cleanupCb_,
  132. goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);
  133. };
  134. /**
  135. * Function which wraps cleanup_().
  136. * @private
  137. */
  138. goog.net.xpc.IframeRelayTransport.cleanupCb_ = function() {
  139. goog.net.xpc.IframeRelayTransport.cleanup_();
  140. };
  141. }
  142. /**
  143. * Maximum sendable size of a payload via a single iframe in IE.
  144. * @type {number}
  145. * @private
  146. */
  147. goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_ = 1800;
  148. /**
  149. * @typedef {{fragments: !Array<string>, received: number, expected: number}}
  150. */
  151. goog.net.xpc.IframeRelayTransport.FragmentInfo;
  152. /**
  153. * Used to track incoming payload fragments. The implementation can process
  154. * incoming fragments from several channels at a time, even if data is
  155. * out-of-order or interleaved.
  156. *
  157. * @type {!Object<string, !goog.net.xpc.IframeRelayTransport.FragmentInfo>}
  158. * @private
  159. */
  160. goog.net.xpc.IframeRelayTransport.fragmentMap_ = {};
  161. /**
  162. * The transport type.
  163. * @type {number}
  164. * @override
  165. */
  166. goog.net.xpc.IframeRelayTransport.prototype.transportType =
  167. goog.net.xpc.TransportTypes.IFRAME_RELAY;
  168. /**
  169. * Connects this transport.
  170. * @override
  171. */
  172. goog.net.xpc.IframeRelayTransport.prototype.connect = function() {
  173. if (!this.getWindow()['xpcRelay']) {
  174. this.getWindow()['xpcRelay'] =
  175. goog.net.xpc.IframeRelayTransport.receiveMessage_;
  176. }
  177. this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);
  178. };
  179. /**
  180. * Processes an incoming message.
  181. *
  182. * @param {string} channelName The name of the channel.
  183. * @param {string} frame The raw frame content.
  184. * @private
  185. */
  186. goog.net.xpc.IframeRelayTransport.receiveMessage_ = function(
  187. channelName, frame) {
  188. var pos = frame.indexOf(':');
  189. var header = frame.substr(0, pos);
  190. var payload = frame.substr(pos + 1);
  191. if (!goog.userAgent.IE || (pos = header.indexOf('|')) == -1) {
  192. // First, the easy case.
  193. var service = header;
  194. } else {
  195. // There was a fragment id in the header, so this is a message
  196. // fragment, not a whole message.
  197. var service = header.substr(0, pos);
  198. var fragmentIdStr = header.substr(pos + 1);
  199. // Separate the message id string and the fragment number. Note that
  200. // there may be a single leading + in the argument to parseInt, but
  201. // this is harmless.
  202. pos = fragmentIdStr.indexOf('+');
  203. var messageIdStr = fragmentIdStr.substr(0, pos);
  204. var fragmentNum = parseInt(fragmentIdStr.substr(pos + 1), 10);
  205. var fragmentInfo =
  206. goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];
  207. if (!fragmentInfo) {
  208. fragmentInfo =
  209. goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr] =
  210. {fragments: [], received: 0, expected: 0};
  211. }
  212. if (goog.string.contains(fragmentIdStr, '++')) {
  213. fragmentInfo.expected = fragmentNum + 1;
  214. }
  215. fragmentInfo.fragments[fragmentNum] = payload;
  216. fragmentInfo.received++;
  217. if (fragmentInfo.received != fragmentInfo.expected) {
  218. return;
  219. }
  220. // We've received all outstanding fragments; combine what we've received
  221. // into payload and fall out to the call to xpcDeliver.
  222. payload = fragmentInfo.fragments.join('');
  223. delete goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];
  224. }
  225. goog.net.xpc.channels[channelName].xpcDeliver(
  226. service, decodeURIComponent(payload));
  227. };
  228. /**
  229. * Handles transport service messages (internal signalling).
  230. * @param {string} payload The message content.
  231. * @override
  232. */
  233. goog.net.xpc.IframeRelayTransport.prototype.transportServiceHandler = function(
  234. payload) {
  235. if (payload == goog.net.xpc.SETUP) {
  236. // TODO(user) Safari swallows the SETUP_ACK from the iframe to the
  237. // container after hitting reload.
  238. this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
  239. this.channel_.notifyConnected();
  240. } else if (payload == goog.net.xpc.SETUP_ACK_) {
  241. this.channel_.notifyConnected();
  242. }
  243. };
  244. /**
  245. * Sends a message.
  246. *
  247. * @param {string} service Name of service this the message has to be delivered.
  248. * @param {string} payload The message content.
  249. * @override
  250. */
  251. goog.net.xpc.IframeRelayTransport.prototype.send = function(service, payload) {
  252. // If we're on IE and the post-encoding payload is large, split it
  253. // into multiple payloads and send each one separately. Otherwise,
  254. // just send the whole thing.
  255. var encodedPayload = encodeURIComponent(payload);
  256. var encodedLen = encodedPayload.length;
  257. var maxSize = goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_;
  258. if (goog.userAgent.IE && encodedLen > maxSize) {
  259. // A probabilistically-unique string used to link together all fragments
  260. // in this message.
  261. var messageIdStr = goog.string.getRandomString();
  262. for (var startIndex = 0, fragmentNum = 0; startIndex < encodedLen;
  263. fragmentNum++) {
  264. var payloadFragment = encodedPayload.substr(startIndex, maxSize);
  265. startIndex += maxSize;
  266. var fragmentIdStr =
  267. messageIdStr + (startIndex >= encodedLen ? '++' : '+') + fragmentNum;
  268. this.send_(service, payloadFragment, fragmentIdStr);
  269. }
  270. } else {
  271. this.send_(service, encodedPayload);
  272. }
  273. };
  274. /**
  275. * Sends an encoded message or message fragment.
  276. * @param {string} service Name of service this the message has to be delivered.
  277. * @param {string} encodedPayload The message content, URI encoded.
  278. * @param {string=} opt_fragmentIdStr If sending a fragment, a string that
  279. * identifies the fragment.
  280. * @private
  281. */
  282. goog.net.xpc.IframeRelayTransport.prototype.send_ = function(
  283. service, encodedPayload, opt_fragmentIdStr) {
  284. // IE requires that we create the onload attribute inline, otherwise the
  285. // handler is not triggered
  286. if (goog.userAgent.IE) {
  287. var div =
  288. this.getWindow().document.createElement(String(goog.dom.TagName.DIV));
  289. // TODO(mlourenco): It might be possible to set the sandbox attribute
  290. // to restrict the privileges of the created iframe.
  291. goog.dom.safe.setInnerHtml(
  292. div, goog.html.SafeHtml.createIframe(null, null, {
  293. 'onload': goog.string.Const.from('this.xpcOnload()'),
  294. 'sandbox': null
  295. }));
  296. var ifr = div.childNodes[0];
  297. div = null;
  298. ifr['xpcOnload'] = goog.net.xpc.IframeRelayTransport.iframeLoadHandler_;
  299. } else {
  300. var ifr = this.getWindow().document.createElement(
  301. String(goog.dom.TagName.IFRAME));
  302. if (goog.userAgent.WEBKIT) {
  303. // safari doesn't fire load-events on iframes.
  304. // keep a reference and remove after a timeout.
  305. goog.net.xpc.IframeRelayTransport.iframeRefs_.push(
  306. {timestamp: goog.now(), iframeElement: ifr});
  307. } else {
  308. goog.events.listen(
  309. ifr, 'load', goog.net.xpc.IframeRelayTransport.iframeLoadHandler_);
  310. }
  311. }
  312. var style = ifr.style;
  313. style.visibility = 'hidden';
  314. style.width = ifr.style.height = '0px';
  315. style.position = 'absolute';
  316. var url = this.peerRelayUri_;
  317. url += '#' + this.channel_.name;
  318. if (this.peerIframeId_) {
  319. url += ',' + this.peerIframeId_;
  320. }
  321. url += '|' + service;
  322. if (opt_fragmentIdStr) {
  323. url += '|' + opt_fragmentIdStr;
  324. }
  325. url += ':' + encodedPayload;
  326. ifr.src = url;
  327. this.getWindow().document.body.appendChild(ifr);
  328. goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'msg sent: ' + url);
  329. };
  330. /**
  331. * The iframe load handler. Gets called as method on the iframe element.
  332. * @private
  333. * @this {Element}
  334. */
  335. goog.net.xpc.IframeRelayTransport.iframeLoadHandler_ = function() {
  336. goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'iframe-load');
  337. goog.dom.removeNode(this);
  338. this.xpcOnload = null;
  339. };
  340. /** @override */
  341. goog.net.xpc.IframeRelayTransport.prototype.disposeInternal = function() {
  342. goog.net.xpc.IframeRelayTransport.base(this, 'disposeInternal');
  343. if (goog.userAgent.WEBKIT) {
  344. goog.net.xpc.IframeRelayTransport.cleanup_(0);
  345. }
  346. };