Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(188)

Side by Side Diff: content/browser/renderer_host/input/buffered_input_router.cc

Issue 20356003: Provided batched input delivery with a BufferedInputRouter (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: BufferedInputRouter unit tests Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2013 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/buffered_input_router.h"
6
7 #include "base/auto_reset.h"
8 #include "content/browser/renderer_host/render_process_host_impl.h"
9 #include "content/browser/renderer_host/render_widget_host_impl.h"
10 #include "content/common/input/input_event_utils.h"
11 #include "content/common/input_messages.h"
12 #include "content/common/view_messages.h"
13 #include "content/public/browser/native_web_keyboard_event.h"
14 #include "content/public/browser/user_metrics.h"
15
16 using base::Time;
17 using base::TimeDelta;
18 using WebKit::WebGestureEvent;
19 using WebKit::WebInputEvent;
20 using WebKit::WebKeyboardEvent;
21 using WebKit::WebMouseEvent;
22 using WebKit::WebMouseWheelEvent;
23 using WebKit::WebTouchEvent;
24
25 namespace content {
26
27 namespace {
28
29 InputEventAckState FromState(InputEventState state) {
30 switch (state) {
31 case INPUT_EVENT_UNHANDLED:
32 return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
33 case INPUT_EVENT_IMPL_THREAD_ABSORBED:
34 return INPUT_EVENT_ACK_STATE_CONSUMED;
35 case INPUT_EVENT_IMPL_THREAD_NO_CONSUMER_EXISTS:
36 return INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
37 case INPUT_EVENT_IMPL_THREAD_BOUNCE_TO_MAIN:
38 return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
39 case INPUT_EVENT_IMPL_THREAD_COULD_NOT_DELIVER:
40 return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
41 case INPUT_EVENT_MAIN_THREAD_ABSORBED:
42 return INPUT_EVENT_ACK_STATE_CONSUMED;
43 case INPUT_EVENT_MAIN_THREAD_PREVENT_DEFAULTED:
44 return INPUT_EVENT_ACK_STATE_CONSUMED;
45 case INPUT_EVENT_MAIN_THREAD_NOT_PREVENT_DEFAULTED:
46 return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
47 case INPUT_EVENT_MAIN_THREAD_NO_HANDLER_EXISTS:
48 return INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS;
49 case INPUT_EVENT_MAIN_THREAD_COULD_NOT_DELIVER:
50 return INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
51 }
52
53 NOTREACHED();
54 return INPUT_EVENT_ACK_STATE_UNKNOWN;
55 }
56
57 } // namespace
58
59 BufferedInputRouter::BufferedInputRouter(RenderProcessHost* process,
60 InputRouterClient* client,
61 int routing_id)
62 : client_(client),
63 process_(process),
64 routing_id_(routing_id),
65 input_queue_(new InputQueue(this)),
aelias_OOO_until_Jul13 2013/08/13 06:11:51 Passing partially constructed "this" pointer here
jdduke (slow) 2013/08/13 15:29:48 Done.
66 queued_gesture_count_(0),
67 has_touch_handler_(false),
68 queued_touch_count_(0),
69 input_queue_override_(NULL),
70 last_input_id_(0),
71 in_flight_packet_id_(0),
72 // TODO(jdduke): Determine a sensible value...
73 delayed_packet_timeout_ms_(10) {}
74
75 BufferedInputRouter::~BufferedInputRouter() {}
76
77 void BufferedInputRouter::Flush() {
78 TRACE_EVENT0("input", "BufferedInputRouter::Flush");
79 DCHECK(in_flight_packet_id_ == 0);
aelias_OOO_until_Jul13 2013/08/13 06:11:51 DCHECK_EQ(0, in_flight_packet_id_);
jdduke (slow) 2013/08/13 15:29:48 Done.
80
81 in_flight_packet_id_ = 0;
aelias_OOO_until_Jul13 2013/08/13 06:11:51 Delete this line, you already DCHECKed it above.
jdduke (slow) 2013/08/13 15:29:48 Done.
82 input_queue_->FlushEventsInCurrentFrame();
83 if (in_flight_packet_id_) {
84 StartDelayedPacketMonitorTimeout(
85 TimeDelta::FromMilliseconds(delayed_packet_timeout_ms_));
86 }
87 }
88
89 bool BufferedInputRouter::SendInput(IPC::Message* message) {
90 scoped_ptr<IPC::Message> scoped_message(message);
aelias_OOO_until_Jul13 2013/08/13 06:11:51 Why take ownership? Why not let the client manage
jdduke (slow) 2013/08/13 15:29:48 This was originally an OVERRIDE of IPC::Sender::Se
91 if (IPC_MESSAGE_ID_CLASS(message->type()) != InputMsgStart) {
aelias_OOO_until_Jul13 2013/08/13 06:11:51 These look like they should be DCHECK_EQ/DCHECK_NE
jdduke (slow) 2013/08/13 15:29:48 Done.
92 NOTREACHED() << "BufferedInputRouter only handles input-related messages.";
93 return false;
94 }
95 if (message->type() == InputMsg_HandleEventPacket::ID) {
96 NOTREACHED() << "WebInputEvents should never be sent via SendInput.";
97 return false;
98 }
99
100 input_queue_->QueueEvent(InputEvent(NewInputID(),
101 INPUT_EVENT_ONE_WAY,
102 *scoped_message), NULL);
103 return true;
104 }
105
106 void BufferedInputRouter::SendMouseEvent(
107 const MouseEventWithLatencyInfo& mouse_event) {
108 if (!client_->OnSendMouseEvent(mouse_event))
109 return;
110 QueueWebEvent(mouse_event.event, mouse_event.latency, false, false);
111 }
112
113 void BufferedInputRouter::SendWheelEvent(
114 const MouseWheelEventWithLatencyInfo& wheel_event) {
115 if (!client_->OnSendWheelEvent(wheel_event))
116 return;
117 QueueWebEvent(wheel_event.event, wheel_event.latency, false, false);
118 }
119
120 void BufferedInputRouter::SendKeyboardEvent(
121 const NativeWebKeyboardEvent& key_event,
122 const ui::LatencyInfo& latency_info) {
123 bool is_shortcut = false;
124 if (!client_->OnSendKeyboardEvent(key_event, latency_info, &is_shortcut))
125 return;
126 int64 event_id = QueueWebEvent(key_event, latency_info, is_shortcut, false);
127 if (event_id) {
128 DCHECK(queued_key_map_.find(event_id) == queued_key_map_.end());
129 queued_key_map_[event_id] = key_event;
130 }
131 }
132
133 void BufferedInputRouter::SendGestureEvent(
134 const GestureEventWithLatencyInfo& gesture_event) {
135 if (!client_->OnSendGestureEvent(gesture_event))
136 return;
137 if (QueueWebEvent(gesture_event.event, gesture_event.latency, false, false))
138 ++queued_gesture_count_;
139 }
140
141 void BufferedInputRouter::SendTouchEvent(
142 const TouchEventWithLatencyInfo& touch_event) {
143 if (!client_->OnSendTouchEvent(touch_event))
144 return;
145 if (QueueWebEvent(touch_event.event, touch_event.latency, false, true))
146 ++queued_touch_count_;
147 }
148
149 void BufferedInputRouter::SendMouseEventImmediately(
150 const MouseEventWithLatencyInfo& mouse_event) {
151 if (!client_->OnSendMouseEventImmediately(mouse_event))
152 return;
153 QueueWebEvent(mouse_event.event, mouse_event.latency, false, false);
154 }
155
156 void BufferedInputRouter::SendTouchEventImmediately(
157 const TouchEventWithLatencyInfo& touch_event) {
158 if (!client_->OnSendTouchEventImmediately(touch_event))
159 return;
160 QueueWebEvent(touch_event.event, touch_event.latency, false, false);
161 }
162
163 void BufferedInputRouter::SendGestureEventImmediately(
164 const GestureEventWithLatencyInfo& gesture_event) {
165 if (!client_->OnSendGestureEventImmediately(gesture_event))
166 return;
167 QueueWebEvent(gesture_event.event, gesture_event.latency, false, false);
168 }
169
170 void BufferedInputRouter::Deliver(const EventPacket& packet) {
171 TRACE_EVENT2("input", "BufferedInputRouter::DeliverPacket",
172 "id", packet.id,
173 "events", packet.events.size());
174 // TODO(jdduke): How should we handle this case? Clear the existing queue?
aelias_OOO_until_Jul13 2013/08/13 06:11:51 I think you should mandate that the process has a
jdduke (slow) 2013/08/13 15:29:48 Done.
175 if (!process_->HasConnection())
176 return;
177
178 DCHECK(packet.id && !in_flight_packet_id_);
aelias_OOO_until_Jul13 2013/08/13 06:11:51 Split this into two separate DCHECKs (the output w
jdduke (slow) 2013/08/13 15:29:48 Done.
179 in_flight_packet_id_ = packet.id;
aelias_OOO_until_Jul13 2013/08/13 06:11:51 Can you put these two lines after the Send() and j
jdduke (slow) 2013/08/13 15:29:48 Yeah, I was thinking of a the case where an ack re
180 client_->IncrementInFlightEventCount();
181 if (!process_->Send(new InputMsg_HandleEventPacket(routing_id_, packet))) {
182 in_flight_packet_id_ = 0;
183 client_->DecrementInFlightEventCount();
184 }
185 }
186
187 void BufferedInputRouter::DidFlush() {
188 TRACE_EVENT0("input", "BufferedInputRouter::DidFlush");
189 StopDelayedPacketMonitorTimeout();
190 client_->DidFlush();
191 }
192
193 void BufferedInputRouter::SetNeedsFlush() {
194 TRACE_EVENT0("input", "BufferedInputRouter::SetNeedsFlush");
195 client_->SetNeedsFlush();
196 }
197
198 void BufferedInputRouter::OnInputEventAck(const InputEvent& acked_event) {
199 if (!IsWebInputEventMessage(acked_event.message))
200 return;
201
202 const WebKit::WebInputEvent* web_event = NULL;
203 ui::LatencyInfo latency_info;
204 if (!CrackWebInputEventMessage(acked_event.message,
205 &web_event,
206 &latency_info,
207 NULL)) {
208 client_->OnUnexpectedEventAck(true);
aelias_OOO_until_Jul13 2013/08/13 06:11:51 I'm not sure what the client is supposed to do wit
jdduke (slow) 2013/08/13 15:29:48 Why should the router know how to handle a bad mes
209 return;
210 }
211
212 InputEventAckState ack_state = FromState(acked_event.state);
213 OnInputEventAck(acked_event.id, *web_event, latency_info, ack_state, true);
214 }
215
216 void BufferedInputRouter::OnInputEventAck(const InputEvent& acked_event,
217 EventInjector* injector) {
218 // Temporarily override the input queue with |injector|.
219 base::AutoReset<EventAckHandler::EventInjector*>
aelias_OOO_until_Jul13 2013/08/13 06:11:51 I don't think this AutoReset trick is a good idea.
jdduke (slow) 2013/08/13 15:29:48 Done.
220 input_queue_override(&input_queue_override_, injector);
221 OnInputEventAck(acked_event);
222 }
223
224 bool BufferedInputRouter::OnMessageReceived(const IPC::Message& message) {
225 bool handled = true;
226 bool message_is_ok = true;
227 IPC_BEGIN_MESSAGE_MAP_EX(BufferedInputRouter, message, message_is_ok)
228 IPC_MESSAGE_HANDLER(InputHostMsg_HandleEventPacket_ACK, OnEventPacketAck)
229 IPC_MESSAGE_HANDLER(ViewHostMsg_HasTouchEventHandlers,
230 OnHasTouchEventHandlers)
231 IPC_MESSAGE_UNHANDLED(handled = false)
232 IPC_END_MESSAGE_MAP()
233
234 if (!message_is_ok)
235 client_->OnUnexpectedEventAck(true);
236
237 return handled;
238 }
239
240 const NativeWebKeyboardEvent*
241 BufferedInputRouter::GetLastKeyboardEvent() const {
242 return queued_key_map_.empty() ? NULL : &queued_key_map_.begin()->second;
243 }
244
245 bool BufferedInputRouter::ShouldForwardTouchEvent() const {
246 return has_touch_handler_ && queued_touch_count_ > 0;
247 }
248
249 bool BufferedInputRouter::ShouldForwardGestureEvent(
250 const GestureEventWithLatencyInfo& touch_event) const {
251 return true;
252 }
253
254 bool BufferedInputRouter::HasQueuedGestureEvents() const {
255 return queued_gesture_count_ > 0;
256 }
257
258 void BufferedInputRouter::OnInputEventAck(
259 int64 event_id,
260 const WebKit::WebInputEvent& web_event,
261 const ui::LatencyInfo& latency_info,
262 InputEventAckState acked_result,
263 bool ack_from_input_queue) {
264 if (WebInputEvent::isKeyboardEventType(web_event.type)) {
265 if (ack_from_input_queue) {
266 KeyMap::iterator key_it = queued_key_map_.find(event_id);
267 DCHECK(key_it != queued_key_map_.end());
aelias_OOO_until_Jul13 2013/08/13 06:11:51 DCHECK_NE(key_it, queued_key_map_.end());
jdduke (slow) 2013/08/13 15:29:48 Done.
268 if (key_it != queued_key_map_.end()) {
aelias_OOO_until_Jul13 2013/08/13 06:11:51 Delete this if().
jdduke (slow) 2013/08/13 15:29:48 Done.
269 NativeWebKeyboardEvent key_event = key_it->second;
270 queued_key_map_.erase(key_it);
271 client_->OnKeyboardEventAck(key_event, acked_result);
272 }
273 } else {
274 DCHECK(event_id == 0);
aelias_OOO_until_Jul13 2013/08/13 06:11:51 DCHECK_EQ(0, event_id);
jdduke (slow) 2013/08/13 15:29:48 Done.
275 client_->OnKeyboardEventAck(
276 static_cast<const NativeWebKeyboardEvent&>(web_event), acked_result);
277 }
278 // WARNING: This BufferedInputRouter can be deallocated at this point
aelias_OOO_until_Jul13 2013/08/13 06:11:51 Should Ctrl-W's consequence be made async so that
jdduke (slow) 2013/08/13 15:29:48 That would be nice; I'm not sure what that would e
279 // (i.e. in the case of Ctrl+W, where the call to
280 // HandleKeyboardEvent destroys this BufferedInputRouter).
281 } else if (web_event.type == WebInputEvent::MouseWheel) {
282 client_->OnWheelEventAck(
283 static_cast<const WebMouseWheelEvent&>(web_event), acked_result);
284 } else if (WebInputEvent::isTouchEventType(web_event.type)) {
285 if (ack_from_input_queue) {
286 DCHECK(queued_touch_count_ > 0);
aelias_OOO_until_Jul13 2013/08/13 06:11:51 DCHECK_GT(queued_touch_count_, 0);
jdduke (slow) 2013/08/13 15:29:48 Done.
287 --queued_touch_count_;
288 }
289 client_->OnTouchEventAck(
290 TouchEventWithLatencyInfo(static_cast<const WebTouchEvent&>(web_event),
291 latency_info), acked_result);
292 } else if (WebInputEvent::isGestureEventType(web_event.type)) {
293 if (ack_from_input_queue) {
294 DCHECK(queued_gesture_count_ > 0);
aelias_OOO_until_Jul13 2013/08/13 06:11:51 DCHECK_GT(queued_touch_count_, 0);
jdduke (slow) 2013/08/13 15:29:48 Done.
295 --queued_gesture_count_;
296 }
297 client_->OnGestureEventAck(
298 static_cast<const WebGestureEvent&>(web_event), acked_result);
299 } else {
300 NOTREACHED() << "Unexpected WebInputEvent in OnInputEventAck";
301 }
302 }
303
304 void BufferedInputRouter::OnEventPacketAck(const EventPacket& packet) {
305 TRACE_EVENT1("input", "BufferedInputRouter::OnEventPacketAck",
306 "id", packet.id);
307 if (!in_flight_packet_id_ || packet.id != in_flight_packet_id_) {
308 client_->OnUnexpectedEventAck(false);
309 return;
310 }
311
312 in_flight_packet_id_ = 0;
313 client_->DecrementInFlightEventCount();
314
315 InputQueue::AckResult ack_result = input_queue_->OnEventPacketAck(packet);
316 if (ack_result == InputQueue::ACK_UNEXPECTED)
317 client_->OnUnexpectedEventAck(false);
318 else if (ack_result == InputQueue::ACK_INVALID)
319 client_->OnUnexpectedEventAck(true);
320 }
321
322 void BufferedInputRouter::OnHasTouchEventHandlers(bool has_handlers) {
323 if (has_touch_handler_ == has_handlers)
324 return;
325 has_touch_handler_ = has_handlers;
326 client_->OnHasTouchEventHandlers(has_handlers);
327 }
328
329 int64 BufferedInputRouter::QueueWebEvent(const WebKit::WebInputEvent& web_event,
330 const ui::LatencyInfo& latency_info,
331 bool is_keyboard_shortcut,
332 bool has_followup) {
333 TRACE_EVENT0("input", "BufferedInputRouter::QueueWebEvent");
334
335 DCHECK(!process_->IgnoreInputEvents());
336 if (FilterWebEvent(web_event, latency_info)) {
337 TRACE_EVENT_INSTANT0("input",
338 "BufferedInputRouter::QueueWebEvent::Filtered",
339 TRACE_EVENT_SCOPE_THREAD);
340 return 0;
341 }
342
343 InputEventType type = has_followup ? INPUT_EVENT_NEEDS_ACK_AND_HAS_FOLLOWUP
344 : INPUT_EVENT_NEEDS_ACK;
345 InputEvent event(NewInputID(),
346 type,
347 InputMsg_HandleInputEvent(routing_id_,
348 &web_event,
349 latency_info,
350 is_keyboard_shortcut));
351
352 // The presence of |input_queue_override_| implies that we are in the
353 // scope of OnInputEventAck with a valid injector.
354 if (input_queue_override_)
355 input_queue_override_->InjectEvent(event, this);
356 else
357 input_queue_->QueueEvent(event, this);
358
359 return event.id;
360 }
361
362 bool BufferedInputRouter::FilterWebEvent(const WebKit::WebInputEvent& web_event,
363 const ui::LatencyInfo& latency_info) {
364 // Perform optional, synchronous event handling, sending ACK messages for
365 // processed events, or proceeding as usual.
366 InputEventAckState filter_ack = client_->FilterInputEvent(web_event,
367 latency_info);
368 switch (filter_ack) {
369 // Send the ACK and early exit.
370 case INPUT_EVENT_ACK_STATE_CONSUMED:
371 case INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS:
372 OnInputEventAck(0, web_event, latency_info, filter_ack, false);
373 // WARNING: |this| may be deleted at this point.
374 return true;
375
376 // Drop the event.
377 case INPUT_EVENT_ACK_STATE_UNKNOWN:
378 return true;
379
380 // Proceed as normal.
381 case INPUT_EVENT_ACK_STATE_NOT_CONSUMED:
382 default:
383 break;
384 };
385
386 return false;
387 }
388
389 void BufferedInputRouter::StartDelayedPacketMonitorTimeout(TimeDelta delay) {
390 Time target_delayed_time = Time::Now() + delay;
aelias_OOO_until_Jul13 2013/08/13 06:11:51 Use TimeTicks::Now() instead.
jdduke (slow) 2013/08/13 15:29:48 Done.
391 if (time_when_signal_delayed_packet_.is_null() ||
392 time_when_signal_delayed_packet_ > target_delayed_time) {
393 // This will effectively clear an existing timeout.
394 time_when_signal_delayed_packet_ = target_delayed_time;
395 }
396
397 // If we already have a timer with the same or shorter duration, then we can
398 // wait for it to finish.
399 if (delayed_packet_timer_.IsRunning() &&
400 delayed_packet_timer_.GetCurrentDelay() <= delay) {
401 return;
402 }
403
404 time_when_signal_delayed_packet_ = target_delayed_time;
405 delayed_packet_timer_.Stop();
406 delayed_packet_timer_.Start(FROM_HERE, delay, this,
407 &BufferedInputRouter::CheckShouldSignalDelayedPacket);
408 }
409
410 void BufferedInputRouter::StopDelayedPacketMonitorTimeout() {
411 time_when_signal_delayed_packet_ = Time();
412 delayed_packet_timer_.Stop();
413 }
414
415 void BufferedInputRouter::CheckShouldSignalDelayedPacket() {
416 if (time_when_signal_delayed_packet_.is_null())
417 return;
418
419 Time now = Time::Now();
420 if (now < time_when_signal_delayed_packet_) {
421 StartDelayedPacketMonitorTimeout(time_when_signal_delayed_packet_ - now);
422 return;
423 }
424
425 TRACE_EVENT1("input", "BufferedInputRouter::EventPacketDelayed",
426 "id", in_flight_packet_id_);
427 input_queue_->OnEventPacketAckDelayed();
428 }
429
430 int64 BufferedInputRouter::NewInputID() {
431 return ++last_input_id_;
432 }
433
434 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698