| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/viz/host/hit_test/hit_test_query.h" |
| |
| #include "base/containers/stack.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "components/viz/common/hit_test/hit_test_region_list.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| |
| namespace viz { |
| namespace { |
| |
| // If we want to add new source type here, consider switching to use |
| // ui::EventPointerType instead of EventSource. |
| bool RegionMatchEventSource(EventSource event_source, uint32_t flags) { |
| if (event_source == EventSource::TOUCH) |
| return (flags & HitTestRegionFlags::kHitTestTouch) != 0u; |
| if (event_source == EventSource::MOUSE) |
| return (flags & HitTestRegionFlags::kHitTestMouse) != 0u; |
| return (flags & (HitTestRegionFlags::kHitTestMouse | |
| HitTestRegionFlags::kHitTestTouch)) != 0u; |
| } |
| |
| bool CheckChildCount(int32_t child_count, size_t child_count_max) { |
| return (child_count >= 0) && |
| (static_cast<size_t>(child_count) < child_count_max); |
| } |
| |
| const std::string GetFlagNames(uint32_t flag) { |
| std::string names = ""; |
| uint32_t mask = 1; |
| |
| while (flag) { |
| std::string name = ""; |
| switch (flag & mask) { |
| case kHitTestMine: |
| name = "Mine"; |
| break; |
| case kHitTestIgnore: |
| name = "Ignore"; |
| break; |
| case kHitTestChildSurface: |
| name = "ChildSurface"; |
| break; |
| case kHitTestAsk: |
| name = "Ask"; |
| break; |
| case kHitTestMouse: |
| name = "Mouse"; |
| break; |
| case kHitTestTouch: |
| name = "Touch"; |
| break; |
| case kHitTestNotActive: |
| name = "NotActive"; |
| break; |
| case kHitTestDebug: |
| name = "Debug"; |
| break; |
| case 0: |
| break; |
| } |
| if (!name.empty()) { |
| names += names.empty() ? name : ", " + name; |
| } |
| |
| flag &= ~mask; |
| mask <<= 1; |
| } |
| |
| return names; |
| } |
| |
| } // namespace |
| |
| HitTestQuery::HitTestQuery(base::RepeatingClosure bad_message_gpu_callback) |
| : bad_message_gpu_callback_(std::move(bad_message_gpu_callback)) {} |
| |
| HitTestQuery::~HitTestQuery() = default; |
| |
| void HitTestQuery::OnAggregatedHitTestRegionListUpdated( |
| const std::vector<AggregatedHitTestRegion>& hit_test_data) { |
| hit_test_data_.clear(); |
| hit_test_data_ = hit_test_data; |
| } |
| |
| Target HitTestQuery::FindTargetForLocation( |
| EventSource event_source, |
| const gfx::PointF& location_in_root) const { |
| if (hit_test_data_.empty()) |
| return Target(); |
| |
| base::ElapsedTimer target_timer; |
| Target target; |
| FindTargetInRegionForLocation(event_source, location_in_root, 0, &target); |
| UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES("Event.VizHitTest.TargetTimeUs", |
| target_timer.Elapsed(), |
| base::TimeDelta::FromMicroseconds(1), |
| base::TimeDelta::FromSeconds(10), 50); |
| return target; |
| } |
| |
| bool HitTestQuery::TransformLocationForTarget( |
| EventSource event_source, |
| const std::vector<FrameSinkId>& target_ancestors, |
| const gfx::PointF& location_in_root, |
| gfx::PointF* transformed_location) const { |
| base::ElapsedTimer transform_timer; |
| |
| if (hit_test_data_.empty()) |
| return false; |
| |
| // Use GetTransformToTarget if |target_ancestors| only has the target. |
| if (target_ancestors.size() == 1u) { |
| gfx::Transform transform; |
| if (!GetTransformToTarget(target_ancestors.front(), &transform)) |
| return false; |
| |
| *transformed_location = location_in_root; |
| transform.TransformPoint(transformed_location); |
| return true; |
| } |
| |
| if (target_ancestors.size() == 0u || |
| target_ancestors[target_ancestors.size() - 1] != |
| hit_test_data_[0].frame_sink_id) { |
| return false; |
| } |
| |
| // TODO(riajiang): Cache the matrix product such that the transform can be |
| // done immediately. crbug/758062. |
| *transformed_location = location_in_root; |
| UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES("Event.VizHitTest.TransformTimeUs", |
| transform_timer.Elapsed(), |
| base::TimeDelta::FromMicroseconds(1), |
| base::TimeDelta::FromSeconds(10), 50); |
| return TransformLocationForTargetRecursively(event_source, target_ancestors, |
| target_ancestors.size() - 1, 0, |
| transformed_location); |
| } |
| |
| bool HitTestQuery::GetTransformToTarget(const FrameSinkId& target, |
| gfx::Transform* transform) const { |
| if (hit_test_data_.empty()) |
| return false; |
| |
| return GetTransformToTargetRecursively(target, 0, transform); |
| } |
| |
| bool HitTestQuery::ContainsActiveFrameSinkId( |
| const FrameSinkId& frame_sink_id) const { |
| for (auto& it : hit_test_data_) { |
| if (it.frame_sink_id == frame_sink_id && |
| !(it.flags & HitTestRegionFlags::kHitTestNotActive)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool HitTestQuery::FindTargetInRegionForLocation( |
| EventSource event_source, |
| const gfx::PointF& location_in_parent, |
| size_t region_index, |
| Target* target) const { |
| gfx::PointF location_transformed(location_in_parent); |
| |
| // HasPerspective() is checked for the transform because the point will not |
| // be transformed correctly for a plane with a different normal. |
| // See https://crbug.com/854247. |
| if (hit_test_data_[region_index].transform().HasPerspective()) { |
| target->frame_sink_id = hit_test_data_[region_index].frame_sink_id; |
| target->location_in_target = gfx::PointF(); |
| target->flags = HitTestRegionFlags::kHitTestAsk; |
| RecordSlowPathHitTestReasons( |
| AsyncHitTestReasons::kPerspectiveTransform | |
| hit_test_data_[region_index].async_hit_test_reasons); |
| return true; |
| } |
| |
| hit_test_data_[region_index].transform().TransformPoint( |
| &location_transformed); |
| if (!gfx::RectF(hit_test_data_[region_index].rect) |
| .Contains(location_transformed)) { |
| return false; |
| } |
| |
| const int32_t region_child_count = hit_test_data_[region_index].child_count; |
| if (!CheckChildCount(region_child_count, |
| hit_test_data_.size() - region_index)) |
| return false; |
| |
| size_t child_region = region_index + 1; |
| size_t child_region_end = child_region + region_child_count; |
| gfx::PointF location_in_target = |
| location_transformed - |
| hit_test_data_[region_index].rect.OffsetFromOrigin(); |
| |
| const uint32_t flags = hit_test_data_[region_index].flags; |
| |
| // Verify that async_hit_test_reasons is set if and only if there's |
| // a kHitTestAsk flag. |
| DCHECK_EQ(!!(flags & HitTestRegionFlags::kHitTestAsk), |
| !!hit_test_data_[region_index].async_hit_test_reasons); |
| |
| if (flags & HitTestRegionFlags::kHitTestAsk) { |
| target->frame_sink_id = hit_test_data_[region_index].frame_sink_id; |
| target->location_in_target = location_in_target; |
| target->flags = flags; |
| RecordSlowPathHitTestReasons( |
| hit_test_data_[region_index].async_hit_test_reasons); |
| return true; |
| } |
| |
| while (child_region < child_region_end) { |
| if (FindTargetInRegionForLocation(event_source, location_in_target, |
| child_region, target)) { |
| return true; |
| } |
| |
| const int32_t child_region_child_count = |
| hit_test_data_[child_region].child_count; |
| if (!CheckChildCount(child_region_child_count, region_child_count)) |
| return false; |
| |
| child_region = child_region + child_region_child_count + 1; |
| } |
| |
| if (!RegionMatchEventSource(event_source, flags)) |
| return false; |
| |
| if (flags & HitTestRegionFlags::kHitTestMine) { |
| target->frame_sink_id = hit_test_data_[region_index].frame_sink_id; |
| target->location_in_target = location_in_target; |
| target->flags = flags; |
| // We record fast path hit testing instances with reason kNotAsyncHitTest. |
| RecordSlowPathHitTestReasons( |
| hit_test_data_[region_index].async_hit_test_reasons); |
| return true; |
| } |
| return false; |
| } |
| |
| bool HitTestQuery::TransformLocationForTargetRecursively( |
| EventSource event_source, |
| const std::vector<FrameSinkId>& target_ancestors, |
| size_t target_ancestor, |
| size_t region_index, |
| gfx::PointF* location_in_target) const { |
| const uint32_t flags = hit_test_data_[region_index].flags; |
| if ((flags & HitTestRegionFlags::kHitTestChildSurface) == 0u && |
| !RegionMatchEventSource(event_source, flags)) { |
| return false; |
| } |
| |
| hit_test_data_[region_index].transform().TransformPoint(location_in_target); |
| location_in_target->Offset(-hit_test_data_[region_index].rect.x(), |
| -hit_test_data_[region_index].rect.y()); |
| if (!target_ancestor) |
| return true; |
| |
| const int32_t region_child_count = hit_test_data_[region_index].child_count; |
| if (!CheckChildCount(region_child_count, |
| hit_test_data_.size() - region_index)) { |
| return false; |
| } |
| |
| size_t child_region = region_index + 1; |
| size_t child_region_end = child_region + region_child_count; |
| while (child_region < child_region_end) { |
| if (hit_test_data_[child_region].frame_sink_id == |
| target_ancestors[target_ancestor - 1]) { |
| return TransformLocationForTargetRecursively( |
| event_source, target_ancestors, target_ancestor - 1, child_region, |
| location_in_target); |
| } |
| |
| const int32_t child_region_child_count = |
| hit_test_data_[child_region].child_count; |
| if (!CheckChildCount(child_region_child_count, region_child_count)) |
| return false; |
| |
| child_region = child_region + child_region_child_count + 1; |
| } |
| |
| return false; |
| } |
| |
| bool HitTestQuery::GetTransformToTargetRecursively( |
| const FrameSinkId& target, |
| size_t region_index, |
| gfx::Transform* transform) const { |
| // TODO(riajiang): Cache the matrix product such that the transform can be |
| // found immediately. |
| if (hit_test_data_[region_index].frame_sink_id == target) { |
| *transform = hit_test_data_[region_index].transform(); |
| transform->Translate(-hit_test_data_[region_index].rect.x(), |
| -hit_test_data_[region_index].rect.y()); |
| return true; |
| } |
| |
| const int32_t region_child_count = hit_test_data_[region_index].child_count; |
| if (!CheckChildCount(region_child_count, |
| hit_test_data_.size() - region_index)) { |
| return false; |
| } |
| |
| size_t child_region = region_index + 1; |
| size_t child_region_end = child_region + region_child_count; |
| while (child_region < child_region_end) { |
| gfx::Transform transform_to_child; |
| if (GetTransformToTargetRecursively(target, child_region, |
| &transform_to_child)) { |
| gfx::Transform region_transform(hit_test_data_[region_index].transform()); |
| region_transform.Translate(-hit_test_data_[region_index].rect.x(), |
| -hit_test_data_[region_index].rect.y()); |
| *transform = transform_to_child * region_transform; |
| return true; |
| } |
| |
| const int32_t child_region_child_count = |
| hit_test_data_[child_region].child_count; |
| if (!CheckChildCount(child_region_child_count, region_child_count)) |
| return false; |
| |
| child_region = child_region + child_region_child_count + 1; |
| } |
| |
| return false; |
| } |
| |
| void HitTestQuery::ReceivedBadMessageFromGpuProcess() const { |
| if (!bad_message_gpu_callback_.is_null()) |
| bad_message_gpu_callback_.Run(); |
| } |
| |
| void HitTestQuery::RecordSlowPathHitTestReasons(uint32_t reasons) const { |
| static const char* kAsyncHitTestReasonsHistogramName = |
| "Event.VizHitTest.AsyncHitTestReasons"; |
| if (reasons == AsyncHitTestReasons::kNotAsyncHitTest) { |
| UMA_HISTOGRAM_ENUMERATION( |
| kAsyncHitTestReasonsHistogramName, |
| AsyncHitTestReasons::kNotAsyncHitTest, |
| AsyncHitTestReasons::kAsyncHitTestReasonCount + 1); |
| return; |
| } |
| |
| for (uint32_t i = 0; i < AsyncHitTestReasons::kAsyncHitTestReasonCount; ++i) { |
| unsigned val = 1 << i; |
| if (reasons & val) { |
| UMA_HISTOGRAM_ENUMERATION( |
| kAsyncHitTestReasonsHistogramName, i + 1, |
| AsyncHitTestReasons::kAsyncHitTestReasonCount + 1); |
| } |
| } |
| } |
| |
| std::string HitTestQuery::PrintHitTestData() const { |
| std::ostringstream oss; |
| base::stack<uint32_t> parents; |
| std::string tabs = ""; |
| |
| for (uint32_t i = 0; i < hit_test_data_.size(); ++i) { |
| const AggregatedHitTestRegion& htr = hit_test_data_[i]; |
| |
| oss << tabs << "Index: " << i << '\n'; |
| oss << tabs << "Children: " << htr.child_count << '\n'; |
| oss << tabs << "Flags: " << GetFlagNames(htr.flags) << '\n'; |
| oss << tabs << "Frame Sink Id: " << htr.frame_sink_id.ToString() << '\n'; |
| oss << tabs << "Rect: " << htr.rect.ToString() << '\n'; |
| oss << tabs << "Transform:" << '\n'; |
| |
| // gfx::Transform::ToString spans multiple lines, so we use an additional |
| // stringstream. |
| { |
| std::string s; |
| std::stringstream transform_ss; |
| |
| transform_ss << htr.transform().ToString() << '\n'; |
| |
| while (getline(transform_ss, s)) { |
| oss << tabs << s << '\n'; |
| } |
| } |
| |
| tabs += "\t\t"; |
| parents.push(i); |
| |
| while (!parents.empty() && |
| parents.top() + hit_test_data_[parents.top()].child_count <= i) { |
| tabs.pop_back(); |
| tabs.pop_back(); |
| |
| parents.pop(); |
| } |
| } |
| |
| return oss.str(); |
| } |
| |
| } // namespace viz |