blob: 958da2c32c534148d79cb03e73c997ef60862781 [file] [log] [blame]
// Copyright 2019 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 "base/test/metrics/histogram_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/css/css_property_names.h"
#include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
namespace blink {
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());
}
};
TEST_F(HitTestingTest, OcclusionHitTest) {
SetBodyInnerHTML(R"HTML(
<style>
div {
width: 100px;
height: 100px;
}
</style>
<div id=target></div>
<div id=occluder></div>
)HTML");
Element* target = GetDocument().getElementById("target");
Element* occluder = GetDocument().getElementById("occluder");
HitTestResult result = target->GetLayoutObject()->HitTestForOcclusion();
EXPECT_EQ(result.InnerNode(), target);
occluder->SetInlineStyleProperty(CSSPropertyID::kMarginTop, "-10px");
UpdateAllLifecyclePhasesForTest();
result = target->GetLayoutObject()->HitTestForOcclusion();
EXPECT_EQ(result.InnerNode(), occluder);
}
TEST_F(HitTestingTest, OcclusionHitTestWithClipPath) {
SetBodyInnerHTML(R"HTML(
<style>
div {
width: 100px;
height: 100px;
}
#occluder {
clip-path: url(#clip);
}
</style>
<svg viewBox="0 0 100 100" width=0>
<clipPath id="clip">
<circle cx="50" cy="50" r="45" stroke="none" />
</clipPath>
</svg>
<div id=target></div>
<div id=occluder></div>
)HTML");
Element* target = GetDocument().getElementById("target");
Element* occluder = GetDocument().getElementById("occluder");
// target and occluder don't overlap, no occlusion.
HitTestResult result = target->GetLayoutObject()->HitTestForOcclusion();
EXPECT_EQ(result.InnerNode(), target);
// target and occluder layout rects overlap, but the overlapping area of the
// occluder is clipped out, so no occlusion.
occluder->SetInlineStyleProperty(CSSPropertyID::kMarginTop, "-4px");
UpdateAllLifecyclePhasesForTest();
result = target->GetLayoutObject()->HitTestForOcclusion();
EXPECT_EQ(result.InnerNode(), target);
// target and clipped area of occluder overlap, so there is occlusion.
occluder->SetInlineStyleProperty(CSSPropertyID::kMarginTop, "-6px");
UpdateAllLifecyclePhasesForTest();
result = target->GetLayoutObject()->HitTestForOcclusion();
EXPECT_EQ(result.InnerNode(), occluder);
}
// crbug.com/1153037
TEST_F(HitTestingTest, LegacyInputElementInFragmentTraversal) {
ScopedLayoutNGFragmentTraversalForTest fragment_traversal_feature(true);
ScopedEditingNGForTest editing_feature(false);
SetBodyInnerHTML(R"HTML(
<style>
body { margin:100px; }
</style>
<input id="target">
)HTML");
const HitTestRequest hit_request(HitTestRequest::kActive);
const HitTestLocation hit_location(PhysicalOffset(110, 110));
HitTestResult hit_result(hit_request, hit_location);
ASSERT_TRUE(GetLayoutView().HitTest(hit_location, hit_result));
ASSERT_TRUE(hit_result.InnerNode());
const auto* layout_object = hit_result.InnerNode()->GetLayoutObject();
ASSERT_TRUE(layout_object);
// In this test we'll use the legacy layout engine for form controls, so the
// INPUT element will generate a LayoutTextControl with an inner editable
// LayoutBlockFlow child. We'll hit-test by traversing the fragment tree
// (rather than the LayoutObject tree). We should hit the inner
// LayoutBlockFlow. Since it is a legacy object and it is also laid out by a
// legacy parent, it will not generate any NG fragments. Check that we hit the
// right node, and that the hit-testing code hasn't incorrectly set an NG
// fragment from an ancestor.
ASSERT_EQ(layout_object->Parent()->GetNode(),
GetDocument().getElementById("target"));
}
TEST_F(HitTestingTest, ScrolledInline) {
SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0;
font-size: 50px;
line-height: 1;
}
#scroller {
width: 400px;
height: 5em;
overflow: scroll;
white-space: pre;
}
</style>
<div id="scroller">line1
line2
line3
line4
line5
line6
line7
line8
line9</div>
)HTML");
// Scroll #scroller by 2 lines. "line3" should be at the top.
Element* scroller = GetElementById("scroller");
scroller->setScrollTop(100);
const auto& text = *To<Text>(GetElementById("scroller")->firstChild());
// Expect to hit test position 12 (beginning of line3).
EXPECT_EQ(PositionWithAffinity(Position(text, 12)),
HitTest(PhysicalOffset(5, 5)));
}
} // namespace blink