Commit c47a6f0b authored by Xiaocheng Hu's avatar Xiaocheng Hu Committed by Commit Bot

Use a transformed bounding box when hit testing transformed elements

Hit testing uses a 1x1 rect (i.e., "bounding box" )at the hit test
location to test intersection with boxes in the tree. The current
implementation always uses a 1x1 bounding box in the local coordinate
space of the box being tested, which is incorrect -- for example, if the
box is scaled by 100x, then using a 1x1 bounding box in the local space
is equivalent to using a 100x100 bounding box in the viewport, and as a
result we hit the transformed box even if the hit test location is still
100px away from it.

This patch fixes the issue by also transforming the bounding box, so
that it is always equivalent to a 1x1 rect in the viewport.

Bug: 1015801
Change-Id: Ia23e33d2d14bc51fcb2d4b0953ed48bc5738e20e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1891110Reviewed-by: default avatarPhilip Rogers <pdr@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#711072}
parent 0cc2eee3
...@@ -47,6 +47,15 @@ HitTestLocation::HitTestLocation(const FloatPoint& point) ...@@ -47,6 +47,15 @@ HitTestLocation::HitTestLocation(const FloatPoint& point)
is_rect_based_(false), is_rect_based_(false),
is_rectilinear_(true) {} is_rectilinear_(true) {}
HitTestLocation::HitTestLocation(const FloatPoint& point,
const PhysicalRect& bounding_box)
: point_(PhysicalOffset::FromFloatPointFloor(point)),
bounding_box_(bounding_box),
transformed_point_(point),
transformed_rect_(FloatRect(bounding_box)),
is_rect_based_(false),
is_rectilinear_(true) {}
HitTestLocation::HitTestLocation(const DoublePoint& point) HitTestLocation::HitTestLocation(const DoublePoint& point)
: HitTestLocation(FloatPoint(point)) {} : HitTestLocation(FloatPoint(point)) {}
......
...@@ -52,6 +52,14 @@ class CORE_EXPORT HitTestLocation { ...@@ -52,6 +52,14 @@ class CORE_EXPORT HitTestLocation {
explicit HitTestLocation(const DoublePoint&); explicit HitTestLocation(const DoublePoint&);
explicit HitTestLocation(const FloatPoint&, const FloatQuad&); explicit HitTestLocation(const FloatPoint&, const FloatQuad&);
explicit HitTestLocation(const PhysicalRect&); explicit HitTestLocation(const PhysicalRect&);
// The bounding box isn't always a 1x1 rect even when the hit test is not
// rect-based. When we hit test a transformed box and transform the hit test
// location into the box's local coordinate space, the bounding box should
// also be transformed accordingly.
explicit HitTestLocation(const FloatPoint& point,
const PhysicalRect& bounding_box);
HitTestLocation(const HitTestLocation&, const PhysicalOffset& offset); HitTestLocation(const HitTestLocation&, const PhysicalOffset& offset);
HitTestLocation(const HitTestLocation&); HitTestLocation(const HitTestLocation&);
~HitTestLocation(); ~HitTestLocation();
......
...@@ -76,6 +76,12 @@ FloatQuad HitTestingTransformState::MappedArea() const { ...@@ -76,6 +76,12 @@ FloatQuad HitTestingTransformState::MappedArea() const {
return accumulated_transform_.Inverse().ProjectQuad(last_planar_area_); return accumulated_transform_.Inverse().ProjectQuad(last_planar_area_);
} }
PhysicalRect HitTestingTransformState::BoundsOfMappedQuad() const {
return PhysicalRectToBeNoop(
accumulated_transform_.Inverse().ClampedBoundsOfProjectedQuad(
last_planar_quad_));
}
PhysicalRect HitTestingTransformState::BoundsOfMappedArea() const { PhysicalRect HitTestingTransformState::BoundsOfMappedArea() const {
return PhysicalRectToBeNoop( return PhysicalRectToBeNoop(
accumulated_transform_.Inverse().ClampedBoundsOfProjectedQuad( accumulated_transform_.Inverse().ClampedBoundsOfProjectedQuad(
......
...@@ -68,6 +68,7 @@ class HitTestingTransformState { ...@@ -68,6 +68,7 @@ class HitTestingTransformState {
FloatPoint MappedPoint() const; FloatPoint MappedPoint() const;
FloatQuad MappedQuad() const; FloatQuad MappedQuad() const;
FloatQuad MappedArea() const; FloatQuad MappedArea() const;
PhysicalRect BoundsOfMappedQuad() const;
PhysicalRect BoundsOfMappedArea() const; PhysicalRect BoundsOfMappedArea() const;
void Flatten(); void Flatten();
......
...@@ -2277,13 +2277,12 @@ PaintLayer* PaintLayer::HitTestLayerByApplyingTransform( ...@@ -2277,13 +2277,12 @@ PaintLayer* PaintLayer::HitTestLayerByApplyingTransform(
// We can't just map hitTestLocation and hitTestRect because they may have // We can't just map hitTestLocation and hitTestRect because they may have
// been flattened (losing z) by our container. // been flattened (losing z) by our container.
FloatPoint local_point = new_transform_state.MappedPoint(); FloatPoint local_point = new_transform_state.MappedPoint();
FloatQuad local_point_quad = new_transform_state.MappedQuad();
PhysicalRect bounds_of_mapped_area = new_transform_state.BoundsOfMappedArea(); PhysicalRect bounds_of_mapped_area = new_transform_state.BoundsOfMappedArea();
base::Optional<HitTestLocation> new_location; base::Optional<HitTestLocation> new_location;
if (recursion_data.location.IsRectBasedTest()) if (recursion_data.location.IsRectBasedTest())
new_location.emplace(local_point, local_point_quad); new_location.emplace(local_point, new_transform_state.MappedQuad());
else else
new_location.emplace(local_point); new_location.emplace(local_point, new_transform_state.BoundsOfMappedQuad());
HitTestRecursionData new_recursion_data(bounds_of_mapped_area, *new_location, HitTestRecursionData new_recursion_data(bounds_of_mapped_area, *new_location,
recursion_data.original_location); recursion_data.original_location);
......
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-transforms-2/#propdef-scale">
<link rel="help" href="http://www.w3.org/TR/cssom-view/#extensions-to-the-document-interface">
<link rel="help" href="https://crbug.com/1015801">
<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#normal {
width: 100px;
height: 10px;
position: absolute;
top: 0px;
}
#scaled {
width: 1px;
height: 1px;
transform: scaleX(100) scaleY(100);
transform-origin: 0px 0px;
position: absolute;
top: 10px;
z-index: 1; /* Hit test #scaled before #normal */
}
</style>
<div id=normal></div>
<div id=scaled></div>
<script>
test(() => {
const result = document.elementFromPoint(50, 9);
assert_equals(result, document.getElementById('normal'));
}, 'Hit test within unscaled box');
test(() => {
const result = document.elementFromPoint(50, 9.1);
assert_equals(result, document.getElementById('scaled'));
}, 'Hit test intersecting scaled box');
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment