Path: blob/trunk/third_party/closure/goog/net/xpc/crosspagechannel.js
1865 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Provides the class CrossPageChannel, the main class in16* goog.net.xpc.17*18* @see ../../demos/xpc/index.html19*/2021goog.provide('goog.net.xpc.CrossPageChannel');2223goog.require('goog.Uri');24goog.require('goog.async.Deferred');25goog.require('goog.async.Delay');26goog.require('goog.dispose');27goog.require('goog.dom');28goog.require('goog.dom.TagName');29goog.require('goog.events');30goog.require('goog.events.EventHandler');31goog.require('goog.events.EventType');32goog.require('goog.json');33goog.require('goog.log');34goog.require('goog.messaging.AbstractChannel');35goog.require('goog.net.xpc');36goog.require('goog.net.xpc.CfgFields');37goog.require('goog.net.xpc.ChannelStates');38goog.require('goog.net.xpc.CrossPageChannelRole');39goog.require('goog.net.xpc.DirectTransport');40goog.require('goog.net.xpc.FrameElementMethodTransport');41goog.require('goog.net.xpc.IframePollingTransport');42goog.require('goog.net.xpc.IframeRelayTransport');43goog.require('goog.net.xpc.NativeMessagingTransport');44goog.require('goog.net.xpc.NixTransport');45goog.require('goog.net.xpc.TransportTypes');46goog.require('goog.net.xpc.UriCfgFields');47goog.require('goog.string');48goog.require('goog.uri.utils');49goog.require('goog.userAgent');50515253/**54* A communication channel between two documents from different domains.55* Provides asynchronous messaging.56*57* @param {Object} cfg Channel configuration object.58* @param {goog.dom.DomHelper=} opt_domHelper The optional dom helper to59* use for looking up elements in the dom.60* @constructor61* @extends {goog.messaging.AbstractChannel}62*/63goog.net.xpc.CrossPageChannel = function(cfg, opt_domHelper) {64goog.net.xpc.CrossPageChannel.base(this, 'constructor');6566for (var i = 0, uriField; uriField = goog.net.xpc.UriCfgFields[i]; i++) {67if (uriField in cfg && !/^https?:\/\//.test(cfg[uriField])) {68throw Error('URI ' + cfg[uriField] + ' is invalid for field ' + uriField);69}70}7172/**73* The configuration for this channel.74* @type {Object}75* @private76*/77this.cfg_ = cfg;7879/**80* The name of the channel. Please use81* <code>updateChannelNameAndCatalog</code> to change this from the transports82* vs changing the property directly.83* @type {string}84*/85this.name = this.cfg_[goog.net.xpc.CfgFields.CHANNEL_NAME] ||86goog.net.xpc.getRandomString(10);8788/**89* The dom helper to use for accessing the dom.90* @type {goog.dom.DomHelper}91* @private92*/93this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();9495/**96* Collects deferred function calls which will be made once the connection97* has been fully set up.98* @type {!Array<function()>}99* @private100*/101this.deferredDeliveries_ = [];102103/**104* An event handler used to listen for load events on peer iframes.105* @type {!goog.events.EventHandler<!goog.net.xpc.CrossPageChannel>}106* @private107*/108this.peerLoadHandler_ = new goog.events.EventHandler(this);109110// If LOCAL_POLL_URI or PEER_POLL_URI is not available, try using111// robots.txt from that host.112cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] =113cfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] ||114goog.uri.utils.getHost(this.domHelper_.getWindow().location.href) +115'/robots.txt';116// PEER_URI is sometimes undefined in tests.117cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =118cfg[goog.net.xpc.CfgFields.PEER_POLL_URI] ||119goog.uri.utils.getHost(cfg[goog.net.xpc.CfgFields.PEER_URI] || '') +120'/robots.txt';121122goog.net.xpc.channels[this.name] = this;123124if (!goog.events.getListener(125window, goog.events.EventType.UNLOAD,126goog.net.xpc.CrossPageChannel.disposeAll_)) {127// Set listener to dispose all registered channels on page unload.128goog.events.listenOnce(129window, goog.events.EventType.UNLOAD,130goog.net.xpc.CrossPageChannel.disposeAll_);131}132133goog.log.info(goog.net.xpc.logger, 'CrossPageChannel created: ' + this.name);134};135goog.inherits(goog.net.xpc.CrossPageChannel, goog.messaging.AbstractChannel);136137138/**139* Regexp for escaping service names.140* @type {RegExp}141* @private142*/143goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_ESCAPE_RE_ =144new RegExp('^%*' + goog.net.xpc.TRANSPORT_SERVICE_ + '$');145146147/**148* Regexp for unescaping service names.149* @type {RegExp}150* @private151*/152goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_UNESCAPE_RE_ =153new RegExp('^%+' + goog.net.xpc.TRANSPORT_SERVICE_ + '$');154155156/**157* A delay between the transport reporting as connected and the calling of the158* connection callback. Sometimes used to paper over timing vulnerabilities.159* @type {goog.async.Delay}160* @private161*/162goog.net.xpc.CrossPageChannel.prototype.connectionDelay_ = null;163164165/**166* A deferred which is set to non-null while a peer iframe is being created167* but has not yet thrown its load event, and which fires when that load event168* arrives.169* @type {goog.async.Deferred}170* @private171*/172goog.net.xpc.CrossPageChannel.prototype.peerWindowDeferred_ = null;173174175/**176* The transport.177* @type {goog.net.xpc.Transport?}178* @private179*/180goog.net.xpc.CrossPageChannel.prototype.transport_ = null;181182183/**184* The channel state.185* @type {number}186* @private187*/188goog.net.xpc.CrossPageChannel.prototype.state_ =189goog.net.xpc.ChannelStates.NOT_CONNECTED;190191192/**193* @override194* @return {boolean} Whether the channel is connected.195*/196goog.net.xpc.CrossPageChannel.prototype.isConnected = function() {197return this.state_ == goog.net.xpc.ChannelStates.CONNECTED;198};199200201/**202* Reference to the window-object of the peer page.203* @type {Object}204* @private205*/206goog.net.xpc.CrossPageChannel.prototype.peerWindowObject_ = null;207208209/**210* Reference to the iframe-element.211* @type {?HTMLIFrameElement}212* @private213*/214goog.net.xpc.CrossPageChannel.prototype.iframeElement_ = null;215216217/**218* Returns the configuration object for this channel.219* Package private. Do not call from outside goog.net.xpc.220*221* @return {Object} The configuration object for this channel.222*/223goog.net.xpc.CrossPageChannel.prototype.getConfig = function() {224return this.cfg_;225};226227228/**229* Returns a reference to the iframe-element.230* Package private. Do not call from outside goog.net.xpc.231*232* @return {?HTMLIFrameElement} A reference to the iframe-element.233*/234goog.net.xpc.CrossPageChannel.prototype.getIframeElement = function() {235return this.iframeElement_;236};237238239/**240* Sets the window object the foreign document resides in.241*242* @param {Object} peerWindowObject The window object of the peer.243*/244goog.net.xpc.CrossPageChannel.prototype.setPeerWindowObject = function(245peerWindowObject) {246this.peerWindowObject_ = peerWindowObject;247};248249250/**251* Returns the window object the foreign document resides in.252*253* @return {Object} The window object of the peer.254* @package255*/256goog.net.xpc.CrossPageChannel.prototype.getPeerWindowObject = function() {257return this.peerWindowObject_;258};259260261/**262* Determines whether the peer window is available (e.g. not closed).263*264* @return {boolean} Whether the peer window is available.265* @package266*/267goog.net.xpc.CrossPageChannel.prototype.isPeerAvailable = function() {268// NOTE(user): This check is not reliable in IE, where a document in an269// iframe does not get unloaded when removing the iframe element from the DOM.270// TODO(user): Find something that works in IE as well.271// NOTE(user): "!this.peerWindowObject_.closed" evaluates to 'false' in IE9272// sometimes even though typeof(this.peerWindowObject_.closed) is boolean and273// this.peerWindowObject_.closed evaluates to 'false'. Casting it to a Boolean274// results in sane evaluation. When this happens, it's in the inner iframe275// when querying its parent's 'closed' status. Note that this is a different276// case than mibuerge@'s note above.277try {278return !!this.peerWindowObject_ && !this.peerWindowObject_.closed;279} catch (e) {280// If the window is closing, an error may be thrown.281return false;282}283};284285286/**287* Determine which transport type to use for this channel / useragent.288* @return {goog.net.xpc.TransportTypes|undefined} The best transport type.289* @private290*/291goog.net.xpc.CrossPageChannel.prototype.determineTransportType_ = function() {292var transportType;293if (goog.isFunction(document.postMessage) ||294goog.isFunction(window.postMessage) ||295// IE8 supports window.postMessage, but296// typeof window.postMessage returns "object"297(goog.userAgent.IE && window.postMessage)) {298transportType = goog.net.xpc.TransportTypes.NATIVE_MESSAGING;299} else if (goog.userAgent.GECKO) {300transportType = goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD;301} else if (302goog.userAgent.IE && this.cfg_[goog.net.xpc.CfgFields.PEER_RELAY_URI]) {303transportType = goog.net.xpc.TransportTypes.IFRAME_RELAY;304} else if (goog.userAgent.IE && goog.net.xpc.NixTransport.isNixSupported()) {305transportType = goog.net.xpc.TransportTypes.NIX;306} else {307transportType = goog.net.xpc.TransportTypes.IFRAME_POLLING;308}309return transportType;310};311312313/**314* Creates the transport for this channel. Chooses from the available315* transport based on the user agent and the configuration.316* @private317*/318goog.net.xpc.CrossPageChannel.prototype.createTransport_ = function() {319// return, if the transport has already been created320if (this.transport_) {321return;322}323324// TODO(user): Use goog.scope.325var CfgFields = goog.net.xpc.CfgFields;326327if (!this.cfg_[CfgFields.TRANSPORT]) {328this.cfg_[CfgFields.TRANSPORT] = this.determineTransportType_();329}330331switch (this.cfg_[CfgFields.TRANSPORT]) {332case goog.net.xpc.TransportTypes.NATIVE_MESSAGING:333var protocolVersion =334this.cfg_[CfgFields.NATIVE_TRANSPORT_PROTOCOL_VERSION] || 2;335this.transport_ = new goog.net.xpc.NativeMessagingTransport(336this, this.cfg_[CfgFields.PEER_HOSTNAME], this.domHelper_,337!!this.cfg_[CfgFields.ONE_SIDED_HANDSHAKE], protocolVersion);338break;339case goog.net.xpc.TransportTypes.NIX:340this.transport_ = new goog.net.xpc.NixTransport(this, this.domHelper_);341break;342case goog.net.xpc.TransportTypes.FRAME_ELEMENT_METHOD:343this.transport_ =344new goog.net.xpc.FrameElementMethodTransport(this, this.domHelper_);345break;346case goog.net.xpc.TransportTypes.IFRAME_RELAY:347this.transport_ =348new goog.net.xpc.IframeRelayTransport(this, this.domHelper_);349break;350case goog.net.xpc.TransportTypes.IFRAME_POLLING:351this.transport_ =352new goog.net.xpc.IframePollingTransport(this, this.domHelper_);353break;354case goog.net.xpc.TransportTypes.DIRECT:355if (this.peerWindowObject_ &&356goog.net.xpc.DirectTransport.isSupported(357/** @type {!Window} */ (this.peerWindowObject_))) {358this.transport_ =359new goog.net.xpc.DirectTransport(this, this.domHelper_);360} else {361goog.log.info(362goog.net.xpc.logger,363'DirectTransport not supported for this window, peer window in' +364' different security context or not set yet.');365}366break;367}368369if (this.transport_) {370goog.log.info(371goog.net.xpc.logger, 'Transport created: ' + this.transport_.getName());372} else {373throw Error('CrossPageChannel: No suitable transport found!');374}375};376377378/**379* Returns the transport type in use for this channel.380* @return {number} Transport-type identifier.381*/382goog.net.xpc.CrossPageChannel.prototype.getTransportType = function() {383return this.transport_.getType();384};385386387/**388* Returns the tranport name in use for this channel.389* @return {string} The transport name.390*/391goog.net.xpc.CrossPageChannel.prototype.getTransportName = function() {392return this.transport_.getName();393};394395396/**397* @return {!Object} Configuration-object to be used by the peer to398* initialize the channel.399*/400goog.net.xpc.CrossPageChannel.prototype.getPeerConfiguration = function() {401var peerCfg = {};402peerCfg[goog.net.xpc.CfgFields.CHANNEL_NAME] = this.name;403peerCfg[goog.net.xpc.CfgFields.TRANSPORT] =404this.cfg_[goog.net.xpc.CfgFields.TRANSPORT];405peerCfg[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE] =406this.cfg_[goog.net.xpc.CfgFields.ONE_SIDED_HANDSHAKE];407408if (this.cfg_[goog.net.xpc.CfgFields.LOCAL_RELAY_URI]) {409peerCfg[goog.net.xpc.CfgFields.PEER_RELAY_URI] =410this.cfg_[goog.net.xpc.CfgFields.LOCAL_RELAY_URI];411}412if (this.cfg_[goog.net.xpc.CfgFields.LOCAL_POLL_URI]) {413peerCfg[goog.net.xpc.CfgFields.PEER_POLL_URI] =414this.cfg_[goog.net.xpc.CfgFields.LOCAL_POLL_URI];415}416if (this.cfg_[goog.net.xpc.CfgFields.PEER_POLL_URI]) {417peerCfg[goog.net.xpc.CfgFields.LOCAL_POLL_URI] =418this.cfg_[goog.net.xpc.CfgFields.PEER_POLL_URI];419}420var role = this.cfg_[goog.net.xpc.CfgFields.ROLE];421if (role) {422peerCfg[goog.net.xpc.CfgFields.ROLE] =423role == goog.net.xpc.CrossPageChannelRole.INNER ?424goog.net.xpc.CrossPageChannelRole.OUTER :425goog.net.xpc.CrossPageChannelRole.INNER;426}427428return peerCfg;429};430431432/**433* Creates the iframe containing the peer page in a specified parent element.434* This method does not connect the channel, connect() still has to be called435* separately.436*437* @param {!Element} parentElm The container element the iframe is appended to.438* @param {Function=} opt_configureIframeCb If present, this function gets439* called with the iframe element as parameter to allow setting properties440* on it before it gets added to the DOM. If absent, the iframe's width and441* height are set to '100%'.442* @param {boolean=} opt_addCfgParam Whether to add the peer configuration as443* URL parameter (default: true).444* @return {!HTMLIFrameElement} The iframe element.445*/446goog.net.xpc.CrossPageChannel.prototype.createPeerIframe = function(447parentElm, opt_configureIframeCb, opt_addCfgParam) {448goog.log.info(goog.net.xpc.logger, 'createPeerIframe()');449450var iframeId = this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID];451if (!iframeId) {452// Create a randomized ID for the iframe element to avoid453// bfcache-related issues.454iframeId = this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID] =455'xpcpeer' + goog.net.xpc.getRandomString(4);456}457458// TODO(user) Opera creates a history-entry when creating an iframe459// programmatically as follows. Find a way which avoids this.460461var iframeElm =462goog.dom.getDomHelper(parentElm).createElement(goog.dom.TagName.IFRAME);463iframeElm.id = iframeElm.name = iframeId;464if (opt_configureIframeCb) {465opt_configureIframeCb(iframeElm);466} else {467iframeElm.style.width = iframeElm.style.height = '100%';468}469470this.cleanUpIncompleteConnection_();471this.peerWindowDeferred_ = new goog.async.Deferred(undefined, this);472var peerUri = this.getPeerUri(opt_addCfgParam);473this.peerLoadHandler_.listenOnceWithScope(474iframeElm, 'load', this.peerWindowDeferred_.callback, false,475this.peerWindowDeferred_);476477if (goog.userAgent.GECKO || goog.userAgent.WEBKIT) {478// Appending the iframe in a timeout to avoid a weird fastback issue, which479// is present in Safari and Gecko.480window.setTimeout(goog.bind(function() {481parentElm.appendChild(iframeElm);482iframeElm.src = peerUri.toString();483goog.log.info(484goog.net.xpc.logger, 'peer iframe created (' + iframeId + ')');485}, this), 1);486} else {487iframeElm.src = peerUri.toString();488parentElm.appendChild(iframeElm);489goog.log.info(490goog.net.xpc.logger, 'peer iframe created (' + iframeId + ')');491}492493return /** @type {!HTMLIFrameElement} */ (iframeElm);494};495496497/**498* Clean up after any incomplete attempt to establish and connect to a peer499* iframe.500* @private501*/502goog.net.xpc.CrossPageChannel.prototype.cleanUpIncompleteConnection_ =503function() {504if (this.peerWindowDeferred_) {505this.peerWindowDeferred_.cancel();506this.peerWindowDeferred_ = null;507}508this.deferredDeliveries_.length = 0;509this.peerLoadHandler_.removeAll();510};511512513/**514* Returns the peer URI, with an optional URL parameter for configuring the peer515* window.516*517* @param {boolean=} opt_addCfgParam Whether to add the peer configuration as518* URL parameter (default: true).519* @return {!goog.Uri} The peer URI.520*/521goog.net.xpc.CrossPageChannel.prototype.getPeerUri = function(opt_addCfgParam) {522var peerUri = this.cfg_[goog.net.xpc.CfgFields.PEER_URI];523if (goog.isString(peerUri)) {524peerUri = this.cfg_[goog.net.xpc.CfgFields.PEER_URI] =525new goog.Uri(peerUri);526}527528// Add the channel configuration used by the peer as URL parameter.529if (opt_addCfgParam !== false) {530peerUri.setParameterValue(531'xpc', goog.json.serialize(this.getPeerConfiguration()));532}533534return peerUri;535};536537538/**539* Initiates connecting the channel. When this method is called, all the540* information needed to connect the channel has to be available.541*542* @override543* @param {Function=} opt_connectCb The function to be called when the544* channel has been connected and is ready to be used.545*/546goog.net.xpc.CrossPageChannel.prototype.connect = function(opt_connectCb) {547this.connectCb_ = opt_connectCb || goog.nullFunction;548549// If this channel was previously closed, transition back to the NOT_CONNECTED550// state to ensure that the connection can proceed (xpcDeliver blocks551// transport messages while the connection state is CLOSED).552if (this.state_ == goog.net.xpc.ChannelStates.CLOSED) {553this.state_ = goog.net.xpc.ChannelStates.NOT_CONNECTED;554}555556// If we know of a peer window whose creation has been requested but is not557// complete, peerWindowDeferred_ will be non-null, and we should block on it.558if (this.peerWindowDeferred_) {559this.peerWindowDeferred_.addCallback(this.continueConnection_);560} else {561this.continueConnection_();562}563};564565566/**567* Continues the connection process once we're as sure as we can be that the568* peer iframe has been created.569* @private570*/571goog.net.xpc.CrossPageChannel.prototype.continueConnection_ = function() {572goog.log.info(goog.net.xpc.logger, 'continueConnection_()');573this.peerWindowDeferred_ = null;574if (this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]) {575this.iframeElement_ = /** @type {?HTMLIFrameElement} */ (576this.domHelper_.getElement(577this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]));578}579if (this.iframeElement_) {580var winObj = this.iframeElement_.contentWindow;581// accessing the window using contentWindow doesn't work in safari582if (!winObj) {583winObj = window.frames[this.cfg_[goog.net.xpc.CfgFields.IFRAME_ID]];584}585this.setPeerWindowObject(winObj);586}587588// if the peer window object has not been set at this point, we assume589// being in an iframe and the channel is meant to be to the containing page590if (!this.peerWindowObject_) {591// throw an error if we are in the top window (== not in an iframe)592if (window == window.top) {593throw Error(594"CrossPageChannel: Can't connect, peer window-object not set.");595} else {596this.setPeerWindowObject(window.parent);597}598}599600this.createTransport_();601602this.transport_.connect();603604// Now we run any deferred deliveries collected while connection was deferred.605while (this.deferredDeliveries_.length > 0) {606this.deferredDeliveries_.shift()();607}608};609610611/**612* Closes the channel.613*/614goog.net.xpc.CrossPageChannel.prototype.close = function() {615this.cleanUpIncompleteConnection_();616this.state_ = goog.net.xpc.ChannelStates.CLOSED;617goog.dispose(this.transport_);618this.transport_ = null;619this.connectCb_ = null;620goog.dispose(this.connectionDelay_);621this.connectionDelay_ = null;622goog.log.info(goog.net.xpc.logger, 'Channel "' + this.name + '" closed');623};624625626/**627* Package-private.628* Called by the transport when the channel is connected.629* @param {number=} opt_delay Delay this number of milliseconds before calling630* the connection callback. Usage is discouraged, but can be used to paper631* over timing vulnerabilities when there is no alternative.632*/633goog.net.xpc.CrossPageChannel.prototype.notifyConnected = function(opt_delay) {634if (this.isConnected() ||635(this.connectionDelay_ && this.connectionDelay_.isActive())) {636return;637}638this.state_ = goog.net.xpc.ChannelStates.CONNECTED;639goog.log.info(goog.net.xpc.logger, 'Channel "' + this.name + '" connected');640goog.dispose(this.connectionDelay_);641if (goog.isDef(opt_delay)) {642this.connectionDelay_ = new goog.async.Delay(this.connectCb_, opt_delay);643this.connectionDelay_.start();644} else {645this.connectionDelay_ = null;646this.connectCb_();647}648};649650651/**652* Called by the transport in case of an unrecoverable failure.653* Package private. Do not call from outside goog.net.xpc.654*/655goog.net.xpc.CrossPageChannel.prototype.notifyTransportError = function() {656goog.log.info(goog.net.xpc.logger, 'Transport Error');657this.close();658};659660661/** @override */662goog.net.xpc.CrossPageChannel.prototype.send = function(serviceName, payload) {663if (!this.isConnected()) {664goog.log.error(goog.net.xpc.logger, 'Can\'t send. Channel not connected.');665return;666}667// Check if the peer is still around.668if (!this.isPeerAvailable()) {669goog.log.error(goog.net.xpc.logger, 'Peer has disappeared.');670this.close();671return;672}673if (goog.isObject(payload)) {674payload = goog.json.serialize(payload);675}676677// Partially URL-encode the service name because some characters (: and |) are678// used as delimiters for some transports, and we want to allow those679// characters in service names.680this.transport_.send(this.escapeServiceName_(serviceName), payload);681};682683684/**685* Delivers messages to the appropriate service-handler. Named xpcDeliver to686* avoid name conflict with {@code deliver} function in superclass687* goog.messaging.AbstractChannel.688*689* @param {string} serviceName The name of the port.690* @param {string} payload The payload.691* @param {string=} opt_origin An optional origin for the message, where the692* underlying transport makes that available. If this is specified, and693* the PEER_HOSTNAME parameter was provided, they must match or the message694* will be rejected.695* @package696*/697goog.net.xpc.CrossPageChannel.prototype.xpcDeliver = function(698serviceName, payload, opt_origin) {699700// This check covers the very rare (but producable) case where the inner frame701// becomes ready and sends its setup message while the outer frame is702// deferring its connect method waiting for the inner frame to be ready. The703// resulting deferral ensures the message will not be processed until the704// channel is fully configured.705if (this.peerWindowDeferred_) {706this.deferredDeliveries_.push(707goog.bind(this.xpcDeliver, this, serviceName, payload, opt_origin));708return;709}710711// Check whether the origin of the message is as expected.712if (!this.isMessageOriginAcceptable(opt_origin)) {713goog.log.warning(714goog.net.xpc.logger, 'Message received from unapproved origin "' +715opt_origin + '" - rejected.');716return;717}718719// If there is another channel still open, the native transport's global720// postMessage listener will still be active. This will mean that messages721// being sent to the now-closed channel will still be received and delivered,722// such as transport service traffic from its previous correspondent in the723// other frame. Ensure these messages don't cause exceptions.724// Example: http://b/12419303725if (this.isDisposed() || this.state_ == goog.net.xpc.ChannelStates.CLOSED) {726goog.log.warning(727goog.net.xpc.logger, 'CrossPageChannel::xpcDeliver(): Channel closed.');728} else if (!serviceName || serviceName == goog.net.xpc.TRANSPORT_SERVICE_) {729this.transport_.transportServiceHandler(payload);730} else {731// only deliver messages if connected732if (this.isConnected()) {733this.deliver(this.unescapeServiceName_(serviceName), payload);734} else {735goog.log.info(736goog.net.xpc.logger,737'CrossPageChannel::xpcDeliver(): Not connected.');738}739}740};741742743/**744* Escape the user-provided service name for sending across the channel. This745* URL-encodes certain special characters so they don't conflict with delimiters746* used by some of the transports, and adds a special prefix if the name747* conflicts with the reserved transport service name.748*749* This is the opposite of {@link #unescapeServiceName_}.750*751* @param {string} name The name of the service to escape.752* @return {string} The escaped service name.753* @private754*/755goog.net.xpc.CrossPageChannel.prototype.escapeServiceName_ = function(name) {756if (goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_ESCAPE_RE_.test(name)) {757name = '%' + name;758}759return name.replace(/[%:|]/g, encodeURIComponent);760};761762763/**764* Unescape the escaped service name that was sent across the channel. This is765* the opposite of {@link #escapeServiceName_}.766*767* @param {string} name The name of the service to unescape.768* @return {string} The unescaped service name.769* @private770*/771goog.net.xpc.CrossPageChannel.prototype.unescapeServiceName_ = function(name) {772name = name.replace(/%[0-9a-f]{2}/gi, decodeURIComponent);773if (goog.net.xpc.CrossPageChannel.TRANSPORT_SERVICE_UNESCAPE_RE_.test(name)) {774return name.substring(1);775} else {776return name;777}778};779780781/**782* Returns the role of this channel (either inner or outer).783* @return {number} The role of this channel.784*/785goog.net.xpc.CrossPageChannel.prototype.getRole = function() {786var role = this.cfg_[goog.net.xpc.CfgFields.ROLE];787if (goog.isNumber(role)) {788return role;789} else {790return window.parent == this.peerWindowObject_ ?791goog.net.xpc.CrossPageChannelRole.INNER :792goog.net.xpc.CrossPageChannelRole.OUTER;793}794};795796797/**798* Sets the channel name. Note, this doesn't establish a unique channel to799* communicate on.800* @param {string} name The new channel name.801*/802goog.net.xpc.CrossPageChannel.prototype.updateChannelNameAndCatalog = function(803name) {804goog.log.fine(goog.net.xpc.logger, 'changing channel name to ' + name);805delete goog.net.xpc.channels[this.name];806this.name = name;807goog.net.xpc.channels[name] = this;808};809810811/**812* Returns whether an incoming message with the given origin is acceptable.813* If an incoming request comes with a specified (non-empty) origin, and the814* PEER_HOSTNAME config parameter has also been provided, the two must match,815* or the message is unacceptable.816* @param {string=} opt_origin The origin associated with the incoming message.817* @return {boolean} Whether the message is acceptable.818* @package819*/820goog.net.xpc.CrossPageChannel.prototype.isMessageOriginAcceptable = function(821opt_origin) {822var peerHostname = this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME];823return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(opt_origin)) ||824goog.string.isEmptyOrWhitespace(goog.string.makeSafe(peerHostname)) ||825opt_origin == this.cfg_[goog.net.xpc.CfgFields.PEER_HOSTNAME];826};827828829/** @override */830goog.net.xpc.CrossPageChannel.prototype.disposeInternal = function() {831this.close();832833this.peerWindowObject_ = null;834this.iframeElement_ = null;835delete goog.net.xpc.channels[this.name];836goog.dispose(this.peerLoadHandler_);837delete this.peerLoadHandler_;838goog.net.xpc.CrossPageChannel.base(this, 'disposeInternal');839};840841842/**843* Disposes all channels.844* @private845*/846goog.net.xpc.CrossPageChannel.disposeAll_ = function() {847for (var name in goog.net.xpc.channels) {848goog.dispose(goog.net.xpc.channels[name]);849}850};851852853