Add viz-host HitTestQuery.

HitTestQuery finds the hit test target, for a given location, in the
AggregatedHitTestRegion list received from HitTestAggregator.

BUG=732400
TEST=viz_unittests

Review-Url: https://codereview.chromium.org/2933493003
Cr-Commit-Position: refs/heads/master@{#488854}
diff --git a/components/viz/common/hit_test/aggregated_hit_test_region.h b/components/viz/common/hit_test/aggregated_hit_test_region.h
index b90bfb19..ab3997a 100644
--- a/components/viz/common/hit_test/aggregated_hit_test_region.h
+++ b/components/viz/common/hit_test/aggregated_hit_test_region.h
@@ -15,7 +15,7 @@
 
 // A AggregatedHitTestRegion element with child_count of kEndOfList indicates
 // the last element and end of the list.
-constexpr int kEndOfList = -1;
+constexpr int32_t kEndOfList = -1;
 
 // An array of AggregatedHitTestRegion elements is used to define the
 // aggregated hit-test data for the Display.
@@ -41,7 +41,7 @@
   // The number of children including their children below this entry.
   // If this element is not matched then child_count elements can be skipped
   // to move to the next entry.
-  int child_count;
+  int32_t child_count;
 };
 
 }  // namespace viz
diff --git a/components/viz/host/BUILD.gn b/components/viz/host/BUILD.gn
index e5d833e..65beb19 100644
--- a/components/viz/host/BUILD.gn
+++ b/components/viz/host/BUILD.gn
@@ -49,6 +49,7 @@
     "//base/test:test_support",
     "//cc/ipc:interfaces",
     "//cc/surfaces",
+    "//components/viz/host/hit_test:unit_tests",
     "//gpu/ipc/host",
     "//mojo/public/cpp/bindings",
     "//services/ui/gpu/interfaces",
