blob: 3308bce15f26815153803dcdafa0c002ae78f3fd [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/renderer/assistant_bindings.h"
#include "base/check.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace chromecast {
namespace shell {
namespace {
const int kMaxMessageQueueSize = 50;
static const int64_t kDelayBetweenReconnectionInMillis = 100;
const char kSetAssistantMessageHandlerMethodName[] =
"setAssistantMessageHandler";
const char kSendAssistantRequestMethodName[] = "sendAssistantRequest";
} // namespace
AssistantBindings::AssistantBindings(content::RenderFrame* frame,
const base::Value::Dict& feature_config)
: CastBinding(frame),
feature_config_(feature_config.Clone()),
message_client_binding_(this),
weak_factory_(this) {
weak_this_ = weak_factory_.GetWeakPtr();
}
AssistantBindings::~AssistantBindings() {}
void AssistantBindings::OnMessage(base::Value message) {
if (assistant_message_handler_.IsEmpty()) {
return;
}
blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
v8::Isolate* isolate = web_frame->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = web_frame->MainWorldScriptContext();
v8::MicrotasksScope microtasks_scope(
context, v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::Context::Scope context_scope(context);
v8::Local<v8::Function> handler = v8::Local<v8::Function>::New(
isolate, std::move(assistant_message_handler_));
std::string json;
base::JSONWriter::Write(message, &json);
v8::Local<v8::Value> message_val =
gin::Converter<std::string>::ToV8(isolate, json);
v8::Local<v8::Value> argv[] = {message_val};
web_frame->CallFunctionEvenIfScriptDisabled(handler, context->Global(),
std::size(argv), argv);
assistant_message_handler_ =
v8::UniquePersistent<v8::Function>(isolate, handler);
}
void AssistantBindings::Install(v8::Local<v8::Object> cast_platform,
v8::Isolate* isolate) {
DVLOG(1) << "Installing AssistantBindings";
InstallBinding(isolate, cast_platform, kSetAssistantMessageHandlerMethodName,
&AssistantBindings::SetAssistantMessageHandler,
base::Unretained(this));
InstallBinding(isolate, cast_platform, kSendAssistantRequestMethodName,
&AssistantBindings::SendAssistantRequest,
base::Unretained(this));
}
void AssistantBindings::SetAssistantMessageHandler(
v8::Local<v8::Function> assistant_message_handler) {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
assistant_message_handler_ =
v8::UniquePersistent<v8::Function>(isolate, assistant_message_handler);
ReconnectMessagePipe();
}
void AssistantBindings::SendAssistantRequest(const std::string& request) {
if (assistant_message_handler_.IsEmpty()) {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
isolate->ThrowException(
v8::String::NewFromUtf8(isolate,
"Error: assistant message handler is not set.",
v8::NewStringType::kInternalized)
.ToLocalChecked());
return;
}
if (!message_pipe_.is_bound()) {
if (v8_to_assistant_queue_.size() < kMaxMessageQueueSize) {
v8_to_assistant_queue_.push_back(request);
} else {
LOG(WARNING) << "Messages sending to assistant overflow, will drop "
"upcoming messages";
}
return;
}
v8_to_assistant_queue_.push_back(request);
FlushV8ToAssistantQueue();
}
void AssistantBindings::ReconnectMessagePipe() {
if (message_client_binding_.is_bound())
message_client_binding_.reset();
if (message_pipe_.is_bound())
message_pipe_.reset();
LOG(INFO) << "Creating message pipe";
const std::string* app_id = feature_config_.FindString("app_id");
DCHECK(app_id) << "Couldn't get app_id from feature config";
GetMojoInterface()->CreateMessagePipe(
*app_id, message_client_binding_.BindNewPipeAndPassRemote(),
message_pipe_.BindNewPipeAndPassReceiver());
reconnect_assistant_timer_.Stop();
}
void AssistantBindings::OnAssistantConnectionError() {
LOG(WARNING) << "Disconnected from assistant. Will reconnect every "
<< kDelayBetweenReconnectionInMillis << " milliseconds";
assistant_.reset();
reconnect_assistant_timer_.Start(
FROM_HERE, base::Milliseconds(kDelayBetweenReconnectionInMillis), this,
&AssistantBindings::ReconnectMessagePipe);
}
void AssistantBindings::FlushV8ToAssistantQueue() {
DCHECK(message_pipe_.is_bound());
for (auto& request : v8_to_assistant_queue_) {
auto value = base::JSONReader::Read(request);
if (!value) {
LOG(ERROR) << "Unable to parse Assistant message JSON.";
continue;
}
message_pipe_->SendMessage(std::move(*value));
}
v8_to_assistant_queue_.clear();
}
const mojo::Remote<chromecast::mojom::AssistantMessageService>&
AssistantBindings::GetMojoInterface() {
if (!assistant_.is_bound()) {
render_frame()->GetBrowserInterfaceBroker()->GetInterface(
assistant_.BindNewPipeAndPassReceiver());
assistant_.set_disconnect_handler(base::BindOnce(
&AssistantBindings::OnAssistantConnectionError, weak_this_));
}
return assistant_;
}
} // namespace shell
} // namespace chromecast