Commit 04dab5a9 authored by Yoshifumi Inoue's avatar Yoshifumi Inoue Committed by Commit Bot

[LayoutNG] Make HitTestResult::LocalPoint() for inline element as same as legacy layout

This patch changes |NGBoxFragmentPainter::NodeAtPoint()| to set offset in
containing block instead of offset in underlying element for inline element as
legacy layout to make hit testing on inline element with ::after pseudo class
with adapting |PositionForPoint()|.

The document[1] contains investigation notes of this CL.

[1] https://bit.ly/2REZ7P9 Hit Test with ::after

Bug: 1043471
Change-Id: I81ada0ccd7bff31a84ce4746785ea83eb175937c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2151775
Commit-Queue: Koji Ishii <kojii@chromium.org>
Auto-Submit: Yoshifumi Inoue <yosin@chromium.org>
Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#759982}
parent 866d1e0d
...@@ -3,11 +3,72 @@ ...@@ -3,11 +3,72 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "third_party/blink/renderer/core/css/css_property_names.h" #include "third_party/blink/renderer/core/css/css_property_names.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
namespace blink { namespace blink {
class HitTestingTest : public RenderingTest {}; class HitTestingTest : public RenderingTest {
protected:
bool LayoutNGEnabled() const {
return RuntimeEnabledFeatures::LayoutNGEnabled();
}
PositionWithAffinity HitTest(const PhysicalOffset offset) {
const HitTestRequest hit_request(HitTestRequest::kActive);
const HitTestLocation hit_location(offset);
HitTestResult hit_result(hit_request, hit_location);
if (!GetLayoutView().HitTest(hit_location, hit_result))
return PositionWithAffinity();
// Simulate |PositionWithAffinityOfHitTestResult()| in
// "selection_controller.cc"
LayoutObject* const layout_object =
hit_result.InnerPossiblyPseudoNode()->GetLayoutObject();
if (!layout_object)
return PositionWithAffinity();
return layout_object->PositionForPoint(hit_result.LocalPoint());
}
};
// http://crbug.com/1043471
TEST_F(HitTestingTest, PseudoElementAfter) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#cd::after { content: 'XYZ'; margin-left: 100px; }");
SetBodyInnerHTML("<div id=ab>ab<span id=cd>cd</span></div>");
const auto& text_ab = *To<Text>(GetElementById("ab")->firstChild());
const auto& text_cd = *To<Text>(GetElementById("cd")->lastChild());
EXPECT_EQ(PositionWithAffinity(Position(text_ab, 0)),
HitTest(PhysicalOffset(5, 5)));
// Because of hit testing at "b", position should be |kDownstream|.
EXPECT_EQ(PositionWithAffinity(Position(text_ab, 1),
LayoutNGEnabled() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(PhysicalOffset(15, 5)));
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 0)),
HitTest(PhysicalOffset(25, 5)));
// Because of hit testing at "d", position should be |kDownstream|.
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 1),
LayoutNGEnabled() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(PhysicalOffset(35, 5)));
// Because of hit testing at right of <span cd>, result position should be
// |kUpstream|.
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 2),
LayoutNGEnabled() ? TextAffinity::kUpstream
: TextAffinity::kDownstream),
HitTest(PhysicalOffset(45, 5)));
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 2),
LayoutNGEnabled() ? TextAffinity::kUpstream
: TextAffinity::kDownstream),
HitTest(PhysicalOffset(55, 5)));
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 2),
LayoutNGEnabled() ? TextAffinity::kUpstream
: TextAffinity::kDownstream),
HitTest(PhysicalOffset(65, 5)));
}
TEST_F(HitTestingTest, OcclusionHitTest) { TEST_F(HitTestingTest, OcclusionHitTest) {
SetBodyInnerHTML(R"HTML( SetBodyInnerHTML(R"HTML(
......
...@@ -818,6 +818,25 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineBox( ...@@ -818,6 +818,25 @@ PositionWithAffinity NGInlineCursor::PositionForPointInInlineBox(
} }
} }
if (container->Type() == NGFragmentItem::kLine) {
// There are no inline items to hit in this line box, e.g. <span> with
// size and border. We try in lines before |this| line in the block.
// See editing/selection/last-empty-inline.html
NGInlineCursor cursor;
cursor.MoveTo(*this);
const PhysicalOffset point_in_line =
point - Current().OffsetInContainerBlock();
for (;;) {
cursor.MoveToPreviousLine();
if (!cursor)
break;
const PhysicalOffset adjusted_point =
point_in_line + cursor.Current().OffsetInContainerBlock();
if (auto position = cursor.PositionForPointInInlineBox(adjusted_point))
return position;
}
}
return PositionWithAffinity(); return PositionWithAffinity();
} }
......
...@@ -1727,9 +1727,25 @@ bool NGBoxFragmentPainter::NodeAtPoint(const HitTestContext& hit_test, ...@@ -1727,9 +1727,25 @@ bool NGBoxFragmentPainter::NodeAtPoint(const HitTestContext& hit_test,
if (fragment.IsInlineBox()) if (fragment.IsInlineBox())
bounds_rect = PhysicalRect(PixelSnappedIntRect(bounds_rect)); bounds_rect = PhysicalRect(PixelSnappedIntRect(bounds_rect));
if (hit_test.location.Intersects(bounds_rect)) { if (hit_test.location.Intersects(bounds_rect)) {
if (hit_test.AddNodeToResult(fragment.NodeForHitTest(), bounds_rect, // We set offset in container block instead of offset in |fragment| like
physical_offset)) // |NGBoxFragmentPainter::HitTestTextFragment()|.
return true; // See http://crbug.com/1043471
if (box_item_ && box_item_->IsInlineBox()) {
if (hit_test.AddNodeToResult(
fragment.NodeForHitTest(), bounds_rect,
physical_offset - box_item_->OffsetInContainerBlock()))
return true;
} else if (paint_fragment_ &&
paint_fragment_->PhysicalFragment().IsInline()) {
if (hit_test.AddNodeToResult(
fragment.NodeForHitTest(), bounds_rect,
physical_offset - paint_fragment_->OffsetInContainerBlock()))
return true;
} else {
if (hit_test.AddNodeToResult(fragment.NodeForHitTest(), bounds_rect,
physical_offset))
return true;
}
} }
} }
......
...@@ -1039,6 +1039,25 @@ PositionWithAffinity NGPaintFragment::PositionForPointInInlineLevelBox( ...@@ -1039,6 +1039,25 @@ PositionWithAffinity NGPaintFragment::PositionForPointInInlineLevelBox(
return child_position.value(); return child_position.value();
} }
if (PhysicalFragment().IsLineBox()) {
// There are no inline items to hit in this line box, e.g. <span> with
// size and border. We try in lines before |this| line in the block.
// See editing/selection/last-empty-inline.html
NGInlineCursor cursor(*Parent());
cursor.MoveTo(*this);
const PhysicalOffset point_in_line = point - OffsetInContainerBlock();
for (;;) {
cursor.MoveToPreviousLine();
if (!cursor)
break;
const NGPaintFragment& line = *cursor.CurrentPaintFragment();
const PhysicalOffset adjusted_point =
point_in_line + line.OffsetInContainerBlock();
if (auto position = line.PositionForPointInInlineLevelBox(adjusted_point))
return position;
}
}
return PositionWithAffinity(); return PositionWithAffinity();
} }
......
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