diff --git a/components/viz/host/hit_test/BUILD.gn b/components/viz/host/hit_test/BUILD.gn
new file mode 100644
index 0000000..c60ca0e
--- /dev/null
+++ b/components/viz/host/hit_test/BUILD.gn
@@ -0,0 +1,36 @@
+# 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.
+
+import("//components/viz/viz.gni")
+import("//testing/test.gni")
+
+viz_source_set("hit_test") {
+  sources = [
+    "hit_test_query.cc",
+    "hit_test_query.h",
+  ]
+
+  public_deps = [
+    "//components/viz/common",
+    "//services/viz/hit_test/public/interfaces",
+    "//ui/gfx",
+    "//ui/gfx/geometry",
+  ]
+}
+
+viz_source_set("unit_tests") {
+  testonly = true
+
+  sources = [
+    "hit_test_query_unittest.cc",
+  ]
+
+  deps = [
+    ":hit_test",
+    "//base",
+    "//base/test:test_support",
+    "//cc:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/viz/host/hit_test/DEPS b/components/viz/host/hit_test/DEPS
new file mode 100644
index 0000000..80523a1
--- /dev/null
+++ b/components/viz/host/hit_test/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+services/viz/hit_test/public/interfaces",
+  "+ui/gfx"
+]
diff --git a/components/viz/host/hit_test/OWNERS b/components/viz/host/hit_test/OWNERS
new file mode 100644
index 0000000..77f21b5
--- /dev/null
+++ b/components/viz/host/hit_test/OWNERS
@@ -0,0 +1 @@
+rjkroege@chromium.org
diff --git a/components/viz/host/hit_test/hit_test_query.cc b/components/viz/host/hit_test/hit_test_query.cc
new file mode 100644
index 0000000..a4bdaa6
--- /dev/null
+++ b/components/viz/host/hit_test/hit_test_query.cc
@@ -0,0 +1,68 @@
+// 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 "services/viz/hit_test/public/interfaces/hit_test_region_list.mojom.h"
+
+namespace viz {
+
+HitTestQuery::HitTestQuery() = default;
+
+HitTestQuery::~HitTestQuery() = default;
+
+Target HitTestQuery::FindTargetForLocation(
+    const gfx::Point& location_in_root) const {
+  Target target;
+  if (!aggregated_hit_test_region_list_size_)
+    return target;
+
+  FindTargetInRegionForLocation(location_in_root,
+                                aggregated_hit_test_region_list_, &target);
+  return target;
+}
+
+bool HitTestQuery::FindTargetInRegionForLocation(
+    const gfx::Point& location_in_parent,
+    AggregatedHitTestRegion* region,
+    Target* target) const {
+  gfx::Point location_transformed(location_in_parent);
+  region->transform.TransformPoint(&location_transformed);
+  if (!region->rect.Contains(location_transformed))
+    return false;
+
+  if (region->child_count < 0 ||
+      region->child_count >
+          (aggregated_hit_test_region_list_ +
+           aggregated_hit_test_region_list_size_ - region - 1)) {
+    return false;
+  }
+  AggregatedHitTestRegion* child_region = region + 1;
+  AggregatedHitTestRegion* child_region_end =
+      child_region + region->child_count;
+  gfx::Point location_in_target(location_transformed);
+  location_in_target.Offset(-region->rect.x(), -region->rect.y());
+  while (child_region < child_region_end) {
+    if (FindTargetInRegionForLocation(location_in_target, child_region,
+                                      target)) {
+      return true;
+    }
+
+    if (child_region->child_count < 0 ||
+        child_region->child_count >= region->child_count) {
+      return false;
+    }
+    child_region = child_region + child_region->child_count + 1;
+  }
+
+  if (region->flags & mojom::kHitTestMine) {
+    target->frame_sink_id = region->frame_sink_id;
+    target->location_in_target = location_in_target;
+    target->flags = region->flags;
+    return true;
+  }
+  return false;
+}
+
+}  // namespace viz
diff --git a/components/viz/host/hit_test/hit_test_query.h b/components/viz/host/hit_test/hit_test_query.h
new file mode 100644
index 0000000..3e3d322
--- /dev/null
+++ b/components/viz/host/hit_test/hit_test_query.h
@@ -0,0 +1,84 @@
+// 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.
+
+#ifndef COMPONENTS_VIZ_HOST_HIT_TEST_HIT_TEST_QUERY_H_
+#define COMPONENTS_VIZ_HOST_HIT_TEST_HIT_TEST_QUERY_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "components/viz/common/hit_test/aggregated_hit_test_region.h"
+#include "ui/gfx/geometry/point.h"
+
+namespace viz {
+
+struct Target {
+  FrameSinkId frame_sink_id;
+  // Coordinates in the coordinate system of the target FrameSinkId.
+  gfx::Point location_in_target;
+  // Different flags are defined in services/viz/hit_test/public/interfaces/
+  // hit_test_region_list.mojom.
+  uint32_t flags = 0;
+};
+
+// Finds the target for a given location based on the AggregatedHitTestRegion
+// list aggregated by HitTestAggregator.
+// TODO(riajiang): Handle 3d space cases correctly.
+class HitTestQuery {
+ public:
+  HitTestQuery();
+  ~HitTestQuery();
+
+  // TODO(riajiang): Read from shmem directly once it's set up and delete this
+  // function. For now, use fake data. Also need to validate the data received.
+  // http://crbug.com/746470
+  void set_aggregated_hit_test_region_list(
+      AggregatedHitTestRegion* aggregated_hit_test_region_list,
+      uint32_t aggregated_hit_test_region_list_size) {
+    aggregated_hit_test_region_list_ = aggregated_hit_test_region_list;
+    aggregated_hit_test_region_list_size_ =
+        aggregated_hit_test_region_list_size;
+  }
+
+  // Finds Target for |location_in_root|, including the FrameSinkId of the
+  // target, updated location in the coordinate system of the target and
+  // hit-test flags for the target.
+  // Assumptions about the AggregatedHitTestRegion list received.
+  // 1. The list is in ascending (front to back) z-order.
+  // 2. Children count includes children of children.
+  // 3. After applying transform to the incoming point, point is in the same
+  // coordinate system as the bounds it is comparing against.
+  // For example,
+  //  +e-------------+
+  //  |   +c---------|
+  //  | 1 |+a--+     |
+  //  |   || 2 |     |
+  //  |   |+b--------|
+  //  |   ||         |
+  //  |   ||   3     |
+  //  +--------------+
+  // In this case, after applying identity transform, 1 is in the coordinate
+  // system of e; apply the transfrom-from-e-to-c and transform-from-c-to-a
+  // then we get 2 in the coordinate system of a; apply the
+  // transfrom-from-e-to-c and transform-from-c-to-b then we get 3 in the
+  // coordinate system of b.
+  Target FindTargetForLocation(const gfx::Point& location_in_root) const;
+
+ private:
+  // Helper function to find |target| for |location_in_parent| in the |region|,
+  // returns true if a target is found and false otherwise. |location_in_parent|
+  // is in the coordinate space of |region|'s parent.
+  bool FindTargetInRegionForLocation(const gfx::Point& location_in_parent,
+                                     AggregatedHitTestRegion* region,
+                                     Target* target) const;
+
+  AggregatedHitTestRegion* aggregated_hit_test_region_list_ = nullptr;
+  uint32_t aggregated_hit_test_region_list_size_ = 0;
+
+  DISALLOW_COPY_AND_ASSIGN(HitTestQuery);
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_HOST_HIT_TEST_HIT_TEST_QUERY_H_
diff --git a/components/viz/host/hit_test/hit_test_query_unittest.cc b/components/viz/host/hit_test/hit_test_query_unittest.cc
new file mode 100644
index 0000000..1f9eadc
--- /dev/null
+++ b/components/viz/host/hit_test/hit_test_query_unittest.cc
@@ -0,0 +1,627 @@
+// 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 <cstdint>
+
+#include "services/viz/hit_test/public/interfaces/hit_test_region_list.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace viz {
+namespace test {
+
+class HitTestQueryTest : public testing::Test {
+ public:
+  HitTestQueryTest() = default;
+  ~HitTestQueryTest() override = default;
+
+  HitTestQuery hit_test_query_;
+
+ private:
+  // testing::Test:
+  void SetUp() override {}
+  void TearDown() override {}
+
+  DISALLOW_COPY_AND_ASSIGN(HitTestQueryTest);
+};
+
+// One surface.
+//
+//  +e---------+
+//  |          |
+//  |          |
+//  |          |
+//  +----------+
+//
+TEST_F(HitTestQueryTest, OneSurface) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  gfx::Rect e_bounds = gfx::Rect(0, 0, 600, 600);
+  gfx::Transform transform_e_to_e;
+  AggregatedHitTestRegion aggregated_hit_test_region_list[1] = {
+      {e_id, mojom::kHitTestMine, e_bounds, transform_e_to_e, 0}  // e
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list, 1);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(600, 600);
+  gfx::Point point3(0, 0);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_id, target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  // point2 is on the bounds of e so no target found.
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(FrameSinkId(), target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target2.location_in_target);
+  EXPECT_FALSE(target2.flags);
+
+  // There's a valid Target for point3, see Rect::Contains.
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(e_id, target3.frame_sink_id);
+  EXPECT_EQ(point3, target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
+}
+
+// One embedder with two children.
+//
+//  +e------------+     Point   maps to
+//  | +c1-+ +c2---|     -----   -------
+//  |1|   | |     |      1        e
+//  | | 2 | | 3   | 4    2        c1
+//  | +---+ |     |      3        c2
+//  +-------------+      4        none
+//
+TEST_F(HitTestQueryTest, OneEmbedderTwoChildren) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  FrameSinkId c1_id = FrameSinkId(2, 2);
+  FrameSinkId c2_id = FrameSinkId(3, 3);
+  gfx::Rect e_bounds_in_e = gfx::Rect(0, 0, 600, 600);
+  gfx::Rect c1_bounds_in_e = gfx::Rect(0, 0, 200, 200);
+  gfx::Rect c2_bounds_in_e = gfx::Rect(0, 0, 400, 400);
+  gfx::Transform transform_e_to_e, transform_e_to_c1, transform_e_to_c2;
+  transform_e_to_c1.Translate(-100, -100);
+  transform_e_to_c2.Translate(-300, -300);
+  AggregatedHitTestRegion aggregated_hit_test_region_list[3] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 2},     // e
+      {c1_id, mojom::kHitTestMine, c1_bounds_in_e, transform_e_to_c1, 0},  // c1
+      {c2_id, mojom::kHitTestMine, c2_bounds_in_e, transform_e_to_c2, 0}   // c2
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list, 3);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(99, 200);
+  gfx::Point point2(150, 150);
+  gfx::Point point3(400, 400);
+  gfx::Point point4(650, 350);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_id, target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(c1_id, target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 50), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(c2_id, target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(100, 100), target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(FrameSinkId(), target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target4.location_in_target);
+  EXPECT_FALSE(target4.flags);
+}
+
+// One embedder with a rotated child.
+TEST_F(HitTestQueryTest, OneEmbedderRotatedChild) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  FrameSinkId c_id = FrameSinkId(2, 2);
+  gfx::Rect e_bounds_in_e = gfx::Rect(0, 0, 600, 600);
+  gfx::Rect c_bounds_in_e = gfx::Rect(0, 0, 1000, 1000);
+  gfx::Transform transform_e_to_e, transform_e_to_c;
+  transform_e_to_c.Translate(-100, -100);
+  transform_e_to_c.Skew(2, 3);
+  transform_e_to_c.Scale(.5f, .7f);
+
+  AggregatedHitTestRegion aggregated_hit_test_region_list[2] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 1},  // e
+      {c_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, c_bounds_in_e,
+       transform_e_to_c, 0}  // c
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list, 2);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(150, 120);  // Point(-22, -12) after transform.
+  gfx::Point point2(550, 400);  // Point(185, 194) after transform.
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_id, target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(c_id, target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(185, 194), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+}
+
+// One embedder with a clipped child with a tab and transparent background.
+//
+//  +e-------------+
+//  |   +c---------|     Point   maps to
+//  | 1 |+a--+     |     -----   -------
+//  |   || 2 |  3  |       1        e
+//  |   |+b--------|       2        a
+//  |   ||         |       3        e ( transparent area in c )
+//  |   ||   4     |       4        b
+//  +--------------+
+//
+TEST_F(HitTestQueryTest, ClippedChildWithTabAndTransparentBackground) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  FrameSinkId c_id = FrameSinkId(2, 2);
+  FrameSinkId a_id = FrameSinkId(3, 3);
+  FrameSinkId b_id = FrameSinkId(4, 4);
+  gfx::Rect e_bounds_in_e = gfx::Rect(0, 0, 600, 600);
+  gfx::Rect c_bounds_in_e = gfx::Rect(0, 0, 800, 800);
+  gfx::Rect a_bounds_in_c = gfx::Rect(0, 0, 200, 100);
+  gfx::Rect b_bounds_in_c = gfx::Rect(0, 0, 800, 600);
+  gfx::Transform transform_e_to_e, transform_e_to_c, transform_c_to_a,
+      transform_c_to_b;
+  transform_e_to_c.Translate(-200, -100);
+  transform_c_to_b.Translate(0, -100);
+  AggregatedHitTestRegion aggregated_hit_test_region_list[4] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 3},  // e
+      {c_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore, c_bounds_in_e,
+       transform_e_to_c, 2},  // c
+      {a_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, a_bounds_in_c,
+       transform_c_to_a, 0},  // a
+      {b_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, b_bounds_in_c,
+       transform_c_to_b, 0}  // b
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list, 4);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(202, 102);
+  gfx::Point point3(403, 103);
+  gfx::Point point4(202, 202);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_id, target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(a_id, target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(2, 2), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(e_id, target3.frame_sink_id);
+  EXPECT_EQ(point3, target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(b_id, target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(2, 2), target4.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target4.flags);
+}
+
+// One embedder with a clipped child with a tab and transparent background, and
+// a child d under that.
+//
+//  +e-------------+
+//  |      +d------|
+//  |   +c-|-------|     Point   maps to
+//  | 1 |+a|-+     |     -----   -------
+//  |   || 2 |  3  |       1        e
+//  |   |+b|-------|       2        a
+//  |   || |       |       3        d
+//  |   || | 4     |       4        b
+//  +--------------+
+//
+TEST_F(HitTestQueryTest, ClippedChildWithChildUnderneath) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  FrameSinkId c_id = FrameSinkId(2, 2);
+  FrameSinkId a_id = FrameSinkId(3, 3);
+  FrameSinkId b_id = FrameSinkId(4, 4);
+  FrameSinkId d_id = FrameSinkId(5, 5);
+  gfx::Rect e_bounds_in_e = gfx::Rect(0, 0, 600, 600);
+  gfx::Rect c_bounds_in_e = gfx::Rect(0, 0, 800, 800);
+  gfx::Rect a_bounds_in_c = gfx::Rect(0, 0, 200, 100);
+  gfx::Rect b_bounds_in_c = gfx::Rect(0, 0, 800, 600);
+  gfx::Rect d_bounds_in_e = gfx::Rect(0, 0, 800, 800);
+  gfx::Transform transform_e_to_e, transform_e_to_c, transform_c_to_a,
+      transform_c_to_b, transform_e_to_d;
+  transform_e_to_c.Translate(-200, -100);
+  transform_c_to_b.Translate(0, -100);
+  transform_e_to_d.Translate(-400, -50);
+  AggregatedHitTestRegion aggregated_hit_test_region_list[5] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 4},  // e
+      {c_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore, c_bounds_in_e,
+       transform_e_to_c, 2},  // c
+      {a_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, a_bounds_in_c,
+       transform_c_to_a, 0},  // a
+      {b_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, b_bounds_in_c,
+       transform_c_to_b, 0},  // b
+      {d_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, d_bounds_in_e,
+       transform_e_to_d, 0}  // d
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list, 5);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(202, 102);
+  gfx::Point point3(450, 150);
+  gfx::Point point4(202, 202);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_id, target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(a_id, target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(2, 2), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(d_id, target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 100), target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(b_id, target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(2, 2), target4.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target4.flags);
+}
+
+// One embedder with two clipped children with a tab and transparent background.
+//
+//  +e-------------+
+//  |   +c1--------|     Point   maps to
+//  | 1 |+a--+     |     -----   -------
+//  |   || 2 |  3  |       1        e
+//  |   |+b--------|       2        a
+//  |   ||         |       3        e ( transparent area in c1 )
+//  |   ||   4     |       4        b
+//  |   +----------|       5        g
+//  |   +c2--------|       6        e ( transparent area in c2 )
+//  |   |+g--+     |       7        h
+//  |   || 5 |  6  |
+//  |   |+h--------|
+//  |   ||         |
+//  |   ||   7     |
+//  +--------------+
+//
+TEST_F(HitTestQueryTest, ClippedChildrenWithTabAndTransparentBackground) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  FrameSinkId c1_id = FrameSinkId(2, 2);
+  FrameSinkId a_id = FrameSinkId(3, 3);
+  FrameSinkId b_id = FrameSinkId(4, 4);
+  FrameSinkId c2_id = FrameSinkId(5, 5);
+  FrameSinkId g_id = FrameSinkId(6, 6);
+  FrameSinkId h_id = FrameSinkId(7, 7);
+  gfx::Rect e_bounds_in_e = gfx::Rect(0, 0, 600, 1200);
+  gfx::Rect c1_bounds_in_e = gfx::Rect(0, 0, 800, 500);
+  gfx::Rect a_bounds_in_c1 = gfx::Rect(0, 0, 200, 100);
+  gfx::Rect b_bounds_in_c1 = gfx::Rect(0, 0, 800, 400);
+  gfx::Rect c2_bounds_in_e = gfx::Rect(0, 0, 800, 500);
+  gfx::Rect g_bounds_in_c2 = gfx::Rect(0, 0, 200, 100);
+  gfx::Rect h_bounds_in_c2 = gfx::Rect(0, 0, 800, 800);
+  gfx::Transform transform_e_to_e, transform_e_to_c1, transform_c1_to_a,
+      transform_c1_to_b, transform_e_to_c2, transform_c2_to_g,
+      transform_c2_to_h;
+  transform_e_to_c1.Translate(-200, -100);
+  transform_c1_to_b.Translate(0, -100);
+  transform_e_to_c2.Translate(-200, -700);
+  transform_c2_to_h.Translate(0, -100);
+  AggregatedHitTestRegion aggregated_hit_test_region_list[7] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 6},  // e
+      {c1_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore,
+       c1_bounds_in_e, transform_e_to_c1, 2},  // c1
+      {a_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, a_bounds_in_c1,
+       transform_c1_to_a, 0},  // a
+      {b_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, b_bounds_in_c1,
+       transform_c1_to_b, 0},  // b
+      {c2_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore,
+       c2_bounds_in_e, transform_e_to_c2, 2},  // c2
+      {g_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, g_bounds_in_c2,
+       transform_c2_to_g, 0},  // g
+      {h_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, h_bounds_in_c2,
+       transform_c2_to_h, 0}  // h
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list, 7);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(202, 102);
+  gfx::Point point3(403, 103);
+  gfx::Point point4(202, 202);
+  gfx::Point point5(250, 750);
+  gfx::Point point6(450, 750);
+  gfx::Point point7(350, 1100);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_id, target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(a_id, target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(2, 2), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(e_id, target3.frame_sink_id);
+  EXPECT_EQ(point3, target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(b_id, target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(2, 2), target4.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target4.flags);
+
+  Target target5 = hit_test_query_.FindTargetForLocation(point5);
+  EXPECT_EQ(g_id, target5.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 50), target5.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target5.flags);
+
+  Target target6 = hit_test_query_.FindTargetForLocation(point6);
+  EXPECT_EQ(e_id, target6.frame_sink_id);
+  EXPECT_EQ(point6, target6.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target6.flags);
+
+  Target target7 = hit_test_query_.FindTargetForLocation(point7);
+  EXPECT_EQ(h_id, target7.frame_sink_id);
+  EXPECT_EQ(gfx::Point(150, 300), target7.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target7.flags);
+}
+
+// Children that are multiple layers deep.
+//
+//  +e--------------------+
+//  |          +c2--------|     Point   maps to
+//  | +c1------|----+     |     -----   -------
+//  |1| +a-----|---+|     |       1        e
+//  | | |+b----|--+||     |       2        g
+//  | | ||+g--+|  |||     |       3        b
+//  | | ||| 2 || 3|||  4  |       4        c2
+//  | | ||+---+|  |||     |
+//  | | |+-----|--+||     |
+//  | | +------| --+|     |
+//  | +--------|----+     |
+//  +---------------------+
+//
+TEST_F(HitTestQueryTest, MultipleLayerChild) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  FrameSinkId c1_id = FrameSinkId(2, 2);
+  FrameSinkId a_id = FrameSinkId(3, 3);
+  FrameSinkId b_id = FrameSinkId(4, 4);
+  FrameSinkId g_id = FrameSinkId(5, 5);
+  FrameSinkId c2_id = FrameSinkId(6, 6);
+  gfx::Rect e_bounds_in_e = gfx::Rect(0, 0, 1000, 1000);
+  gfx::Rect c1_bounds_in_e = gfx::Rect(0, 0, 800, 800);
+  gfx::Rect a_bounds_in_c1 = gfx::Rect(0, 0, 700, 700);
+  gfx::Rect b_bounds_in_a = gfx::Rect(0, 0, 600, 600);
+  gfx::Rect g_bounds_in_b = gfx::Rect(0, 0, 200, 200);
+  gfx::Rect c2_bounds_in_e = gfx::Rect(0, 0, 800, 800);
+  gfx::Transform transform_e_to_e, transform_e_to_c1, transform_c1_to_a,
+      transform_a_to_b, transform_b_to_g, transform_e_to_c2;
+  transform_e_to_c1.Translate(-100, -100);
+  transform_a_to_b.Translate(-50, -30);
+  transform_b_to_g.Translate(-150, -200);
+  transform_e_to_c2.Translate(-400, -50);
+  AggregatedHitTestRegion aggregated_hit_test_region_list[6] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 5},  // e
+      {c1_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore,
+       c1_bounds_in_e, transform_e_to_c1, 3},  // c1
+      {a_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, a_bounds_in_c1,
+       transform_c1_to_a, 2},  // a
+      {b_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, b_bounds_in_a,
+       transform_a_to_b, 1},  // b
+      {g_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, g_bounds_in_b,
+       transform_b_to_g, 0},  // g
+      {c2_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, c2_bounds_in_e,
+       transform_e_to_c2, 0}  // c2
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list, 6);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(300, 350);
+  gfx::Point point3(550, 350);
+  gfx::Point point4(900, 350);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_id, target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(g_id, target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(0, 20), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(b_id, target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(400, 220), target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(c2_id, target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(500, 300), target4.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target4.flags);
+}
+
+// Multiple layers deep of transparent children.
+//
+//  +e--------------------+
+//  |          +c2--------|     Point   maps to
+//  | +c1------|----+     |     -----   -------
+//  |1| +a-----|---+|     |       1        e
+//  | | |+b----|--+||     |       2        e
+//  | | ||+g--+|  |||     |       3        c2
+//  | | ||| 2 || 3|||  4  |       4        c2
+//  | | ||+---+|  |||     |
+//  | | |+-----|--+||     |
+//  | | +------| --+|     |
+//  | +--------|----+     |
+//  +---------------------+
+//
+TEST_F(HitTestQueryTest, MultipleLayerTransparentChild) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  FrameSinkId c1_id = FrameSinkId(2, 2);
+  FrameSinkId a_id = FrameSinkId(3, 3);
+  FrameSinkId b_id = FrameSinkId(4, 4);
+  FrameSinkId g_id = FrameSinkId(5, 5);
+  FrameSinkId c2_id = FrameSinkId(6, 6);
+  gfx::Rect e_bounds_in_e = gfx::Rect(0, 0, 1000, 1000);
+  gfx::Rect c1_bounds_in_e = gfx::Rect(0, 0, 800, 800);
+  gfx::Rect a_bounds_in_c1 = gfx::Rect(0, 0, 700, 700);
+  gfx::Rect b_bounds_in_a = gfx::Rect(0, 0, 600, 600);
+  gfx::Rect g_bounds_in_b = gfx::Rect(0, 0, 200, 200);
+  gfx::Rect c2_bounds_in_e = gfx::Rect(0, 0, 800, 800);
+  gfx::Transform transform_e_to_e, transform_e_to_c1, transform_c1_to_a,
+      transform_a_to_b, transform_b_to_g, transform_e_to_c2;
+  transform_e_to_c1.Translate(-100, -100);
+  transform_a_to_b.Translate(-50, -30);
+  transform_b_to_g.Translate(-150, -200);
+  transform_e_to_c2.Translate(-400, -50);
+  AggregatedHitTestRegion aggregated_hit_test_region_list[6] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 5},  // e
+      {c1_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore,
+       c1_bounds_in_e, transform_e_to_c1, 3},  // c1
+      {a_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore,
+       a_bounds_in_c1, transform_c1_to_a, 2},  // a
+      {b_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore, b_bounds_in_a,
+       transform_a_to_b, 1},  // b
+      {g_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore, g_bounds_in_b,
+       transform_b_to_g, 0},  // g
+      {c2_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, c2_bounds_in_e,
+       transform_e_to_c2, 0}  // c2
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list, 6);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(300, 350);
+  gfx::Point point3(450, 350);
+  gfx::Point point4(900, 350);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_id, target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_TRUE(target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(e_id, target2.frame_sink_id);
+  EXPECT_EQ(point2, target2.location_in_target);
+  EXPECT_TRUE(target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(c2_id, target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 300), target3.location_in_target);
+  EXPECT_TRUE(target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(c2_id, target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(500, 300), target4.location_in_target);
+  EXPECT_TRUE(target4.flags);
+}
+
+TEST_F(HitTestQueryTest, InvalidAggregatedHitTestRegionData) {
+  FrameSinkId e_id = FrameSinkId(1, 1);
+  FrameSinkId c_id = FrameSinkId(2, 2);
+  FrameSinkId a_id = FrameSinkId(3, 3);
+  FrameSinkId b_id = FrameSinkId(4, 4);
+  gfx::Rect e_bounds_in_e = gfx::Rect(0, 0, 600, 600);
+  gfx::Rect c_bounds_in_e = gfx::Rect(0, 0, 800, 800);
+  gfx::Rect a_bounds_in_c = gfx::Rect(0, 0, 200, 100);
+  gfx::Rect b_bounds_in_c = gfx::Rect(0, 0, 800, 600);
+  gfx::Transform transform_e_to_e, transform_e_to_c, transform_c_to_a,
+      transform_c_to_b;
+  transform_e_to_c.Translate(-200, -100);
+  transform_c_to_b.Translate(0, -100);
+  AggregatedHitTestRegion aggregated_hit_test_region_list_min[4] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 3},  // e
+      {c_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore, c_bounds_in_e,
+       transform_e_to_c, INT32_MIN},  // c
+      {a_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, a_bounds_in_c,
+       transform_c_to_a, 0},  // a
+      {b_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, b_bounds_in_c,
+       transform_c_to_b, 0}  // b
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list_min, 4);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(202, 102);
+
+  // |child_count| is invalid, which is a security fault. For now, check to see
+  // if the returned Target is invalid.
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(FrameSinkId(), target1.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target1.location_in_target);
+  EXPECT_FALSE(target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(FrameSinkId(), target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target2.location_in_target);
+  EXPECT_FALSE(target2.flags);
+
+  AggregatedHitTestRegion aggregated_hit_test_region_list_max[4] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e,
+       INT32_MAX},  // e
+      {c_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore, c_bounds_in_e,
+       transform_e_to_c, 2},  // c
+      {a_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, a_bounds_in_c,
+       transform_c_to_a, 0},  // a
+      {b_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, b_bounds_in_c,
+       transform_c_to_b, 0}  // b
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list_max, 4);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(FrameSinkId(), target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target3.location_in_target);
+  EXPECT_FALSE(target3.flags);
+
+  AggregatedHitTestRegion aggregated_hit_test_region_list_bigger[4] = {
+      {e_id, mojom::kHitTestMine, e_bounds_in_e, transform_e_to_e, 3},  // e
+      {c_id, mojom::kHitTestChildSurface | mojom::kHitTestIgnore, c_bounds_in_e,
+       transform_e_to_c, 3},  // c
+      {a_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, a_bounds_in_c,
+       transform_c_to_a, 0},  // a
+      {b_id, mojom::kHitTestChildSurface | mojom::kHitTestMine, b_bounds_in_c,
+       transform_c_to_b, 0}  // b
+  };
+  hit_test_query_.set_aggregated_hit_test_region_list(
+      aggregated_hit_test_region_list_bigger, 4);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(FrameSinkId(), target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target4.location_in_target);
+  EXPECT_FALSE(target4.flags);
+}
+
+}  // namespace test
+}  // namespace viz
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 470e09b9b..3c7c093 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -155,6 +155,7 @@
     "//base/test:test_support",
     "//cc:test_support",
     "//components/viz/common",
