123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- // 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.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 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) {
- 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;
- }
- };
|