Path: blob/trunk/third_party/closure/goog/net/xpc/iframepollingtransport.js
2884 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Contains the iframe polling transport.16*/171819goog.provide('goog.net.xpc.IframePollingTransport');20goog.provide('goog.net.xpc.IframePollingTransport.Receiver');21goog.provide('goog.net.xpc.IframePollingTransport.Sender');2223goog.require('goog.array');24goog.require('goog.dom');25goog.require('goog.dom.TagName');26goog.require('goog.log');27goog.require('goog.log.Level');28goog.require('goog.net.xpc');29goog.require('goog.net.xpc.CfgFields');30goog.require('goog.net.xpc.CrossPageChannelRole');31goog.require('goog.net.xpc.Transport');32goog.require('goog.net.xpc.TransportTypes');33goog.require('goog.userAgent');34353637/**38* Iframe polling transport. Uses hidden iframes to transfer data39* in the fragment identifier of the URL. The peer polls the iframe's location40* for changes.41* Unfortunately, in Safari this screws up the history, because Safari doesn't42* allow to call location.replace() on a window containing a document from a43* different domain (last version tested: 2.0.4).44*45* @param {goog.net.xpc.CrossPageChannel} channel The channel this46* transport belongs to.47* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding48* the correct window.49* @constructor50* @extends {goog.net.xpc.Transport}51* @final52*/53goog.net.xpc.IframePollingTransport = function(channel, opt_domHelper) {54goog.net.xpc.IframePollingTransport.base(this, 'constructor', opt_domHelper);5556/**57* The channel this transport belongs to.58* @type {goog.net.xpc.CrossPageChannel}59* @private60*/61this.channel_ = channel;6263/**64* The URI used to send messages.65* @type {string}66* @private67*/68this.sendUri_ =69this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_POLL_URI];7071/**72* The URI which is polled for incoming messages.73* @type {string}74* @private75*/76this.rcvUri_ =77this.channel_.getConfig()[goog.net.xpc.CfgFields.LOCAL_POLL_URI];7879/**80* The queue to hold messages which can't be sent immediately.81* @type {Array<string>}82* @private83*/84this.sendQueue_ = [];85};86goog.inherits(goog.net.xpc.IframePollingTransport, goog.net.xpc.Transport);878889/**90* The number of times the inner frame will check for evidence of the outer91* frame before it tries its reconnection sequence. These occur at 100ms92* intervals, making this an effective max waiting period of 500ms.93* @type {number}94* @private95*/96goog.net.xpc.IframePollingTransport.prototype.pollsBeforeReconnect_ = 5;979899/**100* The transport type.101* @type {number}102* @protected103* @override104*/105goog.net.xpc.IframePollingTransport.prototype.transportType =106goog.net.xpc.TransportTypes.IFRAME_POLLING;107108109/**110* Sequence counter.111* @type {number}112* @private113*/114goog.net.xpc.IframePollingTransport.prototype.sequence_ = 0;115116117/**118* Flag indicating whether we are waiting for an acknoledgement.119* @type {boolean}120* @private121*/122goog.net.xpc.IframePollingTransport.prototype.waitForAck_ = false;123124125/**126* Flag indicating if channel has been initialized.127* @type {boolean}128* @private129*/130goog.net.xpc.IframePollingTransport.prototype.initialized_ = false;131132133/**134* Reconnection iframe created by inner peer.135* @type {Element}136* @private137*/138goog.net.xpc.IframePollingTransport.prototype.reconnectFrame_ = null;139140141/** @private {goog.net.xpc.IframePollingTransport.Receiver} */142goog.net.xpc.IframePollingTransport.prototype.ackReceiver_;143144145/** @private {goog.net.xpc.IframePollingTransport.Sender} */146goog.net.xpc.IframePollingTransport.prototype.ackSender_;147148149/** @private */150goog.net.xpc.IframePollingTransport.prototype.ackIframeElm_;151152153/** @private */154goog.net.xpc.IframePollingTransport.prototype.ackWinObj_;155156157/** @private {!Function|undefined} */158goog.net.xpc.IframePollingTransport.prototype.checkLocalFramesPresentCb_;159160161/** @private */162goog.net.xpc.IframePollingTransport.prototype.deliveryQueue_;163164165/** @private */166goog.net.xpc.IframePollingTransport.prototype.msgIframeElm_;167168169/** @private */170goog.net.xpc.IframePollingTransport.prototype.msgReceiver_;171172173/** @private */174goog.net.xpc.IframePollingTransport.prototype.msgSender_;175176177/** @private */178goog.net.xpc.IframePollingTransport.prototype.msgWinObj_;179180181/** @private */182goog.net.xpc.IframePollingTransport.prototype.rcvdConnectionSetupAck_;183184185/** @private */186goog.net.xpc.IframePollingTransport.prototype.sentConnectionSetupAck_;187188189/** @private */190goog.net.xpc.IframePollingTransport.prototype.parts_;191192193/**194* The string used to prefix all iframe names and IDs.195* @type {string}196*/197goog.net.xpc.IframePollingTransport.IFRAME_PREFIX = 'googlexpc';198199200/**201* Returns the name/ID of the message frame.202* @return {string} Name of message frame.203* @private204*/205goog.net.xpc.IframePollingTransport.prototype.getMsgFrameName_ = function() {206return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' +207this.channel_.name + '_msg';208};209210211/**212* Returns the name/ID of the ack frame.213* @return {string} Name of ack frame.214* @private215*/216goog.net.xpc.IframePollingTransport.prototype.getAckFrameName_ = function() {217return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' +218this.channel_.name + '_ack';219};220221222/**223* Determines whether the channel is still available. The channel is224* unavailable if the transport was disposed or the peer is no longer225* available.226* @return {boolean} Whether the channel is available.227*/228goog.net.xpc.IframePollingTransport.prototype.isChannelAvailable = function() {229return !this.isDisposed() && this.channel_.isPeerAvailable();230};231232233/**234* Safely retrieves the frames from the peer window. If an error is thrown235* (e.g. the window is closing) an empty frame object is returned.236* @return {!Object<string|number, !Window>} The frames from the peer window.237* @private238*/239goog.net.xpc.IframePollingTransport.prototype.getPeerFrames_ = function() {240try {241if (this.isChannelAvailable()) {242return this.channel_.getPeerWindowObject().frames || {};243}244} catch (e) {245// An error may be thrown if the window is closing.246goog.log.fine(goog.net.xpc.logger, 'error retrieving peer frames');247}248return {};249};250251252/**253* Safely retrieves the peer frame with the specified name.254* @param {string} frameName The name of the peer frame to retrieve.255* @return {!Window} The peer frame with the specified name.256* @private257*/258goog.net.xpc.IframePollingTransport.prototype.getPeerFrame_ = function(259frameName) {260return this.getPeerFrames_()[frameName];261};262263264/**265* Connects this transport.266* @override267*/268goog.net.xpc.IframePollingTransport.prototype.connect = function() {269if (!this.isChannelAvailable()) {270// When the channel is unavailable there is no peer to poll so stop trying271// to connect.272return;273}274275goog.log.fine(goog.net.xpc.logger, 'transport connect called');276if (!this.initialized_) {277goog.log.fine(goog.net.xpc.logger, 'initializing...');278this.constructSenderFrames_();279this.initialized_ = true;280}281this.checkForeignFramesReady_();282};283284285/**286* Creates the iframes which are used to send messages (and acknowledgements)287* to the peer. Sender iframes contain a document from a different origin and288* therefore their content can't be accessed.289* @private290*/291goog.net.xpc.IframePollingTransport.prototype.constructSenderFrames_ =292function() {293var name = this.getMsgFrameName_();294this.msgIframeElm_ = this.constructSenderFrame_(name);295this.msgWinObj_ = this.getWindow().frames[name];296297name = this.getAckFrameName_();298this.ackIframeElm_ = this.constructSenderFrame_(name);299this.ackWinObj_ = this.getWindow().frames[name];300};301302303/**304* Constructs a sending frame the the given id.305* @param {string} id The id.306* @return {!Element} The constructed frame.307* @private308*/309goog.net.xpc.IframePollingTransport.prototype.constructSenderFrame_ = function(310id) {311goog.log.log(312goog.net.xpc.logger, goog.log.Level.FINEST,313'constructing sender frame: ' + id);314var ifr = goog.dom.createElement(goog.dom.TagName.IFRAME);315var s = ifr.style;316s.position = 'absolute';317s.top = '-10px';318s.left = '10px';319s.width = '1px';320s.height = '1px';321ifr.id = ifr.name = id;322ifr.src = this.sendUri_ + '#INITIAL';323this.getWindow().document.body.appendChild(ifr);324return ifr;325};326327328/**329* The protocol for reconnecting is for the inner frame to change channel330* names, and then communicate the new channel name to the outer peer.331* The outer peer looks in a predefined location for the channel name332* upate. It is important to use a completely new channel name, as this333* will ensure that all messaging iframes are not in the bfcache.334* Otherwise, Safari may pollute the history when modifying the location335* of bfcached iframes.336* @private337*/338goog.net.xpc.IframePollingTransport.prototype.maybeInnerPeerReconnect_ =339function() {340// Reconnection has been found to not function on some browsers (eg IE7), so341// it's important that the mechanism only be triggered as a last resort. As342// such, we poll a number of times to find the outer iframe before triggering343// it.344if (this.reconnectFrame_ || this.pollsBeforeReconnect_-- > 0) {345return;346}347348goog.log.log(349goog.net.xpc.logger, goog.log.Level.FINEST,350'Inner peer reconnect triggered.');351this.channel_.updateChannelNameAndCatalog(goog.net.xpc.getRandomString(10));352goog.log.log(353goog.net.xpc.logger, goog.log.Level.FINEST,354'switching channels: ' + this.channel_.name);355this.deconstructSenderFrames_();356this.initialized_ = false;357// Communicate new channel name to outer peer.358this.reconnectFrame_ = this.constructSenderFrame_(359goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_reconnect_' +360this.channel_.name);361};362363364/**365* Scans inner peer for a reconnect message, which will be used to update366* the outer peer's channel name. If a reconnect message is found, the367* sender frames will be cleaned up to make way for the new sender frames.368* Only called by the outer peer.369* @private370*/371goog.net.xpc.IframePollingTransport.prototype.outerPeerReconnect_ = function() {372goog.log.log(373goog.net.xpc.logger, goog.log.Level.FINEST, 'outerPeerReconnect called');374var frames = this.getPeerFrames_();375var length = frames.length;376for (var i = 0; i < length; i++) {377var frameName;378try {379if (frames[i] && frames[i].name) {380frameName = frames[i].name;381}382} catch (e) {383// Do nothing.384}385if (!frameName) {386continue;387}388var message = frameName.split('_');389if (message.length == 3 &&390message[0] == goog.net.xpc.IframePollingTransport.IFRAME_PREFIX &&391message[1] == 'reconnect') {392// This is a legitimate reconnect message from the peer. Start using393// the peer provided channel name, and start a connection over from394// scratch.395this.channel_.name = message[2];396this.deconstructSenderFrames_();397this.initialized_ = false;398break;399}400}401};402403404/**405* Cleans up the existing sender frames owned by this peer. Only called by406* the outer peer.407* @private408*/409goog.net.xpc.IframePollingTransport.prototype.deconstructSenderFrames_ =410function() {411goog.log.log(412goog.net.xpc.logger, goog.log.Level.FINEST,413'deconstructSenderFrames called');414if (this.msgIframeElm_) {415this.msgIframeElm_.parentNode.removeChild(this.msgIframeElm_);416this.msgIframeElm_ = null;417this.msgWinObj_ = null;418}419if (this.ackIframeElm_) {420this.ackIframeElm_.parentNode.removeChild(this.ackIframeElm_);421this.ackIframeElm_ = null;422this.ackWinObj_ = null;423}424};425426427/**428* Checks if the frames in the peer's page are ready. These contain a429* document from the own domain and are the ones messages are received through.430* @private431*/432goog.net.xpc.IframePollingTransport.prototype.checkForeignFramesReady_ =433function() {434// check if the connected iframe ready435if (!(this.isRcvFrameReady_(this.getMsgFrameName_()) &&436this.isRcvFrameReady_(this.getAckFrameName_()))) {437goog.log.log(438goog.net.xpc.logger, goog.log.Level.FINEST,439'foreign frames not (yet) present');440441if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) {442// The outer peer might need a short time to get its frames ready, as443// CrossPageChannel prevents them from getting created until the inner444// peer's frame has thrown its loaded event. This method is a noop for445// the first few times it's called, and then allows the reconnection446// sequence to begin.447this.maybeInnerPeerReconnect_();448} else if (449this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER) {450// The inner peer is either not loaded yet, or the receiving451// frames are simply missing. Since we cannot discern the two cases, we452// should scan for a reconnect message from the inner peer.453this.outerPeerReconnect_();454}455456// start a timer to check again457this.getWindow().setTimeout(goog.bind(this.connect, this), 100);458} else {459goog.log.fine(goog.net.xpc.logger, 'foreign frames present');460461// Create receivers.462this.msgReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver(463this, this.getPeerFrame_(this.getMsgFrameName_()),464goog.bind(this.processIncomingMsg, this));465this.ackReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver(466this, this.getPeerFrame_(this.getAckFrameName_()),467goog.bind(this.processIncomingAck, this));468469this.checkLocalFramesPresent_();470}471};472473474/**475* Checks if the receiving frame is ready.476* @param {string} frameName Which receiving frame to check.477* @return {boolean} Whether the receiving frame is ready.478* @private479*/480goog.net.xpc.IframePollingTransport.prototype.isRcvFrameReady_ = function(481frameName) {482goog.log.log(483goog.net.xpc.logger, goog.log.Level.FINEST,484'checking for receive frame: ' + frameName);485486try {487var winObj = this.getPeerFrame_(frameName);488if (!winObj || winObj.location.href.indexOf(this.rcvUri_) != 0) {489return false;490}491} catch (e) {492return false;493}494return true;495};496497498/**499* Checks if the iframes created in the own document are ready.500* @private501*/502goog.net.xpc.IframePollingTransport.prototype.checkLocalFramesPresent_ =503function() {504505// Are the sender frames ready?506// These contain a document from the peer's domain, therefore we can only507// check if the frame itself is present.508var frames = this.getPeerFrames_();509if (!(frames[this.getAckFrameName_()] && frames[this.getMsgFrameName_()])) {510// start a timer to check again511if (!this.checkLocalFramesPresentCb_) {512this.checkLocalFramesPresentCb_ =513goog.bind(this.checkLocalFramesPresent_, this);514}515this.getWindow().setTimeout(this.checkLocalFramesPresentCb_, 100);516goog.log.fine(goog.net.xpc.logger, 'local frames not (yet) present');517} else {518// Create senders.519this.msgSender_ = new goog.net.xpc.IframePollingTransport.Sender(520this.sendUri_, this.msgWinObj_);521this.ackSender_ = new goog.net.xpc.IframePollingTransport.Sender(522this.sendUri_, this.ackWinObj_);523524goog.log.fine(goog.net.xpc.logger, 'local frames ready');525526this.getWindow().setTimeout(goog.bind(function() {527this.msgSender_.send(goog.net.xpc.SETUP);528this.waitForAck_ = true;529goog.log.fine(goog.net.xpc.logger, 'SETUP sent');530}, this), 100);531}532};533534535/**536* Check if connection is ready.537* @private538*/539goog.net.xpc.IframePollingTransport.prototype.checkIfConnected_ = function() {540if (this.sentConnectionSetupAck_ && this.rcvdConnectionSetupAck_) {541this.channel_.notifyConnected();542543if (this.deliveryQueue_) {544goog.log.fine(545goog.net.xpc.logger, 'delivering queued messages ' +546'(' + this.deliveryQueue_.length + ')');547548for (var i = 0, m; i < this.deliveryQueue_.length; i++) {549m = this.deliveryQueue_[i];550this.channel_.xpcDeliver(m.service, m.payload);551}552delete this.deliveryQueue_;553}554} else {555goog.log.log(556goog.net.xpc.logger, goog.log.Level.FINEST, 'checking if connected: ' +557'ack sent:' + this.sentConnectionSetupAck_ + ', ack rcvd: ' +558this.rcvdConnectionSetupAck_);559}560};561562563/**564* Processes an incoming message.565* @param {string} raw The complete received string.566*/567goog.net.xpc.IframePollingTransport.prototype.processIncomingMsg = function(568raw) {569goog.log.log(570goog.net.xpc.logger, goog.log.Level.FINEST, 'msg received: ' + raw);571572if (raw == goog.net.xpc.SETUP) {573if (!this.ackSender_) {574// Got SETUP msg, but we can't send an ack.575return;576}577578this.ackSender_.send(goog.net.xpc.SETUP_ACK_);579goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'SETUP_ACK sent');580581this.sentConnectionSetupAck_ = true;582this.checkIfConnected_();583584} else if (this.channel_.isConnected() || this.sentConnectionSetupAck_) {585var pos = raw.indexOf('|');586var head = raw.substring(0, pos);587var frame = raw.substring(pos + 1);588589// check if it is a framed message590pos = head.indexOf(',');591if (pos == -1) {592var seq = head;593// send acknowledgement594this.ackSender_.send('ACK:' + seq);595this.deliverPayload_(frame);596} else {597var seq = head.substring(0, pos);598// send acknowledgement599this.ackSender_.send('ACK:' + seq);600601var partInfo = head.substring(pos + 1).split('/');602var part0 = parseInt(partInfo[0], 10);603var part1 = parseInt(partInfo[1], 10);604// create an array to accumulate the parts if this is the605// first frame of a message606if (part0 == 1) {607this.parts_ = [];608}609this.parts_.push(frame);610// deliver the message if this was the last frame of a message611if (part0 == part1) {612this.deliverPayload_(this.parts_.join(''));613delete this.parts_;614}615}616} else {617goog.log.warning(618goog.net.xpc.logger, 'received msg, but channel is not connected');619}620};621622623/**624* Process an incoming acknowdedgement.625* @param {string} msgStr The incoming ack string to process.626*/627goog.net.xpc.IframePollingTransport.prototype.processIncomingAck = function(628msgStr) {629goog.log.log(630goog.net.xpc.logger, goog.log.Level.FINEST, 'ack received: ' + msgStr);631632if (msgStr == goog.net.xpc.SETUP_ACK_) {633this.waitForAck_ = false;634this.rcvdConnectionSetupAck_ = true;635// send the next frame636this.checkIfConnected_();637638} else if (this.channel_.isConnected()) {639if (!this.waitForAck_) {640goog.log.warning(goog.net.xpc.logger, 'got unexpected ack');641return;642}643644var seq = parseInt(msgStr.split(':')[1], 10);645if (seq == this.sequence_) {646this.waitForAck_ = false;647this.sendNextFrame_();648} else {649goog.log.warning(goog.net.xpc.logger, 'got ack with wrong sequence');650}651} else {652goog.log.warning(653goog.net.xpc.logger, 'received ack, but channel not connected');654}655};656657658/**659* Sends a frame (message part).660* @private661*/662goog.net.xpc.IframePollingTransport.prototype.sendNextFrame_ = function() {663// do nothing if we are waiting for an acknowledgement or the664// queue is emtpy665if (this.waitForAck_ || !this.sendQueue_.length) {666return;667}668669var s = this.sendQueue_.shift();670++this.sequence_;671this.msgSender_.send(this.sequence_ + s);672goog.log.log(673goog.net.xpc.logger, goog.log.Level.FINEST,674'msg sent: ' + this.sequence_ + s);675676677this.waitForAck_ = true;678};679680681/**682* Delivers a message.683* @param {string} s The complete message string ("<service_name>:<payload>").684* @private685*/686goog.net.xpc.IframePollingTransport.prototype.deliverPayload_ = function(s) {687// determine the service name and the payload688var pos = s.indexOf(':');689var service = s.substr(0, pos);690var payload = s.substring(pos + 1);691692// deliver the message693if (!this.channel_.isConnected()) {694// as valid messages can come in before a SETUP_ACK has695// been received (because subchannels for msgs and acks are independent),696// delay delivery of early messages until after 'connect'-event697(this.deliveryQueue_ || (this.deliveryQueue_ = [698])).push({service: service, payload: payload});699goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'queued delivery');700} else {701this.channel_.xpcDeliver(service, payload);702}703};704705706// ---- send message ----707708709/**710* Maximal frame length.711* @type {number}712* @private713*/714goog.net.xpc.IframePollingTransport.prototype.MAX_FRAME_LENGTH_ = 3800;715716717/**718* Sends a message. Splits it in multiple frames if too long (exceeds IE's719* URL-length maximum.720* Wireformat: `<seq>[,<frame_no>/<#frames>]|<frame_content>`721*722* @param {string} service Name of service this the message has to be delivered.723* @param {string} payload The message content.724* @override725*/726goog.net.xpc.IframePollingTransport.prototype.send = function(727service, payload) {728var frame = service + ':' + payload;729// put in queue730if (!goog.userAgent.IE || payload.length <= this.MAX_FRAME_LENGTH_) {731this.sendQueue_.push('|' + frame);732} else {733var l = payload.length;734var num = Math.ceil(l / this.MAX_FRAME_LENGTH_); // number of frames735var pos = 0;736var i = 1;737while (pos < l) {738this.sendQueue_.push(739',' + i + '/' + num + '|' +740frame.substr(pos, this.MAX_FRAME_LENGTH_));741i++;742pos += this.MAX_FRAME_LENGTH_;743}744}745this.sendNextFrame_();746};747748749/** @override */750goog.net.xpc.IframePollingTransport.prototype.disposeInternal = function() {751goog.net.xpc.IframePollingTransport.base(this, 'disposeInternal');752753var receivers = goog.net.xpc.IframePollingTransport.receivers_;754goog.array.remove(receivers, this.msgReceiver_);755goog.array.remove(receivers, this.ackReceiver_);756this.msgReceiver_ = this.ackReceiver_ = null;757758goog.dom.removeNode(this.msgIframeElm_);759goog.dom.removeNode(this.ackIframeElm_);760this.msgIframeElm_ = this.ackIframeElm_ = null;761this.msgWinObj_ = this.ackWinObj_ = null;762};763764765/**766* Array holding all Receiver-instances.767* @type {Array<goog.net.xpc.IframePollingTransport.Receiver>}768* @private769*/770goog.net.xpc.IframePollingTransport.receivers_ = [];771772773/**774* Short polling interval.775* @type {number}776* @private777*/778goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ = 10;779780781/**782* Long polling interval.783* @type {number}784* @private785*/786goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_ = 100;787788789/**790* Period how long to use TIME_POLL_SHORT_ before raising polling-interval791* to TIME_POLL_LONG_ after an activity.792* @type {number}793* @private794*/795goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ = 1000;796797798/**799* Polls all receivers.800* @private801*/802goog.net.xpc.IframePollingTransport.receive_ = function() {803var receivers = goog.net.xpc.IframePollingTransport.receivers_;804var receiver;805var rcvd = false;806807808try {809for (var i = 0; receiver = receivers[i]; i++) {810rcvd = rcvd || receiver.receive();811}812} catch (e) {813goog.log.info(goog.net.xpc.logger, 'receive_() failed: ' + e);814815// Notify the channel that the transport had an error.816receiver.transport_.channel_.notifyTransportError();817818// notifyTransportError() closes the channel and disposes the transport.819// If there are no other channels present, this.receivers_ will now be empty820// and there is no need to keep polling.821if (!receivers.length) {822return;823}824}825826var now = goog.now();827if (rcvd) {828goog.net.xpc.IframePollingTransport.lastActivity_ = now;829}830831// Schedule next check.832var t = now - goog.net.xpc.IframePollingTransport.lastActivity_ <833goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ ?834goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ :835goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_;836goog.net.xpc.IframePollingTransport.rcvTimer_ =837window.setTimeout(goog.net.xpc.IframePollingTransport.receiveCb_, t);838};839840841/**842* Callback that wraps receive_ to be used in timers.843* @type {Function}844* @private845*/846goog.net.xpc.IframePollingTransport.receiveCb_ = goog.bind(847goog.net.xpc.IframePollingTransport.receive_,848goog.net.xpc.IframePollingTransport);849850851/**852* Starts the polling loop.853* @private854*/855goog.net.xpc.IframePollingTransport.startRcvTimer_ = function() {856goog.log.fine(goog.net.xpc.logger, 'starting receive-timer');857goog.net.xpc.IframePollingTransport.lastActivity_ = goog.now();858if (goog.net.xpc.IframePollingTransport.rcvTimer_) {859window.clearTimeout(goog.net.xpc.IframePollingTransport.rcvTimer_);860}861goog.net.xpc.IframePollingTransport.rcvTimer_ = window.setTimeout(862goog.net.xpc.IframePollingTransport.receiveCb_,863goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_);864};865866867868/**869* goog.net.xpc.IframePollingTransport.Sender870*871* Utility class to send message-parts to a document from a different origin.872*873* @constructor874* @param {string} url The url the other document will use for polling. Must875* be an http:// or https:// URL.876* @param {Object} windowObj The frame used for sending information to.877* @final878*/879goog.net.xpc.IframePollingTransport.Sender = function(url, windowObj) {880// This class is instantiated from goog.net.xpc.IframePollingTransport, which881// takes its URLs from a goog.net.xpc.CrossPageChannel, which in turns882// sanitizes them. However, since this class can be instantiated from883// elsewhere than IframePollingTransport the url needs to be sanitized884// here too.885if (!/^https?:\/\//.test(url)) {886throw Error('URL ' + url + ' is invalid');887}888889/**890* The URI used to sending messages.891* @type {string}892* @private893*/894this.sanitizedSendUri_ = url;895896/**897* The window object of the iframe used to send messages.898* The script instantiating the Sender won't have access to899* the content of sendFrame_.900* @type {Window}901* @private902*/903this.sendFrame_ = /** @type {Window} */ (windowObj);904905/**906* Cycle counter (used to make sure that sending two identical messages sent907* in direct succession can be recognized as such by the receiver).908* @type {number}909* @private910*/911this.cycle_ = 0;912};913914915/**916* Sends a message-part (frame) to the peer.917* The message-part is encoded and put in the fragment identifier918* of the URL used for sending (and belongs to the origin/domain of the peer).919* @param {string} payload The message to send.920*/921goog.net.xpc.IframePollingTransport.Sender.prototype.send = function(payload) {922this.cycle_ = ++this.cycle_ % 2;923924var url =925this.sanitizedSendUri_ + '#' + this.cycle_ + encodeURIComponent(payload);926927// TODO(user) Find out if try/catch is still needed928929try {930// safari doesn't allow to call location.replace()931if (goog.userAgent.WEBKIT) {932this.sendFrame_.location.href = url;933} else {934this.sendFrame_.location.replace(url);935}936} catch (e) {937goog.log.error(goog.net.xpc.logger, 'sending failed', e);938}939940// Restart receiver timer on short polling interval, to support use-cases941// where we need to capture responses quickly.942goog.net.xpc.IframePollingTransport.startRcvTimer_();943};944945946947/**948* goog.net.xpc.IframePollingTransport.Receiver949*950* @constructor951* @param {goog.net.xpc.IframePollingTransport} transport The transport to952* receive from.953* @param {Object} windowObj The window-object to poll for location-changes.954* @param {Function} callback The callback-function to be called when955* location has changed.956* @final957*/958goog.net.xpc.IframePollingTransport.Receiver = function(959transport, windowObj, callback) {960/**961* The transport to receive from.962* @type {goog.net.xpc.IframePollingTransport}963* @private964*/965this.transport_ = transport;966this.rcvFrame_ = windowObj;967968this.cb_ = callback;969this.currentLoc_ = this.rcvFrame_.location.href.split('#')[0] + '#INITIAL';970971goog.net.xpc.IframePollingTransport.receivers_.push(this);972goog.net.xpc.IframePollingTransport.startRcvTimer_();973};974975976/**977* Polls the location of the receiver-frame for changes.978* @return {boolean} Whether a change has been detected.979*/980goog.net.xpc.IframePollingTransport.Receiver.prototype.receive = function() {981var loc = this.rcvFrame_.location.href;982983if (loc != this.currentLoc_) {984this.currentLoc_ = loc;985var payload = loc.split('#')[1];986if (payload) {987payload = payload.substr(1); // discard first character (cycle)988this.cb_(decodeURIComponent(payload));989}990return true;991} else {992return false;993}994};995996997