| // 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_window_manager_bindings.h" |
| |
| #include <array> |
| #include <tuple> |
| |
| #include "base/check.h" |
| #include "build/build_config.h" |
| #include "chromecast/base/cast_features.h" |
| #include "chromecast/common/feature_constants.h" |
| #include "chromecast/common/mojom/gesture.mojom.h" |
| #include "chromecast/renderer/feature_manager.h" |
| #include "content/public/renderer/render_frame.h" |
| #include "gin/data_object_builder.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" |
| #include "third_party/blink/public/web/web_view.h" |
| |
| namespace chromecast { |
| namespace shell { |
| |
| namespace { |
| |
| const char kBindingsObjectName[] = "windowManager"; |
| const char kOnBackGestureName[] = "onBackGesture"; |
| const char kOnBackGestureProgressName[] = "onBackGestureProgress"; |
| const char kOnBackGestureCancelName[] = "onBackGestureCancel"; |
| const char kOnTopDragGestureDoneName[] = "onTopDragGestureDone"; |
| const char kOnTopDragGestureProgressName[] = "onTopDragGestureProgress"; |
| const char kOnRightDragGestureDoneName[] = "onRightDragGestureDone"; |
| const char kOnRightDragGestureProgressName[] = "onRightDragGestureProgress"; |
| const char kOnTapGestureName[] = "onTapGesture"; |
| const char kOnTapDownGestureName[] = "onTapDownGesture"; |
| const char kCanGoBackName[] = "canGoBack"; |
| const char kCanTopDragName[] = "canTopDrag"; |
| const char kCanRightDragName[] = "canRightDrag"; |
| const char kMinimize[] = "minimize"; |
| const char kMaximize[] = "maximize"; |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| const char kDisplayControlsName[] = "displayControls"; |
| #endif |
| |
| void OnGestureSourceDisconnectionError() { |
| LOG(ERROR) << "Connection error talking to system gesture source."; |
| } |
| |
| void OnWindowDisconnect() { |
| LOG(ERROR) << "Connection error talking to platform activity."; |
| } |
| |
| } // namespace |
| |
| CastWindowManagerBindings::CastWindowManagerBindings( |
| content::RenderFrame* render_frame, |
| const FeatureManager* feature_manager) |
| : CastBinding(render_frame), |
| feature_manager_(feature_manager), |
| handler_receiver_(this) { |
| DCHECK(feature_manager_); |
| } |
| |
| CastWindowManagerBindings::~CastWindowManagerBindings() {} |
| |
| void CastWindowManagerBindings::Install(v8::Local<v8::Object> cast_platform, |
| v8::Isolate* isolate) { |
| if (feature_manager_->FeatureEnabled(feature::kEnableSystemGestures)) { |
| v8::Local<v8::Object> windowManagerObject = |
| EnsureObjectExists(isolate, cast_platform, kBindingsObjectName); |
| |
| // On back bindings. |
| InstallBinding(isolate, windowManagerObject, kCanGoBackName, |
| &CastWindowManagerBindings::SetCanGoBack, |
| base::Unretained(this)); |
| InstallBinding(isolate, windowManagerObject, kOnBackGestureName, |
| &CastWindowManagerBindings::SetV8Callback, |
| base::Unretained(this), &on_back_gesture_callback_); |
| InstallBinding(isolate, windowManagerObject, kOnBackGestureProgressName, |
| &CastWindowManagerBindings::SetV8Callback, |
| base::Unretained(this), &on_back_gesture_progress_callback_); |
| InstallBinding(isolate, windowManagerObject, kOnBackGestureCancelName, |
| &CastWindowManagerBindings::SetV8Callback, |
| base::Unretained(this), &on_back_gesture_cancel_callback_); |
| |
| // Top drag bindings. |
| InstallBinding(isolate, windowManagerObject, kCanTopDragName, |
| &CastWindowManagerBindings::SetCanTopDrag, |
| base::Unretained(this)); |
| InstallBinding(isolate, windowManagerObject, kOnTopDragGestureDoneName, |
| &CastWindowManagerBindings::SetV8Callback, |
| base::Unretained(this), &on_top_drag_gesture_done_callback_); |
| InstallBinding(isolate, windowManagerObject, kOnTopDragGestureProgressName, |
| &CastWindowManagerBindings::SetV8Callback, |
| base::Unretained(this), |
| &on_top_drag_gesture_progress_callback_); |
| |
| // Right drag bindings. |
| InstallBinding(isolate, windowManagerObject, kCanRightDragName, |
| &CastWindowManagerBindings::SetCanRightDrag, |
| base::Unretained(this)); |
| InstallBinding(isolate, windowManagerObject, kOnRightDragGestureDoneName, |
| &CastWindowManagerBindings::SetV8Callback, |
| base::Unretained(this), |
| &on_right_drag_gesture_done_callback_); |
| InstallBinding( |
| isolate, windowManagerObject, kOnRightDragGestureProgressName, |
| &CastWindowManagerBindings::SetV8Callback, base::Unretained(this), |
| &on_right_drag_gesture_progress_callback_); |
| |
| // 'Tap' bindings. |
| InstallBinding(isolate, windowManagerObject, kOnTapGestureName, |
| &CastWindowManagerBindings::SetV8Callback, |
| base::Unretained(this), &on_tap_gesture_callback_); |
| InstallBinding(isolate, windowManagerObject, kOnTapDownGestureName, |
| &CastWindowManagerBindings::SetV8Callback, |
| base::Unretained(this), &on_tap_down_gesture_callback_); |
| } |
| if (feature_manager_->FeatureEnabled(feature::kEnableWindowControls)) { |
| v8::Local<v8::Object> windowManagerObject = |
| EnsureObjectExists(isolate, cast_platform, kBindingsObjectName); |
| |
| InstallBinding(isolate, windowManagerObject, kMaximize, |
| &CastWindowManagerBindings::Show, base::Unretained(this)); |
| InstallBinding(isolate, windowManagerObject, kMinimize, |
| &CastWindowManagerBindings::Hide, base::Unretained(this)); |
| } |
| } |
| |
| v8::Local<v8::Value> CastWindowManagerBindings::SetV8Callback( |
| v8::UniquePersistent<v8::Function>* callback_function, |
| v8::Local<v8::Function> callback) { |
| v8::Isolate* isolate = |
| render_frame()->GetWebFrame()->GetAgentGroupScheduler()->Isolate(); |
| |
| *callback_function = v8::UniquePersistent<v8::Function>(isolate, callback); |
| |
| return v8::Undefined(isolate); |
| } |
| |
| void CastWindowManagerBindings::Show() { |
| BindWindow(); |
| window_->Show(); |
| } |
| |
| void CastWindowManagerBindings::Hide() { |
| BindWindow(); |
| window_->Hide(); |
| } |
| |
| void CastWindowManagerBindings::SetCanGoBack(bool can_go_back) { |
| BindGestureSource(); |
| gesture_source_->SetCanGoBack(can_go_back); |
| } |
| |
| void CastWindowManagerBindings::SetCanTopDrag(bool can_top_drag) { |
| BindGestureSource(); |
| gesture_source_->SetCanTopDrag(can_top_drag); |
| } |
| |
| void CastWindowManagerBindings::SetCanRightDrag(bool can_right_drag) { |
| BindGestureSource(); |
| gesture_source_->SetCanRightDrag(can_right_drag); |
| } |
| |
| void CastWindowManagerBindings::OnTouchInputSupportSet( |
| PersistedResolver resolver, |
| PersistedContext original_context, |
| bool resolve_promise, |
| bool display_controls) { |
| DVLOG(2) << __FUNCTION__; |
| 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); |
| |
| if (resolve_promise) { |
| resolver.Get(isolate) |
| ->Resolve(context, |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| gin::DataObjectBuilder(isolate) |
| .Set(kDisplayControlsName, display_controls) |
| .Build() |
| #else |
| v8::Undefined(isolate) |
| #endif |
| ) |
| .ToChecked(); |
| } else { |
| resolver.Get(isolate)->Reject(context, v8::Undefined(isolate)).ToChecked(); |
| } |
| } |
| |
| void CastWindowManagerBindings::BindGestureSource() { |
| if (gesture_source_.is_bound() && gesture_source_.is_connected()) |
| return; |
| gesture_source_.reset(); |
| render_frame()->GetBrowserInterfaceBroker().GetInterface( |
| gesture_source_.BindNewPipeAndPassReceiver()); |
| gesture_source_.set_disconnect_handler( |
| base::BindRepeating(&OnGestureSourceDisconnectionError)); |
| handler_receiver_.reset(); |
| gesture_source_->Subscribe(handler_receiver_.BindNewPipeAndPassRemote()); |
| } |
| |
| void CastWindowManagerBindings::BindWindow() { |
| if (window_.is_bound() && window_.is_connected()) |
| return; |
| window_.reset(); |
| render_frame()->GetBrowserInterfaceBroker().GetInterface( |
| window_.BindNewPipeAndPassReceiver()); |
| window_.set_disconnect_handler(base::BindRepeating(&OnWindowDisconnect)); |
| } |
| |
| void CastWindowManagerBindings::InvokeV8Callback( |
| v8::UniquePersistent<v8::Function>* callback_function) { |
| if (callback_function->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(*callback_function)); |
| |
| v8::MaybeLocal<v8::Value> maybe_result = |
| handler->Call(context, context->Global(), 0, nullptr); |
| |
| *callback_function = v8::UniquePersistent<v8::Function>(isolate, handler); |
| |
| v8::Local<v8::Value> result; |
| std::ignore = maybe_result.ToLocal(&result); |
| } |
| |
| void CastWindowManagerBindings::InvokeV8Callback( |
| v8::UniquePersistent<v8::Function>* callback_function, |
| const gfx::Point& touch_location) { |
| if (callback_function->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(*callback_function)); |
| |
| v8::Local<v8::Number> touch_x = v8::Integer::New(isolate, touch_location.x()); |
| v8::Local<v8::Number> touch_y = v8::Integer::New(isolate, touch_location.y()); |
| |
| auto args = v8::to_array<v8::Local<v8::Value>>({touch_x, touch_y}); |
| |
| v8::MaybeLocal<v8::Value> maybe_result = |
| handler->Call(context, context->Global(), args.size(), args.data()); |
| |
| *callback_function = v8::UniquePersistent<v8::Function>(isolate, handler); |
| |
| v8::Local<v8::Value> result; |
| std::ignore = maybe_result.ToLocal(&result); |
| } |
| |
| void CastWindowManagerBindings::OnBackGesture( |
| ::chromecast::mojom::GestureHandler::OnBackGestureCallback callback) { |
| // Note: Can't use InvokeV8Callback here because of the OnBackGestureCallback |
| // argument. So we have this boilerplate here until we can get rid of the |
| // unused callback argument. |
| if (on_back_gesture_callback_.IsEmpty()) { |
| std::move(callback).Run(false); |
| 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(on_back_gesture_callback_)); |
| auto result = handler->Call(context, context->Global(), 0, nullptr); |
| |
| on_back_gesture_callback_ = |
| v8::UniquePersistent<v8::Function>(isolate, handler); |
| if (result.IsEmpty()) { |
| LOG(ERROR) << "No value from callback execution; "; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto callback_return_value = result.ToLocalChecked(); |
| bool return_boolean = callback_return_value->BooleanValue(isolate); |
| std::move(callback).Run(return_boolean); |
| } |
| |
| void CastWindowManagerBindings::OnBackGestureProgress( |
| const gfx::Point& touch_location) { |
| InvokeV8Callback(&on_back_gesture_progress_callback_, touch_location); |
| } |
| |
| void CastWindowManagerBindings::OnBackGestureCancel() { |
| InvokeV8Callback(&on_back_gesture_cancel_callback_); |
| } |
| |
| void CastWindowManagerBindings::OnTopDragGestureDone() { |
| InvokeV8Callback(&on_top_drag_gesture_done_callback_); |
| } |
| |
| void CastWindowManagerBindings::OnTopDragGestureProgress( |
| const gfx::Point& touch_location) { |
| InvokeV8Callback(&on_top_drag_gesture_progress_callback_, touch_location); |
| } |
| |
| void CastWindowManagerBindings::OnRightDragGestureDone() { |
| InvokeV8Callback(&on_right_drag_gesture_done_callback_); |
| } |
| |
| void CastWindowManagerBindings::OnRightDragGestureProgress( |
| const gfx::Point& touch_location) { |
| InvokeV8Callback(&on_right_drag_gesture_progress_callback_, touch_location); |
| } |
| |
| void CastWindowManagerBindings::OnTapGesture() { |
| InvokeV8Callback(&on_tap_gesture_callback_); |
| } |
| |
| void CastWindowManagerBindings::OnTapDownGesture() { |
| InvokeV8Callback(&on_tap_down_gesture_callback_); |
| } |
| |
| } // namespace shell |
| } // namespace chromecast |