// 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/cast_demo_bindings.h"
#include <tuple>
#include "base/check.h"
#include "base/task/sequenced_task_runner.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/v8_value_converter.h"
#include "third_party/blink/public/platform/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 int64_t kDelayBetweenReconnectionInMillis = 100;
const char kDemoObjectName[] = "demo";
const char kRecordEventName[] = "recordEvent";
const char kSetRetailerName[] = "setRetailer";
const char kSetStoreIdName[] = "setStoreId";
const char kGetRetailerName[] = "getRetailer";
const char kGetStoreIdName[] = "getStoreId";
const char kSetDefaultVolumeName[] = "setDefaultVolume";
const char kGetDefaultVolumeName[] = "getDefaultVolume";
const char kApplyDefaultVolumeName[] = "applyDefaultVolume";
const char kSetWifiConnectionName[] = "setWifiConnection";
const char kGetAvailableWifiNetworksName[] = "getAvailableWifiNetworks";
const char kGetWifiConnectionStateName[] = "getWifiConnectionState";
const char kRegisterVolumeChangeHandlerName[] = "registerVolumeChangeHandler";
const char kPersistLocalStorageName[] = "persistLocalStorage";
const char kSetVolumeName[] = "setVolume";
} // namespace
CastDemoBindings::CastDemoBindings(content::RenderFrame* render_frame)
: CastBinding(render_frame), binding_(this), weak_factory_(this) {}
CastDemoBindings::~CastDemoBindings() {}
void CastDemoBindings::Install(v8::Local<v8::Object> cast_platform,
v8::Isolate* isolate) {
v8::Local<v8::Object> demo_object =
EnsureObjectExists(isolate, cast_platform, kDemoObjectName);
InstallBinding(isolate, demo_object, kRecordEventName,
&CastDemoBindings::RecordEvent, base::Unretained(this));
InstallBinding(isolate, demo_object, kSetRetailerName,
&CastDemoBindings::SetRetailerName, base::Unretained(this));
InstallBinding(isolate, demo_object, kSetStoreIdName,
&CastDemoBindings::SetStoreId, base::Unretained(this));
InstallBinding(isolate, demo_object, kGetRetailerName,
&CastDemoBindings::GetRetailerName, base::Unretained(this));
InstallBinding(isolate, demo_object, kGetStoreIdName,
&CastDemoBindings::GetStoreId, base::Unretained(this));
InstallBinding(isolate, demo_object, kSetDefaultVolumeName,
&CastDemoBindings::SetDefaultVolumeLevel,
base::Unretained(this));
InstallBinding(isolate, demo_object, kGetDefaultVolumeName,
&CastDemoBindings::GetDefaultVolumeLevel,
base::Unretained(this));
InstallBinding(isolate, demo_object, kApplyDefaultVolumeName,
&CastDemoBindings::ApplyDefaultVolume, base::Unretained(this));
InstallBinding(isolate, demo_object, kSetWifiConnectionName,
&CastDemoBindings::SetWifiCredentials, base::Unretained(this));
InstallBinding(isolate, demo_object, kGetAvailableWifiNetworksName,
&CastDemoBindings::GetAvailableWifiNetworks,
base::Unretained(this));
InstallBinding(isolate, demo_object, kGetWifiConnectionStateName,
&CastDemoBindings::GetConnectionStatus,
base::Unretained(this));
InstallBinding(isolate, demo_object, kRegisterVolumeChangeHandlerName,
&CastDemoBindings::SetVolumeChangeHandler,
base::Unretained(this));
InstallBinding(isolate, demo_object, kPersistLocalStorageName,
&CastDemoBindings::PersistLocalStorage,
base::Unretained(this));
InstallBinding(isolate, demo_object, kSetVolumeName,
&CastDemoBindings::SetVolume, base::Unretained(this));
}
void CastDemoBindings::RecordEvent(const std::string& event_name,
v8::Local<v8::Value> v8_data) {
blink::WebLocalFrame* web_frame = render_frame()->GetWebFrame();
v8::Isolate* v8_isolate = web_frame->GetAgentGroupScheduler()->Isolate();
v8::HandleScope v8_handle_scope(v8_isolate);
v8::Local<v8::Context> v8_context = web_frame->MainWorldScriptContext();
v8::Context::Scope v8_context_scope(v8_context);
std::unique_ptr<content::V8ValueConverter> v8_converter =
content::V8ValueConverter::Create();
v8_converter->SetDateAllowed(true);
v8_converter->SetRegExpAllowed(true);
std::unique_ptr<base::Value> data_ptr =
v8_converter->FromV8Value(v8_data, v8_context);
base::Value data;
if (data_ptr) {
data = base::Value::FromUniquePtrValue(std::move(data_ptr));
}
GetCastDemo()->RecordEvent(event_name, std::move(data));
}
void CastDemoBindings::SetRetailerName(const std::string& retailer_name) {
GetCastDemo()->SetRetailerName(retailer_name);
}
void CastDemoBindings::SetStoreId(const std::string& store_id) {
GetCastDemo()->SetStoreId(store_id);
}
v8::Local<v8::Value> CastDemoBindings::GetRetailerName() {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
auto context = isolate->GetCurrentContext();
v8::Local<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(context).ToLocalChecked();
v8::Global<v8::Promise::Resolver> unique_resolver =
v8::Global<v8::Promise::Resolver>(isolate, resolver);
v8::Global<v8::Context> persisted_context =
v8::Global<v8::Context>(isolate, context);
GetCastDemo()->GetRetailerName(base::BindOnce(
&CastDemoBindings::OnGetRetailerName, base::Unretained(this),
std::move(unique_resolver), std::move(persisted_context)));
return resolver->GetPromise();
}
void CastDemoBindings::OnGetRetailerName(
v8::Global<v8::Promise::Resolver> resolver,
v8::Global<v8::Context> original_context,
const std::string& retailer_name) {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = original_context.Get(isolate);
v8::MicrotasksScope microtasks_scope(
context, v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::Context::Scope context_scope(context);
resolver.Get(isolate)
->Resolve(context, gin::ConvertToV8(isolate, retailer_name))
.ToChecked();
}
v8::Local<v8::Value> CastDemoBindings::GetStoreId() {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
auto context = isolate->GetCurrentContext();
v8::Local<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(context).ToLocalChecked();
v8::Global<v8::Promise::Resolver> unique_resolver =
v8::Global<v8::Promise::Resolver>(isolate, resolver);
v8::Global<v8::Context> persisted_context =
v8::Global<v8::Context>(isolate, context);
GetCastDemo()->GetStoreId(
base::BindOnce(&CastDemoBindings::OnGetStoreId, base::Unretained(this),
std::move(unique_resolver), std::move(persisted_context)));
return resolver->GetPromise();
}
void CastDemoBindings::OnGetStoreId(v8::Global<v8::Promise::Resolver> resolver,
v8::Global<v8::Context> original_context,
const std::string& store_id) {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = original_context.Get(isolate);
v8::MicrotasksScope microtasks_scope(
context, v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::Context::Scope context_scope(context);
resolver.Get(isolate)
->Resolve(context, gin::ConvertToV8(isolate, store_id))
.ToChecked();
}
void CastDemoBindings::SetVolume(float level) {
// This method is deprecated. Provide a workable implementation to support
// development using old content.
SetDefaultVolumeLevel(level);
ApplyDefaultVolume();
}
void CastDemoBindings::SetDefaultVolumeLevel(float level) {
GetCastDemo()->SetDefaultVolumeLevel(level);
}
v8::Local<v8::Value> CastDemoBindings::GetDefaultVolumeLevel() {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
auto context = isolate->GetCurrentContext();
v8::Local<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(context).ToLocalChecked();
v8::Global<v8::Promise::Resolver> unique_resolver =
v8::Global<v8::Promise::Resolver>(isolate, resolver);
v8::Global<v8::Context> persisted_context =
v8::Global<v8::Context>(isolate, context);
GetCastDemo()->GetDefaultVolumeLevel(base::BindOnce(
&CastDemoBindings::OnGetDefaultVolumeLevel, base::Unretained(this),
std::move(unique_resolver), std::move(persisted_context)));
return resolver->GetPromise();
}
void CastDemoBindings::OnGetDefaultVolumeLevel(
v8::Global<v8::Promise::Resolver> resolver,
v8::Global<v8::Context> original_context,
float level) {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = original_context.Get(isolate);
v8::MicrotasksScope microtasks_scope(
context, v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::Context::Scope context_scope(context);
resolver.Get(isolate)
->Resolve(context, gin::ConvertToV8(isolate, level))
.ToChecked();
}
void CastDemoBindings::ApplyDefaultVolume() {
GetCastDemo()->ApplyDefaultVolume();
}
void CastDemoBindings::SetWifiCredentials(const std::string& ssid,
const std::string& psk) {
GetCastDemo()->SetWifiCredentials(ssid, psk);
}
v8::Local<v8::Value> CastDemoBindings::GetAvailableWifiNetworks() {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
auto context = isolate->GetCurrentContext();
v8::Local<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(context).ToLocalChecked();
v8::Global<v8::Promise::Resolver> unique_resolver =
v8::Global<v8::Promise::Resolver>(isolate, resolver);
v8::Global<v8::Context> persisted_context =
v8::Global<v8::Context>(isolate, context);
GetCastDemo()->GetAvailableWifiNetworks(base::BindOnce(
&CastDemoBindings::OnGetAvailableWifiNetworks, base::Unretained(this),
std::move(unique_resolver), std::move(persisted_context)));
return resolver->GetPromise();
}
void CastDemoBindings::OnGetAvailableWifiNetworks(
v8::Global<v8::Promise::Resolver> resolver,
v8::Global<v8::Context> original_context,
base::Value network_list) {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = original_context.Get(isolate);
v8::MicrotasksScope microtasks_scope(
context, v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::Context::Scope context_scope(context);
std::unique_ptr<content::V8ValueConverter> v8_converter =
content::V8ValueConverter::Create();
v8::Local<v8::Value> v8_value =
v8_converter->ToV8Value(network_list, context);
resolver.Get(isolate)
->Resolve(context, gin::ConvertToV8(isolate, v8_value))
.ToChecked();
}
v8::Local<v8::Value> CastDemoBindings::GetConnectionStatus() {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
auto context = isolate->GetCurrentContext();
v8::Local<v8::Promise::Resolver> resolver =
v8::Promise::Resolver::New(context).ToLocalChecked();
v8::Global<v8::Promise::Resolver> unique_resolver =
v8::Global<v8::Promise::Resolver>(isolate, resolver);
v8::Global<v8::Context> persisted_context =
v8::Global<v8::Context>(isolate, context);
GetCastDemo()->GetConnectionStatus(base::BindOnce(
&CastDemoBindings::OnGetConnectionStatus, base::Unretained(this),
std::move(unique_resolver), std::move(persisted_context)));
return resolver->GetPromise();
}
void CastDemoBindings::OnGetConnectionStatus(
v8::Global<v8::Promise::Resolver> resolver,
v8::Global<v8::Context> original_context,
base::Value status) {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = original_context.Get(isolate);
v8::MicrotasksScope microtasks_scope(
context, v8::MicrotasksScope::kDoNotRunMicrotasks);
v8::Context::Scope context_scope(context);
std::unique_ptr<content::V8ValueConverter> v8_converter =
content::V8ValueConverter::Create();
v8::Local<v8::Value> v8_value = v8_converter->ToV8Value(status, context);
resolver.Get(isolate)
->Resolve(context, gin::ConvertToV8(isolate, v8_value))
.ToChecked();
}
void CastDemoBindings::SetVolumeChangeHandler(
v8::Local<v8::Function> volume_change_handler) {
v8::Isolate* isolate =
render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate();
volume_change_handler_ =
v8::UniquePersistent<v8::Function>(isolate, volume_change_handler);
}
void CastDemoBindings::VolumeChanged(float level) {
if (volume_change_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::Context::Scope context_scope(context);
v8::Local<v8::Function> handler =
v8::Local<v8::Function>::New(isolate, std::move(volume_change_handler_));
auto args =
v8::to_array<v8::Local<v8::Value>>({gin::ConvertToV8(isolate, level)});
v8::MaybeLocal<v8::Value> maybe_result =
handler->Call(context, context->Global(), args.size(), args.data());
volume_change_handler_ = v8::UniquePersistent<v8::Function>(isolate, handler);
v8::Local<v8::Value> result;
std::ignore = maybe_result.ToLocal(&result);
}
void CastDemoBindings::PersistLocalStorage() {
GetCastDemo()->PersistLocalStorage();
}
void CastDemoBindings::ReconnectMojo() {
render_frame()->GetBrowserInterfaceBroker().GetInterface(
cast_demo_.BindNewPipeAndPassReceiver());
DCHECK(cast_demo_.is_bound());
cast_demo_.set_disconnect_handler(base::BindOnce(
&CastDemoBindings::OnMojoConnectionError, base::Unretained(this)));
if (binding_.is_bound()) {
binding_.reset();
}
mojo::PendingRemote<mojom::CastDemoVolumeChangeObserver> pending_remote;
binding_.Bind(pending_remote.InitWithNewPipeAndPassReceiver());
cast_demo_->AddVolumeChangeObserver(std::move(pending_remote));
}
void CastDemoBindings::OnMojoConnectionError() {
LOG(WARNING) << "Disconnected from Demo Mojo. Will retry every "
<< kDelayBetweenReconnectionInMillis << " milliseconds.";
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CastDemoBindings::ReconnectMojo,
weak_factory_.GetWeakPtr()),
base::Milliseconds(kDelayBetweenReconnectionInMillis));
}
const mojo::Remote<mojom::CastDemo>& CastDemoBindings::GetCastDemo() {
if (!cast_demo_.is_bound()) {
ReconnectMojo();
}
return cast_demo_;
}
} // namespace shell
} // namespace chromecast