1 // Copyright 2017 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/render_widget_targeter.h"
6
7 #include "base/bind.h"
8 #include "base/metrics/histogram_functions.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "base/rand_util.h"
11 #include "components/viz/common/features.h"
12 #include "components/viz/host/host_frame_sink_manager.h"
13 #include "content/browser/compositor/surface_utils.h"
14 #include "content/browser/renderer_host/input/one_shot_timeout_monitor.h"
15 #include "content/browser/renderer_host/render_widget_host_impl.h"
16 #include "content/browser/renderer_host/render_widget_host_view_base.h"
17 #include "content/public/browser/site_isolation_policy.h"
18 #include "third_party/blink/public/common/input/web_input_event.h"
19 #include "ui/events/blink/blink_event_util.h"
20
21 namespace content {
22
23 namespace {
24
ComputeEventLocation(const blink::WebInputEvent & event)25 gfx::PointF ComputeEventLocation(const blink::WebInputEvent& event) {
26 if (blink::WebInputEvent::IsMouseEventType(event.GetType()) ||
27 event.GetType() == blink::WebInputEvent::kMouseWheel) {
28 return static_cast<const blink::WebMouseEvent&>(event).PositionInWidget();
29 }
30 if (blink::WebInputEvent::IsTouchEventType(event.GetType())) {
31 return static_cast<const blink::WebTouchEvent&>(event)
32 .touches[0]
33 .PositionInWidget();
34 }
35 if (blink::WebInputEvent::IsGestureEventType(event.GetType()))
36 return static_cast<const blink::WebGestureEvent&>(event).PositionInWidget();
37
38 return gfx::PointF();
39 }
40
IsMouseMiddleClick(const blink::WebInputEvent & event)41 bool IsMouseMiddleClick(const blink::WebInputEvent& event) {
42 return (event.GetType() == blink::WebInputEvent::Type::kMouseDown &&
43 static_cast<const blink::WebMouseEvent&>(event).button ==
44 blink::WebPointerProperties::Button::kMiddle);
45 }
46
47 constexpr const char kTracingCategory[] = "input,latency";
48
49 } // namespace
50
51 class TracingUmaTracker {
52 public:
TracingUmaTracker(const char * metric_name)53 explicit TracingUmaTracker(const char* metric_name)
54 : id_(next_id_++),
55 start_time_(base::TimeTicks::Now()),
56 metric_name_(metric_name) {
57 TRACE_EVENT_ASYNC_BEGIN0(
58 kTracingCategory, metric_name_,
59 TRACE_ID_WITH_SCOPE("UmaTracker", TRACE_ID_LOCAL(id_)));
60 }
61 ~TracingUmaTracker() = default;
62 TracingUmaTracker(TracingUmaTracker&& tracker) = default;
63
StopAndRecord()64 void StopAndRecord() {
65 TRACE_EVENT_ASYNC_END0(
66 kTracingCategory, metric_name_,
67 TRACE_ID_WITH_SCOPE("UmaTracker", TRACE_ID_LOCAL(id_)));
68 UmaHistogramTimes(metric_name_, base::TimeTicks::Now() - start_time_);
69 }
70
71 private:
72 const int id_;
73 const base::TimeTicks start_time_;
74
75 // These variables must be string literals and live for the duration
76 // of the program since tracing stores pointers.
77 const char* metric_name_;
78
79 static int next_id_;
80
81 DISALLOW_COPY_AND_ASSIGN(TracingUmaTracker);
82 };
83
84 int TracingUmaTracker::next_id_ = 1;
85
86 RenderWidgetTargetResult::RenderWidgetTargetResult() = default;
87
88 RenderWidgetTargetResult::RenderWidgetTargetResult(
89 const RenderWidgetTargetResult&) = default;
90
RenderWidgetTargetResult(RenderWidgetHostViewBase * in_view,bool in_should_query_view,base::Optional<gfx::PointF> in_location,bool in_latched_target)91 RenderWidgetTargetResult::RenderWidgetTargetResult(
92 RenderWidgetHostViewBase* in_view,
93 bool in_should_query_view,
94 base::Optional<gfx::PointF> in_location,
95 bool in_latched_target)
96 : view(in_view),
97 should_query_view(in_should_query_view),
98 target_location(in_location),
99 latched_target(in_latched_target) {}
100
101 RenderWidgetTargetResult::~RenderWidgetTargetResult() = default;
102
TargetingRequest(base::WeakPtr<RenderWidgetHostViewBase> root_view,const blink::WebInputEvent & event,const ui::LatencyInfo & latency)103 RenderWidgetTargeter::TargetingRequest::TargetingRequest(
104 base::WeakPtr<RenderWidgetHostViewBase> root_view,
105 const blink::WebInputEvent& event,
106 const ui::LatencyInfo& latency) {
107 this->root_view = std::move(root_view);
108 this->location = ComputeEventLocation(event);
109 this->event = event.Clone();
110 this->latency = latency;
111 }
112
TargetingRequest(base::WeakPtr<RenderWidgetHostViewBase> root_view,const gfx::PointF & location,RenderWidgetHostAtPointCallback callback)113 RenderWidgetTargeter::TargetingRequest::TargetingRequest(
114 base::WeakPtr<RenderWidgetHostViewBase> root_view,
115 const gfx::PointF& location,
116 RenderWidgetHostAtPointCallback callback) {
117 this->root_view = std::move(root_view);
118 this->location = location;
119 this->callback = std::move(callback);
120 }
121
122 RenderWidgetTargeter::TargetingRequest::TargetingRequest(
123 TargetingRequest&& request) = default;
124
125 RenderWidgetTargeter::TargetingRequest& RenderWidgetTargeter::TargetingRequest::
126 operator=(TargetingRequest&&) = default;
127
128 RenderWidgetTargeter::TargetingRequest::~TargetingRequest() = default;
129
RunCallback(RenderWidgetHostViewBase * target,base::Optional<gfx::PointF> point)130 void RenderWidgetTargeter::TargetingRequest::RunCallback(
131 RenderWidgetHostViewBase* target,
132 base::Optional<gfx::PointF> point) {
133 if (!callback.is_null()) {
134 std::move(callback).Run(target ? target->GetWeakPtr() : nullptr, point);
135 }
136 }
137
MergeEventIfPossible(const blink::WebInputEvent & new_event)138 bool RenderWidgetTargeter::TargetingRequest::MergeEventIfPossible(
139 const blink::WebInputEvent& new_event) {
140 if (event && !blink::WebInputEvent::IsTouchEventType(new_event.GetType()) &&
141 !blink::WebInputEvent::IsGestureEventType(new_event.GetType()) &&
142 ui::CanCoalesce(new_event, *event.get())) {
143 ui::Coalesce(new_event, event.get());
144 return true;
145 }
146 return false;
147 }
148
IsWebInputEventRequest() const149 bool RenderWidgetTargeter::TargetingRequest::IsWebInputEventRequest() const {
150 return !!event;
151 }
152
GetEvent()153 blink::WebInputEvent* RenderWidgetTargeter::TargetingRequest::GetEvent() {
154 return event.get();
155 }
156
GetRootView() const157 RenderWidgetHostViewBase* RenderWidgetTargeter::TargetingRequest::GetRootView()
158 const {
159 return root_view.get();
160 }
161
GetLocation() const162 gfx::PointF RenderWidgetTargeter::TargetingRequest::GetLocation() const {
163 return location;
164 }
165
GetLatency() const166 const ui::LatencyInfo& RenderWidgetTargeter::TargetingRequest::GetLatency()
167 const {
168 return latency;
169 }
170
RenderWidgetTargeter(Delegate * delegate)171 RenderWidgetTargeter::RenderWidgetTargeter(Delegate* delegate)
172 : trace_id_(base::RandUint64()),
173 is_viz_hit_testing_debug_enabled_(
174 features::IsVizHitTestingDebugEnabled()),
175 delegate_(delegate) {
176 DCHECK(delegate_);
177 }
178
179 RenderWidgetTargeter::~RenderWidgetTargeter() = default;
180
FindTargetAndDispatch(RenderWidgetHostViewBase * root_view,const blink::WebInputEvent & event,const ui::LatencyInfo & latency)181 void RenderWidgetTargeter::FindTargetAndDispatch(
182 RenderWidgetHostViewBase* root_view,
183 const blink::WebInputEvent& event,
184 const ui::LatencyInfo& latency) {
185 DCHECK(blink::WebInputEvent::IsMouseEventType(event.GetType()) ||
186 event.GetType() == blink::WebInputEvent::kMouseWheel ||
187 blink::WebInputEvent::IsTouchEventType(event.GetType()) ||
188 (blink::WebInputEvent::IsGestureEventType(event.GetType()) &&
189 (static_cast<const blink::WebGestureEvent&>(event).SourceDevice() ==
190 blink::WebGestureDevice::kTouchscreen ||
191 static_cast<const blink::WebGestureEvent&>(event).SourceDevice() ==
192 blink::WebGestureDevice::kTouchpad)));
193
194 if (!requests_.empty()) {
195 auto& request = requests_.back();
196 if (request.MergeEventIfPossible(event))
197 return;
198 }
199
200 TargetingRequest request(root_view->GetWeakPtr(), event, latency);
201
202 ResolveTargetingRequest(std::move(request));
203 }
204
FindTargetAndCallback(RenderWidgetHostViewBase * root_view,const gfx::PointF & point,RenderWidgetHostAtPointCallback callback)205 void RenderWidgetTargeter::FindTargetAndCallback(
206 RenderWidgetHostViewBase* root_view,
207 const gfx::PointF& point,
208 RenderWidgetHostAtPointCallback callback) {
209 TargetingRequest request(root_view->GetWeakPtr(), point, std::move(callback));
210
211 ResolveTargetingRequest(std::move(request));
212 }
213
ResolveTargetingRequest(TargetingRequest request)214 void RenderWidgetTargeter::ResolveTargetingRequest(TargetingRequest request) {
215 if (request_in_flight_) {
216 requests_.push(std::move(request));
217 return;
218 }
219
220 RenderWidgetTargetResult result;
221 auto* request_target = request.GetRootView();
222 auto request_target_location = request.GetLocation();
223
224 if (request.IsWebInputEventRequest()) {
225 result = is_autoscroll_in_progress_
226 ? middle_click_result_
227 : delegate_->FindTargetSynchronously(request_target,
228 *request.GetEvent());
229 if (!is_autoscroll_in_progress_ &&
230 IsMouseMiddleClick(*request.GetEvent())) {
231 if (!result.should_query_view)
232 middle_click_result_ = result;
233 }
234 } else {
235 result = delegate_->FindTargetSynchronouslyAtPoint(request_target,
236 request_target_location);
237 }
238 RenderWidgetHostViewBase* target = result.view;
239 async_depth_ = 0;
240 if (!is_autoscroll_in_progress_ && result.should_query_view) {
241 TRACE_EVENT_WITH_FLOW2(
242 "viz,benchmark", "Event.Pipeline", TRACE_ID_GLOBAL(trace_id_),
243 TRACE_EVENT_FLAG_FLOW_OUT, "step", "QueryClient(Start)",
244 "event_location", request.GetLocation().ToString());
245
246 // TODO(kenrb, sadrul): When all event types support asynchronous hit
247 // testing, we should be able to have FindTargetSynchronously return the
248 // view and location to use for the renderer hit test query.
249 // Currently it has to return the surface hit test target, for event types
250 // that ignore |result.should_query_view|, and therefore we have to use
251 // root_view and the original event location for the initial query.
252 // Do not compare hit test results if we are forced to do async hit testing
253 // by HitTestQuery.
254 QueryClient(request_target, request_target_location, nullptr, gfx::PointF(),
255 std::move(request));
256 } else {
257 FoundTarget(target, result.target_location, result.latched_target,
258 &request);
259 }
260 }
261
ViewWillBeDestroyed(RenderWidgetHostViewBase * view)262 void RenderWidgetTargeter::ViewWillBeDestroyed(RenderWidgetHostViewBase* view) {
263 unresponsive_views_.erase(view);
264
265 if (is_autoscroll_in_progress_ && middle_click_result_.view == view) {
266 SetIsAutoScrollInProgress(false);
267 }
268 }
269
HasEventsPendingDispatch() const270 bool RenderWidgetTargeter::HasEventsPendingDispatch() const {
271 return request_in_flight_ || !requests_.empty();
272 }
273
SetIsAutoScrollInProgress(bool autoscroll_in_progress)274 void RenderWidgetTargeter::SetIsAutoScrollInProgress(
275 bool autoscroll_in_progress) {
276 is_autoscroll_in_progress_ = autoscroll_in_progress;
277
278 // If middle click autoscroll ends, reset |middle_click_result_|.
279 if (!autoscroll_in_progress)
280 middle_click_result_ = RenderWidgetTargetResult();
281 }
282
QueryClient(RenderWidgetHostViewBase * target,const gfx::PointF & target_location,RenderWidgetHostViewBase * last_request_target,const gfx::PointF & last_target_location,TargetingRequest request)283 void RenderWidgetTargeter::QueryClient(
284 RenderWidgetHostViewBase* target,
285 const gfx::PointF& target_location,
286 RenderWidgetHostViewBase* last_request_target,
287 const gfx::PointF& last_target_location,
288 TargetingRequest request) {
289 auto* target_client = target->host()->input_target_client();
290 // |target_client| may not be set yet for this |target| on Mac, need to
291 // understand why this happens. https://crbug.com/859492.
292 // We do not verify hit testing result under this circumstance.
293 if (!target_client) {
294 FoundTarget(target, target_location, false, &request);
295 return;
296 }
297
298 request_in_flight_ = std::move(request);
299 async_depth_++;
300
301 TracingUmaTracker tracker("Event.AsyncTargeting.ResponseTime");
302 async_hit_test_timeout_.reset(new OneShotTimeoutMonitor(
303 base::BindOnce(
304 &RenderWidgetTargeter::AsyncHitTestTimedOut,
305 weak_ptr_factory_.GetWeakPtr(), target->GetWeakPtr(), target_location,
306 last_request_target ? last_request_target->GetWeakPtr() : nullptr,
307 last_target_location),
308 async_hit_test_timeout_delay_));
309
310 TRACE_EVENT_WITH_FLOW2(
311 "viz,benchmark", "Event.Pipeline", TRACE_ID_GLOBAL(trace_id_),
312 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "step",
313 "QueryClient", "event_location", request.GetLocation().ToString());
314
315 target_client->FrameSinkIdAt(
316 target_location, trace_id_,
317 base::BindOnce(&RenderWidgetTargeter::FoundFrameSinkId,
318 weak_ptr_factory_.GetWeakPtr(), target->GetWeakPtr(),
319 ++last_request_id_, target_location, std::move(tracker)));
320 }
321
FlushEventQueue()322 void RenderWidgetTargeter::FlushEventQueue() {
323 bool events_being_flushed = false;
324 while (!request_in_flight_ && !requests_.empty()) {
325 auto request = std::move(requests_.front());
326 requests_.pop();
327 // The root-view has gone away. Ignore this event, and try to process the
328 // next event.
329 if (!request.GetRootView())
330 continue;
331
332 // Only notify the delegate once that the current event queue is being
333 // flushed. Once all the events are flushed, notify the delegate again.
334 if (!events_being_flushed) {
335 delegate_->SetEventsBeingFlushed(true);
336 events_being_flushed = true;
337 }
338 ResolveTargetingRequest(std::move(request));
339 }
340 delegate_->SetEventsBeingFlushed(false);
341 }
342
FoundFrameSinkId(base::WeakPtr<RenderWidgetHostViewBase> target,uint32_t request_id,const gfx::PointF & target_location,TracingUmaTracker tracker,const viz::FrameSinkId & frame_sink_id,const gfx::PointF & transformed_location)343 void RenderWidgetTargeter::FoundFrameSinkId(
344 base::WeakPtr<RenderWidgetHostViewBase> target,
345 uint32_t request_id,
346 const gfx::PointF& target_location,
347 TracingUmaTracker tracker,
348 const viz::FrameSinkId& frame_sink_id,
349 const gfx::PointF& transformed_location) {
350 tracker.StopAndRecord();
351
352 uint32_t last_id = last_request_id_;
353 bool in_flight = request_in_flight_.has_value();
354 if (request_id != last_id || !in_flight) {
355 // This is a response to a request that already timed out, so the event
356 // should have already been dispatched. Mark the renderer as responsive
357 // and otherwise ignore this response.
358 unresponsive_views_.erase(target.get());
359 return;
360 }
361
362 TargetingRequest request = std::move(request_in_flight_.value());
363
364 request_in_flight_.reset();
365 async_hit_test_timeout_.reset(nullptr);
366
367 if (is_viz_hit_testing_debug_enabled_ && request.IsWebInputEventRequest() &&
368 request.GetEvent()->GetType() == blink::WebInputEvent::Type::kMouseDown) {
369 hit_test_async_queried_debug_queue_.push_back(target->GetFrameSinkId());
370 }
371
372 auto* view = delegate_->FindViewFromFrameSinkId(frame_sink_id);
373 if (!view)
374 view = target.get();
375
376 // If a client returned an embedded target, then it might be necessary to
377 // continue asking the clients until a client claims an event for itself.
378 if (view == target.get() ||
379 unresponsive_views_.find(view) != unresponsive_views_.end() ||
380 !delegate_->ShouldContinueHitTesting(view)) {
381 // Reduced scope is required since FoundTarget can trigger another query
382 // which would end up linked to the current query.
383 {
384 TRACE_EVENT_WITH_FLOW1("viz,benchmark", "Event.Pipeline",
385 TRACE_ID_GLOBAL(trace_id_),
386 TRACE_EVENT_FLAG_FLOW_IN, "step", "FoundTarget");
387 }
388
389 if (request.IsWebInputEventRequest() &&
390 IsMouseMiddleClick(*request.GetEvent())) {
391 middle_click_result_ = {view, false, transformed_location, false};
392 }
393
394 FoundTarget(view, transformed_location, false, &request);
395 } else {
396 QueryClient(view, transformed_location, target.get(), target_location,
397 std::move(request));
398 }
399 }
400
FoundTarget(RenderWidgetHostViewBase * target,const base::Optional<gfx::PointF> & target_location,bool latched_target,TargetingRequest * request)401 void RenderWidgetTargeter::FoundTarget(
402 RenderWidgetHostViewBase* target,
403 const base::Optional<gfx::PointF>& target_location,
404 bool latched_target,
405 TargetingRequest* request) {
406 DCHECK(request);
407
408 if (SiteIsolationPolicy::UseDedicatedProcessesForAllSites() &&
409 !latched_target) {
410 UMA_HISTOGRAM_COUNTS_100("Event.AsyncTargeting.AsyncClientDepth",
411 async_depth_);
412 }
413
414 // RenderWidgetHostViewMac can be deleted asynchronously, in which case the
415 // View will be valid but there will no longer be a RenderWidgetHostImpl.
416 if (!request->GetRootView() || !request->GetRootView()->GetRenderWidgetHost())
417 return;
418
419 if (is_viz_hit_testing_debug_enabled_ &&
420 !hit_test_async_queried_debug_queue_.empty()) {
421 GetHostFrameSinkManager()->SetHitTestAsyncQueriedDebugRegions(
422 request->GetRootView()->GetRootFrameSinkId(),
423 hit_test_async_queried_debug_queue_);
424 hit_test_async_queried_debug_queue_.clear();
425 }
426
427 if (request->IsWebInputEventRequest()) {
428 delegate_->DispatchEventToTarget(request->GetRootView(), target,
429 request->GetEvent(), request->GetLatency(),
430 target_location);
431 } else {
432 request->RunCallback(target, target_location);
433 }
434
435 FlushEventQueue();
436 }
437
AsyncHitTestTimedOut(base::WeakPtr<RenderWidgetHostViewBase> current_request_target,const gfx::PointF & current_target_location,base::WeakPtr<RenderWidgetHostViewBase> last_request_target,const gfx::PointF & last_target_location)438 void RenderWidgetTargeter::AsyncHitTestTimedOut(
439 base::WeakPtr<RenderWidgetHostViewBase> current_request_target,
440 const gfx::PointF& current_target_location,
441 base::WeakPtr<RenderWidgetHostViewBase> last_request_target,
442 const gfx::PointF& last_target_location) {
443 DCHECK(request_in_flight_);
444
445 TargetingRequest request = std::move(request_in_flight_.value());
446 request_in_flight_.reset();
447
448 if (!request.GetRootView())
449 return;
450
451 // Mark view as unresponsive so further events will not be sent to it.
452 if (current_request_target)
453 unresponsive_views_.insert(current_request_target.get());
454
455 if (request.GetRootView() == current_request_target.get()) {
456 // When a request to the top-level frame times out then the event gets
457 // sent there anyway. It will trigger the hung renderer dialog if the
458 // renderer fails to process it.
459 FoundTarget(current_request_target.get(), current_target_location, false,
460 &request);
461 } else {
462 FoundTarget(last_request_target.get(), last_target_location, false,
463 &request);
464 }
465 }
466
467 } // namespace content
468