1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/browser/renderer_host/input/mouse_wheel_event_queue.h"
6 
7 #include "base/bind.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "build/build_config.h"
10 #include "build/chromeos_buildflags.h"
11 #include "content/public/common/content_features.h"
12 #include "ui/events/base_event_utils.h"
13 #include "ui/events/blink/web_input_event_traits.h"
14 
15 using blink::WebGestureEvent;
16 using blink::WebInputEvent;
17 using blink::WebMouseWheelEvent;
18 using ui::LatencyInfo;
19 
20 namespace content {
21 
MouseWheelEventQueue(MouseWheelEventQueueClient * client)22 MouseWheelEventQueue::MouseWheelEventQueue(MouseWheelEventQueueClient* client)
23     : client_(client),
24       send_wheel_events_async_(false),
25       scrolling_device_(blink::WebGestureDevice::kUninitialized) {
26   DCHECK(client);
27 }
28 
~MouseWheelEventQueue()29 MouseWheelEventQueue::~MouseWheelEventQueue() {
30 }
31 
QueueEvent(const MouseWheelEventWithLatencyInfo & event)32 void MouseWheelEventQueue::QueueEvent(
33     const MouseWheelEventWithLatencyInfo& event) {
34   TRACE_EVENT0("input", "MouseWheelEventQueue::QueueEvent");
35 
36   if (event_sent_for_gesture_ack_ && !wheel_queue_.empty()) {
37     QueuedWebMouseWheelEvent* last_event = wheel_queue_.back().get();
38     if (last_event->CanCoalesceWith(event)) {
39       // Terminate the LatencyInfo of the event before it gets coalesced away.
40       event.latency.Terminate();
41 
42       last_event->CoalesceWith(event);
43       // The deltas for the coalesced event change; the corresponding action
44       // might be different now.
45       last_event->event.event_action =
46           WebMouseWheelEvent::GetPlatformSpecificDefaultEventAction(
47               last_event->event);
48       TRACE_EVENT_INSTANT2("input", "MouseWheelEventQueue::CoalescedWheelEvent",
49                            TRACE_EVENT_SCOPE_THREAD, "total_dx",
50                            last_event->event.delta_x, "total_dy",
51                            last_event->event.delta_y);
52       return;
53     }
54   }
55 
56   MouseWheelEventWithLatencyInfo event_with_action(event.event, event.latency);
57   event_with_action.event.event_action =
58       WebMouseWheelEvent::GetPlatformSpecificDefaultEventAction(event.event);
59   // Update the expected event action before queuing the event. From this point
60   // on, the action should not change.
61   wheel_queue_.push_back(
62       std::make_unique<QueuedWebMouseWheelEvent>(event_with_action));
63   TryForwardNextEventToRenderer();
64   LOCAL_HISTOGRAM_COUNTS_100("Renderer.WheelQueueSize", wheel_queue_.size());
65 }
66 
CanGenerateGestureScroll(blink::mojom::InputEventResultState ack_result) const67 bool MouseWheelEventQueue::CanGenerateGestureScroll(
68     blink::mojom::InputEventResultState ack_result) const {
69   if (ack_result == blink::mojom::InputEventResultState::kConsumed) {
70     TRACE_EVENT_INSTANT0("input", "Wheel Event Consumed",
71                          TRACE_EVENT_SCOPE_THREAD);
72     return false;
73   }
74 
75   if (event_sent_for_gesture_ack_->event.event_action ==
76       blink::WebMouseWheelEvent::EventAction::kPageZoom) {
77     TRACE_EVENT_INSTANT0("input", "Wheel Event Cannot Cause Scroll",
78                          TRACE_EVENT_SCOPE_THREAD);
79     return false;
80   }
81 
82   if (scrolling_device_ != blink::WebGestureDevice::kUninitialized &&
83       scrolling_device_ != blink::WebGestureDevice::kTouchpad) {
84     TRACE_EVENT_INSTANT0("input",
85                          "Autoscroll or Touchscreen Scroll In Progress",
86                          TRACE_EVENT_SCOPE_THREAD);
87     return false;
88   }
89 
90   // When the cursor has entered the autoscroll mode but no mouse move has
91   // arrived yet, We should still ignore wheel scrolling even though no GSB with
92   // autoscroll source has been sent yet.
93   if (client_->IsAutoscrollInProgress()) {
94     TRACE_EVENT_INSTANT0("input", "In Autoscrolling mode",
95                          TRACE_EVENT_SCOPE_THREAD);
96     return false;
97   }
98 
99   return true;
100 }
101 
ProcessMouseWheelAck(const MouseWheelEventWithLatencyInfo & ack_event,blink::mojom::InputEventResultSource ack_source,blink::mojom::InputEventResultState ack_result)102 void MouseWheelEventQueue::ProcessMouseWheelAck(
103     const MouseWheelEventWithLatencyInfo& ack_event,
104     blink::mojom::InputEventResultSource ack_source,
105     blink::mojom::InputEventResultState ack_result) {
106   TRACE_EVENT0("input", "MouseWheelEventQueue::ProcessMouseWheelAck");
107   if (!event_sent_for_gesture_ack_)
108     return;
109 
110   event_sent_for_gesture_ack_->latency.AddNewLatencyFrom(ack_event.latency);
111   client_->OnMouseWheelEventAck(*event_sent_for_gesture_ack_, ack_source,
112                                 ack_result);
113 
114   // If event wasn't consumed then generate a gesture scroll for it.
115   if (CanGenerateGestureScroll(ack_result)) {
116     WebGestureEvent scroll_update(
117         WebInputEvent::Type::kGestureScrollUpdate, WebInputEvent::kNoModifiers,
118         event_sent_for_gesture_ack_->event.TimeStamp(),
119         blink::WebGestureDevice::kTouchpad);
120 
121     scroll_update.SetPositionInWidget(
122         event_sent_for_gesture_ack_->event.PositionInWidget());
123     scroll_update.SetPositionInScreen(
124         event_sent_for_gesture_ack_->event.PositionInScreen());
125 
126 #if !defined(OS_MAC)
127     // Swap X & Y if Shift is down and when there is no horizontal movement.
128     if (event_sent_for_gesture_ack_->event.event_action ==
129             blink::WebMouseWheelEvent::EventAction::kScrollHorizontal &&
130         event_sent_for_gesture_ack_->event.delta_x == 0) {
131       scroll_update.data.scroll_update.delta_x =
132           event_sent_for_gesture_ack_->event.delta_y;
133       scroll_update.data.scroll_update.delta_y =
134           event_sent_for_gesture_ack_->event.delta_x;
135     } else
136 #endif  // OS_MAC
137     {
138       scroll_update.data.scroll_update.delta_x =
139           event_sent_for_gesture_ack_->event.delta_x;
140       scroll_update.data.scroll_update.delta_y =
141           event_sent_for_gesture_ack_->event.delta_y;
142     }
143 
144     if (event_sent_for_gesture_ack_->event.momentum_phase !=
145         blink::WebMouseWheelEvent::kPhaseNone) {
146       scroll_update.data.scroll_update.inertial_phase =
147           WebGestureEvent::InertialPhaseState::kMomentum;
148     } else if (event_sent_for_gesture_ack_->event.phase !=
149                blink::WebMouseWheelEvent::kPhaseNone) {
150       scroll_update.data.scroll_update.inertial_phase =
151           WebGestureEvent::InertialPhaseState::kNonMomentum;
152     }
153 
154     // WebMouseWheelEvent only supports these units for the delta.
155     DCHECK(event_sent_for_gesture_ack_->event.delta_units ==
156                ui::ScrollGranularity::kScrollByPage ||
157            event_sent_for_gesture_ack_->event.delta_units ==
158                ui::ScrollGranularity::kScrollByPrecisePixel ||
159            event_sent_for_gesture_ack_->event.delta_units ==
160                ui::ScrollGranularity::kScrollByPixel ||
161            event_sent_for_gesture_ack_->event.delta_units ==
162                ui::ScrollGranularity::kScrollByPercentage);
163     scroll_update.data.scroll_update.delta_units =
164         event_sent_for_gesture_ack_->event.delta_units;
165 
166     if (event_sent_for_gesture_ack_->event.delta_units ==
167         ui::ScrollGranularity::kScrollByPage) {
168       // Turn page scrolls into a *single* page scroll because
169       // the magnitude the number of ticks is lost when coalescing.
170       if (scroll_update.data.scroll_update.delta_x)
171         scroll_update.data.scroll_update.delta_x =
172             scroll_update.data.scroll_update.delta_x > 0 ? 1 : -1;
173       if (scroll_update.data.scroll_update.delta_y)
174         scroll_update.data.scroll_update.delta_y =
175             scroll_update.data.scroll_update.delta_y > 0 ? 1 : -1;
176     } else {
177       if (event_sent_for_gesture_ack_->event.rails_mode ==
178           WebInputEvent::kRailsModeVertical)
179         scroll_update.data.scroll_update.delta_x = 0;
180       if (event_sent_for_gesture_ack_->event.rails_mode ==
181           WebInputEvent::kRailsModeHorizontal)
182         scroll_update.data.scroll_update.delta_y = 0;
183     }
184 
185     bool current_phase_ended = false;
186     bool scroll_phase_ended = false;
187     bool momentum_phase_ended = false;
188     bool has_phase_info = false;
189 
190     if (event_sent_for_gesture_ack_->event.phase !=
191             blink::WebMouseWheelEvent::kPhaseNone ||
192         event_sent_for_gesture_ack_->event.momentum_phase !=
193             blink::WebMouseWheelEvent::kPhaseNone) {
194       has_phase_info = true;
195       scroll_phase_ended = event_sent_for_gesture_ack_->event.phase ==
196                                blink::WebMouseWheelEvent::kPhaseEnded ||
197                            event_sent_for_gesture_ack_->event.phase ==
198                                blink::WebMouseWheelEvent::kPhaseCancelled;
199       momentum_phase_ended =
200           event_sent_for_gesture_ack_->event.momentum_phase ==
201               blink::WebMouseWheelEvent::kPhaseEnded ||
202           event_sent_for_gesture_ack_->event.momentum_phase ==
203               blink::WebMouseWheelEvent::kPhaseCancelled;
204       current_phase_ended = scroll_phase_ended || momentum_phase_ended;
205     }
206 
207     bool needs_update = scroll_update.data.scroll_update.delta_x != 0 ||
208                         scroll_update.data.scroll_update.delta_y != 0;
209 
210     // For every GSU event record whether it is latched or not.
211     if (needs_update)
212       RecordLatchingUmaMetric(client_->IsWheelScrollInProgress());
213 
214     bool synthetic = event_sent_for_gesture_ack_->event.has_synthetic_phase;
215 
216     // Generally, there should always be a non-zero delta with kPhaseBegan
217     // events. However, sometimes this is not the case and the delta in both
218     // directions is 0. When this occurs, don't call SendScrollBegin because
219     // scroll direction is necessary in order to determine the correct scroller
220     // to target and latch to.
221     if (needs_update && event_sent_for_gesture_ack_->event.phase ==
222                             blink::WebMouseWheelEvent::kPhaseBegan) {
223       send_wheel_events_async_ = true;
224 
225       if (!client_->IsWheelScrollInProgress())
226         SendScrollBegin(scroll_update, synthetic);
227     }
228 
229     if (needs_update) {
230       // It is possible that the wheel event with phaseBegan is consumed and
231       // no GSB is sent.
232       if (!client_->IsWheelScrollInProgress())
233         SendScrollBegin(scroll_update, synthetic);
234       client_->ForwardGestureEventWithLatencyInfo(
235           scroll_update, ui::LatencyInfo(ui::SourceEventType::WHEEL));
236     }
237 
238     if (current_phase_ended && client_->IsWheelScrollInProgress()) {
239       // Send GSE when GSB is sent and no fling is going to happen next.
240       SendScrollEnd(scroll_update, synthetic);
241     }
242   }
243 
244   event_sent_for_gesture_ack_.reset();
245   TryForwardNextEventToRenderer();
246 }
247 
OnGestureScrollEvent(const GestureEventWithLatencyInfo & gesture_event)248 void MouseWheelEventQueue::OnGestureScrollEvent(
249     const GestureEventWithLatencyInfo& gesture_event) {
250   if (gesture_event.event.GetType() ==
251       blink::WebInputEvent::Type::kGestureScrollBegin) {
252     scrolling_device_ = gesture_event.event.SourceDevice();
253   } else if (scrolling_device_ == gesture_event.event.SourceDevice() &&
254              gesture_event.event.GetType() ==
255                  blink::WebInputEvent::Type::kGestureScrollEnd) {
256     scrolling_device_ = blink::WebGestureDevice::kUninitialized;
257   } else if (gesture_event.event.GetType() ==
258              blink::WebInputEvent::Type::kGestureFlingStart) {
259     // With browser side fling we shouldn't reset scrolling_device_ on GFS since
260     // the fling_controller processes the GFS to generate and send GSU events.
261   }
262 }
263 
TryForwardNextEventToRenderer()264 void MouseWheelEventQueue::TryForwardNextEventToRenderer() {
265   TRACE_EVENT0("input", "MouseWheelEventQueue::TryForwardNextEventToRenderer");
266 
267   if (wheel_queue_.empty() || event_sent_for_gesture_ack_)
268     return;
269 
270   event_sent_for_gesture_ack_ = std::move(wheel_queue_.front());
271   wheel_queue_.pop_front();
272 
273   DCHECK(event_sent_for_gesture_ack_->event.phase !=
274              blink::WebMouseWheelEvent::kPhaseNone ||
275          event_sent_for_gesture_ack_->event.momentum_phase !=
276              blink::WebMouseWheelEvent::kPhaseNone);
277   if (event_sent_for_gesture_ack_->event.phase ==
278       blink::WebMouseWheelEvent::kPhaseBegan) {
279     send_wheel_events_async_ = false;
280   } else if (send_wheel_events_async_) {
281     event_sent_for_gesture_ack_->event.dispatch_type =
282         WebInputEvent::DispatchType::kEventNonBlocking;
283   }
284 
285   client_->SendMouseWheelEventImmediately(
286       *event_sent_for_gesture_ack_,
287       base::BindOnce(&MouseWheelEventQueue::ProcessMouseWheelAck,
288                      base::Unretained(this)));
289 }
290 
SendScrollEnd(WebGestureEvent update_event,bool synthetic)291 void MouseWheelEventQueue::SendScrollEnd(WebGestureEvent update_event,
292                                          bool synthetic) {
293   DCHECK(client_->IsWheelScrollInProgress());
294 
295   WebGestureEvent scroll_end(update_event);
296   scroll_end.SetTimeStamp(ui::EventTimeForNow());
297   scroll_end.SetType(WebInputEvent::Type::kGestureScrollEnd);
298   scroll_end.data.scroll_end.synthetic = synthetic;
299   scroll_end.data.scroll_end.inertial_phase =
300       update_event.data.scroll_update.inertial_phase;
301   scroll_end.data.scroll_end.delta_units =
302       update_event.data.scroll_update.delta_units;
303 #if BUILDFLAG(IS_CHROMEOS_ASH)
304   // On ChromeOS wheel events with synthetic momentum_phase ==
305   // blink::WebMouseWheelEvent::kPhaseEnded are generated by the fling
306   // controller to terminate touchpad flings.
307   if (scroll_end.data.scroll_end.inertial_phase ==
308           WebGestureEvent::InertialPhaseState::kMomentum &&
309       synthetic) {
310     scroll_end.data.scroll_end.generated_by_fling_controller = true;
311   }
312 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
313   client_->ForwardGestureEventWithLatencyInfo(
314       scroll_end, ui::LatencyInfo(ui::SourceEventType::WHEEL));
315 }
316 
SendScrollBegin(const WebGestureEvent & gesture_update,bool synthetic)317 void MouseWheelEventQueue::SendScrollBegin(
318     const WebGestureEvent& gesture_update,
319     bool synthetic) {
320   DCHECK(!client_->IsWheelScrollInProgress());
321 
322   WebGestureEvent scroll_begin =
323       ui::ScrollBeginFromScrollUpdate(gesture_update);
324   scroll_begin.data.scroll_begin.synthetic = synthetic;
325 
326   client_->ForwardGestureEventWithLatencyInfo(
327       scroll_begin, ui::LatencyInfo(ui::SourceEventType::WHEEL));
328 }
329 
RecordLatchingUmaMetric(bool latched)330 void MouseWheelEventQueue::RecordLatchingUmaMetric(bool latched) {
331   UMA_HISTOGRAM_BOOLEAN("WheelScrolling.WasLatched", latched);
332 }
333 
334 }  // namespace content
335