+    "//components/viz/host/hit_test",
     "//components/viz/test:test_support",
     "//gpu/command_buffer/client",
     "//gpu/command_buffer/client:gles2_implementation",
diff --git a/components/viz/service/hit_test/DEPS b/components/viz/service/hit_test/DEPS
index e207341..76ad8ea2 100644
--- a/components/viz/service/hit_test/DEPS
+++ b/components/viz/service/hit_test/DEPS
@@ -1,5 +1,10 @@
 include_rules = [
-  "+components/viz/common",
   "+cc/surfaces/surface_observer.h",
   "+services/viz/hit_test/public/interfaces",
 ]
+
+specific_include_rules = {
+  "hit_test_aggregator_unittest.cc": [
+    "+components/viz/host/hit_test/hit_test_query.h",
+  ]
+}
diff --git a/components/viz/service/hit_test/hit_test_aggregator_unittest.cc b/components/viz/service/hit_test/hit_test_aggregator_unittest.cc
index 4a23b4d..8d1cc5e 100644
--- a/components/viz/service/hit_test/hit_test_aggregator_unittest.cc
+++ b/components/viz/service/hit_test/hit_test_aggregator_unittest.cc
@@ -7,6 +7,7 @@
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/local_surface_id.h"
 #include "components/viz/common/surfaces/surface_id.h"
