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