blob: 64e348bb44bfe43388b525ef5ce9694caa11460e [file] [log] [blame]
// Copyright 2017 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 "content/renderer/input/widget_input_handler_manager.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "content/common/input_messages.h"
#include "content/renderer/gpu/render_widget_compositor.h"
#include "content/renderer/ime_event_guard.h"
#include "content/renderer/input/input_handler_manager.h"
#include "content/renderer/input/widget_input_handler_impl.h"
#include "content/renderer/render_thread_impl.h"
#include "content/renderer/render_widget.h"
#include "third_party/WebKit/public/platform/Platform.h"
#include "third_party/WebKit/public/platform/WebCoalescedInputEvent.h"
#include "third_party/WebKit/public/platform/WebKeyboardEvent.h"
#include "third_party/WebKit/public/platform/scheduler/web_main_thread_scheduler.h"
#include "third_party/WebKit/public/web/WebLocalFrame.h"
#include "ui/events/base_event_utils.h"
#if defined(OS_ANDROID)
#include "content/public/common/content_client.h"
#include "content/renderer/android/synchronous_compositor_proxy_mojo.h"
#include "content/renderer/android/synchronous_compositor_registry.h"
#endif
namespace content {
namespace {
void CallCallback(mojom::WidgetInputHandler::DispatchEventCallback callback,
InputEventAckState ack_state,
const ui::LatencyInfo& latency_info,
std::unique_ptr<ui::DidOverscrollParams> overscroll_params,
base::Optional<cc::TouchAction> touch_action) {
std::move(callback).Run(
InputEventAckSource::MAIN_THREAD, latency_info, ack_state,
overscroll_params
? base::Optional<ui::DidOverscrollParams>(*overscroll_params)
: base::nullopt,
touch_action);
}
} // namespace
#if defined(OS_ANDROID)
class SynchronousCompositorProxyRegistry
: public SynchronousCompositorRegistry {
public:
explicit SynchronousCompositorProxyRegistry(
scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner)
: compositor_task_runner_(std::move(compositor_task_runner)) {}
~SynchronousCompositorProxyRegistry() override {
// Ensure the proxy has already been release on the compositor thread
// before destroying this object.
DCHECK(!proxy_);
}
void CreateProxy(ui::SynchronousInputHandlerProxy* handler) {
DCHECK(compositor_task_runner_->BelongsToCurrentThread());
proxy_ = std::make_unique<SynchronousCompositorProxyMojo>(handler);
proxy_->Init();
if (sink_)
proxy_->SetLayerTreeFrameSink(sink_);
}
SynchronousCompositorProxyMojo* proxy() { return proxy_.get(); }
void RegisterLayerTreeFrameSink(
int routing_id,
SynchronousLayerTreeFrameSink* layer_tree_frame_sink) override {
DCHECK(compositor_task_runner_->BelongsToCurrentThread());
DCHECK_EQ(nullptr, sink_);
sink_ = layer_tree_frame_sink;
if (proxy_)
proxy_->SetLayerTreeFrameSink(layer_tree_frame_sink);
}
void UnregisterLayerTreeFrameSink(
int routing_id,
SynchronousLayerTreeFrameSink* layer_tree_frame_sink) override {
DCHECK(compositor_task_runner_->BelongsToCurrentThread());
DCHECK_EQ(layer_tree_frame_sink, sink_);
sink_ = nullptr;
}
void DestroyProxy() {
DCHECK(compositor_task_runner_->BelongsToCurrentThread());
proxy_.reset();
}
private:
scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_;
std::unique_ptr<SynchronousCompositorProxyMojo> proxy_;
SynchronousLayerTreeFrameSink* sink_ = nullptr;
};
#endif
scoped_refptr<WidgetInputHandlerManager> WidgetInputHandlerManager::Create(
base::WeakPtr<RenderWidget> render_widget,
scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner,
blink::scheduler::WebMainThreadScheduler* main_thread_scheduler) {
scoped_refptr<WidgetInputHandlerManager> manager =
new WidgetInputHandlerManager(std::move(render_widget),
std::move(compositor_task_runner),
main_thread_scheduler);
manager->Init();
return manager;
}
WidgetInputHandlerManager::WidgetInputHandlerManager(
base::WeakPtr<RenderWidget> render_widget,
scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner,
blink::scheduler::WebMainThreadScheduler* main_thread_scheduler)
: render_widget_(render_widget),
main_thread_scheduler_(main_thread_scheduler),
input_event_queue_(render_widget->GetInputEventQueue()),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
compositor_task_runner_(std::move(compositor_task_runner)) {
#if defined(OS_ANDROID)
if (compositor_task_runner_) {
synchronous_compositor_registry_ =
std::make_unique<SynchronousCompositorProxyRegistry>(
compositor_task_runner_);
}
#endif
}
void WidgetInputHandlerManager::Init() {
if (compositor_task_runner_) {
bool sync_compositing = false;
#if defined(OS_ANDROID)
sync_compositing = GetContentClient()->UsingSynchronousCompositing();
#endif
compositor_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WidgetInputHandlerManager::InitOnCompositorThread, this,
render_widget_->compositor()->GetInputHandler(),
render_widget_->compositor_deps()->IsScrollAnimatorEnabled(),
sync_compositing));
}
}
WidgetInputHandlerManager::~WidgetInputHandlerManager() = default;
void WidgetInputHandlerManager::AddAssociatedInterface(
mojom::WidgetInputHandlerAssociatedRequest request,
mojom::WidgetInputHandlerHostPtr host) {
if (compositor_task_runner_) {
associated_host_ =
mojo::ThreadSafeInterfacePtr<mojom::WidgetInputHandlerHost>::Create(
host.PassInterface(), compositor_task_runner_);
// Mojo channel bound on compositor thread.
compositor_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&WidgetInputHandlerManager::BindAssociatedChannel, this,
std::move(request)));
} else {
associated_host_ =
mojo::ThreadSafeInterfacePtr<mojom::WidgetInputHandlerHost>::Create(
std::move(host));
// Mojo channel bound on main thread.
BindAssociatedChannel(std::move(request));
}
}
void WidgetInputHandlerManager::AddInterface(
mojom::WidgetInputHandlerRequest request,
mojom::WidgetInputHandlerHostPtr host) {
if (compositor_task_runner_) {
host_ = mojo::ThreadSafeInterfacePtr<mojom::WidgetInputHandlerHost>::Create(
host.PassInterface(), compositor_task_runner_);
// Mojo channel bound on compositor thread.
compositor_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&WidgetInputHandlerManager::BindChannel, this,
std::move(request)));
} else {
host_ = mojo::ThreadSafeInterfacePtr<mojom::WidgetInputHandlerHost>::Create(
std::move(host));
// Mojo channel bound on main thread.
BindChannel(std::move(request));
}
}
void WidgetInputHandlerManager::WillShutdown() {
#if defined(OS_ANDROID)
if (synchronous_compositor_registry_)
synchronous_compositor_registry_->DestroyProxy();
#endif
input_handler_proxy_.reset();
}
void WidgetInputHandlerManager::DispatchNonBlockingEventToMainThread(
ui::WebScopedInputEvent event,
const ui::LatencyInfo& latency_info) {
DCHECK(input_event_queue_);
input_event_queue_->HandleEvent(
std::move(event), latency_info, DISPATCH_TYPE_NON_BLOCKING,
INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING, HandledEventCallback());
}
std::unique_ptr<blink::WebGestureCurve>
WidgetInputHandlerManager::CreateFlingAnimationCurve(
blink::WebGestureDevice device_source,
const blink::WebFloatPoint& velocity,
const blink::WebSize& cumulative_scroll) {
return blink::Platform::Current()->CreateFlingAnimationCurve(
device_source, velocity, cumulative_scroll);
}
void WidgetInputHandlerManager::DidOverscroll(
const gfx::Vector2dF& accumulated_overscroll,
const gfx::Vector2dF& latest_overscroll_delta,
const gfx::Vector2dF& current_fling_velocity,
const gfx::PointF& causal_event_viewport_point,
const cc::OverscrollBehavior& overscroll_behavior) {
mojom::WidgetInputHandlerHost* host = GetWidgetInputHandlerHost();
if (!host)
return;
ui::DidOverscrollParams params;
params.accumulated_overscroll = accumulated_overscroll;
params.latest_overscroll_delta = latest_overscroll_delta;
params.current_fling_velocity = current_fling_velocity;
params.causal_event_viewport_point = causal_event_viewport_point;
params.overscroll_behavior = overscroll_behavior;
host->DidOverscroll(params);
}
void WidgetInputHandlerManager::DidStopFlinging() {
mojom::WidgetInputHandlerHost* host = GetWidgetInputHandlerHost();
if (!host)
return;
host->DidStopFlinging();
}
void WidgetInputHandlerManager::DidAnimateForInput() {
main_thread_scheduler_->DidAnimateForInputOnCompositorThread();
}
void WidgetInputHandlerManager::DidStartScrollingViewport() {
mojom::WidgetInputHandlerHost* host = GetWidgetInputHandlerHost();
if (!host)
return;
host->DidStartScrollingViewport();
}
void WidgetInputHandlerManager::GenerateScrollBeginAndSendToMainThread(
const blink::WebGestureEvent& update_event) {
DCHECK_EQ(update_event.GetType(), blink::WebInputEvent::kGestureScrollUpdate);
blink::WebGestureEvent scroll_begin(update_event);
scroll_begin.SetType(blink::WebInputEvent::kGestureScrollBegin);
scroll_begin.data.scroll_begin.inertial_phase =
update_event.data.scroll_update.inertial_phase;
scroll_begin.data.scroll_begin.delta_x_hint =
update_event.data.scroll_update.delta_x;
scroll_begin.data.scroll_begin.delta_y_hint =
update_event.data.scroll_update.delta_y;
scroll_begin.data.scroll_begin.delta_hint_units =
update_event.data.scroll_update.delta_units;
DispatchNonBlockingEventToMainThread(
ui::WebInputEventTraits::Clone(scroll_begin), ui::LatencyInfo());
}
void WidgetInputHandlerManager::SetWhiteListedTouchAction(
cc::TouchAction touch_action,
uint32_t unique_touch_event_id,
ui::InputHandlerProxy::EventDisposition event_disposition) {
mojom::WidgetInputHandlerHost* host = GetWidgetInputHandlerHost();
if (!host)
return;
InputEventAckState ack_state = InputEventDispositionToAck(event_disposition);
host->SetWhiteListedTouchAction(touch_action, unique_touch_event_id,
ack_state);
}
void WidgetInputHandlerManager::ProcessTouchAction(
cc::TouchAction touch_action) {
// Cancel the touch timeout on TouchActionNone since it is a good hint
// that author doesn't want scrolling.
if (touch_action == cc::TouchAction::kTouchActionNone) {
if (mojom::WidgetInputHandlerHost* host = GetWidgetInputHandlerHost())
host->CancelTouchTimeout();
}
}
mojom::WidgetInputHandlerHost*
WidgetInputHandlerManager::GetWidgetInputHandlerHost() {
if (associated_host_)
return associated_host_.get()->get();
if (host_)
return host_.get()->get();
return nullptr;
}
void WidgetInputHandlerManager::AttachSynchronousCompositor(
mojom::SynchronousCompositorControlHostPtr control_host,
mojom::SynchronousCompositorHostAssociatedPtrInfo host,
mojom::SynchronousCompositorAssociatedRequest compositor_request) {
#if defined(OS_ANDROID)
DCHECK(synchronous_compositor_registry_);
synchronous_compositor_registry_->proxy()->BindChannel(
std::move(control_host), std::move(host), std::move(compositor_request));
#endif
}
void WidgetInputHandlerManager::ObserveGestureEventOnMainThread(
const blink::WebGestureEvent& gesture_event,
const cc::InputHandlerScrollResult& scroll_result) {
if (compositor_task_runner_) {
compositor_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&WidgetInputHandlerManager::ObserveGestureEventOnCompositorThread,
this, gesture_event, scroll_result));
}
}
void WidgetInputHandlerManager::DispatchEvent(
std::unique_ptr<content::InputEvent> event,
mojom::WidgetInputHandler::DispatchEventCallback callback) {
if (!event || !event->web_event) {
// Call |callback| if it was available indicating this event wasn't
// handled.
if (callback) {
std::move(callback).Run(
InputEventAckSource::MAIN_THREAD, ui::LatencyInfo(),
INPUT_EVENT_ACK_STATE_NOT_CONSUMED, base::nullopt, base::nullopt);
}
return;
}
// If TimeTicks is not consistent across processes we cannot use the event's
// platform timestamp in this process. Instead use the time that the event is
// received as the event's timestamp.
if (!base::TimeTicks::IsConsistentAcrossProcesses()) {
event->web_event->SetTimeStampSeconds(
ui::EventTimeStampToSeconds(base::TimeTicks::Now()));
}
if (compositor_task_runner_) {
// If the input_handler_proxy has disappeared ensure we just ack event.
if (!input_handler_proxy_) {
if (callback) {
std::move(callback).Run(
InputEventAckSource::MAIN_THREAD, ui::LatencyInfo(),
INPUT_EVENT_ACK_STATE_NOT_CONSUMED, base::nullopt, base::nullopt);
}
return;
}
CHECK(!main_thread_task_runner_->BelongsToCurrentThread());
input_handler_proxy_->HandleInputEventWithLatencyInfo(
std::move(event->web_event), event->latency_info,
base::BindOnce(
&WidgetInputHandlerManager::DidHandleInputEventAndOverscroll, this,
std::move(callback)));
} else {
HandleInputEvent(std::move(event->web_event), event->latency_info,
std::move(callback));
}
}
void WidgetInputHandlerManager::InitOnCompositorThread(
const base::WeakPtr<cc::InputHandler>& input_handler,
bool smooth_scroll_enabled,
bool sync_compositing) {
input_handler_proxy_ = std::make_unique<ui::InputHandlerProxy>(
input_handler.get(), this,
base::FeatureList::IsEnabled(features::kTouchpadAndWheelScrollLatching),
base::FeatureList::IsEnabled(features::kAsyncWheelEvents));
input_handler_proxy_->set_smooth_scroll_enabled(smooth_scroll_enabled);
#if defined(OS_ANDROID)
if (sync_compositing) {
DCHECK(synchronous_compositor_registry_);
synchronous_compositor_registry_->CreateProxy(input_handler_proxy_.get());
}
#endif
}
void WidgetInputHandlerManager::BindAssociatedChannel(
mojom::WidgetInputHandlerAssociatedRequest request) {
if (!request.is_pending())
return;
// Don't pass the |input_event_queue_| on if we don't have a
// |compositor_task_runner_| as events might get out of order.
WidgetInputHandlerImpl* handler = new WidgetInputHandlerImpl(
this, main_thread_task_runner_,
compositor_task_runner_ ? input_event_queue_ : nullptr, render_widget_);
handler->SetAssociatedBinding(std::move(request));
}
void WidgetInputHandlerManager::BindChannel(
mojom::WidgetInputHandlerRequest request) {
if (!request.is_pending())
return;
// Don't pass the |input_event_queue_| on if we don't have a
// |compositor_task_runner_| as events might get out of order.
WidgetInputHandlerImpl* handler = new WidgetInputHandlerImpl(
this, main_thread_task_runner_,
compositor_task_runner_ ? input_event_queue_ : nullptr, render_widget_);
handler->SetBinding(std::move(request));
}
void WidgetInputHandlerManager::HandleInputEvent(
const ui::WebScopedInputEvent& event,
const ui::LatencyInfo& latency,
mojom::WidgetInputHandler::DispatchEventCallback callback) {
if (!render_widget_ || render_widget_->is_swapped_out() ||
render_widget_->IsClosing()) {
if (callback) {
std::move(callback).Run(InputEventAckSource::MAIN_THREAD, latency,
INPUT_EVENT_ACK_STATE_NOT_CONSUMED, base::nullopt,
base::nullopt);
}
return;
}
auto send_callback = base::BindOnce(
&WidgetInputHandlerManager::HandledInputEvent, this, std::move(callback));
blink::WebCoalescedInputEvent coalesced_event(*event);
render_widget_->HandleInputEvent(coalesced_event, latency,
std::move(send_callback));
}
void WidgetInputHandlerManager::DidHandleInputEventAndOverscroll(
mojom::WidgetInputHandler::DispatchEventCallback callback,
ui::InputHandlerProxy::EventDisposition event_disposition,
ui::WebScopedInputEvent input_event,
const ui::LatencyInfo& latency_info,
std::unique_ptr<ui::DidOverscrollParams> overscroll_params) {
InputEventAckState ack_state = InputEventDispositionToAck(event_disposition);
switch (ack_state) {
case INPUT_EVENT_ACK_STATE_CONSUMED:
main_thread_scheduler_->DidHandleInputEventOnCompositorThread(
*input_event, blink::scheduler::WebMainThreadScheduler::
InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
break;
case INPUT_EVENT_ACK_STATE_NOT_CONSUMED:
case INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING_DUE_TO_FLING:
main_thread_scheduler_->DidHandleInputEventOnCompositorThread(
*input_event, blink::scheduler::WebMainThreadScheduler::
InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
break;
default:
break;
}
if (ack_state == INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING ||
ack_state == INPUT_EVENT_ACK_STATE_SET_NON_BLOCKING_DUE_TO_FLING ||
ack_state == INPUT_EVENT_ACK_STATE_NOT_CONSUMED) {
DCHECK(!overscroll_params);
InputEventDispatchType dispatch_type = callback.is_null()
? DISPATCH_TYPE_NON_BLOCKING
: DISPATCH_TYPE_BLOCKING;
HandledEventCallback handled_event =
base::BindOnce(&WidgetInputHandlerManager::HandledInputEvent, this,
std::move(callback));
input_event_queue_->HandleEvent(std::move(input_event), latency_info,
dispatch_type, ack_state,
std::move(handled_event));
return;
}
if (callback) {
std::move(callback).Run(
InputEventAckSource::COMPOSITOR_THREAD, latency_info, ack_state,
overscroll_params
? base::Optional<ui::DidOverscrollParams>(*overscroll_params)
: base::nullopt,
base::nullopt);
}
}
void WidgetInputHandlerManager::HandledInputEvent(
mojom::WidgetInputHandler::DispatchEventCallback callback,
InputEventAckState ack_state,
const ui::LatencyInfo& latency_info,
std::unique_ptr<ui::DidOverscrollParams> overscroll_params,
base::Optional<cc::TouchAction> touch_action) {
if (!callback)
return;
// This method is called from either the main thread or the compositor thread.
bool is_compositor_thread = compositor_task_runner_ &&
compositor_task_runner_->BelongsToCurrentThread();
// If there is a compositor task runner and the current thread isn't the
// compositor thread proxy it over to the compositor thread.
if (compositor_task_runner_ && !is_compositor_thread) {
compositor_task_runner_->PostTask(
FROM_HERE, base::BindOnce(CallCallback, std::move(callback), ack_state,
latency_info, std::move(overscroll_params),
touch_action));
} else {
// Otherwise call the callback immediately.
std::move(callback).Run(
is_compositor_thread ? InputEventAckSource::COMPOSITOR_THREAD
: InputEventAckSource::MAIN_THREAD,
latency_info, ack_state,
overscroll_params
? base::Optional<ui::DidOverscrollParams>(*overscroll_params)
: base::nullopt,
touch_action);
}
}
void WidgetInputHandlerManager::ObserveGestureEventOnCompositorThread(
const blink::WebGestureEvent& gesture_event,
const cc::InputHandlerScrollResult& scroll_result) {
if (!input_handler_proxy_)
return;
DCHECK(input_handler_proxy_->scroll_elasticity_controller());
input_handler_proxy_->scroll_elasticity_controller()
->ObserveGestureEventAndResult(gesture_event, scroll_result);
}
#if defined(OS_ANDROID)
content::SynchronousCompositorRegistry*
WidgetInputHandlerManager::GetSynchronousCompositorRegistry() {
DCHECK(synchronous_compositor_registry_);
return synchronous_compositor_registry_.get();
}
#endif
} // namespace content