blob: 5c0beabe884a519e7a3314bf1a50d6adb25afb3d [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
if (!cast)
var cast = new Object;
if (!cast.__platform__)
cast.__platform__ = new Object;
// Defines a late-bound port for performing bidirectional communication with
// platform code. Ports are passed in from outside the browser process after
// every pages load. Constructed ports are not initially connected, but they are
// immediately available for writing via a buffer. When the Channel is
// eventually connected, the buffer will be flushed and all subsequent writes
// will be sent directly to the peer.
cast.__platform__.Port = class {
constructor(id, messageHandler) {
this.messageHandler_ = messageHandler;
this.id_ = id;
}
// Sends a message to the port, or buffers the message for later delivery if
// no port is available yet.
sendMessage(message) {
if (this.port_)
this.port_.postMessage(message);
else
this.buffer_.push(message);
}
// Receives a MessagePort from cast.__platform__.connector.
receivePort(port) {
this.port_ = port;
this.port_.onmessage = this.onMessage.bind(this);
this.port_.onmessageerror = this.onMessageError.bind(this);
// Signal the peer that the port was received.
this.port_.postMessage('connected');
// Flush and destroy the pre-connect buffer.
for (var i = 0; i < this.buffer_.length; ++i) {
this.port_.postMessage(this.buffer_[i]);
}
this.buffer = null;
}
onMessage(message) {
this.messageHandler_(message.data);
}
onMessageError() {
// The underlying MessagePort should be long-lived and reliable. Any
// connection drop is likely due to a bug on the other side of the
// FIDL IPC connection.
console.error('Unexpected connection loss for channel ' + this.id);
this.port_ = null;
}
// Called when a message arrives.
messageHandler_ = null;
// Buffer of messages to be sent prior to a port being received.
// Once a port is bound, the buffer is flushed and then destroyed.
buffer_ = [];
// Underlying message transport.
port_ = null;
};
// Creates new string-identified Ports and asynchronously binds them to
// message transport.
cast.__platform__.connector = new class {
// Sets up a Port which will be asynchronously bound to a MessagePort.
// The port is identified by a string |id|, which must not have spaces.
bind(id, messageHandler) {
var binding = new cast.__platform__.Port(id, messageHandler);
this.pending_ports_[id] = binding;
return binding;
}
// Services incoming port connection events via the window.onmessage handler.
onMessageEvent(e) {
// Only process events identified by a magic 'connect' prefix.
// Let all other events pass through to other handlers.
var tokenized = e.data.split(' ');
if (tokenized.length != 2 || tokenized[0] != 'connect')
return;
if (e.ports.length != 1) {
console.error('Expected only one MessagePort, got ' + e.ports.length +
' instead.');
return;
}
var portName = tokenized[1];
if (!(portName in this.pending_ports_)) {
// Bindings must be registered in advance of MessagePorts.
console.error('No handler for port: ' + portName);
return;
}
this.pending_ports_[portName].receivePort(e.ports[0]);
delete this.pending_ports_[portName];
e.stopPropagation();
}
// A map of unconnected ports waiting to be connected with MessagePorts,
// keyed string IDs.
pending_ports_ = {};
}();
window.addEventListener('message',
cast.__platform__.connector.onMessageEvent.bind(
cast.__platform__.connector), true);