text: /**
   * @license
   * Copyright The Closure Library Authors.
   * SPDX-License-Identifier: Apache-2.0
   */
  
  /**
   * @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.requireType('goog.dom.DomHelper');
  goog.requireType('goog.net.xpc.CrossPageChannel');
  
  
  goog.scope(function() {
  'use strict';
  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) {
    'use strict';
    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) {
    'use strict';
    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) {
    'use strict';
    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) {
    'use strict';
    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) {
    'use strict';
    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 allChannels = goog.module.get('goog.net.xpc.CrossPageChannel').channels;
    var channel = allChannels[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 allChannels) {
      var staleChannel = allChannels[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) {
    'use strict';
    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() {
    'use strict';
    // 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() {
    'use strict';
    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() {
    'use strict';
    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() {
    'use strict';
    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) {
    'use strict';
    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) {
    'use strict';
    var messageId = goog.getUid(message);
    if (this.asyncSendsMap_[messageId]) {
      delete this.asyncSendsMap_[messageId];
    }
  
  
    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 (peerProxy === null) {
      goog.log.warning(
          goog.net.xpc.logger, 'Peer window had no global function.');
      return;
    }
  
  
    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() {
    'use strict';
    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() {
    'use strict';
    // 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() {
    'use strict';
    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) {
        'use strict';
        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) {
    'use strict';
    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) {
    'use strict';
    /**
     * 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() {
    'use strict';
    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) {
    'use strict';
    return new DirectTransport.Message_(
        literal['channelName'], literal['service'], literal['payload']);
  };
  });  // goog.scope
  