+#include "components/viz/host/hit_test/hit_test_query.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace viz {
@@ -75,8 +76,6 @@
   void SetUp() override {}
   void TearDown() override { aggregator_.Reset(); }
 
-  TestHitTestAggregator aggregator_;
-
   // Creates a hit test data element with 8 children recursively to
   // the specified depth.  SurfaceIds are generated in sequential order and
   // the method returns the next unused id.
@@ -106,6 +105,9 @@
     aggregator_.SubmitHitTestRegionList(std::move(hit_test_region_list));
     return id;
   }
+
+  TestHitTestAggregator aggregator_;
+  HitTestQuery hit_test_query_;
 };
 
 // TODO(gklassen): Add tests for 3D use cases as suggested by and with
@@ -155,6 +157,30 @@
   EXPECT_EQ(display_surface_id.frame_sink_id(), region->frame_sink_id);
   EXPECT_EQ(gfx::Rect(0, 0, 1024, 768), region->rect);
   EXPECT_EQ(0, region->child_count);
+
+  hit_test_query_.set_aggregated_hit_test_region_list(regions, 1);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(1024, 768);
+  gfx::Point point3(0, 0);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(display_surface_id.frame_sink_id(), target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  // point2 is on the bounds of e so no target found.
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(FrameSinkId(), target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target2.location_in_target);
+  EXPECT_FALSE(target2.flags);
+
+  // There's a valid Target for point3, see Rect::Contains.
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(display_surface_id.frame_sink_id(), target3.frame_sink_id);
+  EXPECT_EQ(point3, target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
 }
 
 // One opaque embedder with two regions.
@@ -177,10 +203,12 @@
   e_hit_test_data->bounds.SetRect(0, 0, 1024, 768);
 
   auto e_hit_test_region_r1 = mojom::HitTestRegion::New();
+  e_hit_test_region_r1->surface_id = e_surface_id;
   e_hit_test_region_r1->flags = mojom::kHitTestMine;
   e_hit_test_region_r1->rect.SetRect(100, 100, 200, 400);
 
   auto e_hit_test_region_r2 = mojom::HitTestRegion::New();
+  e_hit_test_region_r2->surface_id = e_surface_id;
   e_hit_test_region_r2->flags = mojom::kHitTestMine;
   e_hit_test_region_r2->rect.SetRect(400, 100, 300, 400);
 
@@ -229,6 +257,34 @@
   EXPECT_EQ(mojom::kHitTestMine, region->flags);
   EXPECT_EQ(gfx::Rect(400, 100, 300, 400), region->rect);
   EXPECT_EQ(0, region->child_count);
+
+  hit_test_query_.set_aggregated_hit_test_region_list(regions, 3);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(99, 200);
+  gfx::Point point2(150, 150);
+  gfx::Point point3(400, 400);
+  gfx::Point point4(1200, 350);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 50), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(0, 300), target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(FrameSinkId(), target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target4.location_in_target);
+  EXPECT_FALSE(target4.flags);
 }
 
 // One embedder with two children.
