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