blob: 81c46e9288482685468567c3ee217e1d662ae989 [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 "chromecast/bindings/bindings_manager_cast.h"
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "chromecast/bindings/grit/resources.h"
#include "mojo/public/cpp/bindings/connector.h"
#include "third_party/blink/public/common/messaging/string_message_codec.h"
#include "third_party/blink/public/common/messaging/transferable_message.h"
#include "third_party/blink/public/common/messaging/transferable_message_mojom_traits.h"
#include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h"
#include "ui/base/resource/resource_bundle.h"
namespace chromecast {
namespace bindings {
namespace {
const char kNamedMessagePortConnectorBindingsId[] =
"NAMED_MESSAGE_PORT_CONNECTOR";
const char kControlPortConnectMessage[] = "cast.master.connect";
} // namespace
BindingsManagerCast::BindingsManagerCast() : cast_web_contents_(nullptr) {
// NamedMessagePortConnector binding will be injected into page first.
AddBinding(kNamedMessagePortConnectorBindingsId,
ui::ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_PORT_CONNECTOR_JS));
}
BindingsManagerCast::~BindingsManagerCast() = default;
void BindingsManagerCast::AddBinding(base::StringPiece binding_name,
base::StringPiece binding_script) {
bindings_by_id_[binding_name.as_string()] = binding_script.as_string();
}
void BindingsManagerCast::AttachToPage(
chromecast::CastWebContents* cast_web_contents) {
DCHECK(!cast_web_contents_) << "AttachToPage() was called twice.";
DCHECK(cast_web_contents);
cast_web_contents_ = cast_web_contents;
CastWebContents::Observer::Observe(cast_web_contents_);
for (const auto& binding : bindings_by_id_) {
LOG(INFO) << "Register bindings for page. bindingId: " << binding.first;
cast_web_contents_->AddBeforeLoadJavaScript(
binding.first /* binding ID */, {"*"}, binding.second /* binding JS */);
}
}
void BindingsManagerCast::OnPageStateChanged(
CastWebContents* cast_web_contents) {
auto page_state = cast_web_contents->page_state();
switch (page_state) {
case CastWebContents::PageState::IDLE:
case CastWebContents::PageState::LOADING:
case CastWebContents::PageState::CLOSED:
return;
case CastWebContents::PageState::DESTROYED:
case CastWebContents::PageState::ERROR:
connector_.reset();
CastWebContents::Observer::Observe(nullptr);
cast_web_contents_ = nullptr;
return;
case CastWebContents::PageState::LOADED:
OnPageLoaded();
return;
}
}
void BindingsManagerCast::OnPageLoaded() {
DCHECK(cast_web_contents_)
<< "Received PageLoaded event while not observing a page";
// Unbind platform-side MessagePort connector.
if (connector_) {
connector_->set_incoming_receiver(nullptr);
connector_.reset();
}
// Create a pre-connected MessagePipe, this is the way chromium
// implements HTML5 MessagePort.
mojo::ScopedMessagePipeHandle platform_port;
mojo::ScopedMessagePipeHandle page_port;
mojo::CreateMessagePipe(nullptr, &platform_port, &page_port);
connector_ = std::make_unique<mojo::Connector>(
std::move(platform_port), mojo::Connector::SINGLE_THREADED_SEND,
base::ThreadTaskRunnerHandle::Get());
connector_->set_connection_error_handler(base::BindOnce(
&BindingsManagerCast::OnControlPortDisconnected, base::Unretained(this)));
connector_->set_incoming_receiver(this);
// Post page_port to the page so that we could receive messages
// through another end of the pipe, which is platform_port.
// |named_message_port_connector.js| will receive this through
// onmessage event.
std::vector<mojo::ScopedMessagePipeHandle> message_ports;
message_ports.push_back(std::move(page_port));
cast_web_contents_->PostMessageToMainFrame("*", kControlPortConnectMessage,
std::move(message_ports));
}
bool BindingsManagerCast::Accept(mojo::Message* message) {
// Receive MessagePort and forward ports to their corresponding
// binding handlers.
blink::TransferableMessage transferable_message;
if (!blink::mojom::TransferableMessage::DeserializeFromMessage(
std::move(*message), &transferable_message)) {
return false;
}
// One and only one MessagePort should be sent to here.
if (transferable_message.ports.empty()) {
LOG(ERROR) << "TransferableMessage contains no ports.";
}
DCHECK(transferable_message.ports.size() == 1)
<< "Only one control port should be provided";
blink::MessagePortChannel message_port_channel =
std::move(transferable_message.ports[0]);
base::string16 data_utf16;
if (!blink::DecodeStringMessage(transferable_message.encoded_message,
&data_utf16)) {
LOG(ERROR) << "This Message does not contain bindingId";
return false;
}
std::string binding_id;
if (!base::UTF16ToUTF8(data_utf16.data(), data_utf16.size(), &binding_id)) {
return false;
}
// Route the port to corresponding binding backend.
OnPortConnected(binding_id, message_port_channel.ReleaseHandle());
return true;
}
void BindingsManagerCast::OnControlPortDisconnected() {
LOG(INFO) << "NamedMessagePortConnector control port disconnected";
connector_.reset();
}
} // namespace bindings
} // namespace chromecast