// Copyright 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/common/input/web_input_event_traits.h" #include #include #include "base/logging.h" #include "base/strings/stringprintf.h" using base::StringAppendF; using base::SStringPrintf; using blink::WebGestureEvent; using blink::WebInputEvent; using blink::WebKeyboardEvent; using blink::WebMouseEvent; using blink::WebMouseWheelEvent; using blink::WebTouchEvent; using blink::WebTouchPoint; using std::numeric_limits; namespace content { namespace { const int kInvalidTouchIndex = -1; void ApppendEventDetails(const WebKeyboardEvent& event, std::string* result) { StringAppendF(result, "{\n WinCode: %d\n NativeCode: %d\n IsSystem: %d\n" " Text: %s\n UnmodifiedText: %s\n KeyIdentifier: %s\n}", event.windowsKeyCode, event.nativeKeyCode, event.isSystemKey, reinterpret_cast(event.text), reinterpret_cast(event.unmodifiedText), reinterpret_cast(event.keyIdentifier)); } void ApppendEventDetails(const WebMouseEvent& event, std::string* result) { StringAppendF(result, "{\n Button: %d\n Pos: (%d, %d)\n WindowPos: (%d, %d)\n" " GlobalPos: (%d, %d)\n Movement: (%d, %d)\n Clicks: %d\n}", event.button, event.x, event.y, event.windowX, event.windowY, event.globalX, event.globalY, event.movementX, event.movementY, event.clickCount); } void ApppendEventDetails(const WebMouseWheelEvent& event, std::string* result) { StringAppendF(result, "{\n Delta: (%f, %f)\n WheelTicks: (%f, %f)\n Accel: (%f, %f)\n" " ScrollByPage: %d\n HasPreciseScrollingDeltas: %d\n" " Phase: (%d, %d)\n CanRubberband: (%d, %d)\n CanScroll: %d\n}", event.deltaX, event.deltaY, event.wheelTicksX, event.wheelTicksY, event.accelerationRatioX, event.accelerationRatioY, event.scrollByPage, event.hasPreciseScrollingDeltas, event.phase, event.momentumPhase, event.canRubberbandLeft, event.canRubberbandRight, event.canScroll); } void ApppendEventDetails(const WebGestureEvent& event, std::string* result) { StringAppendF(result, "{\n Pos: (%d, %d)\n GlobalPos: (%d, %d)\n SourceDevice: %d\n" " RawData: (%f, %f, %f, %f, %d)\n}", event.x, event.y, event.globalX, event.globalY, event.sourceDevice, event.data.scrollUpdate.deltaX, event.data.scrollUpdate.deltaY, event.data.scrollUpdate.velocityX, event.data.scrollUpdate.velocityY, event.data.scrollUpdate.previousUpdateInSequencePrevented); } void ApppendTouchPointDetails(const WebTouchPoint& point, std::string* result) { StringAppendF(result, " (ID: %d, State: %d, ScreenPos: (%f, %f), Pos: (%f, %f)," " Radius: (%f, %f), Rot: %f, Force: %f),\n", point.id, point.state, point.screenPosition.x, point.screenPosition.y, point.position.x, point.position.y, point.radiusX, point.radiusY, point.rotationAngle, point.force); } void ApppendEventDetails(const WebTouchEvent& event, std::string* result) { StringAppendF(result, "{\n Touches: %u, Cancelable: %d, CausesScrolling: %d," " uniqueTouchEventId: %u\n[\n", event.touchesLength, event.cancelable, event.causesScrollingIfUncanceled, event.uniqueTouchEventId); for (unsigned i = 0; i < event.touchesLength; ++i) ApppendTouchPointDetails(event.touches[i], result); result->append(" ]\n}"); } bool CanCoalesce(const WebKeyboardEvent& event_to_coalesce, const WebKeyboardEvent& event) { return false; } void Coalesce(const WebKeyboardEvent& event_to_coalesce, WebKeyboardEvent* event) { DCHECK(CanCoalesce(event_to_coalesce, *event)); } bool CanCoalesce(const WebMouseEvent& event_to_coalesce, const WebMouseEvent& event) { return event.type == event_to_coalesce.type && event.type == WebInputEvent::MouseMove; } void Coalesce(const WebMouseEvent& event_to_coalesce, WebMouseEvent* event) { DCHECK(CanCoalesce(event_to_coalesce, *event)); // Accumulate movement deltas. int x = event->movementX; int y = event->movementY; *event = event_to_coalesce; event->movementX += x; event->movementY += y; } bool CanCoalesce(const WebMouseWheelEvent& event_to_coalesce, const WebMouseWheelEvent& event) { return event.modifiers == event_to_coalesce.modifiers && event.scrollByPage == event_to_coalesce.scrollByPage && event.phase == event_to_coalesce.phase && event.momentumPhase == event_to_coalesce.momentumPhase && event.hasPreciseScrollingDeltas == event_to_coalesce.hasPreciseScrollingDeltas && event.canScroll == event_to_coalesce.canScroll; } float GetUnacceleratedDelta(float accelerated_delta, float acceleration_ratio) { return accelerated_delta * acceleration_ratio; } float GetAccelerationRatio(float accelerated_delta, float unaccelerated_delta) { if (unaccelerated_delta == 0.f || accelerated_delta == 0.f) return 1.f; return unaccelerated_delta / accelerated_delta; } void Coalesce(const WebMouseWheelEvent& event_to_coalesce, WebMouseWheelEvent* event) { DCHECK(CanCoalesce(event_to_coalesce, *event)); float unaccelerated_x = GetUnacceleratedDelta(event->deltaX, event->accelerationRatioX) + GetUnacceleratedDelta(event_to_coalesce.deltaX, event_to_coalesce.accelerationRatioX); float unaccelerated_y = GetUnacceleratedDelta(event->deltaY, event->accelerationRatioY) + GetUnacceleratedDelta(event_to_coalesce.deltaY, event_to_coalesce.accelerationRatioY); event->deltaX += event_to_coalesce.deltaX; event->deltaY += event_to_coalesce.deltaY; event->wheelTicksX += event_to_coalesce.wheelTicksX; event->wheelTicksY += event_to_coalesce.wheelTicksY; event->accelerationRatioX = GetAccelerationRatio(event->deltaX, unaccelerated_x); event->accelerationRatioY = GetAccelerationRatio(event->deltaY, unaccelerated_y); DCHECK_GE(event_to_coalesce.timeStampSeconds, event->timeStampSeconds); event->timeStampSeconds = event_to_coalesce.timeStampSeconds; } // Returns |kInvalidTouchIndex| iff |event| lacks a touch with an ID of |id|. int GetIndexOfTouchID(const WebTouchEvent& event, int id) { for (unsigned i = 0; i < event.touchesLength; ++i) { if (event.touches[i].id == id) return i; } return kInvalidTouchIndex; } bool CanCoalesce(const WebTouchEvent& event_to_coalesce, const WebTouchEvent& event) { if (event.type != event_to_coalesce.type || event.type != WebInputEvent::TouchMove || event.modifiers != event_to_coalesce.modifiers || event.touchesLength != event_to_coalesce.touchesLength || event.touchesLength > WebTouchEvent::touchesLengthCap) return false; static_assert(WebTouchEvent::touchesLengthCap <= sizeof(int32_t) * 8U, "suboptimal touchesLengthCap size"); // Ensure that we have a 1-to-1 mapping of pointer ids between touches. std::bitset unmatched_event_touches( (1 << event.touchesLength) - 1); for (unsigned i = 0; i < event_to_coalesce.touchesLength; ++i) { int event_touch_index = GetIndexOfTouchID(event, event_to_coalesce.touches[i].id); if (event_touch_index == kInvalidTouchIndex) return false; if (!unmatched_event_touches[event_touch_index]) return false; unmatched_event_touches[event_touch_index] = false; } return unmatched_event_touches.none(); } void Coalesce(const WebTouchEvent& event_to_coalesce, WebTouchEvent* event) { DCHECK(CanCoalesce(event_to_coalesce, *event)); // The WebTouchPoints include absolute position information. So it is // sufficient to simply replace the previous event with the new event-> // However, it is necessary to make sure that all the points have the // correct state, i.e. the touch-points that moved in the last event, but // didn't change in the current event, will have Stationary state. It is // necessary to change them back to Moved state. WebTouchEvent old_event = *event; *event = event_to_coalesce; for (unsigned i = 0; i < event->touchesLength; ++i) { int i_old = GetIndexOfTouchID(old_event, event->touches[i].id); if (old_event.touches[i_old].state == blink::WebTouchPoint::StateMoved) event->touches[i].state = blink::WebTouchPoint::StateMoved; } event->causesScrollingIfUncanceled |= old_event.causesScrollingIfUncanceled; } bool CanCoalesce(const WebGestureEvent& event_to_coalesce, const WebGestureEvent& event) { if (event.type != event_to_coalesce.type || event.sourceDevice != event_to_coalesce.sourceDevice || event.modifiers != event_to_coalesce.modifiers) return false; if (event.type == WebInputEvent::GestureScrollUpdate) return true; // GesturePinchUpdate scales can be combined only if they share a focal point, // e.g., with double-tap drag zoom. if (event.type == WebInputEvent::GesturePinchUpdate && event.x == event_to_coalesce.x && event.y == event_to_coalesce.y) return true; return false; } void Coalesce(const WebGestureEvent& event_to_coalesce, WebGestureEvent* event) { DCHECK(CanCoalesce(event_to_coalesce, *event)); if (event->type == WebInputEvent::GestureScrollUpdate) { event->data.scrollUpdate.deltaX += event_to_coalesce.data.scrollUpdate.deltaX; event->data.scrollUpdate.deltaY += event_to_coalesce.data.scrollUpdate.deltaY; DCHECK_EQ( event->data.scrollUpdate.previousUpdateInSequencePrevented, event_to_coalesce.data.scrollUpdate.previousUpdateInSequencePrevented); } else if (event->type == WebInputEvent::GesturePinchUpdate) { event->data.pinchUpdate.scale *= event_to_coalesce.data.pinchUpdate.scale; // Ensure the scale remains bounded above 0 and below Infinity so that // we can reliably perform operations like log on the values. if (event->data.pinchUpdate.scale < numeric_limits::min()) event->data.pinchUpdate.scale = numeric_limits::min(); else if (event->data.pinchUpdate.scale > numeric_limits::max()) event->data.pinchUpdate.scale = numeric_limits::max(); } } struct WebInputEventToString { template bool Execute(const WebInputEvent& event, std::string* result) const { SStringPrintf(result, "%s (Time: %lf, Modifiers: %d)\n", WebInputEventTraits::GetName(event.type), event.timeStampSeconds, event.modifiers); const EventType& typed_event = static_cast(event); ApppendEventDetails(typed_event, result); return true; } }; struct WebInputEventSize { template bool Execute(WebInputEvent::Type /* type */, size_t* type_size) const { *type_size = sizeof(EventType); return true; } }; struct WebInputEventClone { template bool Execute(const WebInputEvent& event, ScopedWebInputEvent* scoped_event) const { DCHECK_EQ(sizeof(EventType), event.size); *scoped_event = ScopedWebInputEvent( new EventType(static_cast(event))); return true; } }; struct WebInputEventDelete { template bool Execute(WebInputEvent* event, bool* /* dummy_var */) const { if (!event) return false; DCHECK_EQ(sizeof(EventType), event->size); delete static_cast(event); return true; } }; struct WebInputEventCanCoalesce { template bool Execute(const WebInputEvent& event_to_coalesce, const WebInputEvent* event) const { if (event_to_coalesce.type != event->type) return false; DCHECK_EQ(sizeof(EventType), event->size); DCHECK_EQ(sizeof(EventType), event_to_coalesce.size); return CanCoalesce(static_cast(event_to_coalesce), *static_cast(event)); } }; struct WebInputEventCoalesce { template bool Execute(const WebInputEvent& event_to_coalesce, WebInputEvent* event) const { Coalesce(static_cast(event_to_coalesce), static_cast(event)); return true; } }; template bool Apply(Operator op, WebInputEvent::Type type, const ArgIn& arg_in, ArgOut* arg_out) { if (WebInputEvent::isMouseEventType(type)) return op.template Execute(arg_in, arg_out); else if (type == WebInputEvent::MouseWheel) return op.template Execute(arg_in, arg_out); else if (WebInputEvent::isKeyboardEventType(type)) return op.template Execute(arg_in, arg_out); else if (WebInputEvent::isTouchEventType(type)) return op.template Execute(arg_in, arg_out); else if (WebInputEvent::isGestureEventType(type)) return op.template Execute(arg_in, arg_out); NOTREACHED() << "Unknown webkit event type " << type; return false; } } // namespace const char* WebInputEventTraits::GetName(WebInputEvent::Type type) { #define CASE_TYPE(t) case WebInputEvent::t: return #t switch(type) { CASE_TYPE(Undefined); CASE_TYPE(MouseDown); CASE_TYPE(MouseUp); CASE_TYPE(MouseMove); CASE_TYPE(MouseEnter); CASE_TYPE(MouseLeave); CASE_TYPE(ContextMenu); CASE_TYPE(MouseWheel); CASE_TYPE(RawKeyDown); CASE_TYPE(KeyDown); CASE_TYPE(KeyUp); CASE_TYPE(Char); CASE_TYPE(GestureScrollBegin); CASE_TYPE(GestureScrollEnd); CASE_TYPE(GestureScrollUpdate); CASE_TYPE(GestureFlingStart); CASE_TYPE(GestureFlingCancel); CASE_TYPE(GestureShowPress); CASE_TYPE(GestureTap); CASE_TYPE(GestureTapUnconfirmed); CASE_TYPE(GestureTapDown); CASE_TYPE(GestureTapCancel); CASE_TYPE(GestureDoubleTap); CASE_TYPE(GestureTwoFingerTap); CASE_TYPE(GestureLongPress); CASE_TYPE(GestureLongTap); CASE_TYPE(GesturePinchBegin); CASE_TYPE(GesturePinchEnd); CASE_TYPE(GesturePinchUpdate); CASE_TYPE(TouchStart); CASE_TYPE(TouchMove); CASE_TYPE(TouchEnd); CASE_TYPE(TouchCancel); default: // Must include default to let blink::WebInputEvent add new event types // before they're added here. DLOG(WARNING) << "Unhandled WebInputEvent type in WebInputEventTraits::GetName.\n"; break; } #undef CASE_TYPE return ""; } std::string WebInputEventTraits::ToString(const WebInputEvent& event) { std::string result; Apply(WebInputEventToString(), event.type, event, &result); return result; } size_t WebInputEventTraits::GetSize(WebInputEvent::Type type) { size_t size = 0; Apply(WebInputEventSize(), type, type, &size); return size; } ScopedWebInputEvent WebInputEventTraits::Clone(const WebInputEvent& event) { ScopedWebInputEvent scoped_event; Apply(WebInputEventClone(), event.type, event, &scoped_event); return scoped_event.Pass(); } void WebInputEventTraits::Delete(WebInputEvent* event) { if (!event) return; bool dummy_var = false; Apply(WebInputEventDelete(), event->type, event, &dummy_var); } bool WebInputEventTraits::CanCoalesce(const WebInputEvent& event_to_coalesce, const WebInputEvent& event) { // Early out before casting. if (event_to_coalesce.type != event.type) return false; return Apply(WebInputEventCanCoalesce(), event.type, event_to_coalesce, &event); } void WebInputEventTraits::Coalesce(const WebInputEvent& event_to_coalesce, WebInputEvent* event) { DCHECK(event); Apply(WebInputEventCoalesce(), event->type, event_to_coalesce, event); } bool WebInputEventTraits::WillReceiveAckFromRenderer( const WebInputEvent& event) { switch (event.type) { case WebInputEvent::MouseDown: case WebInputEvent::MouseUp: case WebInputEvent::MouseEnter: case WebInputEvent::MouseLeave: case WebInputEvent::ContextMenu: case WebInputEvent::GestureScrollBegin: case WebInputEvent::GestureScrollEnd: case WebInputEvent::GestureShowPress: case WebInputEvent::GestureTapUnconfirmed: case WebInputEvent::GestureTapDown: case WebInputEvent::GestureTapCancel: case WebInputEvent::GesturePinchBegin: case WebInputEvent::GesturePinchEnd: case WebInputEvent::TouchCancel: return false; case WebInputEvent::TouchStart: case WebInputEvent::TouchEnd: return static_cast(event).cancelable; default: return true; } } uint32 WebInputEventTraits::GetUniqueTouchEventId(const WebInputEvent& event) { if (WebInputEvent::isTouchEventType(event.type)) { return static_cast(event).uniqueTouchEventId; } return 0U; } } // namespace content