| // 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 |