@@ -330,6 +386,35 @@
   EXPECT_EQ(c2_surface_id.frame_sink_id(), region->frame_sink_id);
   EXPECT_EQ(gfx::Rect(400, 100, 400, 300), region->rect);
   EXPECT_EQ(0, region->child_count);
+
+  hit_test_query_.set_aggregated_hit_test_region_list(regions, 3);
+
+  // All points are in e's coordinate system when we reach this case.
+  // They all go to e since c1 and c2 cannot receive events.
+  gfx::Point point1(99, 200);
+  gfx::Point point2(150, 150);
+  gfx::Point point3(400, 400);
+  gfx::Point point4(1200, 350);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target2.frame_sink_id);
+  EXPECT_EQ(point2, target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target3.frame_sink_id);
+  EXPECT_EQ(point3, target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(FrameSinkId(), target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(), target4.location_in_target);
+  EXPECT_FALSE(target4.flags);
 }
 
 // Occluded child frame (OOPIF).
@@ -424,6 +509,28 @@
   EXPECT_EQ(c_surface_id.frame_sink_id(), region->frame_sink_id);
   EXPECT_EQ(gfx::Rect(100, 100, 200, 500), region->rect);
   EXPECT_EQ(0, region->child_count);
+
+  hit_test_query_.set_aggregated_hit_test_region_list(regions, 3);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(99, 200);
+  gfx::Point point2(150, 150);
+  gfx::Point point3(250, 250);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(c_surface_id.frame_sink_id(), target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 50), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 50), target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
 }
 
 // Foreground child frame (OOPIF).
