// 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.dom.TagName');
goog.require('goog.dom.safe');
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 {!Function|undefined} */
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 */
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<string|number, !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(goog.dom.TagName.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);
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.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;
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. Must
* be an http:// or https:// URL.
* @param {Object} windowObj The frame used for sending information to.
* @final
*/
goog.net.xpc.IframePollingTransport.Sender = function(url, windowObj) {
// This class is instantiated from goog.net.xpc.IframePollingTransport, which
// takes its URLs from a goog.net.xpc.CrossPageChannel, which in turns
// sanitizes them. However, since this class can be instantiated from
// elsewhere than IframePollingTransport the url needs to be sanitized
// here too.
if (!/^https?:\/\//.test(url)) {
throw new Error('URL ' + url + ' is invalid');
}
/**
* The URI used to sending messages.
* @type {string}
* @private
*/
this.sanitizedSendUri_ = 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 {Window}
* @private
*/
this.sendFrame_ = /** @type {Window} */ (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.sanitizedSendUri_ + '#' + this.cycle_ + encodeURIComponent(payload);
// TODO(user) Find out if try/catch is still needed
try {
// safari doesn't allow to call location.replace()
if (goog.userAgent.WEBKIT) {
goog.dom.safe.setLocationHref(this.sendFrame_.location, 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;
}
};