| // Copyright 2007 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 Contains the iframe polling transport. |
| */ |
| |
| |
| goog.provide('goog.net.xpc.IframePollingTransport'); |
| goog.provide('goog.net.xpc.IframePollingTransport.Receiver'); |
| goog.provide('goog.net.xpc.IframePollingTransport.Sender'); |
| |
| goog.require('goog.array'); |
| goog.require('goog.dom'); |
| goog.require('goog.log'); |
| goog.require('goog.log.Level'); |
| goog.require('goog.net.xpc'); |
| goog.require('goog.net.xpc.CfgFields'); |
| goog.require('goog.net.xpc.CrossPageChannelRole'); |
| goog.require('goog.net.xpc.Transport'); |
| goog.require('goog.net.xpc.TransportTypes'); |
| goog.require('goog.userAgent'); |
| |
| |
| |
| /** |
| * Iframe polling transport. Uses hidden iframes to transfer data |
| * in the fragment identifier of the URL. The peer polls the iframe's location |
| * for changes. |
| * Unfortunately, in Safari this screws up the history, because Safari doesn't |
| * allow to call location.replace() on a window containing a document from a |
| * different domain (last version tested: 2.0.4). |
| * |
| * @param {goog.net.xpc.CrossPageChannel} channel The channel this |
| * transport belongs to. |
| * @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding |
| * the correct window. |
| * @constructor |
| * @extends {goog.net.xpc.Transport} |
| * @final |
| */ |
| goog.net.xpc.IframePollingTransport = function(channel, opt_domHelper) { |
| goog.net.xpc.IframePollingTransport.base(this, 'constructor', opt_domHelper); |
| |
| /** |
| * The channel this transport belongs to. |
| * @type {goog.net.xpc.CrossPageChannel} |
| * @private |
| */ |
| this.channel_ = channel; |
| |
| /** |
| * The URI used to send messages. |
| * @type {string} |
| * @private |
| */ |
| this.sendUri_ = |
| this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_POLL_URI]; |
| |
| /** |
| * The URI which is polled for incoming messages. |
| * @type {string} |
| * @private |
| */ |
| this.rcvUri_ = |
| this.channel_.getConfig()[goog.net.xpc.CfgFields.LOCAL_POLL_URI]; |
| |
| /** |
| * The queue to hold messages which can't be sent immediately. |
| * @type {Array<string>} |
| * @private |
| */ |
| this.sendQueue_ = []; |
| }; |
| goog.inherits(goog.net.xpc.IframePollingTransport, goog.net.xpc.Transport); |
| |
| |
| /** |
| * The number of times the inner frame will check for evidence of the outer |
| * frame before it tries its reconnection sequence. These occur at 100ms |
| * intervals, making this an effective max waiting period of 500ms. |
| * @type {number} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.pollsBeforeReconnect_ = 5; |
| |
| |
| /** |
| * The transport type. |
| * @type {number} |
| * @protected |
| * @override |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.transportType = |
| goog.net.xpc.TransportTypes.IFRAME_POLLING; |
| |
| |
| /** |
| * Sequence counter. |
| * @type {number} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.sequence_ = 0; |
| |
| |
| /** |
| * Flag indicating whether we are waiting for an acknoledgement. |
| * @type {boolean} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.waitForAck_ = false; |
| |
| |
| /** |
| * Flag indicating if channel has been initialized. |
| * @type {boolean} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.initialized_ = false; |
| |
| |
| /** |
| * Reconnection iframe created by inner peer. |
| * @type {Element} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.reconnectFrame_ = null; |
| |
| |
| /** @private {goog.net.xpc.IframePollingTransport.Receiver} */ |
| goog.net.xpc.IframePollingTransport.prototype.ackReceiver_; |
| |
| |
| /** @private {goog.net.xpc.IframePollingTransport.Sender} */ |
| goog.net.xpc.IframePollingTransport.prototype.ackSender_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.ackIframeElm_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.ackWinObj_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.checkLocalFramesPresentCb_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.deliveryQueue_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.msgIframeElm_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.msgReceiver_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.msgSender_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.msgWinObj_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.rcvdConnectionSetupAck_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.sentConnectionSetupAck_; |
| |
| |
| /** @private {boolean} */ |
| goog.net.xpc.IframePollingTransport.prototype.sentConnectionSetup_; |
| |
| |
| /** @private */ |
| goog.net.xpc.IframePollingTransport.prototype.parts_; |
| |
| |
| /** |
| * The string used to prefix all iframe names and IDs. |
| * @type {string} |
| */ |
| goog.net.xpc.IframePollingTransport.IFRAME_PREFIX = 'googlexpc'; |
| |
| |
| /** |
| * Returns the name/ID of the message frame. |
| * @return {string} Name of message frame. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.getMsgFrameName_ = function() { |
| return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' + |
| this.channel_.name + '_msg'; |
| }; |
| |
| |
| /** |
| * Returns the name/ID of the ack frame. |
| * @return {string} Name of ack frame. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.getAckFrameName_ = function() { |
| return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' + |
| this.channel_.name + '_ack'; |
| }; |
| |
| |
| /** |
| * Determines whether the channel is still available. The channel is |
| * unavailable if the transport was disposed or the peer is no longer |
| * available. |
| * @return {boolean} Whether the channel is available. |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.isChannelAvailable = function() { |
| return !this.isDisposed() && this.channel_.isPeerAvailable(); |
| }; |
| |
| |
| /** |
| * Safely retrieves the frames from the peer window. If an error is thrown |
| * (e.g. the window is closing) an empty frame object is returned. |
| * @return {!Object<!Window>} The frames from the peer window. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.getPeerFrames_ = function() { |
| try { |
| if (this.isChannelAvailable()) { |
| return this.channel_.getPeerWindowObject().frames || {}; |
| } |
| } catch (e) { |
| // An error may be thrown if the window is closing. |
| goog.log.fine(goog.net.xpc.logger, 'error retrieving peer frames'); |
| } |
| return {}; |
| }; |
| |
| |
| /** |
| * Safely retrieves the peer frame with the specified name. |
| * @param {string} frameName The name of the peer frame to retrieve. |
| * @return {!Window} The peer frame with the specified name. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.getPeerFrame_ = function( |
| frameName) { |
| return this.getPeerFrames_()[frameName]; |
| }; |
| |
| |
| /** |
| * Connects this transport. |
| * @override |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.connect = function() { |
| if (!this.isChannelAvailable()) { |
| // When the channel is unavailable there is no peer to poll so stop trying |
| // to connect. |
| return; |
| } |
| |
| goog.log.fine(goog.net.xpc.logger, 'transport connect called'); |
| if (!this.initialized_) { |
| goog.log.fine(goog.net.xpc.logger, 'initializing...'); |
| this.constructSenderFrames_(); |
| this.initialized_ = true; |
| } |
| this.checkForeignFramesReady_(); |
| }; |
| |
| |
| /** |
| * Creates the iframes which are used to send messages (and acknowledgements) |
| * to the peer. Sender iframes contain a document from a different origin and |
| * therefore their content can't be accessed. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.constructSenderFrames_ = |
| function() { |
| var name = this.getMsgFrameName_(); |
| this.msgIframeElm_ = this.constructSenderFrame_(name); |
| this.msgWinObj_ = this.getWindow().frames[name]; |
| |
| name = this.getAckFrameName_(); |
| this.ackIframeElm_ = this.constructSenderFrame_(name); |
| this.ackWinObj_ = this.getWindow().frames[name]; |
| }; |
| |
| |
| /** |
| * Constructs a sending frame the the given id. |
| * @param {string} id The id. |
| * @return {!Element} The constructed frame. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.constructSenderFrame_ = |
| function(id) { |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'constructing sender frame: ' + id); |
| var ifr = goog.dom.createElement('iframe'); |
| var s = ifr.style; |
| s.position = 'absolute'; |
| s.top = '-10px'; s.left = '10px'; s.width = '1px'; s.height = '1px'; |
| ifr.id = ifr.name = id; |
| ifr.src = this.sendUri_ + '#INITIAL'; |
| this.getWindow().document.body.appendChild(ifr); |
| return ifr; |
| }; |
| |
| |
| /** |
| * The protocol for reconnecting is for the inner frame to change channel |
| * names, and then communicate the new channel name to the outer peer. |
| * The outer peer looks in a predefined location for the channel name |
| * upate. It is important to use a completely new channel name, as this |
| * will ensure that all messaging iframes are not in the bfcache. |
| * Otherwise, Safari may pollute the history when modifying the location |
| * of bfcached iframes. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.maybeInnerPeerReconnect_ = |
| function() { |
| // Reconnection has been found to not function on some browsers (eg IE7), so |
| // it's important that the mechanism only be triggered as a last resort. As |
| // such, we poll a number of times to find the outer iframe before triggering |
| // it. |
| if (this.reconnectFrame_ || this.pollsBeforeReconnect_-- > 0) { |
| return; |
| } |
| |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'Inner peer reconnect triggered.'); |
| this.channel_.updateChannelNameAndCatalog(goog.net.xpc.getRandomString(10)); |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'switching channels: ' + this.channel_.name); |
| this.deconstructSenderFrames_(); |
| this.initialized_ = false; |
| // Communicate new channel name to outer peer. |
| this.reconnectFrame_ = this.constructSenderFrame_( |
| goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + |
| '_reconnect_' + this.channel_.name); |
| }; |
| |
| |
| /** |
| * Scans inner peer for a reconnect message, which will be used to update |
| * the outer peer's channel name. If a reconnect message is found, the |
| * sender frames will be cleaned up to make way for the new sender frames. |
| * Only called by the outer peer. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.outerPeerReconnect_ = function() { |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'outerPeerReconnect called'); |
| var frames = this.getPeerFrames_(); |
| var length = frames.length; |
| for (var i = 0; i < length; i++) { |
| var frameName; |
| try { |
| if (frames[i] && frames[i].name) { |
| frameName = frames[i].name; |
| } |
| } catch (e) { |
| // Do nothing. |
| } |
| if (!frameName) { |
| continue; |
| } |
| var message = frameName.split('_'); |
| if (message.length == 3 && |
| message[0] == goog.net.xpc.IframePollingTransport.IFRAME_PREFIX && |
| message[1] == 'reconnect') { |
| // This is a legitimate reconnect message from the peer. Start using |
| // the peer provided channel name, and start a connection over from |
| // scratch. |
| this.channel_.name = message[2]; |
| this.deconstructSenderFrames_(); |
| this.initialized_ = false; |
| break; |
| } |
| } |
| }; |
| |
| |
| /** |
| * Cleans up the existing sender frames owned by this peer. Only called by |
| * the outer peer. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.deconstructSenderFrames_ = |
| function() { |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'deconstructSenderFrames called'); |
| if (this.msgIframeElm_) { |
| this.msgIframeElm_.parentNode.removeChild(this.msgIframeElm_); |
| this.msgIframeElm_ = null; |
| this.msgWinObj_ = null; |
| } |
| if (this.ackIframeElm_) { |
| this.ackIframeElm_.parentNode.removeChild(this.ackIframeElm_); |
| this.ackIframeElm_ = null; |
| this.ackWinObj_ = null; |
| } |
| }; |
| |
| |
| /** |
| * Checks if the frames in the peer's page are ready. These contain a |
| * document from the own domain and are the ones messages are received through. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.checkForeignFramesReady_ = |
| function() { |
| // check if the connected iframe ready |
| if (!(this.isRcvFrameReady_(this.getMsgFrameName_()) && |
| this.isRcvFrameReady_(this.getAckFrameName_()))) { |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'foreign frames not (yet) present'); |
| |
| if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) { |
| // The outer peer might need a short time to get its frames ready, as |
| // CrossPageChannel prevents them from getting created until the inner |
| // peer's frame has thrown its loaded event. This method is a noop for |
| // the first few times it's called, and then allows the reconnection |
| // sequence to begin. |
| this.maybeInnerPeerReconnect_(); |
| } else if (this.channel_.getRole() == |
| goog.net.xpc.CrossPageChannelRole.OUTER) { |
| // The inner peer is either not loaded yet, or the receiving |
| // frames are simply missing. Since we cannot discern the two cases, we |
| // should scan for a reconnect message from the inner peer. |
| this.outerPeerReconnect_(); |
| } |
| |
| // start a timer to check again |
| this.getWindow().setTimeout(goog.bind(this.connect, this), 100); |
| } else { |
| goog.log.fine(goog.net.xpc.logger, 'foreign frames present'); |
| |
| // Create receivers. |
| this.msgReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver( |
| this, |
| this.getPeerFrame_(this.getMsgFrameName_()), |
| goog.bind(this.processIncomingMsg, this)); |
| this.ackReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver( |
| this, |
| this.getPeerFrame_(this.getAckFrameName_()), |
| goog.bind(this.processIncomingAck, this)); |
| |
| this.checkLocalFramesPresent_(); |
| } |
| }; |
| |
| |
| /** |
| * Checks if the receiving frame is ready. |
| * @param {string} frameName Which receiving frame to check. |
| * @return {boolean} Whether the receiving frame is ready. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.isRcvFrameReady_ = |
| function(frameName) { |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'checking for receive frame: ' + frameName); |
| /** @preserveTry */ |
| try { |
| var winObj = this.getPeerFrame_(frameName); |
| if (!winObj || winObj.location.href.indexOf(this.rcvUri_) != 0) { |
| return false; |
| } |
| } catch (e) { |
| return false; |
| } |
| return true; |
| }; |
| |
| |
| /** |
| * Checks if the iframes created in the own document are ready. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.checkLocalFramesPresent_ = |
| function() { |
| |
| // Are the sender frames ready? |
| // These contain a document from the peer's domain, therefore we can only |
| // check if the frame itself is present. |
| var frames = this.getPeerFrames_(); |
| if (!(frames[this.getAckFrameName_()] && |
| frames[this.getMsgFrameName_()])) { |
| // start a timer to check again |
| if (!this.checkLocalFramesPresentCb_) { |
| this.checkLocalFramesPresentCb_ = goog.bind( |
| this.checkLocalFramesPresent_, this); |
| } |
| this.getWindow().setTimeout(this.checkLocalFramesPresentCb_, 100); |
| goog.log.fine(goog.net.xpc.logger, 'local frames not (yet) present'); |
| } else { |
| // Create senders. |
| this.msgSender_ = new goog.net.xpc.IframePollingTransport.Sender( |
| this.sendUri_, this.msgWinObj_); |
| this.ackSender_ = new goog.net.xpc.IframePollingTransport.Sender( |
| this.sendUri_, this.ackWinObj_); |
| |
| goog.log.fine(goog.net.xpc.logger, 'local frames ready'); |
| |
| this.getWindow().setTimeout(goog.bind(function() { |
| this.msgSender_.send(goog.net.xpc.SETUP); |
| this.sentConnectionSetup_ = true; |
| this.waitForAck_ = true; |
| goog.log.fine(goog.net.xpc.logger, 'SETUP sent'); |
| }, this), 100); |
| } |
| }; |
| |
| |
| /** |
| * Check if connection is ready. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.checkIfConnected_ = function() { |
| if (this.sentConnectionSetupAck_ && this.rcvdConnectionSetupAck_) { |
| this.channel_.notifyConnected(); |
| |
| if (this.deliveryQueue_) { |
| goog.log.fine(goog.net.xpc.logger, 'delivering queued messages ' + |
| '(' + this.deliveryQueue_.length + ')'); |
| |
| for (var i = 0, m; i < this.deliveryQueue_.length; i++) { |
| m = this.deliveryQueue_[i]; |
| this.channel_.xpcDeliver(m.service, m.payload); |
| } |
| delete this.deliveryQueue_; |
| } |
| } else { |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'checking if connected: ' + |
| 'ack sent:' + this.sentConnectionSetupAck_ + |
| ', ack rcvd: ' + this.rcvdConnectionSetupAck_); |
| } |
| }; |
| |
| |
| /** |
| * Processes an incoming message. |
| * @param {string} raw The complete received string. |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.processIncomingMsg = |
| function(raw) { |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'msg received: ' + raw); |
| |
| if (raw == goog.net.xpc.SETUP) { |
| if (!this.ackSender_) { |
| // Got SETUP msg, but we can't send an ack. |
| return; |
| } |
| |
| this.ackSender_.send(goog.net.xpc.SETUP_ACK_); |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'SETUP_ACK sent'); |
| |
| this.sentConnectionSetupAck_ = true; |
| this.checkIfConnected_(); |
| |
| } else if (this.channel_.isConnected() || this.sentConnectionSetupAck_) { |
| |
| var pos = raw.indexOf('|'); |
| var head = raw.substring(0, pos); |
| var frame = raw.substring(pos + 1); |
| |
| // check if it is a framed message |
| pos = head.indexOf(','); |
| if (pos == -1) { |
| var seq = head; |
| // send acknowledgement |
| this.ackSender_.send('ACK:' + seq); |
| this.deliverPayload_(frame); |
| } else { |
| var seq = head.substring(0, pos); |
| // send acknowledgement |
| this.ackSender_.send('ACK:' + seq); |
| |
| var partInfo = head.substring(pos + 1).split('/'); |
| var part0 = parseInt(partInfo[0], 10); |
| var part1 = parseInt(partInfo[1], 10); |
| // create an array to accumulate the parts if this is the |
| // first frame of a message |
| if (part0 == 1) { |
| this.parts_ = []; |
| } |
| this.parts_.push(frame); |
| // deliver the message if this was the last frame of a message |
| if (part0 == part1) { |
| this.deliverPayload_(this.parts_.join('')); |
| delete this.parts_; |
| } |
| } |
| } else { |
| goog.log.warning(goog.net.xpc.logger, |
| 'received msg, but channel is not connected'); |
| } |
| }; |
| |
| |
| /** |
| * Process an incoming acknowdedgement. |
| * @param {string} msgStr The incoming ack string to process. |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.processIncomingAck = |
| function(msgStr) { |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'ack received: ' + msgStr); |
| |
| if (msgStr == goog.net.xpc.SETUP_ACK_) { |
| this.waitForAck_ = false; |
| this.rcvdConnectionSetupAck_ = true; |
| // send the next frame |
| this.checkIfConnected_(); |
| |
| } else if (this.channel_.isConnected()) { |
| if (!this.waitForAck_) { |
| goog.log.warning(goog.net.xpc.logger, 'got unexpected ack'); |
| return; |
| } |
| |
| var seq = parseInt(msgStr.split(':')[1], 10); |
| if (seq == this.sequence_) { |
| this.waitForAck_ = false; |
| this.sendNextFrame_(); |
| } else { |
| goog.log.warning(goog.net.xpc.logger, 'got ack with wrong sequence'); |
| } |
| } else { |
| goog.log.warning(goog.net.xpc.logger, |
| 'received ack, but channel not connected'); |
| } |
| }; |
| |
| |
| /** |
| * Sends a frame (message part). |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.sendNextFrame_ = function() { |
| // do nothing if we are waiting for an acknowledgement or the |
| // queue is emtpy |
| if (this.waitForAck_ || !this.sendQueue_.length) { |
| return; |
| } |
| |
| var s = this.sendQueue_.shift(); |
| ++this.sequence_; |
| this.msgSender_.send(this.sequence_ + s); |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'msg sent: ' + this.sequence_ + s); |
| |
| |
| this.waitForAck_ = true; |
| }; |
| |
| |
| /** |
| * Delivers a message. |
| * @param {string} s The complete message string ("<service_name>:<payload>"). |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.deliverPayload_ = function(s) { |
| // determine the service name and the payload |
| var pos = s.indexOf(':'); |
| var service = s.substr(0, pos); |
| var payload = s.substring(pos + 1); |
| |
| // deliver the message |
| if (!this.channel_.isConnected()) { |
| // as valid messages can come in before a SETUP_ACK has |
| // been received (because subchannels for msgs and acks are independent), |
| // delay delivery of early messages until after 'connect'-event |
| (this.deliveryQueue_ || (this.deliveryQueue_ = [])). |
| push({service: service, payload: payload}); |
| goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, |
| 'queued delivery'); |
| } else { |
| this.channel_.xpcDeliver(service, payload); |
| } |
| }; |
| |
| |
| // ---- send message ---- |
| |
| |
| /** |
| * Maximal frame length. |
| * @type {number} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.MAX_FRAME_LENGTH_ = 3800; |
| |
| |
| /** |
| * Sends a message. Splits it in multiple frames if too long (exceeds IE's |
| * URL-length maximum. |
| * Wireformat: <seq>[,<frame_no>/<#frames>]|<frame_content> |
| * |
| * @param {string} service Name of service this the message has to be delivered. |
| * @param {string} payload The message content. |
| * @override |
| */ |
| goog.net.xpc.IframePollingTransport.prototype.send = |
| function(service, payload) { |
| var frame = service + ':' + payload; |
| // put in queue |
| if (!goog.userAgent.IE || payload.length <= this.MAX_FRAME_LENGTH_) { |
| this.sendQueue_.push('|' + frame); |
| } |
| else { |
| var l = payload.length; |
| var num = Math.ceil(l / this.MAX_FRAME_LENGTH_); // number of frames |
| var pos = 0; |
| var i = 1; |
| while (pos < l) { |
| this.sendQueue_.push(',' + i + '/' + num + '|' + |
| frame.substr(pos, this.MAX_FRAME_LENGTH_)); |
| i++; |
| pos += this.MAX_FRAME_LENGTH_; |
| } |
| } |
| this.sendNextFrame_(); |
| }; |
| |
| |
| /** @override */ |
| goog.net.xpc.IframePollingTransport.prototype.disposeInternal = function() { |
| goog.net.xpc.IframePollingTransport.base(this, 'disposeInternal'); |
| |
| var receivers = goog.net.xpc.IframePollingTransport.receivers_; |
| goog.array.remove(receivers, this.msgReceiver_); |
| goog.array.remove(receivers, this.ackReceiver_); |
| this.msgReceiver_ = this.ackReceiver_ = null; |
| |
| goog.dom.removeNode(this.msgIframeElm_); |
| goog.dom.removeNode(this.ackIframeElm_); |
| this.msgIframeElm_ = this.ackIframeElm_ = null; |
| this.msgWinObj_ = this.ackWinObj_ = null; |
| }; |
| |
| |
| /** |
| * Array holding all Receiver-instances. |
| * @type {Array<goog.net.xpc.IframePollingTransport.Receiver>} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.receivers_ = []; |
| |
| |
| /** |
| * Short polling interval. |
| * @type {number} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ = 10; |
| |
| |
| /** |
| * Long polling interval. |
| * @type {number} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_ = 100; |
| |
| |
| /** |
| * Period how long to use TIME_POLL_SHORT_ before raising polling-interval |
| * to TIME_POLL_LONG_ after an activity. |
| * @type {number} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ = |
| 1000; |
| |
| |
| /** |
| * Polls all receivers. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.receive_ = function() { |
| var receivers = goog.net.xpc.IframePollingTransport.receivers_; |
| var receiver; |
| var rcvd = false; |
| |
| /** @preserveTry */ |
| try { |
| for (var i = 0; receiver = receivers[i]; i++) { |
| rcvd = rcvd || receiver.receive(); |
| } |
| } catch (e) { |
| goog.log.info(goog.net.xpc.logger, 'receive_() failed: ' + e); |
| |
| // Notify the channel that the transport had an error. |
| receiver.transport_.channel_.notifyTransportError(); |
| |
| // notifyTransportError() closes the channel and disposes the transport. |
| // If there are no other channels present, this.receivers_ will now be empty |
| // and there is no need to keep polling. |
| if (!receivers.length) { |
| return; |
| } |
| } |
| |
| var now = goog.now(); |
| if (rcvd) { |
| goog.net.xpc.IframePollingTransport.lastActivity_ = now; |
| } |
| |
| // Schedule next check. |
| var t = now - goog.net.xpc.IframePollingTransport.lastActivity_ < |
| goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ ? |
| goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ : |
| goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_; |
| goog.net.xpc.IframePollingTransport.rcvTimer_ = window.setTimeout( |
| goog.net.xpc.IframePollingTransport.receiveCb_, t); |
| }; |
| |
| |
| /** |
| * Callback that wraps receive_ to be used in timers. |
| * @type {Function} |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.receiveCb_ = goog.bind( |
| goog.net.xpc.IframePollingTransport.receive_, |
| goog.net.xpc.IframePollingTransport); |
| |
| |
| /** |
| * Starts the polling loop. |
| * @private |
| */ |
| goog.net.xpc.IframePollingTransport.startRcvTimer_ = function() { |
| goog.log.fine(goog.net.xpc.logger, 'starting receive-timer'); |
| goog.net.xpc.IframePollingTransport.lastActivity_ = goog.now(); |
| if (goog.net.xpc.IframePollingTransport.rcvTimer_) { |
| window.clearTimeout(goog.net.xpc.IframePollingTransport.rcvTimer_); |
| } |
| goog.net.xpc.IframePollingTransport.rcvTimer_ = window.setTimeout( |
| goog.net.xpc.IframePollingTransport.receiveCb_, |
| goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_); |
| }; |
| |
| |
| |
| /** |
| * goog.net.xpc.IframePollingTransport.Sender |
| * |
| * Utility class to send message-parts to a document from a different origin. |
| * |
| * @constructor |
| * @param {string} url The url the other document will use for polling. |
| * @param {Object} windowObj The frame used for sending information to. |
| * @final |
| */ |
| goog.net.xpc.IframePollingTransport.Sender = function(url, windowObj) { |
| /** |
| * The URI used to sending messages. |
| * @type {string} |
| * @private |
| */ |
| this.sendUri_ = url; |
| |
| /** |
| * The window object of the iframe used to send messages. |
| * The script instantiating the Sender won't have access to |
| * the content of sendFrame_. |
| * @type {Object} |
| * @private |
| */ |
| this.sendFrame_ = windowObj; |
| |
| /** |
| * Cycle counter (used to make sure that sending two identical messages sent |
| * in direct succession can be recognized as such by the receiver). |
| * @type {number} |
| * @private |
| */ |
| this.cycle_ = 0; |
| }; |
| |
| |
| /** |
| * Sends a message-part (frame) to the peer. |
| * The message-part is encoded and put in the fragment identifier |
| * of the URL used for sending (and belongs to the origin/domain of the peer). |
| * @param {string} payload The message to send. |
| */ |
| goog.net.xpc.IframePollingTransport.Sender.prototype.send = function(payload) { |
| this.cycle_ = ++this.cycle_ % 2; |
| |
| var url = this.sendUri_ + '#' + this.cycle_ + encodeURIComponent(payload); |
| |
| // TODO(user) Find out if try/catch is still needed |
| /** @preserveTry */ |
| try { |
| // safari doesn't allow to call location.replace() |
| if (goog.userAgent.WEBKIT) { |
| this.sendFrame_.location.href = url; |
| } else { |
| this.sendFrame_.location.replace(url); |
| } |
| } catch (e) { |
| goog.log.error(goog.net.xpc.logger, 'sending failed', e); |
| } |
| |
| // Restart receiver timer on short polling interval, to support use-cases |
| // where we need to capture responses quickly. |
| goog.net.xpc.IframePollingTransport.startRcvTimer_(); |
| }; |
| |
| |
| |
| /** |
| * goog.net.xpc.IframePollingTransport.Receiver |
| * |
| * @constructor |
| * @param {goog.net.xpc.IframePollingTransport} transport The transport to |
| * receive from. |
| * @param {Object} windowObj The window-object to poll for location-changes. |
| * @param {Function} callback The callback-function to be called when |
| * location has changed. |
| * @final |
| */ |
| goog.net.xpc.IframePollingTransport.Receiver = function(transport, |
| windowObj, |
| callback) { |
| /** |
| * The transport to receive from. |
| * @type {goog.net.xpc.IframePollingTransport} |
| * @private |
| */ |
| this.transport_ = transport; |
| this.rcvFrame_ = windowObj; |
| |
| this.cb_ = callback; |
| this.currentLoc_ = this.rcvFrame_.location.href.split('#')[0] + '#INITIAL'; |
| |
| goog.net.xpc.IframePollingTransport.receivers_.push(this); |
| goog.net.xpc.IframePollingTransport.startRcvTimer_(); |
| }; |
| |
| |
| /** |
| * Polls the location of the receiver-frame for changes. |
| * @return {boolean} Whether a change has been detected. |
| */ |
| goog.net.xpc.IframePollingTransport.Receiver.prototype.receive = function() { |
| var loc = this.rcvFrame_.location.href; |
| |
| if (loc != this.currentLoc_) { |
| this.currentLoc_ = loc; |
| var payload = loc.split('#')[1]; |
| if (payload) { |
| payload = payload.substr(1); // discard first character (cycle) |
| this.cb_(decodeURIComponent(payload)); |
| } |
| return true; |
| } else { |
| return false; |
| } |
| }; |