blob: 5066bd97867f6d466d6744144b6d3ce72bba334c [file] [log] [blame]
// Copyright 2019 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.
#include "fuchsia/runners/cast/bindings/cast_channel.h"
#include <lib/fit/function.h>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/macros.h"
#include "base/path_service.h"
#include "base/threading/thread_task_runner_handle.h"
#include "fuchsia/common/mem_buffer_util.h"
#include "fuchsia/common/named_message_port_connector.h"
// Unique identifier of the Cast Channel message port, used by the JavaScript
// API to connect to the port.
const char kMessagePortName[] = "cast.__platform__.channel";
CastChannelImpl::CastChannelImpl(
chromium::web::Frame* frame,
webrunner::NamedMessagePortConnector* connector)
: frame_(frame), connector_(connector) {
DCHECK(connector_);
DCHECK(frame_);
connector->Register(
kMessagePortName,
base::BindRepeating(&CastChannelImpl::OnMasterPortReceived,
base::Unretained(this)),
frame_);
// Load the cast.__platform__.channel bundle from the package assets, into a
// mem::Buffer.
base::FilePath assets_path;
CHECK(base::PathService::Get(base::DIR_ASSETS, &assets_path));
fuchsia::mem::Buffer bindings_buf = webrunner::MemBufferFromFile(base::File(
assets_path.AppendASCII("fuchsia/runners/cast/bindings/cast_channel.js"),
base::File::FLAG_OPEN | base::File::FLAG_READ));
CHECK(bindings_buf.vmo);
// Inject the cast.__platform__.channel API to all origins.
std::vector<std::string> origins = {"*"};
// Configure the bundle to be re-injected every time the |frame_| content is
// loaded.
frame_->ExecuteJavaScript(
std::move(origins), std::move(bindings_buf),
chromium::web::ExecuteMode::ON_PAGE_LOAD,
[](bool success) { CHECK(success) << "Couldn't insert bindings."; });
}
CastChannelImpl::~CastChannelImpl() {
connector_->Unregister(frame_, kMessagePortName);
}
void CastChannelImpl::OnMasterPortError() {
master_port_.Unbind();
}
void CastChannelImpl::Connect(ConnectCallback callback) {
// If there is already a bound Cast Channel ready, then return it.
if (pending_channel_) {
callback(std::move(pending_channel_));
return;
}
pending_connect_cb_ = std::move(callback);
if (master_port_) {
master_port_->ReceiveMessage(
fit::bind_member(this, &CastChannelImpl::OnCastChannelMessageReceived));
}
// If there is no master port available at this time, then defer invocation of
// |pending_connect_cb_| until the master port has been received.
}
void CastChannelImpl::OnMasterPortReceived(chromium::web::MessagePortPtr port) {
DCHECK(port);
master_port_ = std::move(port);
master_port_.set_error_handler([this](zx_status_t status) {
ZX_LOG_IF(WARNING, status != ZX_ERR_PEER_CLOSED, status)
<< "Cast Channel master port disconnected.";
OnMasterPortError();
});
if (pending_connect_cb_) {
// Resolve the in-flight Connect call.
Connect(std::move(pending_connect_cb_));
}
}
void CastChannelImpl::OnCastChannelMessageReceived(
chromium::web::WebMessage message) {
if (!message.incoming_transfer ||
!message.incoming_transfer->is_message_port()) {
LOG(WARNING) << "Received a CastChannel without a message port.";
OnMasterPortError();
return;
}
// Fulfill an outstanding Connect() operation, if there is one.
if (pending_connect_cb_) {
pending_connect_cb_(std::move(message.incoming_transfer->message_port()));
pending_connect_cb_ = {};
return;
}
pending_channel_ = std::move(message.incoming_transfer->message_port());
}