| // Copyright 2016 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 "third_party/blink/public/common/input/web_gesture_event.h" |
| |
| #include <limits> |
| |
| #include "ui/gfx/transform.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| bool IsContinuousGestureEvent(WebInputEvent::Type type) { |
| switch (type) { |
| case WebGestureEvent::Type::kGestureScrollUpdate: |
| case WebGestureEvent::Type::kGesturePinchUpdate: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| // Returns the transform matrix corresponding to the gesture event. |
| gfx::Transform GetTransformForEvent(const WebGestureEvent& gesture_event) { |
| gfx::Transform gesture_transform; |
| if (gesture_event.GetType() == WebInputEvent::Type::kGestureScrollUpdate) { |
| gesture_transform.Translate(gesture_event.data.scroll_update.delta_x, |
| gesture_event.data.scroll_update.delta_y); |
| } else if (gesture_event.GetType() == |
| WebInputEvent::Type::kGesturePinchUpdate) { |
| float scale = gesture_event.data.pinch_update.scale; |
| gesture_transform.Translate(-gesture_event.PositionInWidget().x(), |
| -gesture_event.PositionInWidget().y()); |
| gesture_transform.Scale(scale, scale); |
| gesture_transform.Translate(gesture_event.PositionInWidget().x(), |
| gesture_event.PositionInWidget().y()); |
| } else { |
| NOTREACHED() << "Invalid event type for transform retrieval: " |
| << WebInputEvent::GetName(gesture_event.GetType()); |
| } |
| return gesture_transform; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<WebInputEvent> WebGestureEvent::Clone() const { |
| return std::make_unique<WebGestureEvent>(*this); |
| } |
| |
| bool WebGestureEvent::CanCoalesce(const WebInputEvent& event) const { |
| if (!IsGestureEventType(event.GetType())) |
| return false; |
| const WebGestureEvent& gesture_event = |
| static_cast<const WebGestureEvent&>(event); |
| if (GetType() != gesture_event.GetType() || |
| SourceDevice() != gesture_event.SourceDevice() || |
| GetModifiers() != gesture_event.GetModifiers()) |
| return false; |
| |
| if (GetType() == WebInputEvent::Type::kGestureScrollUpdate) |
| return true; |
| |
| // GesturePinchUpdate scales can be combined only if they share a focal point, |
| // e.g., with double-tap drag zoom. |
| // Due to the imprecision of OOPIF coordinate conversions, the positions may |
| // not be exactly equal, so we only require approximate equality. |
| constexpr float kAnchorTolerance = 1.f; |
| if (GetType() == WebInputEvent::Type::kGesturePinchUpdate && |
| (std::abs(PositionInWidget().x() - gesture_event.PositionInWidget().x()) < |
| kAnchorTolerance) && |
| (std::abs(PositionInWidget().y() - gesture_event.PositionInWidget().y()) < |
| kAnchorTolerance)) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void WebGestureEvent::Coalesce(const WebInputEvent& event) { |
| DCHECK(CanCoalesce(event)); |
| const WebGestureEvent& gesture_event = |
| static_cast<const WebGestureEvent&>(event); |
| if (GetType() == WebInputEvent::Type::kGestureScrollUpdate) { |
| data.scroll_update.delta_x += gesture_event.data.scroll_update.delta_x; |
| data.scroll_update.delta_y += gesture_event.data.scroll_update.delta_y; |
| } else if (GetType() == WebInputEvent::Type::kGesturePinchUpdate) { |
| data.pinch_update.scale *= gesture_event.data.pinch_update.scale; |
| // Ensure the scale remains bounded above 0 and below Infinity so that |
| // we can reliably perform operations like log on the values. |
| if (data.pinch_update.scale < std::numeric_limits<float>::min()) |
| data.pinch_update.scale = std::numeric_limits<float>::min(); |
| else if (data.pinch_update.scale > std::numeric_limits<float>::max()) |
| data.pinch_update.scale = std::numeric_limits<float>::max(); |
| } |
| } |
| |
| base::Optional<ui::ScrollInputType> WebGestureEvent::GetScrollInputType() |
| const { |
| if (!IsGestureScroll()) |
| return base::nullopt; |
| switch (SourceDevice()) { |
| case WebGestureDevice::kTouchpad: |
| return ui::ScrollInputType::kWheel; |
| case WebGestureDevice::kTouchscreen: |
| return ui::ScrollInputType::kTouchscreen; |
| case WebGestureDevice::kSyntheticAutoscroll: |
| return ui::ScrollInputType::kAutoscroll; |
| case WebGestureDevice::kScrollbar: |
| return ui::ScrollInputType::kScrollbar; |
| case WebGestureDevice::kUninitialized: |
| return base::nullopt; |
| } |
| } |
| |
| float WebGestureEvent::DeltaXInRootFrame() const { |
| float delta_x = (type_ == WebInputEvent::Type::kGestureScrollBegin) |
| ? data.scroll_begin.delta_x_hint |
| : data.scroll_update.delta_x; |
| |
| bool is_percent = (type_ == WebInputEvent::Type::kGestureScrollBegin) |
| ? data.scroll_begin.delta_hint_units == |
| ui::ScrollGranularity::kScrollByPercentage |
| : data.scroll_update.delta_units == |
| ui::ScrollGranularity::kScrollByPercentage; |
| |
| return is_percent ? delta_x : delta_x / frame_scale_; |
| } |
| |
| float WebGestureEvent::DeltaYInRootFrame() const { |
| float delta_y = (type_ == WebInputEvent::Type::kGestureScrollBegin) |
| ? data.scroll_begin.delta_y_hint |
| : data.scroll_update.delta_y; |
| |
| bool is_percent = (type_ == WebInputEvent::Type::kGestureScrollBegin) |
| ? data.scroll_begin.delta_hint_units == |
| ui::ScrollGranularity::kScrollByPercentage |
| : data.scroll_update.delta_units == |
| ui::ScrollGranularity::kScrollByPercentage; |
| |
| return is_percent ? delta_y : delta_y / frame_scale_; |
| } |
| |
| ui::ScrollGranularity WebGestureEvent::DeltaUnits() const { |
| if (type_ == WebInputEvent::Type::kGestureScrollBegin) |
| return data.scroll_begin.delta_hint_units; |
| if (type_ == WebInputEvent::Type::kGestureScrollUpdate) |
| return data.scroll_update.delta_units; |
| DCHECK(type_ == WebInputEvent::Type::kGestureScrollEnd); |
| return data.scroll_end.delta_units; |
| } |
| |
| WebGestureEvent::InertialPhaseState WebGestureEvent::InertialPhase() const { |
| if (type_ == WebInputEvent::Type::kGestureScrollBegin) |
| return data.scroll_begin.inertial_phase; |
| if (type_ == WebInputEvent::Type::kGestureScrollUpdate) |
| return data.scroll_update.inertial_phase; |
| DCHECK(type_ == WebInputEvent::Type::kGestureScrollEnd); |
| return data.scroll_end.inertial_phase; |
| } |
| |
| bool WebGestureEvent::Synthetic() const { |
| if (type_ == WebInputEvent::Type::kGestureScrollBegin) |
| return data.scroll_begin.synthetic; |
| DCHECK(type_ == WebInputEvent::Type::kGestureScrollEnd); |
| return data.scroll_end.synthetic; |
| } |
| |
| float WebGestureEvent::VelocityX() const { |
| if (type_ == WebInputEvent::Type::kGestureScrollUpdate) |
| return data.scroll_update.velocity_x; |
| DCHECK(type_ == WebInputEvent::Type::kGestureFlingStart); |
| return data.fling_start.velocity_x; |
| } |
| |
| float WebGestureEvent::VelocityY() const { |
| if (type_ == WebInputEvent::Type::kGestureScrollUpdate) |
| return data.scroll_update.velocity_y; |
| DCHECK(type_ == WebInputEvent::Type::kGestureFlingStart); |
| return data.fling_start.velocity_y; |
| } |
| |
| gfx::SizeF WebGestureEvent::TapAreaInRootFrame() const { |
| if (type_ == WebInputEvent::Type::kGestureTwoFingerTap) { |
| return gfx::SizeF(data.two_finger_tap.first_finger_width / frame_scale_, |
| data.two_finger_tap.first_finger_height / frame_scale_); |
| } else if (type_ == WebInputEvent::Type::kGestureLongPress || |
| type_ == WebInputEvent::Type::kGestureLongTap) { |
| return gfx::SizeF(data.long_press.width / frame_scale_, |
| data.long_press.height / frame_scale_); |
| } else if (type_ == WebInputEvent::Type::kGestureTap || |
| type_ == WebInputEvent::Type::kGestureTapUnconfirmed || |
| type_ == WebInputEvent::Type::kGestureDoubleTap) { |
| return gfx::SizeF(data.tap.width / frame_scale_, |
| data.tap.height / frame_scale_); |
| } else if (type_ == WebInputEvent::Type::kGestureTapDown) { |
| return gfx::SizeF(data.tap_down.width / frame_scale_, |
| data.tap_down.height / frame_scale_); |
| } else if (type_ == WebInputEvent::Type::kGestureShowPress) { |
| return gfx::SizeF(data.show_press.width / frame_scale_, |
| data.show_press.height / frame_scale_); |
| } |
| // This function is called for all gestures and determined if the tap |
| // area is empty or not; so return an empty rect here. |
| return gfx::SizeF(); |
| } |
| |
| gfx::PointF WebGestureEvent::PositionInRootFrame() const { |
| return gfx::ScalePoint(position_in_widget_, 1 / frame_scale_) + |
| frame_translate_; |
| } |
| |
| int WebGestureEvent::TapCount() const { |
| DCHECK(type_ == WebInputEvent::Type::kGestureTap); |
| return data.tap.tap_count; |
| } |
| |
| void WebGestureEvent::ApplyTouchAdjustment( |
| const gfx::PointF& root_frame_coords) { |
| // Update the window-relative position of the event so that the node that |
| // was ultimately hit is under this point (i.e. elementFromPoint for the |
| // client co-ordinates in a 'click' event should yield the target). The |
| // global position is intentionally left unmodified because it's intended to |
| // reflect raw co-ordinates unrelated to any content. |
| frame_translate_ = root_frame_coords - |
| gfx::ScalePoint(position_in_widget_, 1 / frame_scale_); |
| } |
| |
| void WebGestureEvent::FlattenTransform() { |
| if (frame_scale_ != 1) { |
| switch (type_) { |
| case WebInputEvent::Type::kGestureScrollBegin: |
| if (data.scroll_begin.delta_hint_units != |
| ui::ScrollGranularity::kScrollByPercentage) { |
| data.scroll_begin.delta_x_hint /= frame_scale_; |
| data.scroll_begin.delta_y_hint /= frame_scale_; |
| } |
| break; |
| case WebInputEvent::Type::kGestureScrollUpdate: |
| if (data.scroll_update.delta_units != |
| ui::ScrollGranularity::kScrollByPercentage) { |
| data.scroll_update.delta_x /= frame_scale_; |
| data.scroll_update.delta_y /= frame_scale_; |
| } |
| break; |
| case WebInputEvent::Type::kGestureTwoFingerTap: |
| data.two_finger_tap.first_finger_width /= frame_scale_; |
| data.two_finger_tap.first_finger_height /= frame_scale_; |
| break; |
| case WebInputEvent::Type::kGestureLongPress: |
| case WebInputEvent::Type::kGestureLongTap: |
| data.long_press.width /= frame_scale_; |
| data.long_press.height /= frame_scale_; |
| break; |
| case WebInputEvent::Type::kGestureTap: |
| case WebInputEvent::Type::kGestureTapUnconfirmed: |
| case WebInputEvent::Type::kGestureDoubleTap: |
| data.tap.width /= frame_scale_; |
| data.tap.height /= frame_scale_; |
| break; |
| case WebInputEvent::Type::kGestureTapDown: |
| data.tap_down.width /= frame_scale_; |
| data.tap_down.height /= frame_scale_; |
| break; |
| case WebInputEvent::Type::kGestureShowPress: |
| data.show_press.width /= frame_scale_; |
| data.show_press.height /= frame_scale_; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| SetPositionInWidget(PositionInRootFrame()); |
| frame_translate_ = gfx::Vector2dF(); |
| frame_scale_ = 1; |
| } |
| |
| // Whether |event_in_queue| is a touchscreen GesturePinchUpdate or |
| // GestureScrollUpdate and has the same modifiers/source as the new |
| // scroll/pinch event. Compatible touchscreen scroll and pinch event pairs |
| // can be logically coalesced. |
| bool WebGestureEvent::IsCompatibleScrollorPinch( |
| const WebGestureEvent& new_event, |
| const WebGestureEvent& event_in_queue) { |
| DCHECK(new_event.GetType() == WebInputEvent::Type::kGestureScrollUpdate || |
| new_event.GetType() == WebInputEvent::Type::kGesturePinchUpdate) |
| << "Invalid event type for pinch/scroll coalescing: " |
| << WebInputEvent::GetName(new_event.GetType()); |
| DLOG_IF(WARNING, new_event.TimeStamp() < event_in_queue.TimeStamp()) |
| << "Event time not monotonic?\n"; |
| return (event_in_queue.GetType() == |
| WebInputEvent::Type::kGestureScrollUpdate || |
| event_in_queue.GetType() == |
| WebInputEvent::Type::kGesturePinchUpdate) && |
| event_in_queue.GetModifiers() == new_event.GetModifiers() && |
| event_in_queue.SourceDevice() == WebGestureDevice::kTouchscreen && |
| new_event.SourceDevice() == WebGestureDevice::kTouchscreen; |
| } |
| |
| std::pair<std::unique_ptr<WebGestureEvent>, std::unique_ptr<WebGestureEvent>> |
| WebGestureEvent::CoalesceScrollAndPinch( |
| const WebGestureEvent* second_last_event, |
| const WebGestureEvent& last_event, |
| const WebGestureEvent& new_event) { |
| DCHECK(!last_event.CanCoalesce(new_event)) |
| << "New event can't be coalesced with the last event in queue directly."; |
| DCHECK(IsContinuousGestureEvent(new_event.GetType())); |
| DCHECK(IsCompatibleScrollorPinch(new_event, last_event)); |
| DCHECK(!second_last_event || |
| IsCompatibleScrollorPinch(new_event, *second_last_event)); |
| |
| auto scroll_event = std::make_unique<WebGestureEvent>( |
| WebInputEvent::Type::kGestureScrollUpdate, new_event.GetModifiers(), |
| new_event.TimeStamp(), new_event.SourceDevice()); |
| scroll_event->primary_pointer_type = new_event.primary_pointer_type; |
| auto pinch_event = std::make_unique<WebGestureEvent>(*scroll_event); |
| pinch_event->SetType(WebInputEvent::Type::kGesturePinchUpdate); |
| pinch_event->SetPositionInWidget( |
| new_event.GetType() == WebInputEvent::Type::kGesturePinchUpdate |
| ? new_event.PositionInWidget() |
| : last_event.PositionInWidget()); |
| |
| gfx::Transform combined_scroll_pinch = GetTransformForEvent(last_event); |
| if (second_last_event) { |
| combined_scroll_pinch.PreconcatTransform( |
| GetTransformForEvent(*second_last_event)); |
| } |
| combined_scroll_pinch.ConcatTransform(GetTransformForEvent(new_event)); |
| |
| float combined_scale = |
| SkScalarToFloat(combined_scroll_pinch.matrix().get(0, 0)); |
| float combined_scroll_pinch_x = |
| SkScalarToFloat(combined_scroll_pinch.matrix().get(0, 3)); |
| float combined_scroll_pinch_y = |
| SkScalarToFloat(combined_scroll_pinch.matrix().get(1, 3)); |
| scroll_event->data.scroll_update.delta_x = |
| (combined_scroll_pinch_x + pinch_event->PositionInWidget().x()) / |
| combined_scale - |
| pinch_event->PositionInWidget().x(); |
| scroll_event->data.scroll_update.delta_y = |
| (combined_scroll_pinch_y + pinch_event->PositionInWidget().y()) / |
| combined_scale - |
| pinch_event->PositionInWidget().y(); |
| pinch_event->data.pinch_update.scale = combined_scale; |
| |
| return std::make_pair(std::move(scroll_event), std::move(pinch_event)); |
| } |
| |
| std::unique_ptr<blink::WebGestureEvent> |
| WebGestureEvent::GenerateInjectedScrollGesture( |
| WebInputEvent::Type type, |
| base::TimeTicks timestamp, |
| WebGestureDevice device, |
| gfx::PointF position_in_widget, |
| gfx::Vector2dF scroll_delta, |
| ui::ScrollGranularity granularity) { |
| std::unique_ptr<WebGestureEvent> generated_gesture_event = |
| std::make_unique<WebGestureEvent>(type, WebInputEvent::kNoModifiers, |
| timestamp, device); |
| DCHECK(generated_gesture_event->IsGestureScroll()); |
| |
| if (type == WebInputEvent::Type::kGestureScrollBegin) { |
| // Gesture events expect the scroll delta to be flipped. Gesture events' |
| // scroll deltas are interpreted as the finger's delta in relation to the |
| // screen (which is the reverse of the scrolling direction). |
| generated_gesture_event->data.scroll_begin.delta_x_hint = -scroll_delta.x(); |
| generated_gesture_event->data.scroll_begin.delta_y_hint = -scroll_delta.y(); |
| generated_gesture_event->data.scroll_begin.inertial_phase = |
| WebGestureEvent::InertialPhaseState::kNonMomentum; |
| generated_gesture_event->data.scroll_begin.delta_hint_units = granularity; |
| } else if (type == WebInputEvent::Type::kGestureScrollUpdate) { |
| generated_gesture_event->data.scroll_update.delta_x = -scroll_delta.x(); |
| generated_gesture_event->data.scroll_update.delta_y = -scroll_delta.y(); |
| generated_gesture_event->data.scroll_update.inertial_phase = |
| WebGestureEvent::InertialPhaseState::kNonMomentum; |
| generated_gesture_event->data.scroll_update.delta_units = granularity; |
| } |
| |
| generated_gesture_event->SetPositionInWidget(position_in_widget); |
| return generated_gesture_event; |
| } |
| |
| } // namespace blink |