@@ -519,6 +626,34 @@
   EXPECT_EQ(e_surface_id.frame_sink_id(), region->frame_sink_id);
   EXPECT_EQ(gfx::Rect(200, 200, 300, 200), region->rect);
   EXPECT_EQ(0, region->child_count);
+
+  hit_test_query_.set_aggregated_hit_test_region_list(regions, 3);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(99, 200);
+  gfx::Point point2(150, 150);
+  gfx::Point point3(250, 250);
+  gfx::Point point4(350, 300);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(c_surface_id.frame_sink_id(), target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 50), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(c_surface_id.frame_sink_id(), target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(150, 150), target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(150, 100), target4.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target4.flags);
 }
 
 // One embedder with a clipped child with a tab and transparent background.
@@ -549,7 +684,7 @@
   auto e_hit_test_region_c = mojom::HitTestRegion::New();
   e_hit_test_region_c->flags = mojom::kHitTestChildSurface;
   e_hit_test_region_c->surface_id = c_surface_id;
-  e_hit_test_region_c->rect.SetRect(200, 100, 1600, 800);
+  e_hit_test_region_c->rect.SetRect(300, 100, 1600, 800);
   e_hit_test_region_c->transform.Translate(200, 100);
 
   e_hit_test_data->regions.push_back(std::move(e_hit_test_region_c));
