Update wmts-hidpi, add nicer-api-docs
This commit is contained in:
@@ -0,0 +1,921 @@
|
||||
// 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.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
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}
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport = function(channel, opt_domHelper) {
|
||||
goog.base(this, 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}
|
||||
* @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;
|
||||
|
||||
|
||||
/**
|
||||
* 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_.name = 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.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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user