Adding mapbox-gl branch
This commit is contained in:
@@ -0,0 +1,861 @@
|
||||
// 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.Uri');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.async.Delay');
|
||||
goog.require('goog.dispose');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.messaging.AbstractChannel');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CfgFields');
|
||||
goog.require('goog.net.xpc.ChannelStates');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.DirectTransport');
|
||||
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.TransportTypes');
|
||||
goog.require('goog.net.xpc.UriCfgFields');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.uri.utils');
|
||||
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.net.xpc.CrossPageChannel.base(this, 'constructor');
|
||||
|
||||
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. Please use
|
||||
* <code>updateChannelNameAndCatalog</code> to change this from the transports
|
||||
* vs changing the property directly.
|
||||
* @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<!goog.net.xpc.CrossPageChannel>}
|
||||
* @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;
|
||||
|
||||
if (!goog.events.getListener(window, goog.events.EventType.UNLOAD,
|
||||
goog.net.xpc.CrossPageChannel.disposeAll_)) {
|
||||
// Set listener to dispose all registered channels on page unload.
|
||||
goog.events.listenOnce(window, goog.events.EventType.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.
|
||||
*
|
||||
* @return {Object} The window object of the peer.
|
||||
* @package
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.getPeerWindowObject = function() {
|
||||
return this.peerWindowObject_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the peer window is available (e.g. not closed).
|
||||
*
|
||||
* @return {boolean} Whether the peer window is available.
|
||||
* @package
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO(user): Use goog.scope.
|
||||
var CfgFields = goog.net.xpc.CfgFields;
|
||||
|
||||
if (!this.cfg_[CfgFields.TRANSPORT]) {
|
||||
this.cfg_[CfgFields.TRANSPORT] =
|
||||
this.determineTransportType_();
|
||||
}
|
||||
|
||||
switch (this.cfg_[CfgFields.TRANSPORT]) {
|
||||
case goog.net.xpc.TransportTypes.NATIVE_MESSAGING:
|
||||
var protocolVersion = this.cfg_[
|
||||
CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] || 2;
|
||||
this.transport_ = new goog.net.xpc.NativeMessagingTransport(
|
||||
this,
|
||||
this.cfg_[CfgFields.PEER_HOSTNAME],
|
||||
this.domHelper_,
|
||||
!!this.cfg_[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;
|
||||
case goog.net.xpc.TransportTypes.DIRECT:
|
||||
if (this.peerWindowObject_ &&
|
||||
goog.net.xpc.DirectTransport.isSupported(/** @type {!Window} */ (
|
||||
this.peerWindowObject_))) {
|
||||
this.transport_ =
|
||||
new goog.net.xpc.DirectTransport(this, this.domHelper_);
|
||||
} else {
|
||||
goog.log.info(
|
||||
goog.net.xpc.logger,
|
||||
'DirectTransport not supported for this window, peer window in' +
|
||||
' different security context or not set yet.');
|
||||
}
|
||||
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_.listenOnceWithScope(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 this channel was previously closed, transition back to the NOT_CONNECTED
|
||||
// state to ensure that the connection can proceed (xpcDeliver blocks
|
||||
// transport messages while the connection state is CLOSED).
|
||||
if (this.state_ == goog.net.xpc.ChannelStates.CLOSED) {
|
||||
this.state_ = goog.net.xpc.ChannelStates.NOT_CONNECTED;
|
||||
}
|
||||
|
||||
// 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 (goog.isDef(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.
|
||||
*
|
||||
* @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.
|
||||
* @package
|
||||
*/
|
||||
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 there is another channel still open, the native transport's global
|
||||
// postMessage listener will still be active. This will mean that messages
|
||||
// being sent to the now-closed channel will still be received and delivered,
|
||||
// such as transport service traffic from its previous correspondent in the
|
||||
// other frame. Ensure these messages don't cause exceptions.
|
||||
// Example: http://b/12419303
|
||||
if (this.isDisposed() || this.state_ == goog.net.xpc.ChannelStates.CLOSED) {
|
||||
goog.log.warning(goog.net.xpc.logger,
|
||||
'CrossPageChannel::xpcDeliver(): Channel closed.');
|
||||
} 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 (goog.isNumber(role)) {
|
||||
return role;
|
||||
} else {
|
||||
return window.parent == this.peerWindowObject_ ?
|
||||
goog.net.xpc.CrossPageChannelRole.INNER :
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the channel name. Note, this doesn't establish a unique channel to
|
||||
* communicate on.
|
||||
* @param {string} name The new channel name.
|
||||
*/
|
||||
goog.net.xpc.CrossPageChannel.prototype.updateChannelNameAndCatalog = function(
|
||||
name) {
|
||||
goog.log.fine(goog.net.xpc.logger, 'changing channel name to ' + name);
|
||||
delete goog.net.xpc.channels[this.name];
|
||||
this.name = name;
|
||||
goog.net.xpc.channels[name] = this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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.isEmptyOrWhitespace(goog.string.makeSafe(opt_origin)) ||
|
||||
goog.string.isEmptyOrWhitespace(goog.string.makeSafe(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.net.xpc.CrossPageChannel.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,36 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Author: dbk@google.com (David Barrett-Kahn)
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
CrossPageChannel: End-to-End Test
|
||||
</title>
|
||||
<script type="text/javascript" src="../../base.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.net.xpc.CrossPageChannelTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Debug box. -->
|
||||
<div style="position:absolute; float: right; top: 10px; right: 10px; width: 500px">
|
||||
Debug [
|
||||
<a href="#" onclick="document.getElementById('debugDiv').innerHTML = '';">
|
||||
clear
|
||||
</a>
|
||||
]:
|
||||
<br />
|
||||
<div id="debugDiv" style="border: 1px #000000 solid; font-size:xx-small; background: #fff;">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,635 @@
|
||||
// Copyright 2013 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 an implementation of a transport that can call methods
|
||||
* directly on a frame. Useful if you want to use XPC for crossdomain messaging
|
||||
* (using another transport), or same domain messaging (using this transport).
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.net.xpc.DirectTransport');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.async.Deferred');
|
||||
goog.require('goog.events.EventHandler');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CfgFields');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.net.xpc.TransportTypes');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
goog.scope(function() {
|
||||
var CfgFields = goog.net.xpc.CfgFields;
|
||||
var CrossPageChannelRole = goog.net.xpc.CrossPageChannelRole;
|
||||
var Deferred = goog.async.Deferred;
|
||||
var EventHandler = goog.events.EventHandler;
|
||||
var Timer = goog.Timer;
|
||||
var Transport = goog.net.xpc.Transport;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A direct window to window method transport.
|
||||
*
|
||||
* If the windows are in the same security context, this transport calls
|
||||
* directly into the other window without using any additional mechanism. This
|
||||
* is mainly used in scenarios where you want to optionally use a cross domain
|
||||
* transport in cross security context situations, or optionally use a direct
|
||||
* transport in same security context situations.
|
||||
*
|
||||
* Note: Global properties are exported by using this transport. One to
|
||||
* communicate with the other window by, currently crosswindowmessaging.channel,
|
||||
* and by using goog.getUid on window, currently closure_uid_[0-9]+.
|
||||
*
|
||||
* @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/document. If omitted, uses the current
|
||||
* document.
|
||||
* @constructor
|
||||
* @extends {Transport}
|
||||
*/
|
||||
goog.net.xpc.DirectTransport = function(channel, opt_domHelper) {
|
||||
goog.net.xpc.DirectTransport.base(this, 'constructor', opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @private {!goog.net.xpc.CrossPageChannel}
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/** @private {!EventHandler<!goog.net.xpc.DirectTransport>} */
|
||||
this.eventHandler_ = new EventHandler(this);
|
||||
this.registerDisposable(this.eventHandler_);
|
||||
|
||||
/**
|
||||
* Timer for connection reattempts.
|
||||
* @private {!Timer}
|
||||
*/
|
||||
this.maybeAttemptToConnectTimer_ = new Timer(
|
||||
DirectTransport.CONNECTION_ATTEMPT_INTERVAL_MS_,
|
||||
this.getWindow());
|
||||
this.registerDisposable(this.maybeAttemptToConnectTimer_);
|
||||
|
||||
/**
|
||||
* Fires once we've received our SETUP_ACK message.
|
||||
* @private {!Deferred}
|
||||
*/
|
||||
this.setupAckReceived_ = new Deferred();
|
||||
|
||||
/**
|
||||
* Fires once we've sent our SETUP_ACK message.
|
||||
* @private {!Deferred}
|
||||
*/
|
||||
this.setupAckSent_ = new Deferred();
|
||||
|
||||
/**
|
||||
* Fires once we're marked connected.
|
||||
* @private {!Deferred}
|
||||
*/
|
||||
this.connected_ = new Deferred();
|
||||
|
||||
/**
|
||||
* The unique ID of this side of the connection. Used to determine when a peer
|
||||
* is reloaded.
|
||||
* @private {string}
|
||||
*/
|
||||
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.
|
||||
* @private {?string}
|
||||
*/
|
||||
this.peerEndpointId_ = null;
|
||||
|
||||
/**
|
||||
* The map of sending messages.
|
||||
* @private {Object}
|
||||
*/
|
||||
this.asyncSendsMap_ = {};
|
||||
|
||||
/**
|
||||
* The original channel name.
|
||||
* @private {string}
|
||||
*/
|
||||
this.originalChannelName_ = this.channel_.name;
|
||||
|
||||
// We reconfigure the channel name to include the role so that we can
|
||||
// communicate in the same window between the different roles on the
|
||||
// same channel.
|
||||
this.channel_.updateChannelNameAndCatalog(
|
||||
DirectTransport.getRoledChannelName_(this.channel_.name,
|
||||
this.channel_.getRole()));
|
||||
|
||||
/**
|
||||
* Flag indicating if this instance of the transport has been initialized.
|
||||
* @private {boolean}
|
||||
*/
|
||||
this.initialized_ = false;
|
||||
|
||||
// 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.
|
||||
|
||||
// Two sided handshake:
|
||||
// SETUP_ACK has to have been received, and sent.
|
||||
this.connected_.awaitDeferred(this.setupAckReceived_);
|
||||
this.connected_.awaitDeferred(this.setupAckSent_);
|
||||
|
||||
this.connected_.addCallback(this.notifyConnected_, this);
|
||||
this.connected_.callback(true);
|
||||
|
||||
this.eventHandler_.
|
||||
listen(this.maybeAttemptToConnectTimer_, Timer.TICK,
|
||||
this.maybeAttemptToConnect_);
|
||||
|
||||
goog.log.info(
|
||||
goog.net.xpc.logger,
|
||||
'DirectTransport created. role=' + this.channel_.getRole());
|
||||
};
|
||||
goog.inherits(goog.net.xpc.DirectTransport, Transport);
|
||||
var DirectTransport = goog.net.xpc.DirectTransport;
|
||||
|
||||
|
||||
/**
|
||||
* @private {number}
|
||||
* @const
|
||||
*/
|
||||
DirectTransport.CONNECTION_ATTEMPT_INTERVAL_MS_ = 100;
|
||||
|
||||
|
||||
/**
|
||||
* The delay to notify the xpc of a successful connection. This is used
|
||||
* to allow both parties to be connected if one party's connection callback
|
||||
* invokes an immediate send.
|
||||
* @private {number}
|
||||
* @const
|
||||
*/
|
||||
DirectTransport.CONNECTION_DELAY_INTERVAL_MS_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Window} peerWindow The peer window to check if DirectTranport is
|
||||
* supported on.
|
||||
* @return {boolean} Whether this transport is supported.
|
||||
*/
|
||||
DirectTransport.isSupported = function(peerWindow) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
return window.document.domain == peerWindow.document.domain;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Tracks the number of DirectTransport 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.
|
||||
* @private {!Object<number>}
|
||||
*/
|
||||
DirectTransport.activeCount_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* Path of global message proxy.
|
||||
* @private {string}
|
||||
* @const
|
||||
*/
|
||||
// TODO(user): Make this configurable using the CfgFields.
|
||||
DirectTransport.GLOBAL_TRANPORT_PATH_ = 'crosswindowmessaging.channel';
|
||||
|
||||
|
||||
/**
|
||||
* The delimiter used for transport service messages.
|
||||
* @private {string}
|
||||
* @const
|
||||
*/
|
||||
DirectTransport.MESSAGE_DELIMITER_ = ',';
|
||||
|
||||
|
||||
/**
|
||||
* Initializes this transport. Registers a method for 'message'-events in the
|
||||
* global scope.
|
||||
* @param {!Window} listenWindow The window to listen to events on.
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.initialize_ = function(listenWindow) {
|
||||
var uid = goog.getUid(listenWindow);
|
||||
var value = DirectTransport.activeCount_[uid] || 0;
|
||||
if (value == 0) {
|
||||
// Set up a handler on the window to proxy messages to class.
|
||||
var globalProxy = goog.getObjectByName(
|
||||
DirectTransport.GLOBAL_TRANPORT_PATH_,
|
||||
listenWindow);
|
||||
if (globalProxy == null) {
|
||||
goog.exportSymbol(
|
||||
DirectTransport.GLOBAL_TRANPORT_PATH_,
|
||||
DirectTransport.messageReceivedHandler_,
|
||||
listenWindow);
|
||||
}
|
||||
}
|
||||
DirectTransport.activeCount_[uid]++;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} channelName The channel name.
|
||||
* @param {string|number} role The role.
|
||||
* @return {string} The formatted channel name including role.
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.getRoledChannelName_ = function(channelName, role) {
|
||||
return channelName + '_' + role;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Object} literal The literal unrenamed message.
|
||||
* @return {boolean} Whether the message was successfully delivered to a
|
||||
* channel.
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.messageReceivedHandler_ = function(literal) {
|
||||
var msg = DirectTransport.Message_.fromLiteral(literal);
|
||||
|
||||
var channelName = msg.channelName;
|
||||
var service = msg.service;
|
||||
var payload = msg.payload;
|
||||
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
var transportMessageType = DirectTransport.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() == CrossPageChannelRole.INNER &&
|
||||
!staleChannel.isConnected() &&
|
||||
service == goog.net.xpc.TRANSPORT_SERVICE_ &&
|
||||
transportMessageType == goog.net.xpc.SETUP) {
|
||||
// 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+).
|
||||
staleChannel.updateChannelNameAndCatalog(channelName);
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @override
|
||||
*/
|
||||
DirectTransport.prototype.transportType = goog.net.xpc.TransportTypes.DIRECT;
|
||||
|
||||
|
||||
/**
|
||||
* Handles transport service messages.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
DirectTransport.prototype.transportServiceHandler = function(payload) {
|
||||
var transportParts = DirectTransport.parseTransportPayload_(payload);
|
||||
var transportMessageType = transportParts[0];
|
||||
var peerEndpointId = transportParts[1];
|
||||
switch (transportMessageType) {
|
||||
case goog.net.xpc.SETUP_ACK_:
|
||||
if (!this.setupAckReceived_.hasFired()) {
|
||||
this.setupAckReceived_.callback(true);
|
||||
}
|
||||
break;
|
||||
case goog.net.xpc.SETUP:
|
||||
this.sendSetupAckMessage_();
|
||||
if ((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.
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.prototype.sendSetupMessage_ = function() {
|
||||
// Although we could send real objects, since some other transports are
|
||||
// limited to strings we also keep this requirement.
|
||||
var payload = goog.net.xpc.SETUP;
|
||||
payload += DirectTransport.MESSAGE_DELIMITER_;
|
||||
payload += this.endpointId_;
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, payload);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends a SETUP_ACK transport service message.
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.prototype.sendSetupAckMessage_ = function() {
|
||||
this.send(goog.net.xpc.TRANSPORT_SERVICE_, goog.net.xpc.SETUP_ACK_);
|
||||
if (!this.setupAckSent_.hasFired()) {
|
||||
this.setupAckSent_.callback(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
DirectTransport.prototype.connect = function() {
|
||||
var win = this.getWindow();
|
||||
if (win) {
|
||||
DirectTransport.initialize_(win);
|
||||
this.initialized_ = true;
|
||||
this.maybeAttemptToConnect_();
|
||||
} else {
|
||||
goog.log.fine(goog.net.xpc.logger, 'connect(): no window to initialize.');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
DirectTransport.prototype.maybeAttemptToConnect_ = function() {
|
||||
var outerRole = this.channel_.getRole() == CrossPageChannelRole.OUTER;
|
||||
if (this.channel_.isConnected()) {
|
||||
this.maybeAttemptToConnectTimer_.stop();
|
||||
return;
|
||||
}
|
||||
this.maybeAttemptToConnectTimer_.start();
|
||||
this.sendSetupMessage_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prepares to send a message.
|
||||
* @param {string} service The name of the service the message is to be
|
||||
* delivered to.
|
||||
* @param {string} payload The message content.
|
||||
* @override
|
||||
*/
|
||||
DirectTransport.prototype.send = function(service, payload) {
|
||||
if (!this.channel_.getPeerWindowObject()) {
|
||||
goog.log.fine(goog.net.xpc.logger, 'send(): window not ready');
|
||||
return;
|
||||
}
|
||||
var channelName = DirectTransport.getRoledChannelName_(
|
||||
this.originalChannelName_,
|
||||
this.getPeerRole_());
|
||||
|
||||
var message = new DirectTransport.Message_(
|
||||
channelName,
|
||||
service,
|
||||
payload);
|
||||
|
||||
if (this.channel_.getConfig()[CfgFields.DIRECT_TRANSPORT_SYNC_MODE]) {
|
||||
this.executeScheduledSend_(message);
|
||||
} else {
|
||||
// Note: goog.async.nextTick doesn't support cancelling or disposal so
|
||||
// leaving as 0ms timer, though this may have performance implications.
|
||||
this.asyncSendsMap_[goog.getUid(message)] =
|
||||
Timer.callOnce(goog.bind(this.executeScheduledSend_, this, message), 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sends the message.
|
||||
* @param {!DirectTransport.Message_} message The message to send.
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.prototype.executeScheduledSend_ = function(message) {
|
||||
var messageId = goog.getUid(message);
|
||||
if (this.asyncSendsMap_[messageId]) {
|
||||
delete this.asyncSendsMap_[messageId];
|
||||
}
|
||||
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var peerProxy = goog.getObjectByName(
|
||||
DirectTransport.GLOBAL_TRANPORT_PATH_,
|
||||
this.channel_.getPeerWindowObject());
|
||||
} catch (error) {
|
||||
goog.log.warning(
|
||||
goog.net.xpc.logger,
|
||||
'Can\'t access other window, ignoring.',
|
||||
error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (goog.isNull(peerProxy)) {
|
||||
goog.log.warning(
|
||||
goog.net.xpc.logger,
|
||||
'Peer window had no global function.');
|
||||
return;
|
||||
}
|
||||
|
||||
/** @preserveTry */
|
||||
try {
|
||||
peerProxy(message.toLiteral());
|
||||
goog.log.info(
|
||||
goog.net.xpc.logger,
|
||||
'send(): channelName=' + message.channelName +
|
||||
' service=' + message.service +
|
||||
' payload=' + message.payload);
|
||||
} catch (error) {
|
||||
goog.log.warning(
|
||||
goog.net.xpc.logger,
|
||||
'Error performing call, ignoring.',
|
||||
error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {goog.net.xpc.CrossPageChannelRole} The role of peer channel (either
|
||||
* inner or outer).
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.prototype.getPeerRole_ = function() {
|
||||
var role = this.channel_.getRole();
|
||||
return role == goog.net.xpc.CrossPageChannelRole.OUTER ?
|
||||
goog.net.xpc.CrossPageChannelRole.INNER :
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Notifies the channel that this transport is connected.
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.prototype.notifyConnected_ = function() {
|
||||
// Add a delay as the connection callback will break if this transport is
|
||||
// synchronous and the callback invokes send() immediately.
|
||||
this.channel_.notifyConnected(
|
||||
this.channel_.getConfig()[CfgFields.DIRECT_TRANSPORT_SYNC_MODE] ?
|
||||
DirectTransport.CONNECTION_DELAY_INTERVAL_MS_ : 0);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
DirectTransport.prototype.disposeInternal = function() {
|
||||
if (this.initialized_) {
|
||||
var listenWindow = this.getWindow();
|
||||
var uid = goog.getUid(listenWindow);
|
||||
var value = --DirectTransport.activeCount_[uid];
|
||||
if (value == 1) {
|
||||
goog.exportSymbol(
|
||||
DirectTransport.GLOBAL_TRANPORT_PATH_,
|
||||
null,
|
||||
listenWindow);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.asyncSendsMap_) {
|
||||
goog.object.forEach(this.asyncSendsMap_, function(timerId) {
|
||||
Timer.clear(timerId);
|
||||
});
|
||||
this.asyncSendsMap_ = null;
|
||||
}
|
||||
|
||||
// Deferred's aren't disposables.
|
||||
if (this.setupAckReceived_) {
|
||||
this.setupAckReceived_.cancel();
|
||||
delete this.setupAckReceived_;
|
||||
}
|
||||
if (this.setupAckSent_) {
|
||||
this.setupAckSent_.cancel();
|
||||
delete this.setupAckSent_;
|
||||
}
|
||||
if (this.connected_) {
|
||||
this.connected_.cancel();
|
||||
delete this.connected_;
|
||||
}
|
||||
|
||||
DirectTransport.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses a transport service payload message.
|
||||
* @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
|
||||
*/
|
||||
DirectTransport.parseTransportPayload_ = function(payload) {
|
||||
var transportParts = /** @type {!Array<?string>} */ (payload.split(
|
||||
DirectTransport.MESSAGE_DELIMITER_));
|
||||
transportParts[1] = transportParts[1] || null; // Usually endpointId.
|
||||
return transportParts;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Message container that gets passed back and forth between windows.
|
||||
* @param {string} channelName The channel name to tranport messages on.
|
||||
* @param {string} service The service to send the payload to.
|
||||
* @param {string} payload The payload to send.
|
||||
* @constructor
|
||||
* @struct
|
||||
* @private
|
||||
*/
|
||||
DirectTransport.Message_ = function(channelName, service, payload) {
|
||||
/**
|
||||
* The name of the channel.
|
||||
* @type {string}
|
||||
*/
|
||||
this.channelName = channelName;
|
||||
|
||||
/**
|
||||
* The service on the channel.
|
||||
* @type {string}
|
||||
*/
|
||||
this.service = service;
|
||||
|
||||
/**
|
||||
* The payload.
|
||||
* @type {string}
|
||||
*/
|
||||
this.payload = payload;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts a message to a literal object.
|
||||
* @return {!Object} The message as a literal object.
|
||||
*/
|
||||
DirectTransport.Message_.prototype.toLiteral = function() {
|
||||
return {
|
||||
'channelName': this.channelName,
|
||||
'service': this.service,
|
||||
'payload': this.payload
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a Message_ from a literal object.
|
||||
* @param {!Object} literal The literal to convert to Message.
|
||||
* @return {!DirectTransport.Message_} The Message.
|
||||
*/
|
||||
DirectTransport.Message_.fromLiteral = function(literal) {
|
||||
return new DirectTransport.Message_(
|
||||
literal['channelName'],
|
||||
literal['service'],
|
||||
literal['payload']);
|
||||
};
|
||||
|
||||
}); // goog.scope
|
||||
@@ -0,0 +1,290 @@
|
||||
// Copyright 2013 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 Tests the direct transport.
|
||||
*/
|
||||
|
||||
goog.provide('goog.net.xpc.DirectTransportTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.labs.userAgent.browser');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.log.Level');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CfgFields');
|
||||
goog.require('goog.net.xpc.CrossPageChannel');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.TransportTypes');
|
||||
goog.require('goog.testing.AsyncTestCase');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.setTestOnly('goog.net.xpc.DirectTransportTest');
|
||||
|
||||
var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall(
|
||||
'Direct transport tests.');
|
||||
|
||||
|
||||
/**
|
||||
* Echo service name.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
var ECHO_SERVICE_NAME = 'echo';
|
||||
|
||||
|
||||
/**
|
||||
* Response service name.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
var RESPONSE_SERVICE_NAME = 'response';
|
||||
|
||||
|
||||
/**
|
||||
* Test Payload.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
var MESSAGE_PAYLOAD_1 = 'This is message payload 1.';
|
||||
|
||||
|
||||
/**
|
||||
* The name id of the peer iframe.
|
||||
* @type {string}
|
||||
* @const
|
||||
*/
|
||||
var PEER_IFRAME_ID = 'peer-iframe';
|
||||
|
||||
|
||||
// Class aliases.
|
||||
var CfgFields;
|
||||
var CrossPageChannel;
|
||||
var CrossPageChannelRole;
|
||||
var TransportTypes;
|
||||
|
||||
var outerXpc;
|
||||
var innerXpc;
|
||||
var peerIframe;
|
||||
var channelName;
|
||||
var messageIsSync = false;
|
||||
|
||||
function setUpPage() {
|
||||
CfgFields = goog.net.xpc.CfgFields;
|
||||
CrossPageChannel = goog.net.xpc.CrossPageChannel;
|
||||
CrossPageChannelRole = goog.net.xpc.CrossPageChannelRole;
|
||||
TransportTypes = goog.net.xpc.TransportTypes;
|
||||
|
||||
// Show debug log
|
||||
var debugDiv = document.createElement('debugDiv');
|
||||
document.body.appendChild(debugDiv);
|
||||
var logger = goog.log.getLogger('goog.net.xpc');
|
||||
logger.setLevel(goog.log.Level.ALL);
|
||||
goog.log.addHandler(logger, function(logRecord) {
|
||||
var msgElm = goog.dom.createDom('div');
|
||||
msgElm.innerHTML = logRecord.getMessage();
|
||||
goog.dom.appendChild(debugDiv, msgElm);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function tearDown() {
|
||||
if (peerIframe) {
|
||||
document.body.removeChild(peerIframe);
|
||||
peerIframe = null;
|
||||
}
|
||||
if (outerXpc) {
|
||||
outerXpc.dispose();
|
||||
outerXpc = null;
|
||||
}
|
||||
if (innerXpc) {
|
||||
innerXpc.dispose();
|
||||
innerXpc = null;
|
||||
}
|
||||
window.iframeLoadHandler = null;
|
||||
channelName = null;
|
||||
messageIsSync = false;
|
||||
}
|
||||
|
||||
|
||||
function createIframe() {
|
||||
peerIframe = document.createElement('iframe');
|
||||
peerIframe.id = PEER_IFRAME_ID;
|
||||
document.body.insertBefore(peerIframe, document.body.firstChild);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests 2 same domain frames using direct transport.
|
||||
*/
|
||||
function testDirectTransport() {
|
||||
// This test has been flaky on IE 8-11 on Win7.
|
||||
// For now, disable.
|
||||
// Flakiness is tracked in http://b/18595666
|
||||
if (goog.labs.userAgent.browser.isIE()) {
|
||||
return;
|
||||
}
|
||||
|
||||
createIframe();
|
||||
channelName = goog.net.xpc.getRandomString(10);
|
||||
outerXpc = new CrossPageChannel(
|
||||
getConfiguration(CrossPageChannelRole.OUTER, PEER_IFRAME_ID));
|
||||
// Outgoing service.
|
||||
outerXpc.registerService(ECHO_SERVICE_NAME, goog.nullFunction);
|
||||
// Incoming service.
|
||||
outerXpc.registerService(
|
||||
RESPONSE_SERVICE_NAME,
|
||||
responseMessageHandler_testDirectTransport);
|
||||
asyncTestCase.waitForAsync('Waiting for xpc connect.');
|
||||
outerXpc.connect(onConnect_testDirectTransport);
|
||||
// inner_peer.html calls this method at end of html.
|
||||
window.iframeLoadHandler = onIframeLoaded_testDirectTransport;
|
||||
peerIframe.src = 'testdata/inner_peer.html';
|
||||
}
|
||||
|
||||
|
||||
function onIframeLoaded_testDirectTransport() {
|
||||
peerIframe.contentWindow.instantiateChannel(
|
||||
getConfiguration(CrossPageChannelRole.INNER));
|
||||
}
|
||||
|
||||
|
||||
function onConnect_testDirectTransport() {
|
||||
assertTrue('XPC over direct channel is connected', outerXpc.isConnected());
|
||||
outerXpc.send(ECHO_SERVICE_NAME, MESSAGE_PAYLOAD_1);
|
||||
}
|
||||
|
||||
|
||||
function responseMessageHandler_testDirectTransport(message) {
|
||||
assertEquals(
|
||||
'Received payload is equal to sent payload.',
|
||||
message,
|
||||
MESSAGE_PAYLOAD_1);
|
||||
asyncTestCase.continueTesting();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests 2 xpc's communicating with each other in the same window.
|
||||
*/
|
||||
function testSameWindowDirectTransport() {
|
||||
channelName = goog.net.xpc.getRandomString(10);
|
||||
|
||||
outerXpc = new CrossPageChannel(getConfiguration(CrossPageChannelRole.OUTER));
|
||||
outerXpc.setPeerWindowObject(self);
|
||||
|
||||
// Outgoing service.
|
||||
outerXpc.registerService(ECHO_SERVICE_NAME, goog.nullFunction);
|
||||
// Incoming service.
|
||||
outerXpc.registerService(
|
||||
RESPONSE_SERVICE_NAME,
|
||||
outerResponseMessageHandler_testSameWindowDirectTransport);
|
||||
asyncTestCase.waitForAsync('Waiting for outer xpc connect.');
|
||||
outerXpc.connect(onOuterConnect_testSameWindowDirectTransport);
|
||||
|
||||
innerXpc = new CrossPageChannel(getConfiguration(CrossPageChannelRole.INNER));
|
||||
innerXpc.setPeerWindowObject(self);
|
||||
// Incoming service.
|
||||
innerXpc.registerService(
|
||||
ECHO_SERVICE_NAME,
|
||||
innerEchoMessageHandler_testSameWindowDirectTransport);
|
||||
// Outgoing service.
|
||||
innerXpc.registerService(
|
||||
RESPONSE_SERVICE_NAME,
|
||||
goog.nullFunction);
|
||||
innerXpc.connect();
|
||||
}
|
||||
|
||||
|
||||
function onOuterConnect_testSameWindowDirectTransport() {
|
||||
assertTrue(
|
||||
'XPC over direct channel, same window, is connected',
|
||||
outerXpc.isConnected());
|
||||
outerXpc.send(ECHO_SERVICE_NAME, MESSAGE_PAYLOAD_1);
|
||||
}
|
||||
|
||||
|
||||
function outerResponseMessageHandler_testSameWindowDirectTransport(message) {
|
||||
assertEquals(
|
||||
'Received payload is equal to sent payload.',
|
||||
message,
|
||||
MESSAGE_PAYLOAD_1);
|
||||
asyncTestCase.continueTesting();
|
||||
}
|
||||
|
||||
|
||||
function innerEchoMessageHandler_testSameWindowDirectTransport(message) {
|
||||
innerXpc.send(RESPONSE_SERVICE_NAME, message);
|
||||
}
|
||||
|
||||
|
||||
function getConfiguration(role, opt_peerFrameId) {
|
||||
var cfg = {};
|
||||
cfg[CfgFields.TRANSPORT] = TransportTypes.DIRECT;
|
||||
if (goog.isDefAndNotNull(opt_peerFrameId)) {
|
||||
cfg[CfgFields.IFRAME_ID] = opt_peerFrameId;
|
||||
}
|
||||
cfg[CfgFields.CHANNEL_NAME] = channelName;
|
||||
cfg[CfgFields.ROLE] = role;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests 2 same domain frames using direct transport using sync mode.
|
||||
*/
|
||||
function testSyncMode() {
|
||||
createIframe();
|
||||
channelName = goog.net.xpc.getRandomString(10);
|
||||
|
||||
var cfg = getConfiguration(CrossPageChannelRole.OUTER, PEER_IFRAME_ID);
|
||||
cfg[CfgFields.DIRECT_TRANSPORT_SYNC_MODE] = true;
|
||||
|
||||
outerXpc = new CrossPageChannel(cfg);
|
||||
// Outgoing service.
|
||||
outerXpc.registerService(ECHO_SERVICE_NAME, goog.nullFunction);
|
||||
// Incoming service.
|
||||
outerXpc.registerService(
|
||||
RESPONSE_SERVICE_NAME,
|
||||
responseMessageHandler_testSyncMode);
|
||||
asyncTestCase.waitForAsync('Waiting for xpc connect.');
|
||||
outerXpc.connect(onConnect_testSyncMode);
|
||||
// inner_peer.html calls this method at end of html.
|
||||
window.iframeLoadHandler = onIframeLoaded_testSyncMode;
|
||||
peerIframe.src = 'testdata/inner_peer.html';
|
||||
}
|
||||
|
||||
|
||||
function onIframeLoaded_testSyncMode() {
|
||||
var cfg = getConfiguration(CrossPageChannelRole.INNER);
|
||||
cfg[CfgFields.DIRECT_TRANSPORT_SYNC_MODE] = true;
|
||||
peerIframe.contentWindow.instantiateChannel(cfg);
|
||||
}
|
||||
|
||||
|
||||
function onConnect_testSyncMode() {
|
||||
assertTrue('XPC over direct channel is connected', outerXpc.isConnected());
|
||||
messageIsSync = true;
|
||||
outerXpc.send(ECHO_SERVICE_NAME, MESSAGE_PAYLOAD_1);
|
||||
messageIsSync = false;
|
||||
}
|
||||
|
||||
|
||||
function responseMessageHandler_testSyncMode(message) {
|
||||
assertTrue('The message response was syncronous', messageIsSync);
|
||||
assertEquals(
|
||||
'Received payload is equal to sent payload.',
|
||||
message,
|
||||
MESSAGE_PAYLOAD_1);
|
||||
asyncTestCase.continueTesting();
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
// 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.log');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.net.xpc.TransportTypes');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @final
|
||||
*/
|
||||
goog.net.xpc.FrameElementMethodTransport = function(channel, opt_domHelper) {
|
||||
goog.net.xpc.FrameElementMethodTransport.base(
|
||||
this, 'constructor', 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<{serviceName: string, payload: string}>}
|
||||
* @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;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.attemptSetupCb_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.outgoing_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.FrameElementMethodTransport.prototype.iframeElm_;
|
||||
|
||||
|
||||
/**
|
||||
* 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,984 @@
|
||||
// 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.log');
|
||||
goog.require('goog.log.Level');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CfgFields');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.net.xpc.TransportTypes');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Iframe polling transport. Uses hidden iframes to transfer data
|
||||
* in the fragment identifier of the URL. The peer polls the iframe's location
|
||||
* for changes.
|
||||
* Unfortunately, in Safari this screws up the history, because Safari doesn't
|
||||
* allow to call location.replace() on a window containing a document from a
|
||||
* different domain (last version tested: 2.0.4).
|
||||
*
|
||||
* @param {goog.net.xpc.CrossPageChannel} channel The channel this
|
||||
* transport belongs to.
|
||||
* @param {goog.dom.DomHelper=} opt_domHelper The dom helper to use for finding
|
||||
* the correct window.
|
||||
* @constructor
|
||||
* @extends {goog.net.xpc.Transport}
|
||||
* @final
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport = function(channel, opt_domHelper) {
|
||||
goog.net.xpc.IframePollingTransport.base(this, 'constructor', opt_domHelper);
|
||||
|
||||
/**
|
||||
* The channel this transport belongs to.
|
||||
* @type {goog.net.xpc.CrossPageChannel}
|
||||
* @private
|
||||
*/
|
||||
this.channel_ = channel;
|
||||
|
||||
/**
|
||||
* The URI used to send messages.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.sendUri_ =
|
||||
this.channel_.getConfig()[goog.net.xpc.CfgFields.PEER_POLL_URI];
|
||||
|
||||
/**
|
||||
* The URI which is polled for incoming messages.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.rcvUri_ =
|
||||
this.channel_.getConfig()[goog.net.xpc.CfgFields.LOCAL_POLL_URI];
|
||||
|
||||
/**
|
||||
* The queue to hold messages which can't be sent immediately.
|
||||
* @type {Array<string>}
|
||||
* @private
|
||||
*/
|
||||
this.sendQueue_ = [];
|
||||
};
|
||||
goog.inherits(goog.net.xpc.IframePollingTransport, goog.net.xpc.Transport);
|
||||
|
||||
|
||||
/**
|
||||
* The number of times the inner frame will check for evidence of the outer
|
||||
* frame before it tries its reconnection sequence. These occur at 100ms
|
||||
* intervals, making this an effective max waiting period of 500ms.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.pollsBeforeReconnect_ = 5;
|
||||
|
||||
|
||||
/**
|
||||
* The transport type.
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.transportType =
|
||||
goog.net.xpc.TransportTypes.IFRAME_POLLING;
|
||||
|
||||
|
||||
/**
|
||||
* Sequence counter.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.sequence_ = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating whether we are waiting for an acknoledgement.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.waitForAck_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Flag indicating if channel has been initialized.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.initialized_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Reconnection iframe created by inner peer.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.reconnectFrame_ = null;
|
||||
|
||||
|
||||
/** @private {goog.net.xpc.IframePollingTransport.Receiver} */
|
||||
goog.net.xpc.IframePollingTransport.prototype.ackReceiver_;
|
||||
|
||||
|
||||
/** @private {goog.net.xpc.IframePollingTransport.Sender} */
|
||||
goog.net.xpc.IframePollingTransport.prototype.ackSender_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.ackIframeElm_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.ackWinObj_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.checkLocalFramesPresentCb_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.deliveryQueue_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.msgIframeElm_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.msgReceiver_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.msgSender_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.msgWinObj_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.rcvdConnectionSetupAck_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.sentConnectionSetupAck_;
|
||||
|
||||
|
||||
/** @private {boolean} */
|
||||
goog.net.xpc.IframePollingTransport.prototype.sentConnectionSetup_;
|
||||
|
||||
|
||||
/** @private */
|
||||
goog.net.xpc.IframePollingTransport.prototype.parts_;
|
||||
|
||||
|
||||
/**
|
||||
* The string used to prefix all iframe names and IDs.
|
||||
* @type {string}
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.IFRAME_PREFIX = 'googlexpc';
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name/ID of the message frame.
|
||||
* @return {string} Name of message frame.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.getMsgFrameName_ = function() {
|
||||
return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' +
|
||||
this.channel_.name + '_msg';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name/ID of the ack frame.
|
||||
* @return {string} Name of ack frame.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.getAckFrameName_ = function() {
|
||||
return goog.net.xpc.IframePollingTransport.IFRAME_PREFIX + '_' +
|
||||
this.channel_.name + '_ack';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the channel is still available. The channel is
|
||||
* unavailable if the transport was disposed or the peer is no longer
|
||||
* available.
|
||||
* @return {boolean} Whether the channel is available.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.isChannelAvailable = function() {
|
||||
return !this.isDisposed() && this.channel_.isPeerAvailable();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Safely retrieves the frames from the peer window. If an error is thrown
|
||||
* (e.g. the window is closing) an empty frame object is returned.
|
||||
* @return {!Object<!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_.updateChannelNameAndCatalog(goog.net.xpc.getRandomString(10));
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'switching channels: ' + this.channel_.name);
|
||||
this.deconstructSenderFrames_();
|
||||
this.initialized_ = false;
|
||||
// Communicate new channel name to outer peer.
|
||||
this.reconnectFrame_ = this.constructSenderFrame_(
|
||||
goog.net.xpc.IframePollingTransport.IFRAME_PREFIX +
|
||||
'_reconnect_' + this.channel_.name);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Scans inner peer for a reconnect message, which will be used to update
|
||||
* the outer peer's channel name. If a reconnect message is found, the
|
||||
* sender frames will be cleaned up to make way for the new sender frames.
|
||||
* Only called by the outer peer.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.outerPeerReconnect_ = function() {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'outerPeerReconnect called');
|
||||
var frames = this.getPeerFrames_();
|
||||
var length = frames.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
var frameName;
|
||||
try {
|
||||
if (frames[i] && frames[i].name) {
|
||||
frameName = frames[i].name;
|
||||
}
|
||||
} catch (e) {
|
||||
// Do nothing.
|
||||
}
|
||||
if (!frameName) {
|
||||
continue;
|
||||
}
|
||||
var message = frameName.split('_');
|
||||
if (message.length == 3 &&
|
||||
message[0] == goog.net.xpc.IframePollingTransport.IFRAME_PREFIX &&
|
||||
message[1] == 'reconnect') {
|
||||
// This is a legitimate reconnect message from the peer. Start using
|
||||
// the peer provided channel name, and start a connection over from
|
||||
// scratch.
|
||||
this.channel_.name = message[2];
|
||||
this.deconstructSenderFrames_();
|
||||
this.initialized_ = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cleans up the existing sender frames owned by this peer. Only called by
|
||||
* the outer peer.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.deconstructSenderFrames_ =
|
||||
function() {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'deconstructSenderFrames called');
|
||||
if (this.msgIframeElm_) {
|
||||
this.msgIframeElm_.parentNode.removeChild(this.msgIframeElm_);
|
||||
this.msgIframeElm_ = null;
|
||||
this.msgWinObj_ = null;
|
||||
}
|
||||
if (this.ackIframeElm_) {
|
||||
this.ackIframeElm_.parentNode.removeChild(this.ackIframeElm_);
|
||||
this.ackIframeElm_ = null;
|
||||
this.ackWinObj_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the frames in the peer's page are ready. These contain a
|
||||
* document from the own domain and are the ones messages are received through.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.checkForeignFramesReady_ =
|
||||
function() {
|
||||
// check if the connected iframe ready
|
||||
if (!(this.isRcvFrameReady_(this.getMsgFrameName_()) &&
|
||||
this.isRcvFrameReady_(this.getAckFrameName_()))) {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'foreign frames not (yet) present');
|
||||
|
||||
if (this.channel_.getRole() == goog.net.xpc.CrossPageChannelRole.INNER) {
|
||||
// The outer peer might need a short time to get its frames ready, as
|
||||
// CrossPageChannel prevents them from getting created until the inner
|
||||
// peer's frame has thrown its loaded event. This method is a noop for
|
||||
// the first few times it's called, and then allows the reconnection
|
||||
// sequence to begin.
|
||||
this.maybeInnerPeerReconnect_();
|
||||
} else if (this.channel_.getRole() ==
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER) {
|
||||
// The inner peer is either not loaded yet, or the receiving
|
||||
// frames are simply missing. Since we cannot discern the two cases, we
|
||||
// should scan for a reconnect message from the inner peer.
|
||||
this.outerPeerReconnect_();
|
||||
}
|
||||
|
||||
// start a timer to check again
|
||||
this.getWindow().setTimeout(goog.bind(this.connect, this), 100);
|
||||
} else {
|
||||
goog.log.fine(goog.net.xpc.logger, 'foreign frames present');
|
||||
|
||||
// Create receivers.
|
||||
this.msgReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver(
|
||||
this,
|
||||
this.getPeerFrame_(this.getMsgFrameName_()),
|
||||
goog.bind(this.processIncomingMsg, this));
|
||||
this.ackReceiver_ = new goog.net.xpc.IframePollingTransport.Receiver(
|
||||
this,
|
||||
this.getPeerFrame_(this.getAckFrameName_()),
|
||||
goog.bind(this.processIncomingAck, this));
|
||||
|
||||
this.checkLocalFramesPresent_();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the receiving frame is ready.
|
||||
* @param {string} frameName Which receiving frame to check.
|
||||
* @return {boolean} Whether the receiving frame is ready.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.prototype.isRcvFrameReady_ =
|
||||
function(frameName) {
|
||||
goog.log.log(goog.net.xpc.logger, goog.log.Level.FINEST,
|
||||
'checking for receive frame: ' + frameName);
|
||||
/** @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.net.xpc.IframePollingTransport.base(this, 'disposeInternal');
|
||||
|
||||
var receivers = goog.net.xpc.IframePollingTransport.receivers_;
|
||||
goog.array.remove(receivers, this.msgReceiver_);
|
||||
goog.array.remove(receivers, this.ackReceiver_);
|
||||
this.msgReceiver_ = this.ackReceiver_ = null;
|
||||
|
||||
goog.dom.removeNode(this.msgIframeElm_);
|
||||
goog.dom.removeNode(this.ackIframeElm_);
|
||||
this.msgIframeElm_ = this.ackIframeElm_ = null;
|
||||
this.msgWinObj_ = this.ackWinObj_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Array holding all Receiver-instances.
|
||||
* @type {Array<goog.net.xpc.IframePollingTransport.Receiver>}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.receivers_ = [];
|
||||
|
||||
|
||||
/**
|
||||
* Short polling interval.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.TIME_POLL_SHORT_ = 10;
|
||||
|
||||
|
||||
/**
|
||||
* Long polling interval.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.TIME_POLL_LONG_ = 100;
|
||||
|
||||
|
||||
/**
|
||||
* Period how long to use TIME_POLL_SHORT_ before raising polling-interval
|
||||
* to TIME_POLL_LONG_ after an activity.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.TIME_SHORT_POLL_AFTER_ACTIVITY_ =
|
||||
1000;
|
||||
|
||||
|
||||
/**
|
||||
* Polls all receivers.
|
||||
* @private
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.receive_ = function() {
|
||||
var receivers = goog.net.xpc.IframePollingTransport.receivers_;
|
||||
var receiver;
|
||||
var rcvd = false;
|
||||
|
||||
/** @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.
|
||||
* @final
|
||||
*/
|
||||
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.
|
||||
* @final
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.Receiver = function(transport,
|
||||
windowObj,
|
||||
callback) {
|
||||
/**
|
||||
* The transport to receive from.
|
||||
* @type {goog.net.xpc.IframePollingTransport}
|
||||
* @private
|
||||
*/
|
||||
this.transport_ = transport;
|
||||
this.rcvFrame_ = windowObj;
|
||||
|
||||
this.cb_ = callback;
|
||||
this.currentLoc_ = this.rcvFrame_.location.href.split('#')[0] + '#INITIAL';
|
||||
|
||||
goog.net.xpc.IframePollingTransport.receivers_.push(this);
|
||||
goog.net.xpc.IframePollingTransport.startRcvTimer_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Polls the location of the receiver-frame for changes.
|
||||
* @return {boolean} Whether a change has been detected.
|
||||
*/
|
||||
goog.net.xpc.IframePollingTransport.Receiver.prototype.receive = function() {
|
||||
var loc = this.rcvFrame_.location.href;
|
||||
|
||||
if (loc != this.currentLoc_) {
|
||||
this.currentLoc_ = loc;
|
||||
var payload = loc.split('#')[1];
|
||||
if (payload) {
|
||||
payload = payload.substr(1); // discard first character (cycle)
|
||||
this.cb_(decodeURIComponent(payload));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2012 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<!--
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-IframePollingTransport-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
NativeMessagingTransport Unit-Tests
|
||||
</title>
|
||||
<script src="../../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.net.xpc.IframePollingTransportTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,289 @@
|
||||
// Copyright 2012 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.
|
||||
|
||||
goog.provide('goog.net.xpc.IframePollingTransportTest');
|
||||
goog.setTestOnly('goog.net.xpc.IframePollingTransportTest');
|
||||
|
||||
goog.require('goog.Timer');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.TagName');
|
||||
goog.require('goog.functions');
|
||||
goog.require('goog.net.xpc.CfgFields');
|
||||
goog.require('goog.net.xpc.CrossPageChannel');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.TransportTypes');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.testing.MockClock');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.testing.recordFunction');
|
||||
|
||||
var mockClock = null;
|
||||
var outerChannel = null;
|
||||
var innerChannel = null;
|
||||
|
||||
function setUp() {
|
||||
mockClock = new goog.testing.MockClock(true /* opt_autoInstall */);
|
||||
|
||||
// Create the peer windows.
|
||||
var outerPeerHostName = 'https://www.youtube.com';
|
||||
var outerPeerWindow = createMockPeerWindow(outerPeerHostName);
|
||||
|
||||
var innerPeerHostName = 'https://www.google.com';
|
||||
var innerPeerWindow = createMockPeerWindow(innerPeerHostName);
|
||||
|
||||
// Create the channels.
|
||||
outerChannel = createChannel(goog.net.xpc.CrossPageChannelRole.OUTER, 'test',
|
||||
outerPeerHostName, outerPeerWindow, innerPeerHostName, innerPeerWindow);
|
||||
innerChannel = createChannel(goog.net.xpc.CrossPageChannelRole.INNER, 'test',
|
||||
innerPeerHostName, innerPeerWindow, outerPeerHostName, outerPeerWindow);
|
||||
}
|
||||
|
||||
|
||||
function tearDown() {
|
||||
outerChannel.dispose();
|
||||
innerChannel.dispose();
|
||||
mockClock.uninstall();
|
||||
}
|
||||
|
||||
|
||||
/** Tests that connection happens normally and callbacks are invoked. */
|
||||
function testConnect() {
|
||||
var outerConnectCallback = goog.testing.recordFunction();
|
||||
var innerConnectCallback = goog.testing.recordFunction();
|
||||
|
||||
// Connect the two channels.
|
||||
outerChannel.connect(outerConnectCallback);
|
||||
innerChannel.connect(innerConnectCallback);
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Check that channels were connected and callbacks invoked.
|
||||
assertEquals(1, outerConnectCallback.getCallCount());
|
||||
assertEquals(1, innerConnectCallback.getCallCount());
|
||||
assertTrue(outerChannel.isConnected());
|
||||
assertTrue(innerChannel.isConnected());
|
||||
}
|
||||
|
||||
|
||||
/** Tests that messages are successfully delivered to the inner peer. */
|
||||
function testSend_outerToInner() {
|
||||
var serviceCallback = goog.testing.recordFunction();
|
||||
|
||||
// Register a service handler in the inner channel.
|
||||
innerChannel.registerService('svc', function(payload) {
|
||||
assertEquals('hello', payload);
|
||||
serviceCallback();
|
||||
});
|
||||
|
||||
// Connect the two channels.
|
||||
outerChannel.connect();
|
||||
innerChannel.connect();
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Send a message.
|
||||
outerChannel.send('svc', 'hello');
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Check that the message was handled.
|
||||
assertEquals(1, serviceCallback.getCallCount());
|
||||
}
|
||||
|
||||
|
||||
/** Tests that messages are successfully delivered to the outer peer. */
|
||||
function testSend_innerToOuter() {
|
||||
var serviceCallback = goog.testing.recordFunction();
|
||||
|
||||
// Register a service handler in the inner channel.
|
||||
outerChannel.registerService('svc', function(payload) {
|
||||
assertEquals('hello', payload);
|
||||
serviceCallback();
|
||||
});
|
||||
|
||||
// Connect the two channels.
|
||||
outerChannel.connect();
|
||||
innerChannel.connect();
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Send a message.
|
||||
innerChannel.send('svc', 'hello');
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Check that the message was handled.
|
||||
assertEquals(1, serviceCallback.getCallCount());
|
||||
}
|
||||
|
||||
|
||||
/** Tests that closing the outer peer does not cause an error. */
|
||||
function testSend_outerPeerClosed() {
|
||||
// Connect the inner channel.
|
||||
innerChannel.connect();
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Close the outer peer before it has a chance to connect.
|
||||
closeWindow(innerChannel.getPeerWindowObject());
|
||||
|
||||
// Allow timers to execute (and fail).
|
||||
mockClock.tick(1000);
|
||||
}
|
||||
|
||||
|
||||
/** Tests that closing the inner peer does not cause an error. */
|
||||
function testSend_innerPeerClosed() {
|
||||
// Connect the outer channel.
|
||||
outerChannel.connect();
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Close the inner peer before it has a chance to connect.
|
||||
closeWindow(outerChannel.getPeerWindowObject());
|
||||
|
||||
// Allow timers to execute (and fail).
|
||||
mockClock.tick(1000);
|
||||
}
|
||||
|
||||
|
||||
/** Tests that partially closing the outer peer does not cause an error. */
|
||||
function testSend_outerPeerClosing() {
|
||||
// Connect the inner channel.
|
||||
innerChannel.connect();
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Close the outer peer before it has a chance to connect, but
|
||||
// leave closed set to false to simulate a partially closed window.
|
||||
closeWindow(innerChannel.getPeerWindowObject());
|
||||
innerChannel.getPeerWindowObject().closed = false;
|
||||
|
||||
// Allow timers to execute (and fail).
|
||||
mockClock.tick(1000);
|
||||
}
|
||||
|
||||
|
||||
/** Tests that partially closing the inner peer does not cause an error. */
|
||||
function testSend_innerPeerClosing() {
|
||||
// Connect the outer channel.
|
||||
outerChannel.connect();
|
||||
mockClock.tick(1000);
|
||||
|
||||
// Close the inner peer before it has a chance to connect, but
|
||||
// leave closed set to false to simulate a partially closed window.
|
||||
closeWindow(outerChannel.getPeerWindowObject());
|
||||
outerChannel.getPeerWindowObject().closed = false;
|
||||
|
||||
// Allow timers to execute (and fail).
|
||||
mockClock.tick(1000);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a channel with the specified configuration, using frame polling.
|
||||
* @param {!goog.net.xpc.CrossPageChannelRole} role The channel role.
|
||||
* @param {string} channelName The channel name.
|
||||
* @param {string} fromHostName The host name of the window hosting the channel.
|
||||
* @param {!Object} fromWindow The window hosting the channel.
|
||||
* @param {string} toHostName The host name of the peer window.
|
||||
* @param {!Object} toWindow The peer window.
|
||||
*/
|
||||
function createChannel(role, channelName, fromHostName, fromWindow, toHostName,
|
||||
toWindow) {
|
||||
|
||||
// Build a channel config using frame polling.
|
||||
var channelConfig = goog.object.create(
|
||||
goog.net.xpc.CfgFields.ROLE,
|
||||
role,
|
||||
goog.net.xpc.CfgFields.PEER_HOSTNAME,
|
||||
toHostName,
|
||||
goog.net.xpc.CfgFields.CHANNEL_NAME,
|
||||
channelName,
|
||||
goog.net.xpc.CfgFields.LOCAL_POLL_URI,
|
||||
fromHostName + '/robots.txt',
|
||||
goog.net.xpc.CfgFields.PEER_POLL_URI,
|
||||
toHostName + '/robots.txt',
|
||||
goog.net.xpc.CfgFields.TRANSPORT,
|
||||
goog.net.xpc.TransportTypes.IFRAME_POLLING);
|
||||
|
||||
// Build the channel.
|
||||
var channel = new goog.net.xpc.CrossPageChannel(channelConfig);
|
||||
channel.setPeerWindowObject(toWindow);
|
||||
|
||||
// Update the transport's getWindow, to return the correct host window.
|
||||
channel.createTransport_();
|
||||
channel.transport_.getWindow = goog.functions.constant(fromWindow);
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a mock window to use as a peer. The peer window will host the frame
|
||||
* elements.
|
||||
* @param {string} url The peer window's initial URL.
|
||||
*/
|
||||
function createMockPeerWindow(url) {
|
||||
var mockPeer = createMockWindow(url);
|
||||
|
||||
// Update the appendChild method to use a mock frame window.
|
||||
mockPeer.document.body.appendChild = function(el) {
|
||||
assertEquals(goog.dom.TagName.IFRAME, el.tagName);
|
||||
mockPeer.frames[el.name] = createMockWindow(el.src);
|
||||
mockPeer.document.body.element.appendChild(el);
|
||||
};
|
||||
|
||||
return mockPeer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a mock window.
|
||||
* @param {string} url The window's initial URL.
|
||||
*/
|
||||
function createMockWindow(url) {
|
||||
// Create the mock window, document and body.
|
||||
var mockWindow = {};
|
||||
var mockDocument = {};
|
||||
var mockBody = {};
|
||||
var mockLocation = {};
|
||||
|
||||
// Configure the mock window's document body.
|
||||
mockBody.element = goog.dom.createDom(goog.dom.TagName.BODY);
|
||||
|
||||
// Configure the mock window's document.
|
||||
mockDocument.body = mockBody;
|
||||
|
||||
// Configure the mock window's location.
|
||||
mockLocation.href = url;
|
||||
mockLocation.replace = function(value) { mockLocation.href = value; };
|
||||
|
||||
// Configure the mock window.
|
||||
mockWindow.document = mockDocument;
|
||||
mockWindow.frames = {};
|
||||
mockWindow.location = mockLocation;
|
||||
mockWindow.setTimeout = goog.Timer.callOnce;
|
||||
|
||||
return mockWindow;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Emulates closing the specified window by clearing frames, document and
|
||||
* location.
|
||||
*/
|
||||
function closeWindow(targetWindow) {
|
||||
// Close any child frame windows.
|
||||
for (var frameName in targetWindow.frames) {
|
||||
closeWindow(targetWindow.frames[frameName]);
|
||||
}
|
||||
|
||||
// Clear the target window, set closed to true.
|
||||
targetWindow.closed = true;
|
||||
targetWindow.frames = null;
|
||||
targetWindow.document = null;
|
||||
targetWindow.location = null;
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
// 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.dom.safe');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.html.SafeHtml');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.log.Level');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CfgFields');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.net.xpc.TransportTypes');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.string.Const');
|
||||
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}
|
||||
* @final
|
||||
*/
|
||||
goog.net.xpc.IframeRelayTransport = function(channel, opt_domHelper) {
|
||||
goog.net.xpc.IframeRelayTransport.base(this, 'constructor', 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');
|
||||
// TODO(user): It might be possible to set the sandbox attribute
|
||||
// to restrict the privileges of the created iframe.
|
||||
goog.dom.safe.setInnerHtml(div,
|
||||
goog.html.SafeHtml.createIframe(null, null, {
|
||||
'onload': goog.string.Const.from('this.xpcOnload()'),
|
||||
'sandbox': null
|
||||
}));
|
||||
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.net.xpc.IframeRelayTransport.base(this, 'disposeInternal');
|
||||
if (goog.userAgent.WEBKIT) {
|
||||
goog.net.xpc.IframeRelayTransport.cleanup_(0);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,648 @@
|
||||
// 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.log');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.net.xpc.TransportTypes');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @final
|
||||
*/
|
||||
goog.net.xpc.NativeMessagingTransport = function(channel, peerHostname,
|
||||
opt_domHelper, opt_oneSidedHandshake, opt_protocolVersion) {
|
||||
goog.net.xpc.NativeMessagingTransport.base(
|
||||
this, 'constructor', 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<!goog.net.xpc.NativeMessagingTransport>}
|
||||
* @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.prototype.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,
|
||||
/** @type {!MessageEvent} */ (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.
|
||||
staleChannel.updateChannelNameAndCatalog(channelName);
|
||||
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.net.xpc.NativeMessagingTransport.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,24 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
NativeMessagingTransport Unit-Tests
|
||||
</title>
|
||||
<script src="../../base.js" type="text/javascript">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.net.xpc.NativeMessagingTransportTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,300 @@
|
||||
// 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.
|
||||
|
||||
goog.provide('goog.net.xpc.NativeMessagingTransportTest');
|
||||
goog.setTestOnly('goog.net.xpc.NativeMessagingTransportTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CfgFields');
|
||||
goog.require('goog.net.xpc.CrossPageChannel');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.NativeMessagingTransport');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
// This test only tests the native messaing transport protocol version 2.
|
||||
// Testing of previous versions and of backward/forward compatibility is done
|
||||
// in crosspagechannel_test.html.
|
||||
|
||||
|
||||
function tearDown() {
|
||||
goog.net.xpc.NativeMessagingTransport.activeCount_ = {};
|
||||
goog.events.removeAll(window.postMessage ? window : document, 'message');
|
||||
}
|
||||
|
||||
|
||||
function testConstructor() {
|
||||
var xpc = getTestChannel();
|
||||
|
||||
var t = new goog.net.xpc.NativeMessagingTransport(xpc, 'http://g.com:80',
|
||||
undefined /* opt_domHelper */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
assertEquals('http://g.com:80', t.peerHostname_);
|
||||
|
||||
var t = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, undefined /* opt_domHelper */,
|
||||
false /* opt_oneSidedHandshake */, 2 /* opt_protocolVersion */);
|
||||
assertEquals('*', t.peerHostname_);
|
||||
t.dispose();
|
||||
}
|
||||
|
||||
|
||||
function testConstructorDom() {
|
||||
var xpc = getTestChannel();
|
||||
|
||||
var t = new goog.net.xpc.NativeMessagingTransport(
|
||||
xpc, 'http://g.com:80', goog.dom.getDomHelper(),
|
||||
false /* opt_oneSidedHandshake */, 2 /* opt_protocolVersion */);
|
||||
assertEquals('http://g.com:80', t.peerHostname_);
|
||||
|
||||
var t = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
assertEquals('*', t.peerHostname_);
|
||||
t.dispose();
|
||||
}
|
||||
|
||||
|
||||
function testDispose() {
|
||||
var xpc = getTestChannel();
|
||||
var listenedObj = window.postMessage ? window : document;
|
||||
|
||||
var t0 = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
assertEquals(0, goog.events.removeAll(listenedObj, 'message'));
|
||||
t0.dispose();
|
||||
assertEquals(0, goog.events.removeAll(listenedObj, 'message'));
|
||||
|
||||
var t1 = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
t1.connect();
|
||||
t1.dispose();
|
||||
assertEquals(0, goog.events.removeAll(listenedObj, 'message'));
|
||||
|
||||
var t2 = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
var t3 = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
t2.connect();
|
||||
t3.connect();
|
||||
t2.dispose();
|
||||
assertEquals(1, goog.events.removeAll(listenedObj, 'message'));
|
||||
}
|
||||
|
||||
|
||||
function testDisposeWithDom() {
|
||||
var xpc = getTestChannel(goog.dom.getDomHelper());
|
||||
var listenedObj = window.postMessage ? window : document;
|
||||
|
||||
var t0 = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
assertEquals(0, goog.events.removeAll(listenedObj, 'message'));
|
||||
t0.dispose();
|
||||
assertEquals(0, goog.events.removeAll(listenedObj, 'message'));
|
||||
|
||||
var t1 = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
t1.connect();
|
||||
t1.dispose();
|
||||
assertEquals(0, goog.events.removeAll(listenedObj, 'message'));
|
||||
|
||||
var t2 = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
var t3 = new goog.net.xpc.NativeMessagingTransport(xpc,
|
||||
null /* peerHostName */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
t2.connect();
|
||||
t3.connect();
|
||||
t2.dispose();
|
||||
assertEquals(1, goog.events.removeAll(listenedObj, 'message'));
|
||||
}
|
||||
|
||||
|
||||
function testBogusMessages() {
|
||||
var e = createMockEvent('bogus_message');
|
||||
assertFalse(goog.net.xpc.NativeMessagingTransport.messageReceived_(e));
|
||||
|
||||
e = createMockEvent('bogus|message');
|
||||
assertFalse(goog.net.xpc.NativeMessagingTransport.messageReceived_(e));
|
||||
|
||||
e = createMockEvent('bogus|message:data');
|
||||
assertFalse(goog.net.xpc.NativeMessagingTransport.messageReceived_(e));
|
||||
}
|
||||
|
||||
|
||||
function testSendingMessagesToUnconnectedInnerPeer() {
|
||||
var xpc = getTestChannel();
|
||||
|
||||
var serviceResult, payloadResult;
|
||||
xpc.xpcDeliver = function(service, payload) {
|
||||
serviceResult = service;
|
||||
payloadResult = payload;
|
||||
};
|
||||
|
||||
// Construct an unconnected inner peer.
|
||||
xpc.getRole = function() {
|
||||
return goog.net.xpc.CrossPageChannelRole.INNER;
|
||||
};
|
||||
xpc.isConnected = function() {
|
||||
return false;
|
||||
};
|
||||
var t = new goog.net.xpc.NativeMessagingTransport(xpc, 'http://g.com',
|
||||
false /* opt_oneSidedHandshake */, 2 /* opt_protocolVersion */);
|
||||
|
||||
// Test a valid message.
|
||||
var e = createMockEvent('test_channel|test_service:test_payload');
|
||||
assertTrue(goog.net.xpc.NativeMessagingTransport.messageReceived_(e));
|
||||
assertEquals('test_service', serviceResult);
|
||||
assertEquals('test_payload', payloadResult);
|
||||
assertEquals('Ensure channel name has not been changed.',
|
||||
'test_channel',
|
||||
t.channel_.name);
|
||||
|
||||
// Test updating a stale inner peer.
|
||||
var e = createMockEvent('new_channel|tp:SETUP');
|
||||
assertTrue(goog.net.xpc.NativeMessagingTransport.messageReceived_(e));
|
||||
assertEquals('tp', serviceResult);
|
||||
assertEquals('SETUP', payloadResult);
|
||||
assertEquals('Ensure channel name has been updated.',
|
||||
'new_channel',
|
||||
t.channel_.name);
|
||||
t.dispose();
|
||||
}
|
||||
|
||||
|
||||
function testSignalConnected_innerFrame() {
|
||||
checkSignalConnected(false /* oneSidedHandshake */,
|
||||
true /* innerFrame */);
|
||||
}
|
||||
|
||||
|
||||
function testSignalConnected_outerFrame() {
|
||||
checkSignalConnected(false /* oneSidedHandshake */,
|
||||
false /* innerFrame */);
|
||||
}
|
||||
|
||||
|
||||
function testSignalConnected_singleSided_innerFrame() {
|
||||
checkSignalConnected(true /* oneSidedHandshake */,
|
||||
true /* innerFrame */);
|
||||
}
|
||||
|
||||
|
||||
function testSignalConnected_singleSided_outerFrame() {
|
||||
checkSignalConnected(true /* oneSidedHandshake */,
|
||||
false /* innerFrame */);
|
||||
}
|
||||
|
||||
|
||||
function checkSignalConnected(oneSidedHandshake, innerFrame,
|
||||
peerProtocolVersion, protocolVersion) {
|
||||
var xpc = getTestChannel();
|
||||
var connected = false;
|
||||
xpc.notifyConnected = function() {
|
||||
if (connected) {
|
||||
fail();
|
||||
} else {
|
||||
connected = true;
|
||||
}
|
||||
};
|
||||
xpc.getRole = function() {
|
||||
return innerFrame ? goog.net.xpc.CrossPageChannelRole.INNER :
|
||||
goog.net.xpc.CrossPageChannelRole.OUTER;
|
||||
};
|
||||
xpc.isConnected = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
var transport = new goog.net.xpc.NativeMessagingTransport(xpc, 'http://g.com',
|
||||
undefined /* opt_domHelper */,
|
||||
oneSidedHandshake /* opt_oneSidedHandshake */,
|
||||
2 /* protocolVerion */);
|
||||
var sentPayloads = [];
|
||||
transport.send = function(service, payload) {
|
||||
assertEquals(goog.net.xpc.TRANSPORT_SERVICE_, service);
|
||||
sentPayloads.push(payload);
|
||||
};
|
||||
function assertSent(payloads) {
|
||||
assertArrayEquals(payloads, sentPayloads);
|
||||
sentPayloads = [];
|
||||
}
|
||||
var endpointId = transport.endpointId_;
|
||||
var peerEndpointId1 = 'abc123';
|
||||
var peerEndpointId2 = 'def234';
|
||||
|
||||
assertFalse(connected);
|
||||
if (!oneSidedHandshake || innerFrame) {
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP_NTPV2 + ',' +
|
||||
peerEndpointId1);
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP);
|
||||
assertSent([goog.net.xpc.SETUP_ACK_NTPV2]);
|
||||
assertFalse(connected);
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP_ACK_NTPV2);
|
||||
assertSent([]);
|
||||
assertTrue(connected);
|
||||
} else {
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP_ACK_NTPV2);
|
||||
assertSent([]);
|
||||
assertFalse(connected);
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP_NTPV2 + ',' +
|
||||
peerEndpointId1);
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP);
|
||||
assertSent([goog.net.xpc.SETUP_ACK_NTPV2]);
|
||||
assertTrue(connected);
|
||||
}
|
||||
|
||||
// Verify that additional transport service traffic doesn't cause duplicate
|
||||
// notifications.
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP_NTPV2 + ',' +
|
||||
peerEndpointId1);
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP);
|
||||
assertSent([goog.net.xpc.SETUP_ACK_NTPV2]);
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP_ACK_NTPV2);
|
||||
assertSent([]);
|
||||
|
||||
// Simulate a reconnection by sending a SETUP message from a frame with a
|
||||
// different endpoint id. No further connection callbacks should fire, but
|
||||
// a new SETUP message should be triggered.
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP_NTPV2 + ',' +
|
||||
peerEndpointId2);
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP);
|
||||
assertSent([goog.net.xpc.SETUP_ACK_NTPV2, goog.net.xpc.SETUP_NTPV2 + ',' +
|
||||
endpointId]);
|
||||
transport.transportServiceHandler(goog.net.xpc.SETUP_ACK_NTPV2);
|
||||
assertSent([]);
|
||||
}
|
||||
|
||||
|
||||
function createMockEvent(data) {
|
||||
var event = {};
|
||||
event.getBrowserEvent = function() { return {data: data} };
|
||||
return event;
|
||||
}
|
||||
|
||||
|
||||
function getTestChannel(opt_domHelper) {
|
||||
var cfg = {};
|
||||
cfg[goog.net.xpc.CfgFields.CHANNEL_NAME] = 'test_channel';
|
||||
return new goog.net.xpc.CrossPageChannel(cfg, opt_domHelper,
|
||||
undefined /* opt_domHelper */, false /* opt_oneSidedHandshake */,
|
||||
2 /* opt_protocolVersion */);
|
||||
}
|
||||
@@ -0,0 +1,483 @@
|
||||
// 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.log');
|
||||
goog.require('goog.net.xpc');
|
||||
goog.require('goog.net.xpc.CfgFields');
|
||||
goog.require('goog.net.xpc.CrossPageChannelRole');
|
||||
goog.require('goog.net.xpc.Transport');
|
||||
goog.require('goog.net.xpc.TransportTypes');
|
||||
goog.require('goog.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}
|
||||
* @final
|
||||
*/
|
||||
goog.net.xpc.NixTransport = function(channel, opt_domHelper) {
|
||||
goog.net.xpc.NixTransport.base(this, 'constructor', 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.net.xpc.NixTransport.base(this, 'disposeInternal');
|
||||
this.nixChannel_ = null;
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/** @suppress {extraProvide} */
|
||||
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.
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
|
||||
This file checks whether the current browser can access properties from same
|
||||
domain iframes. This is currently a problem on the matrix brower ie6 and xp.
|
||||
For some reason it can't access same domain iframes.
|
||||
TODO(user): Figure out why it can't.
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<title>The access checking iframe</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
try {
|
||||
var sameDomainIframeHref = parent.frames['nonexistant'].location.href;
|
||||
parent.sameDomainIframeAccessComplete(true);
|
||||
} catch (e) {
|
||||
parent.sameDomainIframeAccessComplete(false);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,99 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
|
||||
This file is responsible for setting up the inner peer half of an XPC
|
||||
communication channel. It instantiates a CrossPageChannel and attempts to
|
||||
connect to the outer peer. The XPC configuration should match that of the
|
||||
outer peer (i.e. same channel name, polling URIs, etc).
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
|
||||
<head>
|
||||
<title>XPC test inner frame</title>
|
||||
<script src="../../../base.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
goog.require('goog.debug.Logger');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.net.xpc.CrossPageChannel');
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
var channel;
|
||||
var queuedMessage;
|
||||
|
||||
var OBJECT_RESULT_FROM_SERVICE = {'favorites': 'pie'};
|
||||
|
||||
function clearDebug() {
|
||||
document.getElementById('debugDiv').innerHTML = '';
|
||||
}
|
||||
|
||||
function instantiateChannel(cfg) {
|
||||
if (window.channel) {
|
||||
window.channel.dispose();
|
||||
}
|
||||
window.channel = new goog.net.xpc.CrossPageChannel(cfg);
|
||||
window.channel.registerService('echo', echoHandler);
|
||||
window.channel.registerService('response', responseHandler);
|
||||
connectChannel(
|
||||
parent.driver && parent.driver.innerFrameConnected ?
|
||||
goog.bind(parent.driver.innerFrameConnected, parent.driver) : null);
|
||||
}
|
||||
|
||||
function connectChannel(opt_callback) {
|
||||
window.channel.connect(opt_callback || goog.nullFunction);
|
||||
}
|
||||
|
||||
function sendEcho(payload) {
|
||||
window.channel.send('echo', payload);
|
||||
}
|
||||
|
||||
function echoHandler(payload) {
|
||||
window.channel.send('response', payload);
|
||||
return OBJECT_RESULT_FROM_SERVICE;
|
||||
}
|
||||
|
||||
function isConnected() {
|
||||
return window.channel && window.channel.isConnected();
|
||||
}
|
||||
|
||||
function responseHandler(payload) {
|
||||
if (parent.driver && parent.driver.innerFrameGotResponse) {
|
||||
parent.driver.innerFrameGotResponse(payload);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div style="position:absolute">
|
||||
Debug [<a href="#" onclick="clearDebug()">clear</a>]: <br>
|
||||
<div id=debugDiv style="border: 1px #000000 solid; font-size:xx-small"></div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var debugDiv = goog.dom.getElement('debugDiv');
|
||||
var logger = goog.debug.Logger.getLogger('goog.net.xpc');
|
||||
logger.setLevel(goog.debug.Logger.Level.ALL);
|
||||
logger.addHandler(function(logRecord) {
|
||||
var msgElm = goog.dom.createDom('div');
|
||||
msgElm.innerHTML = logRecord.getMessage();
|
||||
goog.dom.appendChild(debugDiv, msgElm);
|
||||
});
|
||||
|
||||
if (parent && parent.iframeLoadHandler) {
|
||||
parent.iframeLoadHandler();
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -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.TransportNames');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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[String(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;
|
||||
@@ -0,0 +1,300 @@
|
||||
// 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,
|
||||
DIRECT: 7
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enum containing transport names. These need to correspond to the
|
||||
* transport class names for createTransport_() to work.
|
||||
* @const {!Object<string,string>}
|
||||
*/
|
||||
goog.net.xpc.TransportNames = {
|
||||
'1': 'NativeMessagingTransport',
|
||||
'2': 'FrameElementMethodTransport',
|
||||
'3': 'IframeRelayTransport',
|
||||
'4': 'IframePollingTransport',
|
||||
'5': 'FlashTransport',
|
||||
'6': 'NixTransport',
|
||||
'7': 'DirectTransport'
|
||||
};
|
||||
|
||||
|
||||
// TODO(user): Add auth token support to other methods.
|
||||
|
||||
|
||||
/**
|
||||
* Field names used on configuration object.
|
||||
* @const
|
||||
*/
|
||||
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',
|
||||
/**
|
||||
* Whether the direct transport runs in synchronous mode. The default is to
|
||||
* emulate the other transports and run asyncronously but there are some
|
||||
* circumstances where syncronous calls are required. If this property is
|
||||
* set to true, the transport will send the messages synchronously.
|
||||
*/
|
||||
DIRECT_TRANSPORT_SYNC_MODE: 'directSyncMode'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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|visibility}
|
||||
*/
|
||||
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|visibility}
|
||||
*/
|
||||
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 {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