@@ -638,7 +773,7 @@
   region = &regions[1];
   EXPECT_EQ(region->flags, mojom::kHitTestChildSurface | mojom::kHitTestIgnore);
   EXPECT_EQ(c_surface_id.frame_sink_id(), region->frame_sink_id);
-  EXPECT_EQ(gfx::Rect(200, 100, 1600, 800), region->rect);
+  EXPECT_EQ(gfx::Rect(300, 100, 1600, 800), region->rect);
   EXPECT_EQ(2, region->child_count);
 
   gfx::Point point(300, 300);
@@ -656,6 +791,34 @@
   EXPECT_EQ(b_surface_id.frame_sink_id(), region->frame_sink_id);
   EXPECT_EQ(gfx::Rect(0, 100, 800, 600), region->rect);
   EXPECT_EQ(0, region->child_count);
+
+  hit_test_query_.set_aggregated_hit_test_region_list(regions, 4);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(100, 50);
+  gfx::Point point3(400, 70);
+  gfx::Point point4(200, 200);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(a_surface_id.frame_sink_id(), target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(0, 50), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target3.frame_sink_id);
+  EXPECT_EQ(point3, target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(b_surface_id.frame_sink_id(), target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(100, 100), target4.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target4.flags);
 }
 
 // Three children deep.
