chromium/components/cast/named_message_port_connector/named_message_port_connector.js

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

'use strict';

if (!window['cast']) {
  /**
   * @const
   */
  // eslint-disable-next-line no-var
  var cast = new Object();
}

if (!cast['__platform__']) {
  /**
   * @const
   */
  cast.__platform__ = {};
}

// Creates named HTML5 MessagePorts that are connected to native code.
cast.__platform__.PortConnector = new class {
  constructor() {
    /** @private {MessagePort} */
    this.controlPort_ = null;

    // A map of ports waiting to be published to the controlPort_, keyed by
    // string IDs.
    /** @private {Object<string, MessagePort>} */
    this.pendingPorts_ = {};

    /** @private */
    this.listener = this.onMessageEvent.bind(this);

    window.addEventListener(
        'message',
        this.listener,
        true,  // Let the listener handle events before they hit the DOM tree.
    );
  }

  /**
   * Returns a MessagePort whose channel will be passed to the native code.
   * The channel can be used immediately after construction. Outgoing messages
   * will be automatically buffered until the connection is established.
   * @param {string} id The ID of the port being registered.
   * @return {MessagePort}
   */
  bind(id) {
    const channel = new MessageChannel();
    if (this.controlPort_) {
      this.sendPort(id, channel.port2);
    } else {
      this.pendingPorts_[id] = channel.port2;
    }

    return channel.port1;
  }

  /**
   * Sends a MessagePort to the remote NamedMessagePortConnector.
   * @param {string} portId The name of the port to send over the control port.
   * @param {MessagePort} port The port being sent.
   */
  sendPort(portId, port) {
    this.controlPort_.postMessage(portId, [port]);
  }

  /**
   * Handles frame message events to receive a connection "control port" from
   * native code.
   * @param {Event} messageEvent
   */
  onMessageEvent(messageEvent) {
    // Only process window.onmessage events which are intended for this class.
    if (messageEvent.data != 'cast.master.connect') {
      return;
    }

    if (messageEvent.ports.length != 1) {
      console.error(
          'Expected only one MessagePort, got ' + messageEvent.ports.length +
          ' instead.');
      for (const port of messageEvent.ports) {
        port.close();
      }
      return;
    }

    this.controlPort_ = messageEvent.ports[0];
    for (const portId in this.pendingPorts_) {
      this.sendPort(portId, this.pendingPorts_[portId]);
    }
    this.pendingPorts_ = null;

    messageEvent.stopPropagation();

    // No need to receive more onmessage events.
    window.removeEventListener('message', this.listener);
  }
}
();