Update wmts-hidpi, add nicer-api-docs
This commit is contained in:
@@ -0,0 +1,805 @@
|
||||
// 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 Provides the class CrossPageChannel, the main class in
|
||||
* goog.net.xpc.
|
||||
*
|
||||
* @see ../../demos/xpc/index.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.CrossPageChannel');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.async.Delay');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.messaging.AbstractChannel');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.FrameElementMethodTransport');
|
||||
goog.require('goog.net.xpc.IframePollingTransport');
|
||||
goog.require('goog.net.xpc.IframeRelayTransport');
|
||||
goog.require('goog.net.xpc.NativeMessagingTransport');
|
||||
goog.require('goog.net.xpc.NixTransport');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A communication channel between two documents from different domains.
|
||||
* Provides asynchronous messaging.
|
||||
*
|
||||
* @param {Object} cfg Channel configuration object.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The optional dom helper to
|
||||
* use for looking up elements in the dom.
|
||||
* @constructor
|
||||
* @extends {goog.messaging.AbstractChannel}
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel = function(cfg, opt_domHelper) {
|
||||
goog.base(this);
|
||||
|
||||
for (var i = 0, uriField; uriField = goog.net.xpc.UriCfgFields[i]; i++) {
|
||||
if (uriField in cfg && !/^https?:\/\//.test(cfg[uriField])) {
|
||||
throw Error('URI ' + cfg[uriField] + ' is invalid for field ' + uriField);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration for this channel.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
this.cfg_ = cfg;
|
||||
|
||||
/**
|
||||
* The name of the channel.
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = this.cfg_[goog.net.xpc.CfgFields.CHANNEL_NAME] ||
|
||||
goog.net.xpc.getRandomString(10);
|
||||
|
||||
/**
|
||||
* The dom helper to use for accessing the dom.
|
||||
* @type {goog.dom.DomHelper}
|
||||
* @private
|
||||
*/
|
||||
this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
|
||||
|
||||
/**
|
||||
* Collects deferred function calls which will be made once the connection
|
||||
* has been fully set up.
|
||||
* @type {!Array.<function()>}
|
||||
* @private
|
||||
*/
|
||||
this.deferredDeliveries_ = [];
|
||||
|
||||
/**
|
||||
* An event handler used to listen for load events on peer iframes.
|
||||
* @type {!goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.peerLoadHandler_ = new goog.events.EventHandler(this);
|
||||
|
||||
// If LOCAL_POLL_URI or PEER_POLL_URI is not available, try using
|
||||
// robots.txt from that host.
|
||||
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] =
|
||||
cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] ||
|
||||
goog.uri.utils.getHost(this.domHelper_.getWindow().location.href) +
|
||||
'/robots.txt';
|
||||
// PEER_URI is sometimes undefined in tests.
|
||||
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =
|
||||
cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] ||
|
||||
goog.uri.utils.getHost(cfg[goog.net.xpc.CfgFields.PEER_URI] || '') +
|
||||
'/robots.txt';
|
||||
|
||||
goog.net.xpc.channels[this.name] = this;
|
||||
|
||||
goog.events.listen(window, 'unload',
|
||||
goog.net.xpc.CrossPageChannel.disposeAll_);
|
||||
|
||||
goog.log.info(goog.net.xpc.logger, 'CrossPageChannel created: ' + this.name);
|
||||
};
|
||||
goog.inherits(goog.net.xpc.CrossPageChannel, goog.messaging.AbstractChannel);
|
||||
|
||||
|
||||
/**
|
||||
* Regexp for escaping service names.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_ESCAPE_RE_ =
|
||||
new RegExp('^%*' + goog.net.xpc.TRANSPORT_SERVICE_ + '$');
|
||||
|
||||
|
||||
/**
|
||||
* Regexp for unescaping service names.
|
||||
* @type {RegExp}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_UNESCAPE_RE_ =
|
||||
new RegExp('^%+' + goog.net.xpc.TRANSPORT_SERVICE_ + '$');
|
||||
|
||||
|
||||
/**
|
||||
* A delay between the transport reporting as connected and the calling of the
|
||||
* connection callback. Sometimes used to paper over timing vulnerabilities.
|
||||
* @type {goog.async.Delay}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.connectionDelay_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* A deferred which is set to non-null while a peer iframe is being created
|
||||
* but has not yet thrown its load event, and which fires when that load event
|
||||
* arrives.
|
||||
* @type {goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.peerWindowDeferred_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The transport.
|
||||
* @type {goog.net.xpc.Transport?}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.transport_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* The channel state.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.state_ =
|
||||
goog.net.xpc.ChannelStates.NOT_CONNECTED;
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return {boolean} Whether the channel is connected.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.isConnected = function() {
|
||||
return this.state_ == goog.net.xpc.ChannelStates.CONNECTED;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reference to the window-object of the peer page.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.peerWindowObject_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Reference to the iframe-element.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.iframeElement_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the configuration object for this channel.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @return {Object} The configuration object for this channel.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getConfig = function() {
|
||||
return this.cfg_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a reference to the iframe-element.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @return {Object} A reference to the iframe-element.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getIframeElement = function() {
|
||||
return this.iframeElement_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the window object the foreign document resides in.
|
||||
*
|
||||
* @param {Object} peerWindowObject The window object of the peer.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.setPeerWindowObject =
|
||||
function(peerWindowObject) {
|
||||
this.peerWindowObject_ = peerWindowObject;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the window object the foreign document resides in.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @return {Object} The window object of the peer.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getPeerWindowObject = function() {
|
||||
return this.peerWindowObject_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the peer window is available (e.g. not closed).
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @return {boolean} Whether the peer window is available.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.isPeerAvailable = function() {
|
||||
// NOTE(user): This check is not reliable in IE, where a document in an
|
||||
// iframe does not get unloaded when removing the iframe element from the DOM.
|
||||
// TODO(user): Find something that works in IE as well.
|
||||
// NOTE(user): "!this.peerWindowObject_.closed" evaluates to 'false' in IE9
|
||||
// sometimes even though typeof(this.peerWindowObject_.closed) is boolean and
|
||||
// this.peerWindowObject_.closed evaluates to 'false'. Casting it to a Boolean
|
||||
// results in sane evaluation. When this happens, it's in the inner iframe
|
||||
// when querying its parent's 'closed' status. Note that this is a different
|
||||
// case than mibuerge@'s note above.
|
||||
try {
|
||||
return !!this.peerWindowObject_ && !Boolean(this.peerWindowObject_.closed);
|
||||
} catch (e) {
|
||||
// If the window is closing, an error may be thrown.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determine which transport type to use for this channel / useragent.
|
||||
* @return {goog.net.xpc.TransportTypes|undefined} The best transport type.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.determineTransportType_ = function() {
|
||||
var transportType;
|
||||
if (goog.isFunction(document.postMessage) ||
|
||||
goog.isFunction(window.postMessage) ||
|
||||
// IE8 supports window.postMessage, but
|
||||
// typeof window.postMessage returns "object"
|
||||
(goog.userAgent.IE && window.postMessage)) {
|
||||
transportType = goog.net.xpc.TransportTypes.NATIVE_MESSAGING;
|
||||
} else if (goog.userAgent.GECKO) {
|
||||
transportType = goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD;
|
||||
} else if (goog.userAgent.IE &&
|
||||
this.cfg_[goog.net.xpc.CfgFields.PEER_RELAY_URI]) {
|
||||
transportType = goog.net.xpc.TransportTypes.IFRAME_RELAY;
|
||||
} else if (goog.userAgent.IE && goog.net.xpc.NixTransport.isNixSupported()) {
|
||||
transportType = goog.net.xpc.TransportTypes.NIX;
|
||||
} else {
|
||||
transportType = goog.net.xpc.TransportTypes.IFRAME_POLLING;
|
||||
}
|
||||
return transportType;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the transport for this channel. Chooses from the available
|
||||
* transport based on the user agent and the configuration.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.createTransport_ = function() {
|
||||
// return, if the transport has already been created
|
||||
if (this.transport_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.cfg_[goog.net.xpc.CfgFields.TRANSPORT]) {
|
||||
this.cfg_[goog.net.xpc.CfgFields.TRANSPORT] =
|
||||
this.determineTransportType_();
|
||||
}
|
||||
|
||||
switch (this.cfg_[goog.net.xpc.CfgFields.TRANSPORT]) {
|
||||
case goog.net.xpc.TransportTypes.NATIVE_MESSAGING:
|
||||
var protocolVersion = this.cfg_[
|
||||
goog.net.xpc.CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] || 2;
|
||||
this.transport_ = new goog.net.xpc.NativeMessagingTransport(
|
||||
this,
|
||||
this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME],
|
||||
this.domHelper_,
|
||||
!!this.cfg_[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE],
|
||||
protocolVersion);
|
||||
break;
|
||||
case goog.net.xpc.TransportTypes.NIX:
|
||||
this.transport_ = new goog.net.xpc.NixTransport(this, this.domHelper_);
|
||||
break;
|
||||
case goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD:
|
||||
this.transport_ =
|
||||
new goog.net.xpc.FrameElementMethodTransport(this, this.domHelper_);
|
||||
break;
|
||||
case goog.net.xpc.TransportTypes.IFRAME_RELAY:
|
||||
this.transport_ =
|
||||
new goog.net.xpc.IframeRelayTransport(this, this.domHelper_);
|
||||
break;
|
||||
case goog.net.xpc.TransportTypes.IFRAME_POLLING:
|
||||
this.transport_ =
|
||||
new goog.net.xpc.IframePollingTransport(this, this.domHelper_);
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.transport_) {
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'Transport created: ' + this.transport_.getName());
|
||||
} else {
|
||||
throw Error('CrossPageChannel: No suitable transport found!');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the transport type in use for this channel.
|
||||
* @return {number} Transport-type identifier.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getTransportType = function() {
|
||||
return this.transport_.getType();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the tranport name in use for this channel.
|
||||
* @return {string} The transport name.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getTransportName = function() {
|
||||
return this.transport_.getName();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {Object} Configuration-object to be used by the peer to
|
||||
* initialize the channel.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getPeerConfiguration = function() {
|
||||
var peerCfg = {};
|
||||
peerCfg[goog.net.xpc.CfgFields.CHANNEL_NAME] = this.name;
|
||||
peerCfg[goog.net.xpc.CfgFields.TRANSPORT] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.TRANSPORT];
|
||||
peerCfg[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE];
|
||||
|
||||
if (this.cfg_[goog.net.xpc.CfgFields.LOCAL_RELAY_URI]) {
|
||||
peerCfg[goog.net.xpc.CfgFields.PEER_RELAY_URI] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.LOCAL_RELAY_URI];
|
||||
}
|
||||
if (this.cfg_[goog.net.xpc.CfgFields.LOCAL_POLL_URI]) {
|
||||
peerCfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.LOCAL_POLL_URI];
|
||||
}
|
||||
if (this.cfg_[goog.net.xpc.CfgFields.PEER_POLL_URI]) {
|
||||
peerCfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] =
|
||||
this.cfg_[goog.net.xpc.CfgFields.PEER_POLL_URI];
|
||||
}
|
||||
var role = this.cfg_[goog.net.xpc.CfgFields.ROLE];
|
||||
if (role) {
|
||||
peerCfg[goog.net.xpc.CfgFields.ROLE] =
|
||||
role == goog.net.xpc.CrossPageChannelRole.INNER ?
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER :
|
||||
goog.net.xpc.CrossPageChannelRole.INNER;
|
||||
}
|
||||
|
||||
return peerCfg;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the iframe containing the peer page in a specified parent element.
|
||||
* This method does not connect the channel, connect() still has to be called
|
||||
* separately.
|
||||
*
|
||||
* @param {!Element} parentElm The container element the iframe is appended to.
|
||||
* @param {Function=} opt_configureIframeCb If present, this function gets
|
||||
* called with the iframe element as parameter to allow setting properties
|
||||
* on it before it gets added to the DOM. If absent, the iframe's width and
|
||||
* height are set to '100%'.
|
||||
* @param {boolean=} opt_addCfgParam Whether to add the peer configuration as
|
||||
* URL parameter (default: true).
|
||||
* @return {!HTMLIFrameElement} The iframe element.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.createPeerIframe = function(
|
||||
parentElm, opt_configureIframeCb, opt_addCfgParam) {
|
||||
goog.log.info(goog.net.xpc.logger, 'createPeerIframe()');
|
||||
|
||||
var iframeId = this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID];
|
||||
if (!iframeId) {
|
||||
// Create a randomized ID for the iframe element to avoid
|
||||
// bfcache-related issues.
|
||||
iframeId = this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID] =
|
||||
'xpcpeer' + goog.net.xpc.getRandomString(4);
|
||||
}
|
||||
|
||||
// TODO(user) Opera creates a history-entry when creating an iframe
|
||||
// programmatically as follows. Find a way which avoids this.
|
||||
|
||||
var iframeElm = goog.dom.getDomHelper(parentElm).createElement('IFRAME');
|
||||
iframeElm.id = iframeElm.name = iframeId;
|
||||
if (opt_configureIframeCb) {
|
||||
opt_configureIframeCb(iframeElm);
|
||||
} else {
|
||||
iframeElm.style.width = iframeElm.style.height = '100%';
|
||||
}
|
||||
|
||||
this.cleanUpIncompleteConnection_();
|
||||
this.peerWindowDeferred_ =
|
||||
new goog.async.Deferred(undefined, this);
|
||||
var peerUri = this.getPeerUri(opt_addCfgParam);
|
||||
this.peerLoadHandler_.listenOnce(iframeElm, 'load',
|
||||
this.peerWindowDeferred_.callback, false, this.peerWindowDeferred_);
|
||||
|
||||
if (goog.userAgent.GECKO || goog.userAgent.WEBKIT) {
|
||||
// Appending the iframe in a timeout to avoid a weird fastback issue, which
|
||||
// is present in Safari and Gecko.
|
||||
window.setTimeout(
|
||||
goog.bind(function() {
|
||||
parentElm.appendChild(iframeElm);
|
||||
iframeElm.src = peerUri.toString();
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'peer iframe created (' + iframeId + ')');
|
||||
}, this), 1);
|
||||
} else {
|
||||
iframeElm.src = peerUri.toString();
|
||||
parentElm.appendChild(iframeElm);
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'peer iframe created (' + iframeId + ')');
|
||||
}
|
||||
|
||||
return /** @type {!HTMLIFrameElement} */ (iframeElm);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Clean up after any incomplete attempt to establish and connect to a peer
|
||||
* iframe.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.cleanUpIncompleteConnection_ =
|
||||
function() {
|
||||
if (this.peerWindowDeferred_) {
|
||||
this.peerWindowDeferred_.cancel();
|
||||
this.peerWindowDeferred_ = null;
|
||||
}
|
||||
this.deferredDeliveries_.length = 0;
|
||||
this.peerLoadHandler_.removeAll();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the peer URI, with an optional URL parameter for configuring the peer
|
||||
* window.
|
||||
*
|
||||
* @param {boolean=} opt_addCfgParam Whether to add the peer configuration as
|
||||
* URL parameter (default: true).
|
||||
* @return {!goog.Uri} The peer URI.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getPeerUri = function(opt_addCfgParam) {
|
||||
var peerUri = this.cfg_[goog.net.xpc.CfgFields.PEER_URI];
|
||||
if (goog.isString(peerUri)) {
|
||||
peerUri = this.cfg_[goog.net.xpc.CfgFields.PEER_URI] =
|
||||
new goog.Uri(peerUri);
|
||||
}
|
||||
|
||||
// Add the channel configuration used by the peer as URL parameter.
|
||||
if (opt_addCfgParam !== false) {
|
||||
peerUri.setParameterValue('xpc',
|
||||
goog.json.serialize(
|
||||
this.getPeerConfiguration()));
|
||||
}
|
||||
|
||||
return peerUri;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initiates connecting the channel. When this method is called, all the
|
||||
* information needed to connect the channel has to be available.
|
||||
*
|
||||
* @override
|
||||
* @param {Function=} opt_connectCb The function to be called when the
|
||||
* channel has been connected and is ready to be used.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.connect = function(opt_connectCb) {
|
||||
this.connectCb_ = opt_connectCb || goog.nullFunction;
|
||||
|
||||
// If we know of a peer window whose creation has been requested but is not
|
||||
// complete, peerWindowDeferred_ will be non-null, and we should block on it.
|
||||
if (this.peerWindowDeferred_) {
|
||||
this.peerWindowDeferred_.addCallback(this.continueConnection_);
|
||||
} else {
|
||||
this.continueConnection_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Continues the connection process once we're as sure as we can be that the
|
||||
* peer iframe has been created.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.continueConnection_ = function() {
|
||||
goog.log.info(goog.net.xpc.logger, 'continueConnection_()');
|
||||
this.peerWindowDeferred_ = null;
|
||||
if (this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]) {
|
||||
this.iframeElement_ = this.domHelper_.getElement(
|
||||
this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]);
|
||||
}
|
||||
if (this.iframeElement_) {
|
||||
var winObj = this.iframeElement_.contentWindow;
|
||||
// accessing the window using contentWindow doesn't work in safari
|
||||
if (!winObj) {
|
||||
winObj = window.frames[this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]];
|
||||
}
|
||||
this.setPeerWindowObject(winObj);
|
||||
}
|
||||
|
||||
// if the peer window object has not been set at this point, we assume
|
||||
// being in an iframe and the channel is meant to be to the containing page
|
||||
if (!this.peerWindowObject_) {
|
||||
// throw an error if we are in the top window (== not in an iframe)
|
||||
if (window == window.top) {
|
||||
throw Error(
|
||||
"CrossPageChannel: Can't connect, peer window-object not set.");
|
||||
} else {
|
||||
this.setPeerWindowObject(window.parent);
|
||||
}
|
||||
}
|
||||
|
||||
this.createTransport_();
|
||||
|
||||
this.transport_.connect();
|
||||
|
||||
// Now we run any deferred deliveries collected while connection was deferred.
|
||||
while (this.deferredDeliveries_.length > 0) {
|
||||
this.deferredDeliveries_.shift()();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Closes the channel.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.close = function() {
|
||||
this.cleanUpIncompleteConnection_();
|
||||
this.state_ = goog.net.xpc.ChannelStates.CLOSED;
|
||||
goog.dispose(this.transport_);
|
||||
this.transport_ = null;
|
||||
this.connectCb_ = null;
|
||||
goog.dispose(this.connectionDelay_);
|
||||
this.connectionDelay_ = null;
|
||||
goog.log.info(goog.net.xpc.logger, 'Channel "' + this.name + '" closed');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Package-private.
|
||||
* Called by the transport when the channel is connected.
|
||||
* @param {number=} opt_delay Delay this number of milliseconds before calling
|
||||
* the connection callback. Usage is discouraged, but can be used to paper
|
||||
* over timing vulnerabilities when there is no alternative.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.notifyConnected = function(opt_delay) {
|
||||
if (this.isConnected() ||
|
||||
(this.connectionDelay_ && this.connectionDelay_.isActive())) {
|
||||
return;
|
||||
}
|
||||
this.state_ = goog.net.xpc.ChannelStates.CONNECTED;
|
||||
goog.log.info(goog.net.xpc.logger, 'Channel "' + this.name + '" connected');
|
||||
goog.dispose(this.connectionDelay_);
|
||||
if (opt_delay) {
|
||||
this.connectionDelay_ =
|
||||
new goog.async.Delay(this.connectCb_, opt_delay);
|
||||
this.connectionDelay_.start();
|
||||
} else {
|
||||
this.connectionDelay_ = null;
|
||||
this.connectCb_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Alias for notifyConected, for backward compatibility reasons.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.notifyConnected_ =
|
||||
goog.net.xpc.CrossPageChannel.prototype.notifyConnected;
|
||||
|
||||
|
||||
/**
|
||||
* Called by the transport in case of an unrecoverable failure.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.notifyTransportError = function() {
|
||||
goog.log.info(goog.net.xpc.logger, 'Transport Error');
|
||||
this.close();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.CrossPageChannel.prototype.send = function(serviceName, payload) {
|
||||
if (!this.isConnected()) {
|
||||
goog.log.error(goog.net.xpc.logger, 'Can\'t send. Channel not connected.');
|
||||
return;
|
||||
}
|
||||
// Check if the peer is still around.
|
||||
if (!this.isPeerAvailable()) {
|
||||
goog.log.error(goog.net.xpc.logger, 'Peer has disappeared.');
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
if (goog.isObject(payload)) {
|
||||
payload = goog.json.serialize(payload);
|
||||
}
|
||||
|
||||
// Partially URL-encode the service name because some characters (: and |) are
|
||||
// used as delimiters for some transports, and we want to allow those
|
||||
// characters in service names.
|
||||
this.transport_.send(this.escapeServiceName_(serviceName), payload);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delivers messages to the appropriate service-handler. Named xpcDeliver to
|
||||
* avoid name conflict with {@code deliver} function in superclass
|
||||
* goog.messaging.AbstractChannel.
|
||||
*
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @param {string} serviceName The name of the port.
|
||||
* @param {string} payload The payload.
|
||||
* @param {string=} opt_origin An optional origin for the message, where the
|
||||
* underlying transport makes that available. If this is specified, and
|
||||
* the PEER_HOSTNAME parameter was provided, they must match or the message
|
||||
* will be rejected.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.xpcDeliver = function(
|
||||
serviceName, payload, opt_origin) {
|
||||
|
||||
// This check covers the very rare (but producable) case where the inner frame
|
||||
// becomes ready and sends its setup message while the outer frame is
|
||||
// deferring its connect method waiting for the inner frame to be ready. The
|
||||
// resulting deferral ensures the message will not be processed until the
|
||||
// channel is fully configured.
|
||||
if (this.peerWindowDeferred_) {
|
||||
this.deferredDeliveries_.push(
|
||||
goog.bind(this.xpcDeliver, this, serviceName, payload, opt_origin));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether the origin of the message is as expected.
|
||||
if (!this.isMessageOriginAcceptable_(opt_origin)) {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'Message received from unapproved origin "' +
|
||||
opt_origin + '" - rejected.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isDisposed()) {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'CrossPageChannel::xpcDeliver(): Disposed.');
|
||||
} else if (!serviceName ||
|
||||
serviceName == goog.net.xpc.TRANSPORT_SERVICE_) {
|
||||
this.transport_.transportServiceHandler(payload);
|
||||
} else {
|
||||
// only deliver messages if connected
|
||||
if (this.isConnected()) {
|
||||
this.deliver(this.unescapeServiceName_(serviceName), payload);
|
||||
} else {
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'CrossPageChannel::xpcDeliver(): Not connected.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Escape the user-provided service name for sending across the channel. This
|
||||
* URL-encodes certain special characters so they don't conflict with delimiters
|
||||
* used by some of the transports, and adds a special prefix if the name
|
||||
* conflicts with the reserved transport service name.
|
||||
*
|
||||
* This is the opposite of {@link #unescapeServiceName_}.
|
||||
*
|
||||
* @param {string} name The name of the service to escape.
|
||||
* @return {string} The escaped service name.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.escapeServiceName_ = function(name) {
|
||||
if (goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_ESCAPE_RE_.test(name)) {
|
||||
name = '%' + name;
|
||||
}
|
||||
return name.replace(/[%:|]/g, encodeURIComponent);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Unescape the escaped service name that was sent across the channel. This is
|
||||
* the opposite of {@link #escapeServiceName_}.
|
||||
*
|
||||
* @param {string} name The name of the service to unescape.
|
||||
* @return {string} The unescaped service name.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.unescapeServiceName_ = function(name) {
|
||||
name = name.replace(/%[0-9a-f]{2}/gi, decodeURIComponent);
|
||||
if (goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_UNESCAPE_RE_.test(name)) {
|
||||
return name.substring(1);
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the role of this channel (either inner or outer).
|
||||
* @return {number} The role of this channel.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getRole = function() {
|
||||
var role = this.cfg_[goog.net.xpc.CfgFields.ROLE];
|
||||
if (role) {
|
||||
return role;
|
||||
} else {
|
||||
return window.parent == this.peerWindowObject_ ?
|
||||
goog.net.xpc.CrossPageChannelRole.INNER :
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether an incoming message with the given origin is acceptable.
|
||||
* If an incoming request comes with a specified (non-empty) origin, and the
|
||||
* PEER_HOSTNAME config parameter has also been provided, the two must match,
|
||||
* or the message is unacceptable.
|
||||
* @param {string=} opt_origin The origin associated with the incoming message.
|
||||
* @return {boolean} Whether the message is acceptable.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.isMessageOriginAcceptable_ = function(
|
||||
opt_origin) {
|
||||
var peerHostname = this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME];
|
||||
return goog.string.isEmptySafe(opt_origin) ||
|
||||
goog.string.isEmptySafe(peerHostname) ||
|
||||
opt_origin == this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME];
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.CrossPageChannel.prototype.disposeInternal = function() {
|
||||
this.close();
|
||||
|
||||
this.peerWindowObject_ = null;
|
||||
this.iframeElement_ = null;
|
||||
delete goog.net.xpc.channels[this.name];
|
||||
goog.dispose(this.peerLoadHandler_);
|
||||
delete this.peerLoadHandler_;
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes all channels.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.disposeAll_ = function() {
|
||||
for (var name in goog.net.xpc.channels) {
|
||||
goog.dispose(goog.net.xpc.channels[name]);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 Provides the enum for the role of the CrossPageChannel.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.CrossPageChannelRole');
|
||||
|
||||
|
||||
/**
|
||||
* The role of the peer.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannelRole = {
|
||||
OUTER: 0,
|
||||
INNER: 1
|
||||
};
|
||||
@@ -0,0 +1,254 @@
|
||||
// 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 frame element method transport for cross-domain
|
||||
* communication. It exploits the fact that FF lets a page in an
|
||||
* iframe call a method on the iframe-element it is contained in, even if the
|
||||
* containing page is from a different domain.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.FrameElementMethodTransport');
|
||||
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Frame-element method transport.
|
||||
*
|
||||
* Firefox allows a document within an iframe to call methods on the
|
||||
* iframe-element added by the containing document.
|
||||
* NOTE(user): Tested in all FF versions starting from 1.0
|
||||
*
|
||||
* @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.FrameElementMethodTransport = function(channel, opt_domHelper) {
|
||||
goog.base(this, opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @type {goog.net.xpc.CrossPageChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
// To transfer messages, this transport basically uses normal function calls,
|
||||
// which are synchronous. To avoid endless recursion, the delivery has to
|
||||
// be artificially made asynchronous.
|
||||
|
||||
/**
|
||||
* Array for queued messages.
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.queue_ = [];
|
||||
|
||||
/**
|
||||
* Callback function which wraps deliverQueued_.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.deliverQueuedCb_ = goog.bind(this.deliverQueued_, this);
|
||||
};
|
||||
goog.inherits(goog.net.xpc.FrameElementMethodTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD;
|
||||
|
||||
|
||||
/**
|
||||
* Flag used to enforce asynchronous messaging semantics.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.recursive_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Timer used to enforce asynchronous message delivery.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.timer_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Holds the function to send messages to the peer
|
||||
* (once it becomes available).
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.outgoing_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Connect this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.connect = function() {
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER) {
|
||||
// get shortcut to iframe-element
|
||||
this.iframeElm_ = this.channel_.getIframeElement();
|
||||
|
||||
// add the gateway function to the iframe-element
|
||||
// (to be called by the peer)
|
||||
this.iframeElm_['XPC_toOuter'] = goog.bind(this.incoming_, this);
|
||||
|
||||
// at this point we just have to wait for a notification from the peer...
|
||||
|
||||
} else {
|
||||
this.attemptSetup_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Only used from within an iframe. Attempts to attach the method
|
||||
* to be used for sending messages by the containing document. Has to
|
||||
* wait until the containing document has finished. Therefore calls
|
||||
* itself in a timeout if not successful.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.attemptSetup_ = function() {
|
||||
var retry = true;
|
||||
/** @preserveTry */
|
||||
try {
|
||||
if (!this.iframeElm_) {
|
||||
// throws security exception when called too early
|
||||
this.iframeElm_ = this.getWindow().frameElement;
|
||||
}
|
||||
// check if iframe-element and the gateway-function to the
|
||||
// outer-frame are present
|
||||
// TODO(user) Make sure the following code doesn't throw any exceptions
|
||||
if (this.iframeElm_ && this.iframeElm_['XPC_toOuter']) {
|
||||
// get a reference to the gateway function
|
||||
this.outgoing_ = this.iframeElm_['XPC_toOuter'];
|
||||
// attach the gateway function the other document will use
|
||||
this.iframeElm_['XPC_toOuter']['XPC_toInner'] =
|
||||
goog.bind(this.incoming_, this);
|
||||
// stop retrying
|
||||
retry = false;
|
||||
// notify outer frame
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
|
||||
// notify channel that the transport is ready
|
||||
this.channel_.notifyConnected();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'exception caught while attempting setup: ' + e);
|
||||
}
|
||||
// retry necessary?
|
||||
if (retry) {
|
||||
if (!this.attemptSetupCb_) {
|
||||
this.attemptSetupCb_ = goog.bind(this.attemptSetup_, this);
|
||||
}
|
||||
this.getWindow().setTimeout(this.attemptSetupCb_, 100);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.transportServiceHandler =
|
||||
function(payload) {
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER &&
|
||||
!this.channel_.isConnected() && payload == goog.net.xpc.SETUP_ACK_) {
|
||||
// get a reference to the gateway function
|
||||
this.outgoing_ = this.iframeElm_['XPC_toOuter']['XPC_toInner'];
|
||||
// notify the channel we're ready
|
||||
this.channel_.notifyConnected();
|
||||
} else {
|
||||
throw Error('Got unexpected transport message.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Process incoming message.
|
||||
* @param {string} serviceName The name of the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message to process.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.incoming_ =
|
||||
function(serviceName, payload) {
|
||||
if (!this.recursive_ && this.queue_.length == 0) {
|
||||
this.channel_.xpcDeliver(serviceName, payload);
|
||||
}
|
||||
else {
|
||||
this.queue_.push({serviceName: serviceName, payload: payload});
|
||||
if (this.queue_.length == 1) {
|
||||
this.timer_ = this.getWindow().setTimeout(this.deliverQueuedCb_, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delivers queued messages.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.deliverQueued_ =
|
||||
function() {
|
||||
while (this.queue_.length) {
|
||||
var msg = this.queue_.shift();
|
||||
this.channel_.xpcDeliver(msg.serviceName, msg.payload);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Send a message
|
||||
* @param {string} service The name off the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.send =
|
||||
function(service, payload) {
|
||||
this.recursive_ = true;
|
||||
this.outgoing_(service, payload);
|
||||
this.recursive_ = false;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.disposeInternal =
|
||||
function() {
|
||||
goog.net.xpc.FrameElementMethodTransport.superClass_.disposeInternal.call(
|
||||
this);
|
||||
this.outgoing_ = null;
|
||||
this.iframeElm_ = null;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,394 @@
|
||||
// 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 relay tranport.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.IframeRelayTransport');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Iframe relay transport. Creates hidden iframes containing a document
|
||||
* from the peer's origin. Data is transferred in the fragment identifier.
|
||||
* Therefore the document loaded in the iframes can be served from the
|
||||
* browser's cache.
|
||||
*
|
||||
* @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.IframeRelayTransport = 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 relay data to the peer.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.peerRelayUri_ =
|
||||
this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_RELAY_URI];
|
||||
|
||||
/**
|
||||
* The id of the iframe the peer page lives in.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.peerIframeId_ =
|
||||
this.channel_.getConfig()[goog.net.xpc.CfgFields.IFRAME_ID];
|
||||
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
goog.net.xpc.IframeRelayTransport.startCleanupTimer_();
|
||||
}
|
||||
};
|
||||
goog.inherits(goog.net.xpc.IframeRelayTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
/**
|
||||
* Array to keep references to the relay-iframes. Used only if
|
||||
* there is no way to detect when the iframes are loaded. In that
|
||||
* case the relay-iframes are removed after a timeout.
|
||||
* @type {Array.<Object>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.iframeRefs_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Interval at which iframes are destroyed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_ = 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Time after which a relay-iframe is destroyed.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_ = 3000;
|
||||
|
||||
|
||||
/**
|
||||
* The cleanup timer id.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.cleanupTimer_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Starts the cleanup timer.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.startCleanupTimer_ = function() {
|
||||
if (!goog.net.xpc.IframeRelayTransport.cleanupTimer_) {
|
||||
goog.net.xpc.IframeRelayTransport.cleanupTimer_ = window.setTimeout(
|
||||
function() { goog.net.xpc.IframeRelayTransport.cleanup_(); },
|
||||
goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove all relay-iframes which are older than the maximal age.
|
||||
* @param {number=} opt_maxAge The maximal age in milliseconds.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.cleanup_ = function(opt_maxAge) {
|
||||
var now = goog.now();
|
||||
var maxAge =
|
||||
opt_maxAge || goog.net.xpc.IframeRelayTransport.IFRAME_MAX_AGE_;
|
||||
|
||||
while (goog.net.xpc.IframeRelayTransport.iframeRefs_.length &&
|
||||
now - goog.net.xpc.IframeRelayTransport.iframeRefs_[0].timestamp >=
|
||||
maxAge) {
|
||||
var ifr = goog.net.xpc.IframeRelayTransport.iframeRefs_.
|
||||
shift().iframeElement;
|
||||
goog.dom.removeNode(ifr);
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'iframe removed');
|
||||
}
|
||||
|
||||
goog.net.xpc.IframeRelayTransport.cleanupTimer_ = window.setTimeout(
|
||||
goog.net.xpc.IframeRelayTransport.cleanupCb_,
|
||||
goog.net.xpc.IframeRelayTransport.CLEANUP_INTERVAL_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function which wraps cleanup_().
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.cleanupCb_ = function() {
|
||||
goog.net.xpc.IframeRelayTransport.cleanup_();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Maximum sendable size of a payload via a single iframe in IE.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_ = 1800;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{fragments: !Array.<string>, received: number, expected: number}}
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.FragmentInfo;
|
||||
|
||||
|
||||
/**
|
||||
* Used to track incoming payload fragments. The implementation can process
|
||||
* incoming fragments from several channels at a time, even if data is
|
||||
* out-of-order or interleaved.
|
||||
*
|
||||
* @type {!Object.<string, !goog.net.xpc.IframeRelayTransport.FragmentInfo>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.fragmentMap_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.IFRAME_RELAY;
|
||||
|
||||
|
||||
/**
|
||||
* Connects this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.connect = function() {
|
||||
if (!this.getWindow()['xpcRelay']) {
|
||||
this.getWindow()['xpcRelay'] =
|
||||
goog.net.xpc.IframeRelayTransport.receiveMessage_;
|
||||
}
|
||||
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Processes an incoming message.
|
||||
*
|
||||
* @param {string} channelName The name of the channel.
|
||||
* @param {string} frame The raw frame content.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.receiveMessage_ =
|
||||
function(channelName, frame) {
|
||||
var pos = frame.indexOf(':');
|
||||
var header = frame.substr(0, pos);
|
||||
var payload = frame.substr(pos + 1);
|
||||
|
||||
if (!goog.userAgent.IE || (pos = header.indexOf('|')) == -1) {
|
||||
// First, the easy case.
|
||||
var service = header;
|
||||
} else {
|
||||
// There was a fragment id in the header, so this is a message
|
||||
// fragment, not a whole message.
|
||||
var service = header.substr(0, pos);
|
||||
var fragmentIdStr = header.substr(pos + 1);
|
||||
|
||||
// Separate the message id string and the fragment number. Note that
|
||||
// there may be a single leading + in the argument to parseInt, but
|
||||
// this is harmless.
|
||||
pos = fragmentIdStr.indexOf('+');
|
||||
var messageIdStr = fragmentIdStr.substr(0, pos);
|
||||
var fragmentNum = parseInt(fragmentIdStr.substr(pos + 1), 10);
|
||||
var fragmentInfo =
|
||||
goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];
|
||||
if (!fragmentInfo) {
|
||||
fragmentInfo =
|
||||
goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr] =
|
||||
{fragments: [], received: 0, expected: 0};
|
||||
}
|
||||
|
||||
if (goog.string.contains(fragmentIdStr, '++')) {
|
||||
fragmentInfo.expected = fragmentNum + 1;
|
||||
}
|
||||
fragmentInfo.fragments[fragmentNum] = payload;
|
||||
fragmentInfo.received++;
|
||||
|
||||
if (fragmentInfo.received != fragmentInfo.expected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We've received all outstanding fragments; combine what we've received
|
||||
// into payload and fall out to the call to xpcDeliver.
|
||||
payload = fragmentInfo.fragments.join('');
|
||||
delete goog.net.xpc.IframeRelayTransport.fragmentMap_[messageIdStr];
|
||||
}
|
||||
|
||||
goog.net.xpc.channels[channelName].
|
||||
xpcDeliver(service, decodeURIComponent(payload));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages (internal signalling).
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.transportServiceHandler =
|
||||
function(payload) {
|
||||
if (payload == goog.net.xpc.SETUP) {
|
||||
// TODO(user) Safari swallows the SETUP_ACK from the iframe to the
|
||||
// container after hitting reload.
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
|
||||
this.channel_.notifyConnected();
|
||||
}
|
||||
else if (payload == goog.net.xpc.SETUP_ACK_) {
|
||||
this.channel_.notifyConnected();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
*
|
||||
* @param {string} service Name of service this the message has to be delivered.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.send = function(service, payload) {
|
||||
// If we're on IE and the post-encoding payload is large, split it
|
||||
// into multiple payloads and send each one separately. Otherwise,
|
||||
// just send the whole thing.
|
||||
var encodedPayload = encodeURIComponent(payload);
|
||||
var encodedLen = encodedPayload.length;
|
||||
var maxSize = goog.net.xpc.IframeRelayTransport.IE_PAYLOAD_MAX_SIZE_;
|
||||
|
||||
if (goog.userAgent.IE && encodedLen > maxSize) {
|
||||
// A probabilistically-unique string used to link together all fragments
|
||||
// in this message.
|
||||
var messageIdStr = goog.string.getRandomString();
|
||||
|
||||
for (var startIndex = 0, fragmentNum = 0; startIndex < encodedLen;
|
||||
fragmentNum++) {
|
||||
var payloadFragment = encodedPayload.substr(startIndex, maxSize);
|
||||
startIndex += maxSize;
|
||||
var fragmentIdStr =
|
||||
messageIdStr + (startIndex >= encodedLen ? '++' : '+') + fragmentNum;
|
||||
this.send_(service, payloadFragment, fragmentIdStr);
|
||||
}
|
||||
} else {
|
||||
this.send_(service, encodedPayload);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends an encoded message or message fragment.
|
||||
* @param {string} service Name of service this the message has to be delivered.
|
||||
* @param {string} encodedPayload The message content, URI encoded.
|
||||
* @param {string=} opt_fragmentIdStr If sending a fragment, a string that
|
||||
* identifies the fragment.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.prototype.send_ =
|
||||
function(service, encodedPayload, opt_fragmentIdStr) {
|
||||
// IE requires that we create the onload attribute inline, otherwise the
|
||||
// handler is not triggered
|
||||
if (goog.userAgent.IE) {
|
||||
var div = this.getWindow().document.createElement('div');
|
||||
div.innerHTML = '<iframe onload="this.xpcOnload()"></iframe>';
|
||||
var ifr = div.childNodes[0];
|
||||
div = null;
|
||||
ifr['xpcOnload'] = goog.net.xpc.IframeRelayTransport.iframeLoadHandler_;
|
||||
} else {
|
||||
var ifr = this.getWindow().document.createElement('iframe');
|
||||
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
// safari doesn't fire load-events on iframes.
|
||||
// keep a reference and remove after a timeout.
|
||||
goog.net.xpc.IframeRelayTransport.iframeRefs_.push({
|
||||
timestamp: goog.now(),
|
||||
iframeElement: ifr
|
||||
});
|
||||
} else {
|
||||
goog.events.listen(ifr, 'load',
|
||||
goog.net.xpc.IframeRelayTransport.iframeLoadHandler_);
|
||||
}
|
||||
}
|
||||
|
||||
var style = ifr.style;
|
||||
style.visibility = 'hidden';
|
||||
style.width = ifr.style.height = '0px';
|
||||
style.position = 'absolute';
|
||||
|
||||
var url = this.peerRelayUri_;
|
||||
url += '#' + this.channel_.name;
|
||||
if (this.peerIframeId_) {
|
||||
url += ',' + this.peerIframeId_;
|
||||
}
|
||||
url += '|' + service;
|
||||
if (opt_fragmentIdStr) {
|
||||
url += '|' + opt_fragmentIdStr;
|
||||
}
|
||||
url += ':' + encodedPayload;
|
||||
|
||||
ifr.src = url;
|
||||
|
||||
this.getWindow().document.body.appendChild(ifr);
|
||||
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'msg sent: ' + url);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The iframe load handler. Gets called as method on the iframe element.
|
||||
* @private
|
||||
* @this Element
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport.iframeLoadHandler_ = function() {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST, 'iframe-load');
|
||||
goog.dom.removeNode(this);
|
||||
this.xpcOnload = null;
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.IframeRelayTransport.prototype.disposeInternal = function() {
|
||||
goog.base(this, 'disposeInternal');
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
goog.net.xpc.IframeRelayTransport.cleanup_(0);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,649 @@
|
||||
// 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 class which uses native messaging
|
||||
* facilities for cross domain communication.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.NativeMessagingTransport');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The native messaging transport
|
||||
*
|
||||
* Uses document.postMessage() to send messages to other documents.
|
||||
* Receiving is done by listening on 'message'-events on the document.
|
||||
*
|
||||
* @param {goog.net.xpc.CrossPageChannel} channel The channel this
|
||||
* transport belongs to.
|
||||
* @param {string} peerHostname The hostname (protocol, domain, and port) of the
|
||||
* peer.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for
|
||||
* finding the correct window/document.
|
||||
* @param {boolean=} opt_oneSidedHandshake If this is true, only the outer
|
||||
* transport sends a SETUP message and expects a SETUP_ACK. The inner
|
||||
* transport goes connected when it receives the SETUP.
|
||||
* @param {number=} opt_protocolVersion Which version of its setup protocol the
|
||||
* transport should use. The default is '2'.
|
||||
* @constructor
|
||||
* @extends {goog.net.xpc.Transport}
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport = function(channel, peerHostname,
|
||||
opt_domHelper, opt_oneSidedHandshake, opt_protocolVersion) {
|
||||
goog.base(this, opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @type {goog.net.xpc.CrossPageChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/**
|
||||
* Which version of the transport's protocol should be used.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.protocolVersion_ = opt_protocolVersion || 2;
|
||||
goog.asserts.assert(this.protocolVersion_ >= 1);
|
||||
goog.asserts.assert(this.protocolVersion_ <= 2);
|
||||
|
||||
/**
|
||||
* The hostname of the peer. This parameterizes all calls to postMessage, and
|
||||
* should contain the precise protocol, domain, and port of the peer window.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.peerHostname_ = peerHostname || '*';
|
||||
|
||||
/**
|
||||
* The event handler.
|
||||
* @type {!goog.events.EventHandler}
|
||||
* @private
|
||||
*/
|
||||
this.eventHandler_ = new goog.events.EventHandler(this);
|
||||
|
||||
/**
|
||||
* Timer for connection reattempts.
|
||||
* @type {!goog.Timer}
|
||||
* @private
|
||||
*/
|
||||
this.maybeAttemptToConnectTimer_ = new goog.Timer(100, this.getWindow());
|
||||
|
||||
/**
|
||||
* Whether one-sided handshakes are enabled.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.oneSidedHandshake_ = !!opt_oneSidedHandshake;
|
||||
|
||||
/**
|
||||
* Fires once we've received our SETUP_ACK message.
|
||||
* @type {!goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
this.setupAckReceived_ = new goog.async.Deferred();
|
||||
|
||||
/**
|
||||
* Fires once we've sent our SETUP_ACK message.
|
||||
* @type {!goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
this.setupAckSent_ = new goog.async.Deferred();
|
||||
|
||||
/**
|
||||
* Fires once we're marked connected.
|
||||
* @type {!goog.async.Deferred}
|
||||
* @private
|
||||
*/
|
||||
this.connected_ = new goog.async.Deferred();
|
||||
|
||||
/**
|
||||
* The unique ID of this side of the connection. Used to determine when a peer
|
||||
* is reloaded.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.endpointId_ = goog.net.xpc.getRandomString(10);
|
||||
|
||||
/**
|
||||
* The unique ID of the peer. If we get a message from a peer with an ID we
|
||||
* don't expect, we reset the connection.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.peerEndpointId_ = null;
|
||||
|
||||
// We don't want to mark ourselves connected until we have sent whatever
|
||||
// message will cause our counterpart in the other frame to also declare
|
||||
// itself connected, if there is such a message. Otherwise we risk a user
|
||||
// message being sent in advance of that message, and it being discarded.
|
||||
if (this.oneSidedHandshake_) {
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) {
|
||||
// One sided handshake, inner frame:
|
||||
// SETUP_ACK must be received.
|
||||
this.connected_.awaitDeferred(this.setupAckReceived_);
|
||||
} else {
|
||||
// One sided handshake, outer frame:
|
||||
// SETUP_ACK must be sent.
|
||||
this.connected_.awaitDeferred(this.setupAckSent_);
|
||||
}
|
||||
} else {
|
||||
// Two sided handshake:
|
||||
// SETUP_ACK has to have been received, and sent.
|
||||
this.connected_.awaitDeferred(this.setupAckReceived_);
|
||||
if (this.protocolVersion_ == 2) {
|
||||
this.connected_.awaitDeferred(this.setupAckSent_);
|
||||
}
|
||||
}
|
||||
this.connected_.addCallback(this.notifyConnected_, this);
|
||||
this.connected_.callback(true);
|
||||
|
||||
this.eventHandler_.
|
||||
listen(this.maybeAttemptToConnectTimer_, goog.Timer.TICK,
|
||||
this.maybeAttemptToConnect_);
|
||||
|
||||
goog.log.info(goog.net.xpc.logger, 'NativeMessagingTransport created. ' +
|
||||
'protocolVersion=' + this.protocolVersion_ + ', oneSidedHandshake=' +
|
||||
this.oneSidedHandshake_ + ', role=' + this.channel_.getRole());
|
||||
};
|
||||
goog.inherits(goog.net.xpc.NativeMessagingTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
/**
|
||||
* Length of the delay in milliseconds between the channel being connected and
|
||||
* the connection callback being called, in cases where coverage of timing flaws
|
||||
* is required.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.CONNECTION_DELAY_MS_ = 200;
|
||||
|
||||
|
||||
/**
|
||||
* Current determination of peer's protocol version, or null for unknown.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.peerProtocolVersion_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating if this instance of the transport has been initialized.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.initialized_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.NATIVE_MESSAGING;
|
||||
|
||||
|
||||
/**
|
||||
* The delimiter used for transport service messages.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_ = ',';
|
||||
|
||||
|
||||
/**
|
||||
* Tracks the number of NativeMessagingTransport channels that have been
|
||||
* initialized but not disposed yet in a map keyed by the UID of the window
|
||||
* object. This allows for multiple windows to be initiallized and listening
|
||||
* for messages.
|
||||
* @type {Object.<number>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.activeCount_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Id of a timer user during postMessage sends.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.sendTimerId_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the peer transport protocol version could be as indicated.
|
||||
* @param {number} version The version to check for.
|
||||
* @return {boolean} Whether the peer transport protocol version is as
|
||||
* indicated, or null.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.couldPeerVersionBe_ =
|
||||
function(version) {
|
||||
return this.peerProtocolVersion_ == null ||
|
||||
this.peerProtocolVersion_ == version;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes this transport. Registers a listener for 'message'-events
|
||||
* on the document.
|
||||
* @param {Window} listenWindow The window to listen to events on.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.initialize_ = function(listenWindow) {
|
||||
var uid = goog.getUid(listenWindow);
|
||||
var value = goog.net.xpc.NativeMessagingTransport.activeCount_[uid];
|
||||
if (!goog.isNumber(value)) {
|
||||
value = 0;
|
||||
}
|
||||
if (value == 0) {
|
||||
// Listen for message-events. These are fired on window in FF3 and on
|
||||
// document in Opera.
|
||||
goog.events.listen(
|
||||
listenWindow.postMessage ? listenWindow : listenWindow.document,
|
||||
'message',
|
||||
goog.net.xpc.NativeMessagingTransport.messageReceived_,
|
||||
false,
|
||||
goog.net.xpc.NativeMessagingTransport);
|
||||
}
|
||||
goog.net.xpc.NativeMessagingTransport.activeCount_[uid] = value + 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Processes an incoming message-event.
|
||||
* @param {goog.events.BrowserEvent} msgEvt The message event.
|
||||
* @return {boolean} True if message was successfully delivered to a channel.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.messageReceived_ = function(msgEvt) {
|
||||
var data = msgEvt.getBrowserEvent().data;
|
||||
|
||||
if (!goog.isString(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var headDelim = data.indexOf('|');
|
||||
var serviceDelim = data.indexOf(':');
|
||||
|
||||
// make sure we got something reasonable
|
||||
if (headDelim == -1 || serviceDelim == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var channelName = data.substring(0, headDelim);
|
||||
var service = data.substring(headDelim + 1, serviceDelim);
|
||||
var payload = data.substring(serviceDelim + 1);
|
||||
|
||||
goog.log.fine(goog.net.xpc.logger,
|
||||
'messageReceived: channel=' + channelName +
|
||||
', service=' + service + ', payload=' + payload);
|
||||
|
||||
// Attempt to deliver message to the channel. Keep in mind that it may not
|
||||
// exist for several reasons, including but not limited to:
|
||||
// - a malformed message
|
||||
// - the channel simply has not been created
|
||||
// - channel was created in a different namespace
|
||||
// - message was sent to the wrong window
|
||||
// - channel has become stale (e.g. caching iframes and back clicks)
|
||||
var channel = goog.net.xpc.channels[channelName];
|
||||
if (channel) {
|
||||
channel.xpcDeliver(service, payload, msgEvt.getBrowserEvent().origin);
|
||||
return true;
|
||||
}
|
||||
|
||||
var transportMessageType =
|
||||
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_(payload)[0];
|
||||
|
||||
// Check if there are any stale channel names that can be updated.
|
||||
for (var staleChannelName in goog.net.xpc.channels) {
|
||||
var staleChannel = goog.net.xpc.channels[staleChannelName];
|
||||
if (staleChannel.getRole() == goog.net.xpc.CrossPageChannelRole.INNER &&
|
||||
!staleChannel.isConnected() &&
|
||||
service == goog.net.xpc.TRANSPORT_SERVICE_ &&
|
||||
(transportMessageType == goog.net.xpc.SETUP ||
|
||||
transportMessageType == goog.net.xpc.SETUP_NTPV2)) {
|
||||
// Inner peer received SETUP message but channel names did not match.
|
||||
// Start using the channel name sent from outer peer. The channel name
|
||||
// of the inner peer can easily become out of date, as iframe's and their
|
||||
// JS state get cached in many browsers upon page reload or history
|
||||
// navigation (particularly Firefox 1.5+). We can trust the outer peer,
|
||||
// since we only accept postMessage messages from the same hostname that
|
||||
// originally setup the channel.
|
||||
goog.log.fine(goog.net.xpc.logger,
|
||||
'changing channel name to ' + channelName);
|
||||
staleChannel.name = channelName;
|
||||
// Remove old stale pointer to channel.
|
||||
delete goog.net.xpc.channels[staleChannelName];
|
||||
// Create fresh pointer to channel.
|
||||
goog.net.xpc.channels[channelName] = staleChannel;
|
||||
staleChannel.xpcDeliver(service, payload);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Failed to find a channel to deliver this message to, so simply ignore it.
|
||||
goog.log.info(goog.net.xpc.logger, 'channel name mismatch; message ignored"');
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.transportServiceHandler =
|
||||
function(payload) {
|
||||
var transportParts =
|
||||
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_(payload);
|
||||
var transportMessageType = transportParts[0];
|
||||
var peerEndpointId = transportParts[1];
|
||||
switch (transportMessageType) {
|
||||
case goog.net.xpc.SETUP_ACK_:
|
||||
this.setPeerProtocolVersion_(1);
|
||||
if (!this.setupAckReceived_.hasFired()) {
|
||||
this.setupAckReceived_.callback(true);
|
||||
}
|
||||
break;
|
||||
case goog.net.xpc.SETUP_ACK_NTPV2:
|
||||
if (this.protocolVersion_ == 2) {
|
||||
this.setPeerProtocolVersion_(2);
|
||||
if (!this.setupAckReceived_.hasFired()) {
|
||||
this.setupAckReceived_.callback(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case goog.net.xpc.SETUP:
|
||||
this.setPeerProtocolVersion_(1);
|
||||
this.sendSetupAckMessage_(1);
|
||||
break;
|
||||
case goog.net.xpc.SETUP_NTPV2:
|
||||
if (this.protocolVersion_ == 2) {
|
||||
var prevPeerProtocolVersion = this.peerProtocolVersion_;
|
||||
this.setPeerProtocolVersion_(2);
|
||||
this.sendSetupAckMessage_(2);
|
||||
if ((prevPeerProtocolVersion == 1 || this.peerEndpointId_ != null) &&
|
||||
this.peerEndpointId_ != peerEndpointId) {
|
||||
// Send a new SETUP message since the peer has been replaced.
|
||||
goog.log.info(goog.net.xpc.logger,
|
||||
'Sending SETUP and changing peer ID to: ' + peerEndpointId);
|
||||
this.sendSetupMessage_();
|
||||
}
|
||||
this.peerEndpointId_ = peerEndpointId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a SETUP transport service message of the correct protocol number for
|
||||
* our current situation.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.sendSetupMessage_ =
|
||||
function() {
|
||||
// 'real' (legacy) v1 transports don't know about there being v2 ones out
|
||||
// there, and we shouldn't either.
|
||||
goog.asserts.assert(!(this.protocolVersion_ == 1 &&
|
||||
this.peerProtocolVersion_ == 2));
|
||||
|
||||
if (this.protocolVersion_ == 2 && this.couldPeerVersionBe_(2)) {
|
||||
var payload = goog.net.xpc.SETUP_NTPV2;
|
||||
payload += goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_;
|
||||
payload += this.endpointId_;
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, payload);
|
||||
}
|
||||
|
||||
// For backward compatibility reasons, the V1 SETUP message can be sent by
|
||||
// both V1 and V2 transports. Once a V2 transport has 'heard' another V2
|
||||
// transport it starts ignoring V1 messages, so the V2 message must be sent
|
||||
// first.
|
||||
if (this.couldPeerVersionBe_(1)) {
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a SETUP_ACK transport service message of the correct protocol number
|
||||
* for our current situation.
|
||||
* @param {number} protocolVersion The protocol version of the SETUP message
|
||||
* which gave rise to this ack message.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.sendSetupAckMessage_ =
|
||||
function(protocolVersion) {
|
||||
goog.asserts.assert(this.protocolVersion_ != 1 || protocolVersion != 2,
|
||||
'Shouldn\'t try to send a v2 setup ack in v1 mode.');
|
||||
if (this.protocolVersion_ == 2 && this.couldPeerVersionBe_(2) &&
|
||||
protocolVersion == 2) {
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_NTPV2);
|
||||
} else if (this.couldPeerVersionBe_(1) && protocolVersion == 1) {
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.setupAckSent_.hasFired()) {
|
||||
this.setupAckSent_.callback(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to set the peer protocol number. Downgrades from 2 to 1 are not
|
||||
* permitted.
|
||||
* @param {number} version The new protocol number.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.setPeerProtocolVersion_ =
|
||||
function(version) {
|
||||
if (version > this.peerProtocolVersion_) {
|
||||
this.peerProtocolVersion_ = version;
|
||||
}
|
||||
if (this.peerProtocolVersion_ == 1) {
|
||||
if (!this.setupAckSent_.hasFired() && !this.oneSidedHandshake_) {
|
||||
this.setupAckSent_.callback(true);
|
||||
}
|
||||
this.peerEndpointId_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Connects this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.connect = function() {
|
||||
goog.net.xpc.NativeMessagingTransport.initialize_(this.getWindow());
|
||||
this.initialized_ = true;
|
||||
this.maybeAttemptToConnect_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Connects to other peer. In the case of the outer peer, the setup messages are
|
||||
* likely sent before the inner peer is ready to receive them. Therefore, this
|
||||
* function will continue trying to send the SETUP message until the inner peer
|
||||
* responds. In the case of the inner peer, it will occasionally have its
|
||||
* channel name fall out of sync with the outer peer, particularly during
|
||||
* soft-reloads and history navigations.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.maybeAttemptToConnect_ =
|
||||
function() {
|
||||
// In a one-sided handshake, the outer frame does not send a SETUP message,
|
||||
// but the inner frame does.
|
||||
var outerFrame = this.channel_.getRole() ==
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER;
|
||||
if ((this.oneSidedHandshake_ && outerFrame) ||
|
||||
this.channel_.isConnected() ||
|
||||
this.isDisposed()) {
|
||||
this.maybeAttemptToConnectTimer_.stop();
|
||||
return;
|
||||
}
|
||||
this.maybeAttemptToConnectTimer_.start();
|
||||
this.sendSetupMessage_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
* @param {string} service The name off the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.send = function(service,
|
||||
payload) {
|
||||
var win = this.channel_.getPeerWindowObject();
|
||||
if (!win) {
|
||||
goog.log.fine(goog.net.xpc.logger, 'send(): window not ready');
|
||||
return;
|
||||
}
|
||||
|
||||
this.send = function(service, payload) {
|
||||
// In IE8 (and perhaps elsewhere), it seems like postMessage is sometimes
|
||||
// implemented as a synchronous call. That is, calling it synchronously
|
||||
// calls whatever listeners it has, and control is not returned to the
|
||||
// calling thread until those listeners are run. This produces different
|
||||
// ordering to all other browsers, and breaks this protocol. This timer
|
||||
// callback is introduced to produce standard behavior across all browsers.
|
||||
var transport = this;
|
||||
var channelName = this.channel_.name;
|
||||
var sendFunctor = function() {
|
||||
transport.sendTimerId_ = 0;
|
||||
|
||||
try {
|
||||
// postMessage is a method of the window object, except in some
|
||||
// versions of Opera, where it is a method of the document object. It
|
||||
// also seems that the appearance of postMessage on the peer window
|
||||
// object can sometimes be delayed.
|
||||
var obj = win.postMessage ? win : win.document;
|
||||
if (!obj.postMessage) {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'Peer window had no postMessage function.');
|
||||
return;
|
||||
}
|
||||
|
||||
obj.postMessage(channelName + '|' + service + ':' + payload,
|
||||
transport.peerHostname_);
|
||||
goog.log.fine(goog.net.xpc.logger, 'send(): service=' + service +
|
||||
' payload=' + payload + ' to hostname=' + transport.peerHostname_);
|
||||
} catch (error) {
|
||||
// There is some evidence (not totally convincing) that postMessage can
|
||||
// be missing or throw errors during a narrow timing window during
|
||||
// startup. This protects against that.
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'Error performing postMessage, ignoring.', error);
|
||||
}
|
||||
};
|
||||
this.sendTimerId_ = goog.Timer.callOnce(sendFunctor, 0);
|
||||
};
|
||||
this.send(service, payload);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Notify the channel that this transport is connected. If either transport is
|
||||
* protocol v1, a short delay is required to paper over timing vulnerabilities
|
||||
* in that protocol version.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.notifyConnected_ =
|
||||
function() {
|
||||
var delay = (this.protocolVersion_ == 1 || this.peerProtocolVersion_ == 1) ?
|
||||
goog.net.xpc.NativeMessagingTransport.CONNECTION_DELAY_MS_ : undefined;
|
||||
this.channel_.notifyConnected(delay);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.NativeMessagingTransport.prototype.disposeInternal = function() {
|
||||
if (this.initialized_) {
|
||||
var listenWindow = this.getWindow();
|
||||
var uid = goog.getUid(listenWindow);
|
||||
var value = goog.net.xpc.NativeMessagingTransport.activeCount_[uid];
|
||||
goog.net.xpc.NativeMessagingTransport.activeCount_[uid] = value - 1;
|
||||
if (value == 1) {
|
||||
goog.events.unlisten(
|
||||
listenWindow.postMessage ? listenWindow : listenWindow.document,
|
||||
'message',
|
||||
goog.net.xpc.NativeMessagingTransport.messageReceived_,
|
||||
false,
|
||||
goog.net.xpc.NativeMessagingTransport);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.sendTimerId_) {
|
||||
goog.Timer.clear(this.sendTimerId_);
|
||||
this.sendTimerId_ = 0;
|
||||
}
|
||||
|
||||
goog.dispose(this.eventHandler_);
|
||||
delete this.eventHandler_;
|
||||
|
||||
goog.dispose(this.maybeAttemptToConnectTimer_);
|
||||
delete this.maybeAttemptToConnectTimer_;
|
||||
|
||||
this.setupAckReceived_.cancel();
|
||||
delete this.setupAckReceived_;
|
||||
this.setupAckSent_.cancel();
|
||||
delete this.setupAckSent_;
|
||||
this.connected_.cancel();
|
||||
delete this.connected_;
|
||||
|
||||
// Cleaning up this.send as it is an instance method, created in
|
||||
// goog.net.xpc.NativeMessagingTransport.prototype.send and has a closure over
|
||||
// this.channel_.peerWindowObject_.
|
||||
delete this.send;
|
||||
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse a transport service payload message. For v1, it is simply expected to
|
||||
* be 'SETUP' or 'SETUP_ACK'. For v2, an example setup message is
|
||||
* 'SETUP_NTPV2,abc123', where the second part is the endpoint id. The v2 setup
|
||||
* ack message is simply 'SETUP_ACK_NTPV2'.
|
||||
* @param {string} payload The payload.
|
||||
* @return {!Array.<?string>} An array with the message type as the first member
|
||||
* and the endpoint id as the second, if one was sent, or null otherwise.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport.parseTransportPayload_ =
|
||||
function(payload) {
|
||||
var transportParts = /** @type {!Array.<?string>} */ (payload.split(
|
||||
goog.net.xpc.NativeMessagingTransport.MESSAGE_DELIMITER_));
|
||||
transportParts[1] = transportParts[1] || null;
|
||||
return transportParts;
|
||||
};
|
||||
@@ -0,0 +1,479 @@
|
||||
// Copyright 2008 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 NIX (Native IE XDC) method transport for
|
||||
* cross-domain communication. It exploits the fact that Internet Explorer
|
||||
* allows a window that is the parent of an iframe to set said iframe window's
|
||||
* opener property to an object. This object can be a function that in turn
|
||||
* can be used to send a message despite same-origin constraints. Note that
|
||||
* this function, if a pure JavaScript object, opens up the possibilitiy of
|
||||
* gaining a hold of the context of the other window and in turn, attacking
|
||||
* it. This implementation therefore wraps the JavaScript objects used inside
|
||||
* a VBScript class. Since VBScript objects are passed in JavaScript as a COM
|
||||
* wrapper (like DOM objects), they are thus opaque to JavaScript
|
||||
* (except for the interface they expose). This therefore provides a safe
|
||||
* method of transport.
|
||||
*
|
||||
*
|
||||
* Initially based on FrameElementTransport which shares some similarities
|
||||
* to this method.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.NixTransport');
|
||||
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.reflect');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* NIX method transport.
|
||||
*
|
||||
* NOTE(user): NIX method tested in all IE versions starting from 6.0.
|
||||
*
|
||||
* @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.NixTransport = 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 authorization token, if any, used by this transport.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.authToken_ = channel[goog.net.xpc.CfgFields.AUTH_TOKEN] || '';
|
||||
|
||||
/**
|
||||
* The authorization token, if any, that must be sent by the other party
|
||||
* for setup to occur.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.remoteAuthToken_ =
|
||||
channel[goog.net.xpc.CfgFields.REMOTE_AUTH_TOKEN] || '';
|
||||
|
||||
// Conduct the setup work for NIX in general, if need be.
|
||||
goog.net.xpc.NixTransport.conductGlobalSetup_(this.getWindow());
|
||||
|
||||
// Setup aliases so that VBScript can call these methods
|
||||
// on the transport class, even if they are renamed during
|
||||
// compression.
|
||||
this[goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE] = this.handleMessage_;
|
||||
this[goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL] = this.createChannel_;
|
||||
};
|
||||
goog.inherits(goog.net.xpc.NixTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
// Consts for NIX. VBScript doesn't allow items to start with _ for some
|
||||
// reason, so we need to make these names quite unique, as they will go into
|
||||
// the global namespace.
|
||||
|
||||
|
||||
/**
|
||||
* Global name of the Wrapper VBScript class.
|
||||
* Note that this class will be stored in the *global*
|
||||
* namespace (i.e. window in browsers).
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_WRAPPER = 'GCXPC____NIXVBS_wrapper';
|
||||
|
||||
|
||||
/**
|
||||
* Global name of the GetWrapper VBScript function. This
|
||||
* constant is used by JavaScript to call this function.
|
||||
* Note that this function will be stored in the *global*
|
||||
* namespace (i.e. window in browsers).
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_GET_WRAPPER = 'GCXPC____NIXVBS_get_wrapper';
|
||||
|
||||
|
||||
/**
|
||||
* The name of the handle message method used by the wrapper class
|
||||
* when calling the transport.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE = 'GCXPC____NIXJS_handle_message';
|
||||
|
||||
|
||||
/**
|
||||
* The name of the create channel method used by the wrapper class
|
||||
* when calling the transport.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL = 'GCXPC____NIXJS_create_channel';
|
||||
|
||||
|
||||
/**
|
||||
* A "unique" identifier that is stored in the wrapper
|
||||
* class so that the wrapper can be distinguished from
|
||||
* other objects easily.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.NixTransport.NIX_ID_FIELD = 'GCXPC____NIXVBS_container';
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the installed version of IE supports accessing window.opener
|
||||
* after it has been set to a non-Window/null value. NIX relies on this being
|
||||
* possible.
|
||||
* @return {boolean} Whether window.opener behavior is compatible with NIX.
|
||||
*/
|
||||
goog.net.xpc.NixTransport.isNixSupported = function() {
|
||||
var isSupported = false;
|
||||
try {
|
||||
var oldOpener = window.opener;
|
||||
// The compiler complains (as it should!) if we set window.opener to
|
||||
// something other than a window or null.
|
||||
window.opener = /** @type {Window} */ ({});
|
||||
isSupported = goog.reflect.canAccessProperty(window, 'opener');
|
||||
window.opener = oldOpener;
|
||||
} catch (e) { }
|
||||
return isSupported;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Conducts the global setup work for the NIX transport method.
|
||||
* This function creates and then injects into the page the
|
||||
* VBScript code necessary to create the NIX wrapper class.
|
||||
* Note that this method can be called multiple times, as
|
||||
* it internally checks whether the work is necessary before
|
||||
* proceeding.
|
||||
* @param {Window} listenWindow The window containing the affected page.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.conductGlobalSetup_ = function(listenWindow) {
|
||||
if (listenWindow['nix_setup_complete']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject the VBScript code needed.
|
||||
var vbscript =
|
||||
// We create a class to act as a wrapper for
|
||||
// a Javascript call, to prevent a break in of
|
||||
// the context.
|
||||
'Class ' + goog.net.xpc.NixTransport.NIX_WRAPPER + '\n ' +
|
||||
|
||||
// An internal member for keeping track of the
|
||||
// transport for which this wrapper exists.
|
||||
'Private m_Transport\n' +
|
||||
|
||||
// An internal member for keeping track of the
|
||||
// auth token associated with the context that
|
||||
// created this wrapper. Used for validation
|
||||
// purposes.
|
||||
'Private m_Auth\n' +
|
||||
|
||||
// Method for internally setting the value
|
||||
// of the m_Transport property. We have the
|
||||
// isEmpty check to prevent the transport
|
||||
// from being overridden with an illicit
|
||||
// object by a malicious party.
|
||||
'Public Sub SetTransport(transport)\n' +
|
||||
'If isEmpty(m_Transport) Then\n' +
|
||||
'Set m_Transport = transport\n' +
|
||||
'End If\n' +
|
||||
'End Sub\n' +
|
||||
|
||||
// Method for internally setting the value
|
||||
// of the m_Auth property. We have the
|
||||
// isEmpty check to prevent the transport
|
||||
// from being overridden with an illicit
|
||||
// object by a malicious party.
|
||||
'Public Sub SetAuth(auth)\n' +
|
||||
'If isEmpty(m_Auth) Then\n' +
|
||||
'm_Auth = auth\n' +
|
||||
'End If\n' +
|
||||
'End Sub\n' +
|
||||
|
||||
// Returns the auth token to the gadget, so it can
|
||||
// confirm a match before initiating the connection
|
||||
'Public Function GetAuthToken()\n ' +
|
||||
'GetAuthToken = m_Auth\n' +
|
||||
'End Function\n' +
|
||||
|
||||
// A wrapper method which causes a
|
||||
// message to be sent to the other context.
|
||||
'Public Sub SendMessage(service, payload)\n ' +
|
||||
'Call m_Transport.' +
|
||||
goog.net.xpc.NixTransport.NIX_HANDLE_MESSAGE + '(service, payload)\n' +
|
||||
'End Sub\n' +
|
||||
|
||||
// Method for setting up the inner->outer
|
||||
// channel.
|
||||
'Public Sub CreateChannel(channel)\n ' +
|
||||
'Call m_Transport.' +
|
||||
goog.net.xpc.NixTransport.NIX_CREATE_CHANNEL + '(channel)\n' +
|
||||
'End Sub\n' +
|
||||
|
||||
// An empty field with a unique identifier to
|
||||
// prevent the code from confusing this wrapper
|
||||
// with a run-of-the-mill value found in window.opener.
|
||||
'Public Sub ' + goog.net.xpc.NixTransport.NIX_ID_FIELD + '()\n ' +
|
||||
'End Sub\n' +
|
||||
'End Class\n ' +
|
||||
|
||||
// Function to get a reference to the wrapper.
|
||||
'Function ' +
|
||||
goog.net.xpc.NixTransport.NIX_GET_WRAPPER + '(transport, auth)\n' +
|
||||
'Dim wrap\n' +
|
||||
'Set wrap = New ' + goog.net.xpc.NixTransport.NIX_WRAPPER + '\n' +
|
||||
'wrap.SetTransport transport\n' +
|
||||
'wrap.SetAuth auth\n' +
|
||||
'Set ' + goog.net.xpc.NixTransport.NIX_GET_WRAPPER + ' = wrap\n' +
|
||||
'End Function';
|
||||
|
||||
try {
|
||||
listenWindow.execScript(vbscript, 'vbscript');
|
||||
listenWindow['nix_setup_complete'] = true;
|
||||
}
|
||||
catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'exception caught while attempting global setup: ' + e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.NIX;
|
||||
|
||||
|
||||
/**
|
||||
* Keeps track of whether the local setup has completed (i.e.
|
||||
* the initial work towards setting the channel up has been
|
||||
* completed for this end).
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.localSetupCompleted_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* The NIX channel used to talk to the other page. This
|
||||
* object is in fact a reference to a VBScript class
|
||||
* (see above) and as such, is in fact a COM wrapper.
|
||||
* When using this object, make sure to not access methods
|
||||
* without calling them, otherwise a COM error will be thrown.
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.nixChannel_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Connect this transport.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.connect = function() {
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.OUTER) {
|
||||
this.attemptOuterSetup_();
|
||||
} else {
|
||||
this.attemptInnerSetup_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to setup the channel from the perspective
|
||||
* of the outer (read: container) page. This method
|
||||
* will attempt to create a NIX wrapper for this transport
|
||||
* and place it into the "opener" property of the inner
|
||||
* page's window object. If it fails, it will continue
|
||||
* to loop until it does so.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.attemptOuterSetup_ = function() {
|
||||
if (this.localSetupCompleted_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get shortcut to iframe-element that contains the inner
|
||||
// page.
|
||||
var innerFrame = this.channel_.getIframeElement();
|
||||
|
||||
try {
|
||||
// Attempt to place the NIX wrapper object into the inner
|
||||
// frame's opener property.
|
||||
var theWindow = this.getWindow();
|
||||
var getWrapper = theWindow[goog.net.xpc.NixTransport.NIX_GET_WRAPPER];
|
||||
innerFrame.contentWindow.opener = getWrapper(this, this.authToken_);
|
||||
this.localSetupCompleted_ = true;
|
||||
}
|
||||
catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'exception caught while attempting setup: ' + e);
|
||||
}
|
||||
|
||||
// If the retry is necessary, reattempt this setup.
|
||||
if (!this.localSetupCompleted_) {
|
||||
this.getWindow().setTimeout(goog.bind(this.attemptOuterSetup_, this), 100);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to setup the channel from the perspective
|
||||
* of the inner (read: iframe) page. This method
|
||||
* will attempt to *read* the opener object from the
|
||||
* page's opener property. If it succeeds, this object
|
||||
* is saved into nixChannel_ and the channel is confirmed
|
||||
* with the container by calling CreateChannel with an instance
|
||||
* of a wrapper for *this* page. Note that if this method
|
||||
* fails, it will continue to loop until it succeeds.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.attemptInnerSetup_ = function() {
|
||||
if (this.localSetupCompleted_) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var opener = this.getWindow().opener;
|
||||
|
||||
// Ensure that the object contained inside the opener
|
||||
// property is in fact a NIX wrapper.
|
||||
if (opener && goog.net.xpc.NixTransport.NIX_ID_FIELD in opener) {
|
||||
this.nixChannel_ = opener;
|
||||
|
||||
// Ensure that the NIX channel given to use is valid.
|
||||
var remoteAuthToken = this.nixChannel_['GetAuthToken']();
|
||||
|
||||
if (remoteAuthToken != this.remoteAuthToken_) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'Invalid auth token from other party');
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete the construction of the channel by sending our own
|
||||
// wrapper to the container via the channel they gave us.
|
||||
var theWindow = this.getWindow();
|
||||
var getWrapper = theWindow[goog.net.xpc.NixTransport.NIX_GET_WRAPPER];
|
||||
this.nixChannel_['CreateChannel'](getWrapper(this, this.authToken_));
|
||||
|
||||
this.localSetupCompleted_ = true;
|
||||
|
||||
// Notify channel that the transport is ready.
|
||||
this.channel_.notifyConnected();
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'exception caught while attempting setup: ' + e);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the retry is necessary, reattempt this setup.
|
||||
if (!this.localSetupCompleted_) {
|
||||
this.getWindow().setTimeout(goog.bind(this.attemptInnerSetup_, this), 100);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal method called by the inner page, via the
|
||||
* NIX wrapper, to complete the setup of the channel.
|
||||
*
|
||||
* @param {Object} channel The NIX wrapper of the
|
||||
* inner page.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.createChannel_ = function(channel) {
|
||||
// Verify that the channel is in fact a NIX wrapper.
|
||||
if (typeof channel != 'unknown' ||
|
||||
!(goog.net.xpc.NixTransport.NIX_ID_FIELD in channel)) {
|
||||
goog.log.error(goog.net.xpc.logger,
|
||||
'Invalid NIX channel given to createChannel_');
|
||||
}
|
||||
|
||||
this.nixChannel_ = channel;
|
||||
|
||||
// Ensure that the NIX channel given to use is valid.
|
||||
var remoteAuthToken = this.nixChannel_['GetAuthToken']();
|
||||
|
||||
if (remoteAuthToken != this.remoteAuthToken_) {
|
||||
goog.log.error(goog.net.xpc.logger, 'Invalid auth token from other party');
|
||||
return;
|
||||
}
|
||||
|
||||
// Indicate to the CrossPageChannel that the channel is setup
|
||||
// and ready to use.
|
||||
this.channel_.notifyConnected();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Internal method called by the other page, via the NIX wrapper,
|
||||
* to deliver a message.
|
||||
* @param {string} serviceName The name of the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message to process.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.handleMessage_ =
|
||||
function(serviceName, payload) {
|
||||
/** @this {goog.net.xpc.NixTransport} */
|
||||
var deliveryHandler = function() {
|
||||
this.channel_.xpcDeliver(serviceName, payload);
|
||||
};
|
||||
this.getWindow().setTimeout(goog.bind(deliveryHandler, this), 1);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
* @param {string} service The name of the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.NixTransport.prototype.send = function(service, payload) {
|
||||
// Verify that the NIX channel we have is valid.
|
||||
if (typeof(this.nixChannel_) !== 'unknown') {
|
||||
goog.log.error(goog.net.xpc.logger, 'NIX channel not connected');
|
||||
}
|
||||
|
||||
// Send the message via the NIX wrapper object.
|
||||
this.nixChannel_['SendMessage'](service, payload);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.net.xpc.NixTransport.prototype.disposeInternal = function() {
|
||||
goog.base(this, 'disposeInternal');
|
||||
this.nixChannel_ = null;
|
||||
};
|
||||
72
nicer-api-docs/closure-library/closure/goog/net/xpc/relay.js
Normal file
72
nicer-api-docs/closure-library/closure/goog/net/xpc/relay.js
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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 Standalone script to be included in the relay-document
|
||||
* used by goog.net.xpc.IframeRelayTransport. This script will decode the
|
||||
* fragment identifier, determine the target window object and deliver
|
||||
* the data to it.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.relay');
|
||||
|
||||
(function() {
|
||||
// Decode the fragement identifier.
|
||||
// location.href is expected to be structured as follows:
|
||||
// <url>#<channel_name>[,<iframe_id>]|<data>
|
||||
|
||||
// Get the fragment identifier.
|
||||
var raw = window.location.hash;
|
||||
if (!raw) {
|
||||
return;
|
||||
}
|
||||
if (raw.charAt(0) == '#') {
|
||||
raw = raw.substring(1);
|
||||
}
|
||||
var pos = raw.indexOf('|');
|
||||
var head = raw.substring(0, pos).split(',');
|
||||
var channelName = head[0];
|
||||
var iframeId = head.length == 2 ? head[1] : null;
|
||||
var frame = raw.substring(pos + 1);
|
||||
|
||||
// Find the window object of the peer.
|
||||
//
|
||||
// The general structure of the frames looks like this:
|
||||
// - peer1
|
||||
// - relay2
|
||||
// - peer2
|
||||
// - relay1
|
||||
//
|
||||
// We are either relay1 or relay2.
|
||||
|
||||
var win;
|
||||
if (iframeId) {
|
||||
// We are relay2 and need to deliver the data to peer2.
|
||||
win = window.parent.frames[iframeId];
|
||||
} else {
|
||||
// We are relay1 and need to deliver the data to peer1.
|
||||
win = window.parent.parent;
|
||||
}
|
||||
|
||||
// Deliver the data.
|
||||
try {
|
||||
win['xpcRelay'](channelName, frame);
|
||||
} catch (e) {
|
||||
// Nothing useful can be done here.
|
||||
// It would be great to inform the sender the delivery of this message
|
||||
// failed, but this is not possible because we are already in the receiver's
|
||||
// domain at this point.
|
||||
}
|
||||
})();
|
||||
105
nicer-api-docs/closure-library/closure/goog/net/xpc/transport.js
Normal file
105
nicer-api-docs/closure-library/closure/goog/net/xpc/transport.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// 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 base class for transports.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.Transport');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.net.xpc');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The base class for transports.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for
|
||||
* finding the window objects.
|
||||
* @constructor
|
||||
* @extends {goog.Disposable};
|
||||
*/
|
||||
goog.net.xpc.Transport = function(opt_domHelper) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* The dom helper to use for finding the window objects to reference.
|
||||
* @type {goog.dom.DomHelper}
|
||||
* @private
|
||||
*/
|
||||
this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
|
||||
};
|
||||
goog.inherits(goog.net.xpc.Transport, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @protected
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.transportType = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} The transport type identifier.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.getType = function() {
|
||||
return this.transportType;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the window associated with this transport instance.
|
||||
* @return {Window} The window to use.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.getWindow = function() {
|
||||
return this.domHelper_.getWindow();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the transport name.
|
||||
* @return {string} the transport name.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.getName = function() {
|
||||
return goog.net.xpc.TransportNames[this.transportType] || '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages (internal signalling).
|
||||
* @param {string} payload The message content.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.transportServiceHandler = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Connects this transport.
|
||||
* The transport implementation is expected to call
|
||||
* CrossPageChannel.prototype.notifyConnected when the channel is ready
|
||||
* to be used.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.connect = goog.abstractMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Sends a message.
|
||||
* @param {string} service The name off the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
*/
|
||||
goog.net.xpc.Transport.prototype.send = goog.abstractMethod;
|
||||
293
nicer-api-docs/closure-library/closure/goog/net/xpc/xpc.js
Normal file
293
nicer-api-docs/closure-library/closure/goog/net/xpc/xpc.js
Normal file
@@ -0,0 +1,293 @@
|
||||
// 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 Provides the namesspace for client-side communication
|
||||
* between pages originating from different domains (it works also
|
||||
* with pages from the same domain, but doing that is kinda
|
||||
* pointless).
|
||||
*
|
||||
* The only publicly visible class is goog.net.xpc.CrossPageChannel.
|
||||
*
|
||||
* Note: The preferred name for the main class would have been
|
||||
* CrossDomainChannel. But as there already is a class named like
|
||||
* that (which serves a different purpose) in the maps codebase,
|
||||
* CrossPageChannel was chosen to avoid confusion.
|
||||
*
|
||||
* CrossPageChannel abstracts the underlying transport mechanism to
|
||||
* provide a common interface in all browsers.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO(user)
|
||||
- resolve fastback issues in Safari (IframeRelayTransport)
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Namespace for CrossPageChannel
|
||||
*/
|
||||
goog.provide('goog.net.xpc');
|
||||
goog.provide('goog.net.xpc.CfgFields');
|
||||
goog.provide('goog.net.xpc.ChannelStates');
|
||||
goog.provide('goog.net.xpc.TransportNames');
|
||||
goog.provide('goog.net.xpc.TransportTypes');
|
||||
goog.provide('goog.net.xpc.UriCfgFields');
|
||||
|
||||
goog.require('goog.log');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Enum used to identify transport types.
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.xpc.TransportTypes = {
|
||||
NATIVE_MESSAGING: 1,
|
||||
FRAME_ELEMENT_METHOD: 2,
|
||||
IFRAME_RELAY: 3,
|
||||
IFRAME_POLLING: 4,
|
||||
FLASH: 5,
|
||||
NIX: 6
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enum containing transport names. These need to correspond to the
|
||||
* transport class names for createTransport_() to work.
|
||||
* @type {Object}
|
||||
*/
|
||||
goog.net.xpc.TransportNames = {
|
||||
'1': 'NativeMessagingTransport',
|
||||
'2': 'FrameElementMethodTransport',
|
||||
'3': 'IframeRelayTransport',
|
||||
'4': 'IframePollingTransport',
|
||||
'5': 'FlashTransport',
|
||||
'6': 'NixTransport'
|
||||
};
|
||||
|
||||
|
||||
// TODO(user): Add auth token support to other methods.
|
||||
|
||||
|
||||
/**
|
||||
* Field names used on configuration object.
|
||||
* @type {Object}
|
||||
*/
|
||||
goog.net.xpc.CfgFields = {
|
||||
/**
|
||||
* Channel name identifier.
|
||||
* Both peers have to be initialized with
|
||||
* the same channel name. If not present, a channel name is
|
||||
* generated (which then has to transferred to the peer somehow).
|
||||
*/
|
||||
CHANNEL_NAME: 'cn',
|
||||
/**
|
||||
* Authorization token. If set, NIX will use this authorization token
|
||||
* to validate the setup.
|
||||
*/
|
||||
AUTH_TOKEN: 'at',
|
||||
/**
|
||||
* Remote party's authorization token. If set, NIX will validate this
|
||||
* authorization token against that sent by the other party.
|
||||
*/
|
||||
REMOTE_AUTH_TOKEN: 'rat',
|
||||
/**
|
||||
* The URI of the peer page.
|
||||
*/
|
||||
PEER_URI: 'pu',
|
||||
/**
|
||||
* Ifame-ID identifier.
|
||||
* The id of the iframe element the peer-document lives in.
|
||||
*/
|
||||
IFRAME_ID: 'ifrid',
|
||||
/**
|
||||
* Transport type identifier.
|
||||
* The transport type to use. Possible values are entries from
|
||||
* goog.net.xpc.TransportTypes. If not present, the transport is
|
||||
* determined automatically based on the useragent's capabilities.
|
||||
*/
|
||||
TRANSPORT: 'tp',
|
||||
/**
|
||||
* Local relay URI identifier (IframeRelayTransport-specific).
|
||||
* The URI (can't contain a fragment identifier) used by the peer to
|
||||
* relay data through.
|
||||
*/
|
||||
LOCAL_RELAY_URI: 'lru',
|
||||
/**
|
||||
* Peer relay URI identifier (IframeRelayTransport-specific).
|
||||
* The URI (can't contain a fragment identifier) used to relay data
|
||||
* to the peer.
|
||||
*/
|
||||
PEER_RELAY_URI: 'pru',
|
||||
/**
|
||||
* Local poll URI identifier (IframePollingTransport-specific).
|
||||
* The URI (can't contain a fragment identifier)which is polled
|
||||
* to receive data from the peer.
|
||||
*/
|
||||
LOCAL_POLL_URI: 'lpu',
|
||||
/**
|
||||
* Local poll URI identifier (IframePollingTransport-specific).
|
||||
* The URI (can't contain a fragment identifier) used to send data
|
||||
* to the peer.
|
||||
*/
|
||||
PEER_POLL_URI: 'ppu',
|
||||
/**
|
||||
* The hostname of the peer window, including protocol, domain, and port
|
||||
* (if specified). Used for security sensitive applications that make
|
||||
* use of NativeMessagingTransport (i.e. most applications).
|
||||
*/
|
||||
PEER_HOSTNAME: 'ph',
|
||||
/**
|
||||
* Usually both frames using a connection initially send a SETUP message to
|
||||
* each other, and each responds with a SETUP_ACK. A frame marks itself
|
||||
* connected when it receives that SETUP_ACK. If this parameter is true
|
||||
* however, the channel it is passed to will not send a SETUP, but rather will
|
||||
* wait for one from its peer and mark itself connected when that arrives.
|
||||
* Peer iframes created using such a channel will send SETUP however, and will
|
||||
* wait for SETUP_ACK before marking themselves connected. The goal is to
|
||||
* cope with a situation where the availability of the URL for the peer frame
|
||||
* cannot be relied on, eg when the application is offline. Without this
|
||||
* setting, the primary frame will attempt to send its SETUP message every
|
||||
* 100ms, forever. This floods the javascript console with uncatchable
|
||||
* security warnings, and fruitlessly burns CPU. There is one scenario this
|
||||
* mode will not support, and that is reconnection by the outer frame, ie the
|
||||
* creation of a new channel object to connect to a peer iframe which was
|
||||
* already communicating with a previous channel object of the same name. If
|
||||
* that behavior is needed, this mode should not be used. Reconnection by
|
||||
* inner frames is supported in this mode however.
|
||||
*/
|
||||
ONE_SIDED_HANDSHAKE: 'osh',
|
||||
/**
|
||||
* The frame role (inner or outer). Used to explicitly indicate the role for
|
||||
* each peer whenever the role cannot be reliably determined (e.g. the two
|
||||
* peer windows are not parent/child frames). If unspecified, the role will
|
||||
* be dynamically determined, assuming a parent/child frame setup.
|
||||
*/
|
||||
ROLE: 'role',
|
||||
/**
|
||||
* Which version of the native transport startup protocol should be used, the
|
||||
* default being '2'. Version 1 had various timing vulnerabilities, which
|
||||
* had to be compensated for by introducing delays, and is deprecated. V1
|
||||
* and V2 are broadly compatible, although the more robust timing and lack
|
||||
* of delays is not gained unless both sides are using V2. The only
|
||||
* unsupported case of cross-protocol interoperation is where a connection
|
||||
* starts out with V2 at both ends, and one of the ends reconnects as a V1.
|
||||
* All other initial startup and reconnection scenarios are supported.
|
||||
*/
|
||||
NATIVE_TRANSPORT_PROTOCOL_VERSION: 'nativeProtocolVersion'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Config properties that need to be URL sanitized.
|
||||
* @type {Array}.<string>
|
||||
*/
|
||||
goog.net.xpc.UriCfgFields = [
|
||||
goog.net.xpc.CfgFields.PEER_URI,
|
||||
goog.net.xpc.CfgFields.LOCAL_RELAY_URI,
|
||||
goog.net.xpc.CfgFields.PEER_RELAY_URI,
|
||||
goog.net.xpc.CfgFields.LOCAL_POLL_URI,
|
||||
goog.net.xpc.CfgFields.PEER_POLL_URI
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
goog.net.xpc.ChannelStates = {
|
||||
NOT_CONNECTED: 1,
|
||||
CONNECTED: 2,
|
||||
CLOSED: 3
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The name of the transport service (used for internal signalling).
|
||||
* @type {string}
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.net.xpc.TRANSPORT_SERVICE_ = 'tp';
|
||||
|
||||
|
||||
/**
|
||||
* Transport signaling message: setup.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.SETUP = 'SETUP';
|
||||
|
||||
|
||||
/**
|
||||
* Transport signaling message: setup for native transport protocol v2.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.SETUP_NTPV2 = 'SETUP_NTPV2';
|
||||
|
||||
|
||||
/**
|
||||
* Transport signaling message: setup acknowledgement.
|
||||
* @type {string}
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
goog.net.xpc.SETUP_ACK_ = 'SETUP_ACK';
|
||||
|
||||
|
||||
/**
|
||||
* Transport signaling message: setup acknowledgement.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.SETUP_ACK_NTPV2 = 'SETUP_ACK_NTPV2';
|
||||
|
||||
|
||||
/**
|
||||
* Object holding active channels.
|
||||
* Package private. Do not call from outside goog.net.xpc.
|
||||
*
|
||||
* @type {Object.<string, goog.net.xpc.CrossPageChannel>}
|
||||
*/
|
||||
goog.net.xpc.channels = {};
|
||||
|
||||
|
||||
/**
|
||||
* Returns a random string.
|
||||
* @param {number} length How many characters the string shall contain.
|
||||
* @param {string=} opt_characters The characters used.
|
||||
* @return {string} The random string.
|
||||
*/
|
||||
goog.net.xpc.getRandomString = function(length, opt_characters) {
|
||||
var chars = opt_characters || goog.net.xpc.randomStringCharacters_;
|
||||
var charsLength = chars.length;
|
||||
var s = '';
|
||||
while (length-- > 0) {
|
||||
s += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The default characters used for random string generation.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.randomStringCharacters_ =
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
* @type {goog.log.Logger}
|
||||
*/
|
||||
goog.net.xpc.logger = goog.log.getLogger('goog.net.xpc');
|
||||
Reference in New Issue
Block a user