@@ -790,6 +953,34 @@
   EXPECT_EQ(c3_surface_id.frame_sink_id(), region->frame_sink_id);
   EXPECT_EQ(gfx::Rect(100, 100, 300, 300), region->rect);
   EXPECT_EQ(0, region->child_count);
+
+  hit_test_query_.set_aggregated_hit_test_region_list(regions, 4);
+
+  // All points are in e's coordinate system when we reach this case.
+  gfx::Point point1(1, 1);
+  gfx::Point point2(100, 150);
+  gfx::Point point3(250, 450);
+  gfx::Point point4(450, 550);
+
+  Target target1 = hit_test_query_.FindTargetForLocation(point1);
+  EXPECT_EQ(e_surface_id.frame_sink_id(), target1.frame_sink_id);
+  EXPECT_EQ(point1, target1.location_in_target);
+  EXPECT_EQ(mojom::kHitTestMine, target1.flags);
+
+  Target target2 = hit_test_query_.FindTargetForLocation(point2);
+  EXPECT_EQ(c3_surface_id.frame_sink_id(), target2.frame_sink_id);
+  EXPECT_EQ(gfx::Point(0, 50), target2.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target2.flags);
+
+  Target target3 = hit_test_query_.FindTargetForLocation(point3);
+  EXPECT_EQ(c2_surface_id.frame_sink_id(), target3.frame_sink_id);
+  EXPECT_EQ(gfx::Point(50, 250), target3.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target3.flags);
+
+  Target target4 = hit_test_query_.FindTargetForLocation(point4);
+  EXPECT_EQ(c1_surface_id.frame_sink_id(), target4.frame_sink_id);
+  EXPECT_EQ(gfx::Point(150, 250), target4.location_in_target);
+  EXPECT_EQ(mojom::kHitTestChildSurface | mojom::kHitTestMine, target4.flags);
 }
 
 // Missing / late child.