Add TransformedHitTestLocation helper

This moves code for transforming a HitTestLocation using with an
AffineTransform from the helper function
SVGLayoutSupport::TransformToUserSpaceAndCheckClipping into a helper
class TransformedHitTestLocation.

Calls to SVGLayoutSupport::IntersectsClipPath are hoisted into callers
and SVGLayoutSupport::TransformToUserSpaceAndCheckClipping is removed.

Change-Id: I52f95ba2480df86a509285c2ec2edea776517bc6
Reviewed-on: https://chromium-review.googlesource.com/c/1282930
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
Commit-Queue: Fredrik Söderquist <fs@opera.com>
Cr-Commit-Position: refs/heads/master@{#601099}
diff --git a/third_party/blink/renderer/core/layout/svg/BUILD.gn b/third_party/blink/renderer/core/layout/svg/BUILD.gn
index 470debe..8d013890 100644
--- a/third_party/blink/renderer/core/layout/svg/BUILD.gn
+++ b/third_party/blink/renderer/core/layout/svg/BUILD.gn
@@ -91,5 +91,7 @@
     "svg_text_metrics.h",
     "svg_text_query.cc",
     "svg_text_query.h",
+    "transformed_hit_test_location.cc",
+    "transformed_hit_test_location.h",
   ]
 }
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_container.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_container.cc
index 23ec081..c2de62b 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_container.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_container.cc
@@ -28,6 +28,7 @@
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
+#include "third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h"
 #include "third_party/blink/renderer/core/paint/svg_container_painter.h"
 
 namespace blink {
@@ -179,13 +180,12 @@
     const LayoutPoint& accumulated_offset,
     HitTestAction hit_test_action) {
   DCHECK_EQ(accumulated_offset, LayoutPoint());
-  base::Optional<HitTestLocation> local_storage;
-  const HitTestLocation* local_location =
-      SVGLayoutSupport::TransformToUserSpaceAndCheckClipping(
-          *this, LocalToSVGParentTransform(), location_in_container,
-          local_storage);
+  TransformedHitTestLocation local_location(location_in_container,
+                                            LocalToSVGParentTransform());
   if (!local_location)
     return false;
+  if (!SVGLayoutSupport::IntersectsClipPath(*this, *local_location))
+    return false;
 
   if (SVGLayoutSupport::HitTestChildren(LastChild(), result, *local_location,
                                         accumulated_offset, hit_test_action))
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
index 4ef7808..cd3401b 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
+#include "third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/svg_foreign_object_painter.h"
 #include "third_party/blink/renderer/core/svg/svg_foreign_object_element.h"
@@ -130,23 +131,13 @@
     const LayoutPoint& accumulated_offset,
     HitTestAction) {
   DCHECK_EQ(accumulated_offset, LayoutPoint());
-  AffineTransform local_transform = LocalSVGTransform();
-  if (!local_transform.IsInvertible())
+  TransformedHitTestLocation local_location(location_in_parent,
+                                            LocalSVGTransform());
+  if (!local_location)
     return false;
 
-  AffineTransform inverse = local_transform.Inverse();
-  base::Optional<HitTestLocation> local_location;
-  if (location_in_parent.IsRectBasedTest()) {
-    local_location.emplace(
-        inverse.MapPoint(location_in_parent.TransformedPoint()),
-        inverse.MapQuad(location_in_parent.TransformedRect()));
-  } else {
-    local_location.emplace(
-        (inverse.MapPoint(location_in_parent.TransformedPoint())));
-  }
-
-  // |local_point| already includes the offset of the <foreignObject> element,
-  // but PaintLayer::HitTestLayer assumes it has not been.
+  // |local_location| already includes the offset of the <foreignObject>
+  // element, but PaintLayer::HitTestLayer assumes it has not been.
   HitTestLocation local_without_offset(
       *local_location, -ToLayoutSize(Layer()->LayoutBoxLocation()));
   HitTestResult layer_result(result.GetHitTestRequest(), local_without_offset);
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_image.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_image.cc
index 1db7446..12f9d1c 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_image.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_image.cc
@@ -34,6 +34,7 @@
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
+#include "third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h"
 #include "third_party/blink/renderer/core/paint/svg_image_painter.h"
 #include "third_party/blink/renderer/core/svg/svg_image_element.h"
 #include "third_party/blink/renderer/platform/geometry/length_functions.h"
@@ -170,7 +171,7 @@
                                  const HitTestLocation& location_in_container,
                                  const LayoutPoint& accumulated_offset,
                                  HitTestAction hit_test_action) {
-  DCHECK(accumulated_offset == LayoutPoint());
+  DCHECK_EQ(accumulated_offset, LayoutPoint());
   // We only draw in the forground phase, so we only hit-test then.
   if (hit_test_action != kHitTestForeground)
     return false;
@@ -182,13 +183,12 @@
   if (hit_rules.require_visible && style.Visibility() != EVisibility::kVisible)
     return false;
 
-  base::Optional<HitTestLocation> local_storage;
-  const HitTestLocation* local_location =
-      SVGLayoutSupport::TransformToUserSpaceAndCheckClipping(
-          *this, LocalToSVGParentTransform(), location_in_container,
-          local_storage);
+  TransformedHitTestLocation local_location(location_in_container,
+                                            LocalToSVGParentTransform());
   if (!local_location)
     return false;
+  if (!SVGLayoutSupport::IntersectsClipPath(*this, *local_location))
+    return false;
 
   if (hit_rules.can_hit_fill || hit_rules.can_hit_bounding_box) {
     if (local_location->Intersects(object_bounding_box_)) {
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_root.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_root.cc
index b016e40..12f9895a 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_root.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_root.cc
@@ -35,6 +35,7 @@
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
+#include "third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/svg_root_painter.h"
 #include "third_party/blink/renderer/core/svg/graphics/svg_image.h"
@@ -523,23 +524,9 @@
       (local_border_box_location.Intersects(PhysicalContentBoxRect()) ||
        (!ShouldApplyViewportClip() &&
         local_border_box_location.Intersects(VisualOverflowRect())))) {
-    const AffineTransform& local_to_border_box_transform =
-        LocalToBorderBoxTransform();
-    if (local_to_border_box_transform.IsInvertible()) {
-      AffineTransform inverse = local_to_border_box_transform.Inverse();
-      FloatPoint local_point =
-          inverse.MapPoint(local_border_box_location.TransformedPoint());
-
-      base::Optional<HitTestLocation> local_location;
-      if (location_in_container.IsRectBasedTest()) {
-        FloatQuad quad_in_container =
-            local_border_box_location.TransformedRect();
-
-        local_location.emplace(local_point, inverse.MapQuad(quad_in_container));
-      } else {
-        local_location.emplace(local_point);
-      }
-
+    TransformedHitTestLocation local_location(local_border_box_location,
+                                              LocalToBorderBoxTransform());
+    if (local_location) {
       LayoutPoint accumulated_offset_for_children;
       if (SVGLayoutSupport::HitTestChildren(
               LastChild(), result, *local_location,
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
index 187e189..b8f1e08 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_shape.cc
@@ -35,6 +35,7 @@
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
+#include "third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h"
 #include "third_party/blink/renderer/core/paint/svg_shape_painter.h"
 #include "third_party/blink/renderer/core/svg/svg_geometry_element.h"
 #include "third_party/blink/renderer/core/svg/svg_length_context.h"
@@ -347,13 +348,12 @@
   if (hit_test_action != kHitTestForeground)
     return false;
 
-  base::Optional<HitTestLocation> local_storage;
-  const HitTestLocation* local_location =
-      SVGLayoutSupport::TransformToUserSpaceAndCheckClipping(
-          *this, LocalToSVGParentTransform(), location_in_parent,
-          local_storage);
+  TransformedHitTestLocation local_location(location_in_parent,
+                                            LocalToSVGParentTransform());
   if (!local_location)
     return false;
+  if (!SVGLayoutSupport::IntersectsClipPath(*this, *local_location))
+    return false;
 
   PointerEventsHitRules hit_rules(
       PointerEventsHitRules::SVG_GEOMETRY_HITTESTING,
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc
index b187ea3..ae2a595d 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_text.cc
@@ -41,6 +41,7 @@
 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_text_layout_attributes_builder.h"
+#include "third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h"
 #include "third_party/blink/renderer/core/paint/svg_text_painter.h"
 #include "third_party/blink/renderer/core/style/shadow_list.h"
 #include "third_party/blink/renderer/core/svg/svg_text_element.h"
@@ -314,13 +315,12 @@
   if (hit_test_action != kHitTestForeground)
     return false;
 
-  base::Optional<HitTestLocation> local_storage;
-  const HitTestLocation* local_location =
-      SVGLayoutSupport::TransformToUserSpaceAndCheckClipping(
-          *this, LocalToSVGParentTransform(), location_in_parent,
-          local_storage);
+  TransformedHitTestLocation local_location(location_in_parent,
+                                            LocalToSVGParentTransform());
   if (!local_location)
     return false;
+  if (!SVGLayoutSupport::IntersectsClipPath(*this, *local_location))
+    return false;
 
   if (LayoutBlock::NodeAtPoint(result, *local_location, accumulated_offset,
                                hit_test_action))
diff --git a/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc b/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc
index c1e6a52..4cd2fcc 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc
+++ b/third_party/blink/renderer/core/layout/svg/svg_layout_support.cc
@@ -412,6 +412,11 @@
 }
 
 bool SVGLayoutSupport::IntersectsClipPath(const LayoutObject& object,
+                                          const HitTestLocation& location) {
+  return IntersectsClipPath(object, location.TransformedPoint());
+}
+
+bool SVGLayoutSupport::IntersectsClipPath(const LayoutObject& object,
                                           const FloatPoint& point) {
   ClipPathOperation* clip_path_operation = object.StyleRef().ClipPath();
   if (!clip_path_operation)
@@ -430,36 +435,6 @@
                                                   point);
 }
 
-const HitTestLocation* SVGLayoutSupport::TransformToUserSpaceAndCheckClipping(
-    const LayoutObject& object,
-    const AffineTransform& local_transform,
-    const HitTestLocation& location_in_parent,
-    base::Optional<HitTestLocation>& local_storage) {
-  // Use a fast path for an identity transform which creates no new
-  // HitTestLocation objects or inverse AffineTransforms, and performs no
-  // matrix multiplies.
-  if (local_transform.IsIdentity()) {
-    if (IntersectsClipPath(object, location_in_parent.TransformedPoint()))
-      return &location_in_parent;
-    return nullptr;
-  }
-  if (!local_transform.IsInvertible())
-    return nullptr;
-  const AffineTransform inverse = local_transform.Inverse();
-  if (location_in_parent.IsRectBasedTest()) {
-    local_storage.emplace(
-        HitTestLocation(inverse.MapPoint(location_in_parent.TransformedPoint()),
-                        inverse.MapQuad(location_in_parent.TransformedRect())));
-  } else {
-    local_storage.emplace(HitTestLocation(
-        inverse.MapPoint(location_in_parent.TransformedPoint())));
-  }
-
-  if (IntersectsClipPath(object, local_storage->TransformedPoint()))
-    return &*local_storage;
-  return nullptr;
-}
-
 bool SVGLayoutSupport::HitTestChildren(LayoutObject* last_child,
                                        HitTestResult& result,
                                        const HitTestLocation& location,
diff --git a/third_party/blink/renderer/core/layout/svg/svg_layout_support.h b/third_party/blink/renderer/core/layout/svg/svg_layout_support.h
index 4f2261a..e683dddc 100644
--- a/third_party/blink/renderer/core/layout/svg/svg_layout_support.h
+++ b/third_party/blink/renderer/core/layout/svg/svg_layout_support.h
@@ -75,18 +75,7 @@
 
   // Determine whether the passed point intersects the clip path of |object|.
   static bool IntersectsClipPath(const LayoutObject&, const FloatPoint&);
-
-  // Transform |location_in_parent| to |object|'s user-space and check if it is
-  // within the clipping area. Returns a pointer to a HitTestLocation object
-  // to use as the local location. Returns nullptr if the transform is singular
-  // or the point is outside the clipping area. The object backing
-  // the pointer is either |location_in_parent| or an emplacement of
-  // |local_storage|.
-  static const HitTestLocation* TransformToUserSpaceAndCheckClipping(
-      const LayoutObject&,
-      const AffineTransform& local_transform,
-      const HitTestLocation& location_in_parent,
-      base::Optional<HitTestLocation>& local_storage);
+  static bool IntersectsClipPath(const LayoutObject&, const HitTestLocation&);
 
   // Shared child hit-testing code between LayoutSVGRoot/LayoutSVGContainer.
   static bool HitTestChildren(LayoutObject* last_child,
diff --git a/third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.cc b/third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.cc
new file mode 100644
index 0000000..a576fcb
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.cc
@@ -0,0 +1,40 @@
+// Copyright 2018 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 "third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h"
+
+#include "third_party/blink/renderer/platform/transforms/affine_transform.h"
+
+namespace blink {
+
+namespace {
+
+const HitTestLocation* InverseTransformLocationIfNeeded(
+    const HitTestLocation& location,
+    const AffineTransform& transform,
+    base::Optional<HitTestLocation>& storage) {
+  if (transform.IsIdentity())
+    return &location;
+  if (!transform.IsInvertible())
+    return nullptr;
+  const AffineTransform inverse = transform.Inverse();
+  FloatPoint transformed_point = inverse.MapPoint(location.TransformedPoint());
+  if (UNLIKELY(location.IsRectBasedTest())) {
+    storage.emplace(transformed_point,
+                    inverse.MapQuad(location.TransformedRect()));
+  } else {
+    storage.emplace(transformed_point);
+  }
+  return &*storage;
+}
+
+}  // namespace
+
+TransformedHitTestLocation::TransformedHitTestLocation(
+    const HitTestLocation& location,
+    const AffineTransform& transform)
+    : location_(
+          InverseTransformLocationIfNeeded(location, transform, storage_)) {}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h b/third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h
new file mode 100644
index 0000000..07cfec0
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/svg/transformed_hit_test_location.h
@@ -0,0 +1,48 @@
+// Copyright 2018 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 THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_SVG_TRANSFORMED_HIT_TEST_LOCATION_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_SVG_TRANSFORMED_HIT_TEST_LOCATION_H_
+
+#include "base/optional.h"
+#include "third_party/blink/renderer/core/layout/hit_test_location.h"
+
+namespace blink {
+
+class AffineTransform;
+
+// Helper class handling the application of a AffineTransform to a
+// HitTestLocation - producing a new, transformed, HitTestLocation if needed.
+//
+// Encapsulates logic to avoid creating/copying the HitTestLocation for example
+// if the AffineTransform is the identity.
+class TransformedHitTestLocation {
+  DISALLOW_NEW();
+
+ public:
+  // The AffineTransform passed is expected to be the "forward"
+  // transform. The inverse will computed and applied (as needed.)
+  //
+  // If the transform is singular, the bool operator will return
+  // false, in which case the object cannot (must not) be used.
+  TransformedHitTestLocation(const HitTestLocation&, const AffineTransform&);
+
+  const HitTestLocation* operator->() const {
+    DCHECK(location_);
+    return location_;
+  }
+  const HitTestLocation& operator*() const {
+    DCHECK(location_);
+    return *location_;
+  }
+  explicit operator bool() const { return location_; }
+
+ private:
+  base::Optional<HitTestLocation> storage_;
+  const HitTestLocation* location_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_SVG_TRANSFORMED_HIT_TEST_LOCATION_H_