summaryrefslogtreecommitdiff
path: root/chromium/content/renderer/gpu/input_handler_proxy.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/renderer/gpu/input_handler_proxy.cc')
-rw-r--r--chromium/content/renderer/gpu/input_handler_proxy.cc456
1 files changed, 456 insertions, 0 deletions
diff --git a/chromium/content/renderer/gpu/input_handler_proxy.cc b/chromium/content/renderer/gpu/input_handler_proxy.cc
new file mode 100644
index 00000000000..1f88e627841
--- /dev/null
+++ b/chromium/content/renderer/gpu/input_handler_proxy.cc
@@ -0,0 +1,456 @@
+// Copyright (c) 2013 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/gpu/input_handler_proxy.h"
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "content/renderer/gpu/input_handler_proxy_client.h"
+#include "third_party/WebKit/public/platform/Platform.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/base/latency_info.h"
+
+using WebKit::WebFloatPoint;
+using WebKit::WebFloatSize;
+using WebKit::WebGestureEvent;
+using WebKit::WebInputEvent;
+using WebKit::WebMouseWheelEvent;
+using WebKit::WebPoint;
+using WebKit::WebTouchEvent;
+
+namespace {
+
+void SendScrollLatencyUma(const WebInputEvent& event,
+ const ui::LatencyInfo& latency_info) {
+ if (!(event.type == WebInputEvent::GestureScrollBegin ||
+ event.type == WebInputEvent::GestureScrollUpdate ||
+ event.type == WebInputEvent::GestureScrollUpdateWithoutPropagation))
+ return;
+
+ ui::LatencyInfo::LatencyMap::const_iterator it =
+ latency_info.latency_components.find(std::make_pair(
+ ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0));
+
+ if (it == latency_info.latency_components.end())
+ return;
+
+ base::TimeDelta delta = base::TimeTicks::HighResNow() - it->second.event_time;
+ for (size_t i = 0; i < it->second.event_count; ++i) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Event.Latency.RendererImpl.GestureScroll",
+ delta.InMicroseconds(),
+ 0,
+ 200000,
+ 100);
+ }
+} // namespace
+
+}
+
+namespace content {
+
+InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler)
+ : client_(NULL),
+ input_handler_(input_handler),
+#ifndef NDEBUG
+ expect_scroll_update_end_(false),
+ expect_pinch_update_end_(false),
+#endif
+ gesture_scroll_on_impl_thread_(false),
+ gesture_pinch_on_impl_thread_(false),
+ fling_may_be_active_on_main_thread_(false),
+ fling_overscrolled_horizontally_(false),
+ fling_overscrolled_vertically_(false) {
+ input_handler_->BindToClient(this);
+}
+
+InputHandlerProxy::~InputHandlerProxy() {}
+
+void InputHandlerProxy::WillShutdown() {
+ input_handler_ = NULL;
+ DCHECK(client_);
+ client_->WillShutdown();
+}
+
+void InputHandlerProxy::SetClient(InputHandlerProxyClient* client) {
+ DCHECK(!client_ || !client);
+ client_ = client;
+}
+
+InputHandlerProxy::EventDisposition
+InputHandlerProxy::HandleInputEventWithLatencyInfo(
+ const WebInputEvent& event,
+ const ui::LatencyInfo& latency_info) {
+ DCHECK(input_handler_);
+
+ SendScrollLatencyUma(event, latency_info);
+
+ InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event);
+ if (disposition != DID_NOT_HANDLE)
+ input_handler_->SetLatencyInfoForInputEvent(latency_info);
+ return disposition;
+}
+
+InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent(
+ const WebInputEvent& event) {
+ DCHECK(client_);
+ DCHECK(input_handler_);
+
+ if (event.type == WebInputEvent::MouseWheel) {
+ const WebMouseWheelEvent& wheel_event =
+ *static_cast<const WebMouseWheelEvent*>(&event);
+ if (wheel_event.scrollByPage) {
+ // TODO(jamesr): We don't properly handle scroll by page in the compositor
+ // thread, so punt it to the main thread. http://crbug.com/236639
+ return DID_NOT_HANDLE;
+ }
+ cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
+ gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::Wheel);
+ switch (scroll_status) {
+ case cc::InputHandler::ScrollStarted: {
+ TRACE_EVENT_INSTANT2(
+ "renderer",
+ "InputHandlerProxy::handle_input wheel scroll",
+ TRACE_EVENT_SCOPE_THREAD,
+ "deltaX",
+ -wheel_event.deltaX,
+ "deltaY",
+ -wheel_event.deltaY);
+ bool did_scroll = input_handler_->ScrollBy(
+ gfx::Point(wheel_event.x, wheel_event.y),
+ gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY));
+ input_handler_->ScrollEnd();
+ return did_scroll ? DID_HANDLE : DROP_EVENT;
+ }
+ case cc::InputHandler::ScrollIgnored:
+ // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
+ // to properly sync scrollability it's safer to send the event to the
+ // main thread. Change back to DROP_EVENT once we have synchronization
+ // bugs sorted out.
+ return DID_NOT_HANDLE;
+ case cc::InputHandler::ScrollOnMainThread:
+ return DID_NOT_HANDLE;
+ }
+ } else if (event.type == WebInputEvent::GestureScrollBegin) {
+ DCHECK(!gesture_scroll_on_impl_thread_);
+#ifndef NDEBUG
+ DCHECK(!expect_scroll_update_end_);
+ expect_scroll_update_end_ = true;
+#endif
+ const WebGestureEvent& gesture_event =
+ *static_cast<const WebGestureEvent*>(&event);
+ cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
+ gfx::Point(gesture_event.x, gesture_event.y),
+ cc::InputHandler::Gesture);
+ switch (scroll_status) {
+ case cc::InputHandler::ScrollStarted:
+ gesture_scroll_on_impl_thread_ = true;
+ return DID_HANDLE;
+ case cc::InputHandler::ScrollOnMainThread:
+ return DID_NOT_HANDLE;
+ case cc::InputHandler::ScrollIgnored:
+ return DROP_EVENT;
+ }
+ } else if (event.type == WebInputEvent::GestureScrollUpdate) {
+#ifndef NDEBUG
+ DCHECK(expect_scroll_update_end_);
+#endif
+
+ if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_)
+ return DID_NOT_HANDLE;
+
+ const WebGestureEvent& gesture_event =
+ *static_cast<const WebGestureEvent*>(&event);
+ bool did_scroll = input_handler_->ScrollBy(
+ gfx::Point(gesture_event.x, gesture_event.y),
+ gfx::Vector2dF(-gesture_event.data.scrollUpdate.deltaX,
+ -gesture_event.data.scrollUpdate.deltaY));
+ return did_scroll ? DID_HANDLE : DROP_EVENT;
+ } else if (event.type == WebInputEvent::GestureScrollEnd) {
+#ifndef NDEBUG
+ DCHECK(expect_scroll_update_end_);
+ expect_scroll_update_end_ = false;
+#endif
+ input_handler_->ScrollEnd();
+
+ if (!gesture_scroll_on_impl_thread_)
+ return DID_NOT_HANDLE;
+
+ gesture_scroll_on_impl_thread_ = false;
+ return DID_HANDLE;
+ } else if (event.type == WebInputEvent::GesturePinchBegin) {
+#ifndef NDEBUG
+ DCHECK(!expect_pinch_update_end_);
+ expect_pinch_update_end_ = true;
+#endif
+ input_handler_->PinchGestureBegin();
+ gesture_pinch_on_impl_thread_ = true;
+ return DID_HANDLE;
+ } else if (event.type == WebInputEvent::GesturePinchEnd) {
+#ifndef NDEBUG
+ DCHECK(expect_pinch_update_end_);
+ expect_pinch_update_end_ = false;
+#endif
+ gesture_pinch_on_impl_thread_ = false;
+ input_handler_->PinchGestureEnd();
+ return DID_HANDLE;
+ } else if (event.type == WebInputEvent::GesturePinchUpdate) {
+#ifndef NDEBUG
+ DCHECK(expect_pinch_update_end_);
+#endif
+ const WebGestureEvent& gesture_event =
+ *static_cast<const WebGestureEvent*>(&event);
+ input_handler_->PinchGestureUpdate(
+ gesture_event.data.pinchUpdate.scale,
+ gfx::Point(gesture_event.x, gesture_event.y));
+ return DID_HANDLE;
+ } else if (event.type == WebInputEvent::GestureFlingStart) {
+ const WebGestureEvent& gesture_event =
+ *static_cast<const WebGestureEvent*>(&event);
+ return HandleGestureFling(gesture_event);
+ } else if (event.type == WebInputEvent::GestureFlingCancel) {
+ if (CancelCurrentFling())
+ return DID_HANDLE;
+ else if (!fling_may_be_active_on_main_thread_)
+ return DROP_EVENT;
+ } else if (event.type == WebInputEvent::TouchStart) {
+ const WebTouchEvent& touch_event =
+ *static_cast<const WebTouchEvent*>(&event);
+ if (!input_handler_->HaveTouchEventHandlersAt(touch_event.touches[0]
+ .position))
+ return DROP_EVENT;
+ } else if (WebInputEvent::isKeyboardEventType(event.type)) {
+ CancelCurrentFling();
+ }
+
+ return DID_NOT_HANDLE;
+}
+
+InputHandlerProxy::EventDisposition
+InputHandlerProxy::HandleGestureFling(
+ const WebGestureEvent& gesture_event) {
+ cc::InputHandler::ScrollStatus scroll_status;
+
+ if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) {
+ scroll_status = input_handler_->ScrollBegin(
+ gfx::Point(gesture_event.x, gesture_event.y),
+ cc::InputHandler::NonBubblingGesture);
+ } else {
+ if (!gesture_scroll_on_impl_thread_)
+ scroll_status = cc::InputHandler::ScrollOnMainThread;
+ else
+ scroll_status = input_handler_->FlingScrollBegin();
+ }
+
+#ifndef NDEBUG
+ expect_scroll_update_end_ = false;
+#endif
+
+ switch (scroll_status) {
+ case cc::InputHandler::ScrollStarted: {
+ if (gesture_event.sourceDevice == WebGestureEvent::Touchpad)
+ input_handler_->ScrollEnd();
+
+ fling_curve_.reset(client_->CreateFlingAnimationCurve(
+ gesture_event.sourceDevice,
+ WebFloatPoint(gesture_event.data.flingStart.velocityX,
+ gesture_event.data.flingStart.velocityY),
+ WebKit::WebSize()));
+ fling_overscrolled_horizontally_ = false;
+ fling_overscrolled_vertically_ = false;
+ TRACE_EVENT_ASYNC_BEGIN0(
+ "renderer",
+ "InputHandlerProxy::HandleGestureFling::started",
+ this);
+ fling_parameters_.delta =
+ WebFloatPoint(gesture_event.data.flingStart.velocityX,
+ gesture_event.data.flingStart.velocityY);
+ fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
+ fling_parameters_.globalPoint =
+ WebPoint(gesture_event.globalX, gesture_event.globalY);
+ fling_parameters_.modifiers = gesture_event.modifiers;
+ fling_parameters_.sourceDevice = gesture_event.sourceDevice;
+ input_handler_->ScheduleAnimation();
+ return DID_HANDLE;
+ }
+ case cc::InputHandler::ScrollOnMainThread: {
+ TRACE_EVENT_INSTANT0("renderer",
+ "InputHandlerProxy::HandleGestureFling::"
+ "scroll_on_main_thread",
+ TRACE_EVENT_SCOPE_THREAD);
+ fling_may_be_active_on_main_thread_ = true;
+ return DID_NOT_HANDLE;
+ }
+ case cc::InputHandler::ScrollIgnored: {
+ TRACE_EVENT_INSTANT0(
+ "renderer",
+ "InputHandlerProxy::HandleGestureFling::ignored",
+ TRACE_EVENT_SCOPE_THREAD);
+ if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) {
+ // We still pass the curve to the main thread if there's nothing
+ // scrollable, in case something
+ // registers a handler before the curve is over.
+ return DID_NOT_HANDLE;
+ }
+ return DROP_EVENT;
+ }
+ }
+ return DID_NOT_HANDLE;
+}
+
+void InputHandlerProxy::Animate(base::TimeTicks time) {
+ if (!fling_curve_)
+ return;
+
+ double monotonic_time_sec = (time - base::TimeTicks()).InSecondsF();
+ if (!fling_parameters_.startTime) {
+ fling_parameters_.startTime = monotonic_time_sec;
+ input_handler_->ScheduleAnimation();
+ return;
+ }
+
+ if (fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime,
+ this)) {
+ input_handler_->ScheduleAnimation();
+ } else {
+ TRACE_EVENT_INSTANT0("renderer",
+ "InputHandlerProxy::animate::flingOver",
+ TRACE_EVENT_SCOPE_THREAD);
+ CancelCurrentFling();
+ }
+}
+
+void InputHandlerProxy::MainThreadHasStoppedFlinging() {
+ fling_may_be_active_on_main_thread_ = false;
+}
+
+void InputHandlerProxy::DidOverscroll(const cc::DidOverscrollParams& params) {
+ DCHECK(client_);
+ if (fling_curve_) {
+ static const int kFlingOverscrollThreshold = 1;
+ fling_overscrolled_horizontally_ |=
+ std::abs(params.accumulated_overscroll.x()) >=
+ kFlingOverscrollThreshold;
+ fling_overscrolled_vertically_ |=
+ std::abs(params.accumulated_overscroll.y()) >=
+ kFlingOverscrollThreshold;
+ }
+
+ client_->DidOverscroll(params);
+}
+
+bool InputHandlerProxy::CancelCurrentFling() {
+ bool had_fling_animation = fling_curve_;
+ if (had_fling_animation &&
+ fling_parameters_.sourceDevice == WebGestureEvent::Touchscreen) {
+ input_handler_->ScrollEnd();
+ TRACE_EVENT_ASYNC_END0(
+ "renderer",
+ "InputHandlerProxy::HandleGestureFling::started",
+ this);
+ }
+
+ TRACE_EVENT_INSTANT1("renderer",
+ "InputHandlerProxy::CancelCurrentFling",
+ TRACE_EVENT_SCOPE_THREAD,
+ "had_fling_animation",
+ had_fling_animation);
+ fling_curve_.reset();
+ gesture_scroll_on_impl_thread_ = false;
+ fling_parameters_ = WebKit::WebActiveWheelFlingParameters();
+ return had_fling_animation;
+}
+
+bool InputHandlerProxy::TouchpadFlingScroll(
+ const WebFloatSize& increment) {
+ WebMouseWheelEvent synthetic_wheel;
+ synthetic_wheel.type = WebInputEvent::MouseWheel;
+ synthetic_wheel.deltaX = increment.width;
+ synthetic_wheel.deltaY = increment.height;
+ synthetic_wheel.hasPreciseScrollingDeltas = true;
+ synthetic_wheel.x = fling_parameters_.point.x;
+ synthetic_wheel.y = fling_parameters_.point.y;
+ synthetic_wheel.globalX = fling_parameters_.globalPoint.x;
+ synthetic_wheel.globalY = fling_parameters_.globalPoint.y;
+ synthetic_wheel.modifiers = fling_parameters_.modifiers;
+
+ InputHandlerProxy::EventDisposition disposition =
+ HandleInputEvent(synthetic_wheel);
+ switch (disposition) {
+ case DID_HANDLE:
+ return true;
+ case DROP_EVENT:
+ break;
+ case DID_NOT_HANDLE:
+ TRACE_EVENT_INSTANT0("renderer",
+ "InputHandlerProxy::scrollBy::AbortFling",
+ TRACE_EVENT_SCOPE_THREAD);
+ // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
+ // main thread. In this case we need to schedule a commit and transfer the
+ // fling curve over to the main thread and run the rest of the wheels from
+ // there. This can happen when flinging a page that contains a scrollable
+ // subarea that we can't scroll on the thread if the fling starts outside
+ // the subarea but then is flung "under" the pointer.
+ client_->TransferActiveWheelFlingAnimation(fling_parameters_);
+ fling_may_be_active_on_main_thread_ = true;
+ CancelCurrentFling();
+ break;
+ }
+
+ return false;
+}
+
+static gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) {
+ return gfx::Vector2dF(-increment.width, -increment.height);
+}
+
+void InputHandlerProxy::scrollBy(const WebFloatSize& increment) {
+ WebFloatSize clipped_increment;
+ if (!fling_overscrolled_horizontally_)
+ clipped_increment.width = increment.width;
+ if (!fling_overscrolled_vertically_)
+ clipped_increment.height = increment.height;
+
+ if (clipped_increment == WebFloatSize())
+ return;
+
+ TRACE_EVENT2("renderer",
+ "InputHandlerProxy::scrollBy",
+ "x",
+ clipped_increment.width,
+ "y",
+ clipped_increment.height);
+
+ bool did_scroll = false;
+
+ switch (fling_parameters_.sourceDevice) {
+ case WebGestureEvent::Touchpad:
+ did_scroll = TouchpadFlingScroll(clipped_increment);
+ break;
+ case WebGestureEvent::Touchscreen:
+ clipped_increment = ToClientScrollIncrement(clipped_increment);
+ did_scroll = input_handler_->ScrollBy(fling_parameters_.point,
+ clipped_increment);
+ break;
+ }
+
+ if (did_scroll) {
+ fling_parameters_.cumulativeScroll.width += clipped_increment.width;
+ fling_parameters_.cumulativeScroll.height += clipped_increment.height;
+ }
+}
+
+void InputHandlerProxy::notifyCurrentFlingVelocity(
+ const WebFloatSize& velocity) {
+ TRACE_EVENT2("renderer",
+ "InputHandlerProxy::notifyCurrentFlingVelocity",
+ "vx",
+ velocity.width,
+ "vy",
+ velocity.height);
+ input_handler_->NotifyCurrentFlingVelocity(ToClientScrollIncrement(velocity));
+}
+
+} // namespace content