| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/events/blink/blink_event_util.h" |
| |
| #include <stddef.h> |
| |
| #include <algorithm> |
| #include <bitset> |
| #include <limits> |
| #include <memory> |
| |
| #include "base/time/time.h" |
| #include "base/trace_event/typed_macros.h" |
| #include "build/build_config.h" |
| #include "third_party/blink/public/common/input/web_input_event.h" |
| #include "third_party/blink/public/common/input/web_mouse_wheel_event.h" |
| #include "third_party/blink/public/common/input/web_pointer_event.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/gesture_detection/gesture_event_data.h" |
| #include "ui/events/gesture_detection/motion_event.h" |
| #include "ui/events/gesture_event_details.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| #include "ui/events/types/event_type.h" |
| #include "ui/gfx/geometry/angle_conversions.h" |
| #include "ui/gfx/geometry/point_f.h" |
| #include "ui/gfx/geometry/transform.h" |
| #include "ui/gfx/geometry/vector2d_f.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "ui/events/android/gesture_event_android.h" |
| #include "ui/events/android/gesture_event_type.h" |
| #endif |
| |
| using blink::WebGestureDevice; |
| using blink::WebGestureEvent; |
| using blink::WebInputEvent; |
| using blink::WebMouseEvent; |
| using blink::WebMouseWheelEvent; |
| using blink::WebPointerEvent; |
| using blink::WebPointerProperties; |
| using blink::WebTouchEvent; |
| using blink::WebTouchPoint; |
| |
| namespace ui { |
| namespace { |
| |
| WebInputEvent::Type ToWebTouchEventType(MotionEvent::Action action) { |
| switch (action) { |
| case MotionEvent::Action::DOWN: |
| return WebInputEvent::Type::kTouchStart; |
| case MotionEvent::Action::MOVE: |
| return WebInputEvent::Type::kTouchMove; |
| case MotionEvent::Action::UP: |
| return WebInputEvent::Type::kTouchEnd; |
| case MotionEvent::Action::CANCEL: |
| return WebInputEvent::Type::kTouchCancel; |
| case MotionEvent::Action::POINTER_DOWN: |
| return WebInputEvent::Type::kTouchStart; |
| case MotionEvent::Action::POINTER_UP: |
| return WebInputEvent::Type::kTouchEnd; |
| case MotionEvent::Action::NONE: |
| case MotionEvent::Action::HOVER_ENTER: |
| case MotionEvent::Action::HOVER_EXIT: |
| case MotionEvent::Action::HOVER_MOVE: |
| case MotionEvent::Action::BUTTON_PRESS: |
| case MotionEvent::Action::BUTTON_RELEASE: |
| break; |
| } |
| NOTREACHED() << "Invalid MotionEvent::Action = " << action; |
| return WebInputEvent::Type::kUndefined; |
| } |
| |
| // Note that the action index is meaningful only in the context of |
| // |Action::POINTER_UP| and |Action::POINTER_DOWN|; other actions map directly |
| // to WebTouchPoint::State. |
| WebTouchPoint::State ToWebTouchPointState(const MotionEvent& event, |
| size_t pointer_index) { |
| switch (event.GetAction()) { |
| case MotionEvent::Action::DOWN: |
| return WebTouchPoint::State::kStatePressed; |
| case MotionEvent::Action::MOVE: |
| return WebTouchPoint::State::kStateMoved; |
| case MotionEvent::Action::UP: |
| return WebTouchPoint::State::kStateReleased; |
| case MotionEvent::Action::CANCEL: |
| return WebTouchPoint::State::kStateCancelled; |
| case MotionEvent::Action::POINTER_DOWN: |
| return static_cast<int>(pointer_index) == event.GetActionIndex() |
| ? WebTouchPoint::State::kStatePressed |
| : WebTouchPoint::State::kStateStationary; |
| case MotionEvent::Action::POINTER_UP: |
| return static_cast<int>(pointer_index) == event.GetActionIndex() |
| ? WebTouchPoint::State::kStateReleased |
| : WebTouchPoint::State::kStateStationary; |
| case MotionEvent::Action::NONE: |
| case MotionEvent::Action::HOVER_ENTER: |
| case MotionEvent::Action::HOVER_EXIT: |
| case MotionEvent::Action::HOVER_MOVE: |
| case MotionEvent::Action::BUTTON_PRESS: |
| case MotionEvent::Action::BUTTON_RELEASE: |
| break; |
| } |
| NOTREACHED() << "Invalid MotionEvent::Action."; |
| return WebTouchPoint::State::kStateUndefined; |
| } |
| |
| WebPointerProperties::PointerType ToWebPointerType( |
| MotionEvent::ToolType tool_type) { |
| switch (tool_type) { |
| case MotionEvent::ToolType::UNKNOWN: |
| return WebPointerProperties::PointerType::kUnknown; |
| case MotionEvent::ToolType::FINGER: |
| return WebPointerProperties::PointerType::kTouch; |
| case MotionEvent::ToolType::STYLUS: |
| return WebPointerProperties::PointerType::kPen; |
| case MotionEvent::ToolType::MOUSE: |
| return WebPointerProperties::PointerType::kMouse; |
| case MotionEvent::ToolType::ERASER: |
| return WebPointerProperties::PointerType::kEraser; |
| } |
| NOTREACHED() << "Invalid MotionEvent::ToolType = " << tool_type; |
| return WebPointerProperties::PointerType::kUnknown; |
| } |
| |
| WebPointerProperties::PointerType ToWebPointerType( |
| EventPointerType event_pointer_type) { |
| switch (event_pointer_type) { |
| case EventPointerType::kUnknown: |
| return WebPointerProperties::PointerType::kUnknown; |
| case EventPointerType::kMouse: |
| return WebPointerProperties::PointerType::kMouse; |
| case EventPointerType::kPen: |
| return WebPointerProperties::PointerType::kPen; |
| case EventPointerType::kTouch: |
| return WebPointerProperties::PointerType::kTouch; |
| case EventPointerType::kEraser: |
| return WebPointerProperties::PointerType::kEraser; |
| default: |
| NOTREACHED() << "Invalid EventPointerType = " |
| << static_cast<int>(event_pointer_type); |
| return WebPointerProperties::PointerType::kUnknown; |
| } |
| } |
| |
| WebPointerProperties::Button ToWebPointerButton(int android_button_state) { |
| if (android_button_state & MotionEvent::BUTTON_PRIMARY) |
| return WebPointerProperties::Button::kLeft; |
| else if (android_button_state & MotionEvent::BUTTON_SECONDARY) |
| return WebPointerProperties::Button::kRight; |
| else if (android_button_state & MotionEvent::BUTTON_TERTIARY) |
| return WebPointerProperties::Button::kMiddle; |
| else if (android_button_state & MotionEvent::BUTTON_BACK) |
| return WebPointerProperties::Button::kBack; |
| else if (android_button_state & MotionEvent::BUTTON_FORWARD) |
| return WebPointerProperties::Button::kForward; |
| else if (android_button_state & MotionEvent::BUTTON_STYLUS_PRIMARY) |
| return WebPointerProperties::Button::kLeft; |
| else if (android_button_state & MotionEvent::BUTTON_STYLUS_SECONDARY) |
| return WebPointerProperties::Button::kRight; |
| else |
| return WebPointerProperties::Button::kNoButton; |
| } |
| |
| WebTouchPoint CreateWebTouchPoint(const MotionEvent& event, |
| size_t pointer_index) { |
| WebTouchPoint touch; |
| |
| SetWebPointerPropertiesFromMotionEventData( |
| touch, event.GetPointerId(pointer_index), |
| event.GetPressure(pointer_index), event.GetOrientation(pointer_index), |
| event.GetTiltX(pointer_index), event.GetTiltY(pointer_index), |
| event.GetTwist(pointer_index), event.GetTangentialPressure(pointer_index), |
| 0 /* no button changed */, event.GetToolType(pointer_index)); |
| |
| touch.state = ToWebTouchPointState(event, pointer_index); |
| touch.SetPositionInWidget(event.GetX(pointer_index), |
| event.GetY(pointer_index)); |
| touch.SetPositionInScreen(event.GetRawX(pointer_index), |
| event.GetRawY(pointer_index)); |
| touch.device_id = event.GetSourceDeviceId(pointer_index); |
| |
| // A note on touch ellipse specifications: |
| // |
| // Android MotionEvent provides the major and minor axes of the touch ellipse, |
| // as well as the orientation of the major axis clockwise from vertical, in |
| // radians. See: |
| // http://developer.android.com/reference/android/view/MotionEvent.html |
| // |
| // The proposed extension to W3C Touch Events specifies the touch ellipse |
| // using two radii along x- & y-axes and a positive acute rotation angle in |
| // degrees. See: |
| // http://dvcs.w3.org/hg/webevents/raw-file/default/touchevents.html |
| |
| float major_radius = event.GetTouchMajor(pointer_index) / 2.f; |
| float minor_radius = event.GetTouchMinor(pointer_index) / 2.f; |
| float orientation_deg = gfx::RadToDeg(event.GetOrientation(pointer_index)); |
| |
| DCHECK_GE(major_radius, 0); |
| DCHECK_GE(minor_radius, 0); |
| DCHECK_GE(major_radius, minor_radius); |
| // Orientation lies in [-180, 180] for a stylus, and [-90, 90] for other |
| // touchscreen inputs. There are exceptions on Android when a device is |
| // rotated, yielding touch orientations in the range of [-180, 180]. |
| // Regardless, normalise to [-90, 90), allowing a small tolerance to account |
| // for floating point conversion. |
| // TODO(e_hakkinen): Also pass unaltered stylus orientation, avoiding loss of |
| // quadrant information, see crbug.com/493728. |
| DCHECK_GT(orientation_deg, -180.01f); |
| DCHECK_LT(orientation_deg, 180.01f); |
| if (orientation_deg >= 90.f) |
| orientation_deg -= 180.f; |
| else if (orientation_deg < -90.f) |
| orientation_deg += 180.f; |
| if (orientation_deg >= 0) { |
| // The case orientation_deg == 0 is handled here on purpose: although the |
| // 'else' block is equivalent in this case, we want to pass the 0 value |
| // unchanged (and 0 is the default value for many devices that don't |
| // report elliptical touches). |
| touch.radius_x = minor_radius; |
| touch.radius_y = major_radius; |
| touch.rotation_angle = orientation_deg; |
| } else { |
| touch.radius_x = major_radius; |
| touch.radius_y = minor_radius; |
| touch.rotation_angle = orientation_deg + 90; |
| } |
| |
| return touch; |
| } |
| |
| } // namespace |
| |
| blink::WebTouchEvent CreateWebTouchEventFromMotionEvent( |
| const MotionEvent& event, |
| bool moved_beyond_slop_region, |
| bool hovering) { |
| static_assert(static_cast<int>(MotionEvent::MAX_TOUCH_POINT_COUNT) == |
| static_cast<int>(blink::WebTouchEvent::kTouchesLengthCap), |
| "inconsistent maximum number of active touch points"); |
| |
| blink::WebTouchEvent result(ToWebTouchEventType(event.GetAction()), |
| EventFlagsToWebEventModifiers(event.GetFlags()), |
| event.GetEventTime()); |
| result.dispatch_type = result.GetType() == WebInputEvent::Type::kTouchCancel |
| ? WebInputEvent::DispatchType::kEventNonBlocking |
| : WebInputEvent::DispatchType::kBlocking; |
| result.moved_beyond_slop_region = moved_beyond_slop_region; |
| result.hovering = hovering; |
| |
| // TODO(mustaq): MotionEvent flags seems unrelated, should use |
| // metaState instead? |
| |
| DCHECK_NE(event.GetUniqueEventId(), 0U); |
| result.unique_touch_event_id = event.GetUniqueEventId(); |
| result.touches_length = |
| std::min(static_cast<unsigned>(event.GetPointerCount()), |
| static_cast<unsigned>(WebTouchEvent::kTouchesLengthCap)); |
| DCHECK_GT(result.touches_length, 0U); |
| |
| for (size_t i = 0; i < result.touches_length; ++i) |
| result.touches[i] = CreateWebTouchPoint(event, i); |
| |
| return result; |
| } |
| |
| int EventFlagsToWebEventModifiers(int flags) { |
| int modifiers = 0; |
| |
| if (flags & EF_SHIFT_DOWN) |
| modifiers |= blink::WebInputEvent::kShiftKey; |
| if (flags & EF_CONTROL_DOWN) |
| modifiers |= blink::WebInputEvent::kControlKey; |
| if (flags & EF_ALT_DOWN) |
| modifiers |= blink::WebInputEvent::kAltKey; |
| if (flags & EF_COMMAND_DOWN) |
| modifiers |= blink::WebInputEvent::kMetaKey; |
| if (flags & EF_ALTGR_DOWN) |
| modifiers |= blink::WebInputEvent::kAltGrKey; |
| if (flags & EF_NUM_LOCK_ON) |
| modifiers |= blink::WebInputEvent::kNumLockOn; |
| if (flags & EF_CAPS_LOCK_ON) |
| modifiers |= blink::WebInputEvent::kCapsLockOn; |
| if (flags & EF_SCROLL_LOCK_ON) |
| modifiers |= blink::WebInputEvent::kScrollLockOn; |
| if (flags & EF_LEFT_MOUSE_BUTTON) |
| modifiers |= blink::WebInputEvent::kLeftButtonDown; |
| if (flags & EF_MIDDLE_MOUSE_BUTTON) |
| modifiers |= blink::WebInputEvent::kMiddleButtonDown; |
| if (flags & EF_RIGHT_MOUSE_BUTTON) |
| modifiers |= blink::WebInputEvent::kRightButtonDown; |
| if (flags & EF_BACK_MOUSE_BUTTON) |
| modifiers |= blink::WebInputEvent::kBackButtonDown; |
| if (flags & EF_FORWARD_MOUSE_BUTTON) |
| modifiers |= blink::WebInputEvent::kForwardButtonDown; |
| if (flags & EF_IS_REPEAT) |
| modifiers |= blink::WebInputEvent::kIsAutoRepeat; |
| if (flags & EF_TOUCH_ACCESSIBILITY) |
| modifiers |= blink::WebInputEvent::kIsTouchAccessibility; |
| |
| return modifiers; |
| } |
| |
| WebGestureEvent CreateWebGestureEvent(const GestureEventDetails& details, |
| base::TimeTicks timestamp, |
| const gfx::PointF& location, |
| const gfx::PointF& raw_location, |
| int flags, |
| uint32_t unique_touch_event_id) { |
| WebGestureDevice source_device = WebGestureDevice::kUninitialized; |
| switch (details.device_type()) { |
| case GestureDeviceType::DEVICE_TOUCHSCREEN: |
| source_device = WebGestureDevice::kTouchscreen; |
| break; |
| case GestureDeviceType::DEVICE_TOUCHPAD: |
| source_device = WebGestureDevice::kTouchpad; |
| break; |
| case GestureDeviceType::DEVICE_UNKNOWN: |
| NOTREACHED() << "Unknown device type is not allowed"; |
| break; |
| } |
| WebGestureEvent gesture(WebInputEvent::Type::kUndefined, |
| EventFlagsToWebEventModifiers(flags), timestamp, |
| source_device); |
| |
| gesture.SetPositionInWidget(location); |
| gesture.SetPositionInScreen(raw_location); |
| |
| gesture.is_source_touch_event_set_blocking = |
| details.is_source_touch_event_set_blocking(); |
| gesture.primary_pointer_type = |
| ToWebPointerType(details.primary_pointer_type()); |
| gesture.primary_unique_touch_event_id = |
| details.primary_unique_touch_event_id(); |
| gesture.unique_touch_event_id = unique_touch_event_id; |
| gesture.GetModifiableEventLatencyMetadata() = |
| details.GetEventLatencyMetadata(); |
| |
| switch (details.type()) { |
| case ET_GESTURE_SHOW_PRESS: |
| gesture.SetType(WebInputEvent::Type::kGestureShowPress); |
| gesture.data.show_press.width = |
| IfNanUseMaxFloat(details.bounding_box_f().width()); |
| gesture.data.show_press.height = |
| IfNanUseMaxFloat(details.bounding_box_f().height()); |
| break; |
| case ET_GESTURE_DOUBLE_TAP: |
| gesture.SetType(WebInputEvent::Type::kGestureDoubleTap); |
| DCHECK_EQ(1, details.tap_count()); |
| gesture.data.tap.tap_count = details.tap_count(); |
| gesture.data.tap.width = |
| IfNanUseMaxFloat(details.bounding_box_f().width()); |
| gesture.data.tap.height = |
| IfNanUseMaxFloat(details.bounding_box_f().height()); |
| gesture.SetNeedsWheelEvent(source_device == WebGestureDevice::kTouchpad); |
| break; |
| case ET_GESTURE_TAP: |
| gesture.SetType(WebInputEvent::Type::kGestureTap); |
| DCHECK_GE(details.tap_count(), 1); |
| gesture.data.tap.tap_count = details.tap_count(); |
| gesture.data.tap.width = |
| IfNanUseMaxFloat(details.bounding_box_f().width()); |
| gesture.data.tap.height = |
| IfNanUseMaxFloat(details.bounding_box_f().height()); |
| break; |
| case ET_GESTURE_TAP_UNCONFIRMED: |
| gesture.SetType(WebInputEvent::Type::kGestureTapUnconfirmed); |
| DCHECK_EQ(1, details.tap_count()); |
| gesture.data.tap.tap_count = details.tap_count(); |
| gesture.data.tap.width = |
| IfNanUseMaxFloat(details.bounding_box_f().width()); |
| gesture.data.tap.height = |
| IfNanUseMaxFloat(details.bounding_box_f().height()); |
| break; |
| case ET_GESTURE_SHORT_PRESS: |
| gesture.SetType(WebInputEvent::Type::kGestureShortPress); |
| gesture.data.long_press.width = |
| IfNanUseMaxFloat(details.bounding_box_f().width()); |
| gesture.data.long_press.height = |
| IfNanUseMaxFloat(details.bounding_box_f().height()); |
| break; |
| case ET_GESTURE_LONG_PRESS: |
| gesture.SetType(WebInputEvent::Type::kGestureLongPress); |
| gesture.data.long_press.width = |
| IfNanUseMaxFloat(details.bounding_box_f().width()); |
| gesture.data.long_press.height = |
| IfNanUseMaxFloat(details.bounding_box_f().height()); |
| break; |
| case ET_GESTURE_LONG_TAP: |
| gesture.SetType(WebInputEvent::Type::kGestureLongTap); |
| gesture.data.long_press.width = |
| IfNanUseMaxFloat(details.bounding_box_f().width()); |
| gesture.data.long_press.height = |
| IfNanUseMaxFloat(details.bounding_box_f().height()); |
| break; |
| case ET_GESTURE_TWO_FINGER_TAP: |
| gesture.SetType(blink::WebInputEvent::Type::kGestureTwoFingerTap); |
| gesture.data.two_finger_tap.first_finger_width = |
| IfNanUseMaxFloat(details.first_finger_width()); |
| gesture.data.two_finger_tap.first_finger_height = |
| IfNanUseMaxFloat(details.first_finger_height()); |
| break; |
| case ET_GESTURE_SCROLL_BEGIN: |
| gesture.SetType(WebInputEvent::Type::kGestureScrollBegin); |
| gesture.data.scroll_begin.pointer_count = details.touch_points(); |
| gesture.data.scroll_begin.delta_x_hint = |
| IfNanUseMaxFloat(details.scroll_x_hint()); |
| gesture.data.scroll_begin.delta_y_hint = |
| IfNanUseMaxFloat(details.scroll_y_hint()); |
| gesture.data.scroll_begin.delta_hint_units = details.scroll_begin_units(); |
| gesture.data.scroll_begin.inertial_phase = |
| WebGestureEvent::InertialPhaseState::kNonMomentum; |
| break; |
| case ET_GESTURE_SCROLL_UPDATE: |
| gesture.SetType(WebInputEvent::Type::kGestureScrollUpdate); |
| gesture.data.scroll_update.delta_x = IfNanUseMaxFloat(details.scroll_x()); |
| gesture.data.scroll_update.delta_y = IfNanUseMaxFloat(details.scroll_y()); |
| gesture.data.scroll_update.delta_units = details.scroll_update_units(); |
| gesture.data.scroll_update.inertial_phase = |
| WebGestureEvent::InertialPhaseState::kNonMomentum; |
| break; |
| case ET_GESTURE_SCROLL_END: |
| gesture.SetType(WebInputEvent::Type::kGestureScrollEnd); |
| gesture.data.scroll_end.inertial_phase = |
| WebGestureEvent::InertialPhaseState::kNonMomentum; |
| break; |
| case ET_SCROLL_FLING_START: |
| gesture.SetType(WebInputEvent::Type::kGestureFlingStart); |
| gesture.data.fling_start.velocity_x = |
| IfNanUseMaxFloat(details.velocity_x()); |
| gesture.data.fling_start.velocity_y = |
| IfNanUseMaxFloat(details.velocity_y()); |
| break; |
| case ET_SCROLL_FLING_CANCEL: |
| gesture.SetType(WebInputEvent::Type::kGestureFlingCancel); |
| break; |
| case ET_GESTURE_PINCH_BEGIN: |
| gesture.SetType(WebInputEvent::Type::kGesturePinchBegin); |
| gesture.SetNeedsWheelEvent(source_device == WebGestureDevice::kTouchpad); |
| break; |
| case ET_GESTURE_PINCH_UPDATE: |
| gesture.SetType(WebInputEvent::Type::kGesturePinchUpdate); |
| gesture.data.pinch_update.scale = details.scale(); |
| gesture.SetNeedsWheelEvent(source_device == WebGestureDevice::kTouchpad); |
| break; |
| case ET_GESTURE_PINCH_END: |
| gesture.SetType(WebInputEvent::Type::kGesturePinchEnd); |
| gesture.SetNeedsWheelEvent(source_device == WebGestureDevice::kTouchpad); |
| break; |
| case ET_GESTURE_TAP_CANCEL: |
| gesture.SetType(WebInputEvent::Type::kGestureTapCancel); |
| break; |
| case ET_GESTURE_TAP_DOWN: |
| gesture.SetType(WebInputEvent::Type::kGestureTapDown); |
| gesture.data.tap_down.tap_down_count = details.tap_down_count(); |
| gesture.data.tap_down.width = |
| IfNanUseMaxFloat(details.bounding_box_f().width()); |
| gesture.data.tap_down.height = |
| IfNanUseMaxFloat(details.bounding_box_f().height()); |
| break; |
| case ET_GESTURE_BEGIN: |
| case ET_GESTURE_END: |
| case ET_GESTURE_SWIPE: |
| // The caller is responsible for discarding these gestures appropriately. |
| gesture.SetType(WebInputEvent::Type::kUndefined); |
| break; |
| default: |
| NOTREACHED() << "EventType provided wasn't a valid gesture event: " |
| << details.type(); |
| } |
| |
| return gesture; |
| } |
| |
| float IfNanUseMaxFloat(float value) { |
| if (std::isnan(value)) |
| return std::numeric_limits<float>::max(); |
| return value; |
| } |
| |
| WebGestureEvent CreateWebGestureEventFromGestureEventData( |
| const GestureEventData& data) { |
| return CreateWebGestureEvent(data.details, data.time, |
| gfx::PointF(data.x, data.y), |
| gfx::PointF(data.raw_x, data.raw_y), data.flags, |
| data.unique_touch_event_id); |
| } |
| |
| std::unique_ptr<blink::WebInputEvent> ScaleWebInputEvent( |
| const blink::WebInputEvent& event, |
| float scale, |
| absl::optional<int64_t> trace_id) { |
| return TranslateAndScaleWebInputEvent(event, gfx::Vector2dF(0, 0), scale, |
| trace_id); |
| } |
| |
| std::unique_ptr<blink::WebInputEvent> TranslateAndScaleWebInputEvent( |
| const blink::WebInputEvent& event, |
| const gfx::Vector2dF& delta, |
| float scale, |
| absl::optional<int64_t> trace_id) { |
| std::unique_ptr<blink::WebInputEvent> scaled_event; |
| if (scale == 1.f && delta.IsZero()) { |
| return scaled_event; |
| } |
| if (event.GetType() == blink::WebMouseEvent::Type::kMouseWheel) { |
| blink::WebMouseWheelEvent* wheel_event = new blink::WebMouseWheelEvent; |
| scaled_event.reset(wheel_event); |
| *wheel_event = static_cast<const blink::WebMouseWheelEvent&>(event); |
| wheel_event->SetPositionInWidget( |
| gfx::ScalePoint(wheel_event->PositionInWidget() + delta, scale)); |
| if (wheel_event->delta_units != ui::ScrollGranularity::kScrollByPage) { |
| wheel_event->delta_x *= scale; |
| wheel_event->delta_y *= scale; |
| wheel_event->wheel_ticks_x *= scale; |
| wheel_event->wheel_ticks_y *= scale; |
| } |
| } else if (blink::WebInputEvent::IsMouseEventType(event.GetType())) { |
| blink::WebMouseEvent* mouse_event = new blink::WebMouseEvent; |
| scaled_event.reset(mouse_event); |
| *mouse_event = static_cast<const blink::WebMouseEvent&>(event); |
| mouse_event->SetPositionInWidget( |
| gfx::ScalePoint(mouse_event->PositionInWidget() + delta, scale)); |
| // Do not scale movement of raw movement events. |
| if (!mouse_event->is_raw_movement_event) { |
| mouse_event->movement_x *= scale; |
| mouse_event->movement_y *= scale; |
| } |
| } else if (blink::WebInputEvent::IsTouchEventType(event.GetType())) { |
| blink::WebTouchEvent* touch_event = new blink::WebTouchEvent; |
| scaled_event.reset(touch_event); |
| *touch_event = static_cast<const blink::WebTouchEvent&>(event); |
| for (unsigned i = 0; i < touch_event->touches_length; i++) { |
| touch_event->touches[i].SetPositionInWidget(gfx::ScalePoint( |
| touch_event->touches[i].PositionInWidget() + delta, scale)); |
| touch_event->touches[i].radius_x *= scale; |
| touch_event->touches[i].radius_y *= scale; |
| } |
| } else if (blink::WebInputEvent::IsGestureEventType(event.GetType())) { |
| blink::WebGestureEvent* gesture_event = new blink::WebGestureEvent; |
| scaled_event.reset(gesture_event); |
| *gesture_event = static_cast<const blink::WebGestureEvent&>(event); |
| gesture_event->SetPositionInWidget( |
| gfx::ScalePoint(gesture_event->PositionInWidget() + delta, scale)); |
| switch (gesture_event->GetType()) { |
| case blink::WebInputEvent::Type::kGestureScrollUpdate: |
| if (gesture_event->data.scroll_update.delta_units == |
| ui::ScrollGranularity::kScrollByPixel || |
| gesture_event->data.scroll_update.delta_units == |
| ui::ScrollGranularity::kScrollByPrecisePixel) { |
| gesture_event->data.scroll_update.delta_x *= scale; |
| gesture_event->data.scroll_update.delta_y *= scale; |
| } |
| break; |
| case blink::WebInputEvent::Type::kGestureScrollBegin: |
| if (gesture_event->data.scroll_begin.delta_hint_units == |
| ui::ScrollGranularity::kScrollByPixel || |
| gesture_event->data.scroll_begin.delta_hint_units == |
| ui::ScrollGranularity::kScrollByPrecisePixel) { |
| gesture_event->data.scroll_begin.delta_x_hint *= scale; |
| gesture_event->data.scroll_begin.delta_y_hint *= scale; |
| } |
| break; |
| |
| case blink::WebInputEvent::Type::kGesturePinchUpdate: |
| // Scale in pinch gesture is DSF agnostic. |
| break; |
| |
| case blink::WebInputEvent::Type::kGestureDoubleTap: |
| case blink::WebInputEvent::Type::kGestureTap: |
| case blink::WebInputEvent::Type::kGestureTapUnconfirmed: |
| gesture_event->data.tap.width *= scale; |
| gesture_event->data.tap.height *= scale; |
| break; |
| |
| case blink::WebInputEvent::Type::kGestureTapDown: |
| gesture_event->data.tap_down.width *= scale; |
| gesture_event->data.tap_down.height *= scale; |
| break; |
| |
| case blink::WebInputEvent::Type::kGestureShowPress: |
| gesture_event->data.show_press.width *= scale; |
| gesture_event->data.show_press.height *= scale; |
| break; |
| |
| case blink::WebInputEvent::Type::kGestureLongPress: |
| case blink::WebInputEvent::Type::kGestureLongTap: |
| gesture_event->data.long_press.width *= scale; |
| gesture_event->data.long_press.height *= scale; |
| break; |
| |
| case blink::WebInputEvent::Type::kGestureTwoFingerTap: |
| gesture_event->data.two_finger_tap.first_finger_width *= scale; |
| gesture_event->data.two_finger_tap.first_finger_height *= scale; |
| break; |
| |
| case blink::WebInputEvent::Type::kGestureFlingStart: |
| gesture_event->data.fling_start.velocity_x *= scale; |
| gesture_event->data.fling_start.velocity_y *= scale; |
| break; |
| |
| // These event does not have location data. |
| case blink::WebInputEvent::Type::kGesturePinchBegin: |
| case blink::WebInputEvent::Type::kGesturePinchEnd: |
| case blink::WebInputEvent::Type::kGestureTapCancel: |
| case blink::WebInputEvent::Type::kGestureFlingCancel: |
| case blink::WebInputEvent::Type::kGestureScrollEnd: |
| break; |
| |
| // TODO(oshima): Find out if ContextMenu needs to be scaled. |
| default: |
| break; |
| } |
| |
| if (gesture_event->GetType() == |
| blink::WebInputEvent::Type::kGestureScrollUpdate && |
| trace_id.has_value()) { |
| TRACE_EVENT("input", "TranslateAndScaleWebInputEvent", |
| [trace_id_value = *trace_id, |
| delta_x = gesture_event->data.scroll_update.delta_x, |
| delta_y = gesture_event->data.scroll_update.delta_y]( |
| perfetto::EventContext& ctx) { |
| auto* event = |
| ctx.event<perfetto::protos::pbzero::ChromeTrackEvent>(); |
| auto* scroll_data = event->set_scroll_deltas(); |
| scroll_data->set_trace_id(trace_id_value); |
| scroll_data->set_original_delta_x(delta_x); |
| scroll_data->set_original_delta_y(delta_y); |
| }); |
| } |
| } |
| return scaled_event; |
| } |
| |
| WebInputEvent::Type ToWebMouseEventType(MotionEvent::Action action) { |
| switch (action) { |
| case MotionEvent::Action::DOWN: |
| case MotionEvent::Action::BUTTON_PRESS: |
| return WebInputEvent::Type::kMouseDown; |
| case MotionEvent::Action::MOVE: |
| case MotionEvent::Action::HOVER_MOVE: |
| return WebInputEvent::Type::kMouseMove; |
| case MotionEvent::Action::HOVER_ENTER: |
| return WebInputEvent::Type::kMouseEnter; |
| case MotionEvent::Action::HOVER_EXIT: |
| return WebInputEvent::Type::kMouseLeave; |
| case MotionEvent::Action::UP: |
| case MotionEvent::Action::BUTTON_RELEASE: |
| return WebInputEvent::Type::kMouseUp; |
| case MotionEvent::Action::NONE: |
| case MotionEvent::Action::CANCEL: |
| case MotionEvent::Action::POINTER_DOWN: |
| case MotionEvent::Action::POINTER_UP: |
| break; |
| } |
| NOTREACHED() << "Invalid MotionEvent::Action = " << action; |
| return WebInputEvent::Type::kUndefined; |
| } |
| |
| void SetWebPointerPropertiesFromMotionEventData( |
| WebPointerProperties& webPointerProperties, |
| int pointer_id, |
| float pressure, |
| float orientation_rad, |
| float tilt_x, |
| float tilt_y, |
| float twist, |
| float tangential_pressure, |
| int android_buttons_changed, |
| MotionEvent::ToolType tool_type) { |
| webPointerProperties.id = pointer_id; |
| webPointerProperties.force = pressure; |
| |
| if (tool_type == MotionEvent::ToolType::STYLUS) { |
| // A stylus points to a direction specified by orientation and tilts to |
| // the opposite direction. Coordinate system is left-handed. |
| webPointerProperties.tilt_x = tilt_x; |
| webPointerProperties.tilt_y = tilt_y; |
| webPointerProperties.twist = twist; |
| webPointerProperties.tangential_pressure = tangential_pressure; |
| } else { |
| webPointerProperties.tilt_x = webPointerProperties.tilt_y = 0; |
| webPointerProperties.twist = 0; |
| webPointerProperties.tangential_pressure = 0; |
| } |
| |
| webPointerProperties.button = ToWebPointerButton(android_buttons_changed); |
| webPointerProperties.pointer_type = ToWebPointerType(tool_type); |
| } |
| |
| int WebEventModifiersToEventFlags(int modifiers) { |
| int flags = 0; |
| |
| if (modifiers & blink::WebInputEvent::kShiftKey) |
| flags |= EF_SHIFT_DOWN; |
| if (modifiers & blink::WebInputEvent::kControlKey) |
| flags |= EF_CONTROL_DOWN; |
| if (modifiers & blink::WebInputEvent::kAltKey) |
| flags |= EF_ALT_DOWN; |
| if (modifiers & blink::WebInputEvent::kAltGrKey) |
| flags |= EF_ALTGR_DOWN; |
| if (modifiers & blink::WebInputEvent::kMetaKey) |
| flags |= EF_COMMAND_DOWN; |
| if (modifiers & blink::WebInputEvent::kCapsLockOn) |
| flags |= EF_CAPS_LOCK_ON; |
| if (modifiers & blink::WebInputEvent::kNumLockOn) |
| flags |= EF_NUM_LOCK_ON; |
| if (modifiers & blink::WebInputEvent::kScrollLockOn) |
| flags |= EF_SCROLL_LOCK_ON; |
| if (modifiers & blink::WebInputEvent::kLeftButtonDown) |
| flags |= EF_LEFT_MOUSE_BUTTON; |
| if (modifiers & blink::WebInputEvent::kMiddleButtonDown) |
| flags |= EF_MIDDLE_MOUSE_BUTTON; |
| if (modifiers & blink::WebInputEvent::kRightButtonDown) |
| flags |= EF_RIGHT_MOUSE_BUTTON; |
| if (modifiers & blink::WebInputEvent::kBackButtonDown) |
| flags |= EF_BACK_MOUSE_BUTTON; |
| if (modifiers & blink::WebInputEvent::kForwardButtonDown) |
| flags |= EF_FORWARD_MOUSE_BUTTON; |
| if (modifiers & blink::WebInputEvent::kIsAutoRepeat) |
| flags |= EF_IS_REPEAT; |
| |
| return flags; |
| } |
| |
| blink::WebInputEvent::Modifiers DomCodeToWebInputEventModifiers(DomCode code) { |
| switch (KeycodeConverter::DomCodeToLocation(code)) { |
| case DomKeyLocation::LEFT: |
| return blink::WebInputEvent::kIsLeft; |
| case DomKeyLocation::RIGHT: |
| return blink::WebInputEvent::kIsRight; |
| case DomKeyLocation::NUMPAD: |
| return blink::WebInputEvent::kIsKeyPad; |
| case DomKeyLocation::STANDARD: |
| break; |
| } |
| return static_cast<blink::WebInputEvent::Modifiers>(0); |
| } |
| |
| bool IsContinuousGestureEvent(WebInputEvent::Type type) { |
| switch (type) { |
| case blink::WebGestureEvent::Type::kGestureScrollUpdate: |
| case blink::WebGestureEvent::Type::kGesturePinchUpdate: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| EventPointerType WebPointerTypeToEventPointerType( |
| WebPointerProperties::PointerType type) { |
| switch (type) { |
| case WebPointerProperties::PointerType::kMouse: |
| return EventPointerType::kMouse; |
| case WebPointerProperties::PointerType::kPen: |
| return EventPointerType::kPen; |
| case WebPointerProperties::PointerType::kEraser: |
| return EventPointerType::kEraser; |
| case WebPointerProperties::PointerType::kTouch: |
| return EventPointerType::kTouch; |
| case WebPointerProperties::PointerType::kUnknown: |
| return EventPointerType::kUnknown; |
| } |
| NOTREACHED() << "Invalid pointer type"; |
| return EventPointerType::kUnknown; |
| } |
| |
| blink::WebGestureEvent ScrollBeginFromScrollUpdate( |
| const blink::WebGestureEvent& gesture_update) { |
| DCHECK(gesture_update.GetType() == WebInputEvent::Type::kGestureScrollUpdate); |
| |
| WebGestureEvent scroll_begin(gesture_update); |
| scroll_begin.SetType(WebInputEvent::Type::kGestureScrollBegin); |
| |
| scroll_begin.data.scroll_begin.delta_x_hint = |
| gesture_update.data.scroll_update.delta_x; |
| scroll_begin.data.scroll_begin.delta_y_hint = |
| gesture_update.data.scroll_update.delta_y; |
| scroll_begin.data.scroll_begin.delta_hint_units = |
| gesture_update.data.scroll_update.delta_units; |
| scroll_begin.data.scroll_begin.target_viewport = false; |
| scroll_begin.data.scroll_begin.inertial_phase = |
| gesture_update.data.scroll_update.inertial_phase; |
| scroll_begin.data.scroll_begin.synthetic = false; |
| scroll_begin.data.scroll_begin.pointer_count = 0; |
| scroll_begin.data.scroll_begin.scrollable_area_element_id = 0; |
| |
| return scroll_begin; |
| } |
| |
| #if BUILDFLAG(IS_ANDROID) |
| std::unique_ptr<WebGestureEvent> CreateWebGestureEventFromGestureEventAndroid( |
| const GestureEventAndroid& event) { |
| WebInputEvent::Type event_type = WebInputEvent::Type::kUndefined; |
| switch (event.type()) { |
| case GESTURE_EVENT_TYPE_PINCH_BEGIN: |
| event_type = WebInputEvent::Type::kGesturePinchBegin; |
| break; |
| case GESTURE_EVENT_TYPE_PINCH_BY: |
| event_type = WebInputEvent::Type::kGesturePinchUpdate; |
| break; |
| case GESTURE_EVENT_TYPE_PINCH_END: |
| event_type = WebInputEvent::Type::kGesturePinchEnd; |
| break; |
| case GESTURE_EVENT_TYPE_SCROLL_START: |
| event_type = WebInputEvent::Type::kGestureScrollBegin; |
| break; |
| case GESTURE_EVENT_TYPE_SCROLL_BY: |
| event_type = WebInputEvent::Type::kGestureScrollUpdate; |
| break; |
| case GESTURE_EVENT_TYPE_SCROLL_END: |
| event_type = WebInputEvent::Type::kGestureScrollEnd; |
| break; |
| case GESTURE_EVENT_TYPE_FLING_START: |
| event_type = WebInputEvent::Type::kGestureFlingStart; |
| break; |
| case GESTURE_EVENT_TYPE_FLING_CANCEL: |
| event_type = WebInputEvent::Type::kGestureFlingCancel; |
| break; |
| case GESTURE_EVENT_TYPE_DOUBLE_TAP: |
| event_type = WebInputEvent::Type::kGestureDoubleTap; |
| break; |
| default: |
| NOTREACHED() << "Unknown gesture event type"; |
| return std::make_unique<WebGestureEvent>(); |
| } |
| auto web_event = std::make_unique<WebGestureEvent>( |
| event_type, WebInputEvent::kNoModifiers, |
| base::TimeTicks() + base::Milliseconds(event.time())); |
| // NOTE: Source gesture events are synthetic ones that simulate |
| // gesture from keyboard (zoom in/out) for now. Should populate Blink |
| // event's fields better when extended to handle more cases. |
| web_event->SetPositionInWidget(event.location()); |
| web_event->SetPositionInScreen(event.screen_location()); |
| web_event->SetSourceDevice(WebGestureDevice::kTouchscreen); |
| if (event.synthetic_scroll()) |
| web_event->SetSourceDevice(WebGestureDevice::kSyntheticAutoscroll); |
| if (event_type == WebInputEvent::Type::kGesturePinchUpdate) { |
| web_event->data.pinch_update.scale = event.scale(); |
| } else if (event_type == WebInputEvent::Type::kGestureScrollBegin) { |
| web_event->data.scroll_begin.delta_x_hint = event.delta_x(); |
| web_event->data.scroll_begin.delta_y_hint = event.delta_y(); |
| web_event->data.scroll_begin.target_viewport = event.target_viewport(); |
| } else if (event_type == WebInputEvent::Type::kGestureScrollUpdate) { |
| web_event->data.scroll_update.delta_x = event.delta_x(); |
| web_event->data.scroll_update.delta_y = event.delta_y(); |
| } else if (event_type == WebInputEvent::Type::kGestureFlingStart) { |
| web_event->data.fling_start.velocity_x = event.velocity_x(); |
| web_event->data.fling_start.velocity_y = event.velocity_y(); |
| web_event->data.fling_start.target_viewport = event.target_viewport(); |
| } else if (event_type == WebInputEvent::Type::kGestureFlingCancel) { |
| web_event->data.fling_cancel.prevent_boosting = event.prevent_boosting(); |
| if (event.synthetic_scroll()) |
| web_event->data.fling_cancel.target_viewport = true; |
| } else if (event_type == WebInputEvent::Type::kGestureDoubleTap) { |
| // Set the tap count to 1 even for DoubleTap, in order to be consistent with |
| // double tap behavior on a mobile viewport. See https://crbug.com/234986 |
| // for context. |
| web_event->data.tap.tap_count = 1; |
| } |
| |
| return web_event; |
| } |
| #endif |
| |
| } // namespace ui |