123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851 |
- // 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.dom.TagName');
- 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 {?HTMLIFrameElement}
- * @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 {?HTMLIFrameElement} 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_ && !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(goog.dom.TagName.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_ = /** @type {?HTMLIFrameElement} */ (
- 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_();
- }
- };
- /**
- * 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.
- * @package
- */
- 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]);
- }
- };
|