blob: 0a2179c0d337fe805d3812608e1e77f64d866f78 [file] [log] [blame]
// 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 "third_party/blink/renderer/core/layout/layout_view.h"
#include "build/build_config.h"
#include "third_party/blink/public/mojom/webpreferences/web_preferences.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/page/named_pages_mapper.h"
#include "third_party/blink/renderer/core/page/print_context.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 LayoutViewTest : public RenderingTest {
public:
LayoutViewTest()
: RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()) {}
};
TEST_F(LayoutViewTest, UpdateCountersLayout) {
SetBodyInnerHTML(R"HTML(
<style>
div.incX { counter-increment: x }
div.incY { counter-increment: y }
div::before { content: counter(y) }
</style>
<div id=inc></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
Element* inc = GetDocument().getElementById("inc");
inc->setAttribute("class", "incX");
GetDocument().UpdateStyleAndLayoutTree();
EXPECT_FALSE(GetDocument().View()->NeedsLayout());
UpdateAllLifecyclePhasesForTest();
inc->setAttribute("class", "incY");
GetDocument().UpdateStyleAndLayoutTree();
EXPECT_TRUE(GetDocument().View()->NeedsLayout());
}
TEST_F(LayoutViewTest, DisplayNoneFrame) {
SetBodyInnerHTML(R"HTML(
<iframe id="iframe" style="display:none"></iframe>
)HTML");
auto* iframe = To<HTMLIFrameElement>(GetDocument().getElementById("iframe"));
Document* frame_doc = iframe->contentDocument();
ASSERT_TRUE(frame_doc);
frame_doc->OverrideIsInitialEmptyDocument();
frame_doc->View()->BeginLifecycleUpdates();
UpdateAllLifecyclePhasesForTest();
LayoutObject* view = frame_doc->GetLayoutView();
ASSERT_TRUE(view);
EXPECT_FALSE(view->CanHaveChildren());
EXPECT_FALSE(frame_doc->documentElement()->GetComputedStyle());
frame_doc->body()->setInnerHTML(R"HTML(
<div id="div"></div>
)HTML");
EXPECT_FALSE(frame_doc->NeedsLayoutTreeUpdate());
}
TEST_F(LayoutViewTest, NamedPages) {
ScopedNamedPagesForTest named_pages_enabler(true);
SetBodyInnerHTML(R"HTML(
<!DOCTYPE html>
<style>
div:empty { height:10px; }
</style>
<!-- First page: -->
<div></div>
<!-- Second page: -->
<div style="break-before:page;"></div>
<!-- Third page: -->
<div style="page:yksi;"></div>
<!-- Fourth page: -->
<div style="page:yksi;">
<div style="page:yksi; break-before:page;"></div>
<!-- Fifth page: -->
<div style="page:yksi; break-before:page;"></div>
</div>
<!-- Sixth page: -->
<div style="page:kaksi;"></div>
<!-- Seventh page: -->
<div style="page:maksitaksi;"></div>
<!-- Eighth page: -->
<div></div>
<!-- Ninth page: -->
<div style="page:yksi;"></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
const LayoutView* view = GetDocument().GetLayoutView();
ASSERT_TRUE(view);
ScopedPrintContext print_context(&GetDocument().View()->GetFrame());
print_context->BeginPrintMode(500, 500);
const NamedPagesMapper* mapper = view->GetNamedPagesMapper();
ASSERT_TRUE(mapper);
EXPECT_EQ(mapper->NamedPageAtIndex(0), AtomicString());
EXPECT_EQ(mapper->NamedPageAtIndex(1), AtomicString());
EXPECT_EQ(mapper->NamedPageAtIndex(2), "yksi");
EXPECT_EQ(mapper->NamedPageAtIndex(3), "yksi");
EXPECT_EQ(mapper->NamedPageAtIndex(4), "yksi");
EXPECT_EQ(mapper->NamedPageAtIndex(5), "kaksi");
EXPECT_EQ(mapper->NamedPageAtIndex(6), "maksitaksi");
EXPECT_EQ(mapper->NamedPageAtIndex(7), AtomicString());
EXPECT_EQ(mapper->NamedPageAtIndex(8), "yksi");
EXPECT_EQ(mapper->NamedPageAtIndex(9), "yksi");
EXPECT_EQ(mapper->NamedPageAtIndex(100), "yksi");
EXPECT_EQ(mapper->LastPageName(), "yksi");
}
struct HitTestConfig {
bool layout_ng;
mojom::EditingBehavior editing_behavior;
};
class LayoutViewHitTestTest : public testing::WithParamInterface<HitTestConfig>,
private ScopedLayoutNGForTest,
public RenderingTest {
public:
LayoutViewHitTestTest()
: ScopedLayoutNGForTest(GetParam().layout_ng),
RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()) {}
protected:
bool LayoutNG() { return RuntimeEnabledFeatures::LayoutNGEnabled(); }
bool IsAndroidOrWindowsEditingBehavior() {
return GetParam().editing_behavior ==
mojom::EditingBehavior::kEditingAndroidBehavior ||
GetParam().editing_behavior ==
mojom::EditingBehavior::kEditingWindowsBehavior;
}
void SetUp() override {
RenderingTest::SetUp();
GetFrame().GetSettings()->SetEditingBehaviorType(
GetParam().editing_behavior);
}
PositionWithAffinity HitTest(int left, int top) {
const HitTestRequest hit_request(HitTestRequest::kActive);
const HitTestLocation hit_location(PhysicalOffset(left, top));
HitTestResult hit_result(hit_request, hit_location);
if (!GetLayoutView().HitTest(hit_location, hit_result))
return PositionWithAffinity();
return hit_result.GetPosition();
}
};
INSTANTIATE_TEST_SUITE_P(
All,
LayoutViewHitTestTest,
::testing::Values(
// Legacy
HitTestConfig{false, mojom::EditingBehavior::kEditingMacBehavior},
HitTestConfig{false, mojom::EditingBehavior::kEditingWindowsBehavior},
HitTestConfig{false, mojom::EditingBehavior::kEditingUnixBehavior},
HitTestConfig{false, mojom::EditingBehavior::kEditingAndroidBehavior},
HitTestConfig{false, mojom::EditingBehavior::kEditingChromeOSBehavior},
// LayoutNG
HitTestConfig{true, mojom::EditingBehavior::kEditingMacBehavior},
HitTestConfig{true, mojom::EditingBehavior::kEditingWindowsBehavior},
HitTestConfig{true, mojom::EditingBehavior::kEditingUnixBehavior},
HitTestConfig{true, mojom::EditingBehavior::kEditingAndroidBehavior},
HitTestConfig{true, mojom::EditingBehavior::kEditingChromeOSBehavior}));
TEST_P(LayoutViewHitTestTest, EmptySpan) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#target { width: 50px; }"
"b { border: solid 5px green; }");
SetBodyInnerHTML("<div id=target>AB<b></b></div>");
auto& target = *GetElementById("target");
auto& ab = *To<Text>(target.firstChild());
const auto after_ab =
PositionWithAffinity(Position(ab, 2), TextAffinity::kUpstream);
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(0, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(5, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(10, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(15, 5));
EXPECT_EQ(after_ab, HitTest(20, 5));
EXPECT_EQ(after_ab, HitTest(25, 5));
EXPECT_EQ(after_ab, HitTest(30, 5));
EXPECT_EQ(after_ab, HitTest(35, 5));
EXPECT_EQ(after_ab, HitTest(40, 5));
EXPECT_EQ(after_ab, HitTest(45, 5));
EXPECT_EQ(after_ab, HitTest(50, 5));
EXPECT_EQ(after_ab, HitTest(55, 5));
}
// http://crbug.com/1171070
// See also, FloatLeft*, DOM order of "float" should not affect hit testing.
TEST_P(LayoutViewHitTestTest, FloatLeftLeft) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#target { width: 70px; }"
".float { float: left; margin-right: 10px; }");
SetBodyInnerHTML("<div id=target><div class=float>ab</div>xy</div>");
// NGFragmentItem
// [0] kLine (30,0)x(20,10)
// [1] kBox/Floating (0,0)x(20,10)
// [2] kText "xy" (30,0)x(20,10)
auto& target = *GetElementById("target");
auto& ab = *To<Text>(target.firstChild()->firstChild());
auto& xy = *To<Text>(target.lastChild());
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(0, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(5, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(15, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(20, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(25, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(30, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(35, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(40, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(45, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(50, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(55, 5));
}
// http://crbug.com/1171070
// See also, FloatLeft*, DOM order of "float" should not affect hit testing.
TEST_P(LayoutViewHitTestTest, FloatLeftMiddle) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#target { width: 70px; }"
".float { float: left; margin-right: 10px; }");
SetBodyInnerHTML("<div id=target>x<div class=float>ab</div>y</div>");
// NGFragmentItem
// [0] kLine (30,0)x(20,10)
// [1] kText "x" (30,0)x(10,10)
// [1] kBox/Floating (0,0)x(20,10)
// [2] kText "y" (40,0)x(10,10)
auto& target = *GetElementById("target");
auto& ab = *To<Text>(target.firstChild()->nextSibling()->firstChild());
auto& x = *To<Text>(target.firstChild());
auto& y = *To<Text>(target.lastChild());
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(0, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(5, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(15, 5));
EXPECT_EQ(PositionWithAffinity(Position(x, 0)), HitTest(20, 5));
EXPECT_EQ(PositionWithAffinity(Position(x, 0)), HitTest(25, 5));
EXPECT_EQ(PositionWithAffinity(Position(x, 0)), HitTest(30, 5));
EXPECT_EQ(PositionWithAffinity(Position(x, 0)), HitTest(35, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 0)), HitTest(40, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 0)), HitTest(45, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 1), TextAffinity::kUpstream),
HitTest(50, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 1), TextAffinity::kUpstream),
HitTest(55, 5));
}
// http://crbug.com/1171070
// See also, FloatLeft*, DOM order of "float" should not affect hit testing.
TEST_P(LayoutViewHitTestTest, FloatLeftRight) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#target { width: 70px; }"
".float { float: left; margin-right: 10px; }");
SetBodyInnerHTML("<div id=target>xy<div class=float>ab</div></div>");
// NGFragmentItem
// [0] kLine (30,0)x(20,10)
// [1] kText "xy" (30,0)x(20,10)
// [2] kBox/Floating (0,0)x(20,10)
auto& target = *GetElementById("target");
auto& ab = *To<Text>(target.lastChild()->firstChild());
auto& xy = *To<Text>(target.firstChild());
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(0, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(5, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(15, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(20, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(25, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(30, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(35, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(40, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(45, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(50, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(55, 5));
}
// http://crbug.com/1171070
// See also, FloatRight*, DOM order of "float" should not affect hit testing.
TEST_P(LayoutViewHitTestTest, FloatRightLeft) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#target { width: 50px; }"
".float { float: right; }");
SetBodyInnerHTML("<div id=target>xy<div class=float>ab</div></div>");
// NGFragmentItem
// [0] kLine (0,0)x(20,10)
// [1] kBox/Floating (30,0)x(20,10)
auto& target = *GetElementById("target");
auto& ab = *To<Text>(target.lastChild()->firstChild());
auto& xy = *To<Text>(target.firstChild());
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(0, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(5, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(15, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(20, 5))
<< "at right of 'xy'";
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(25, 5))
<< "right of 'xy'";
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(30, 5))
<< "inside float";
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(35, 5))
<< "inside float";
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(40, 5))
<< "inside float";
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(45, 5))
<< "inside float";
// |HitTestResult| holds <body>.
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(50, 5))
<< "at right side of float";
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(55, 5))
<< "right of float";
}
// http://crbug.com/1171070
// See also, FloatRight*, DOM order of "float" should not affect hit testing.
TEST_P(LayoutViewHitTestTest, FloatRightMiddle) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#target { width: 50px; }"
".float { float: right; }");
SetBodyInnerHTML("<div id=target>x<div class=float>ab</div>y</div>");
// NGFragmentItem
// [0] kLine (0,0)x(20,10)
// [1] kText "x" (0,0)x(10,10)
// [2] kBox/Floating (30,0)x(20,10)
// [3] kText "y" (10,0)x(10,10)
auto& target = *GetElementById("target");
auto& ab = *To<Text>(target.firstChild()->nextSibling()->firstChild());
auto& x = *To<Text>(target.firstChild());
auto& y = *To<Text>(target.lastChild());
EXPECT_EQ(PositionWithAffinity(Position(x, 0)), HitTest(0, 5));
EXPECT_EQ(PositionWithAffinity(Position(x, 0)), HitTest(5, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 0)), HitTest(15, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 1), TextAffinity::kUpstream),
HitTest(20, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 1), TextAffinity::kUpstream),
HitTest(25, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(30, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(35, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(40, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(45, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 1), TextAffinity::kUpstream),
HitTest(50, 5));
EXPECT_EQ(PositionWithAffinity(Position(y, 1), TextAffinity::kUpstream),
HitTest(55, 5));
}
// http://crbug.com/1171070
// See also, FloatRight*, DOM order of "float" should not affect hit testing.
TEST_P(LayoutViewHitTestTest, FloatRightRight) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#target { width: 50px; }"
".float { float: right; }");
SetBodyInnerHTML("<div id=target><div class=float>ab</div>xy</div>");
// [0] kLine (0,0)x(20,10)
// [1] kBox/Floating (30,0)x(20,10)
// [2] kText "xy" (0,0)x(20,10)
auto& target = *GetElementById("target");
auto& ab = *To<Text>(target.firstChild()->firstChild());
auto& xy = *To<Text>(target.lastChild());
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(0, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(5, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(15, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(20, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(25, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(30, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(35, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(40, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(45, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(50, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(55, 5));
}
TEST_P(LayoutViewHitTestTest, PositionAbsolute) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/10px Ahem; }"
"#target { width: 70px; }"
".abspos { position: absolute; left: 40px; top: 0px; }");
SetBodyInnerHTML("<div id=target><div class=abspos>ab</div>xy</div>");
// NGFragmentItem
// [0] kLine (0,0)x(20,10)
// [2] kText "xy" (30,0)x(20,10)
// Note: position:absolute isn't in NGFragmentItems of #target.
auto& target = *GetElementById("target");
auto& ab = *To<Text>(target.firstChild()->firstChild());
auto& xy = *To<Text>(target.lastChild());
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(0, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 0)), HitTest(5, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(15, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(20, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(25, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(30, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(35, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(40, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 0)), HitTest(45, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(50, 5));
EXPECT_EQ(PositionWithAffinity(Position(ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(55, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(60, 5));
EXPECT_EQ(PositionWithAffinity(Position(xy, 2), TextAffinity::kUpstream),
HitTest(65, 5));
}
TEST_P(LayoutViewHitTestTest, HitTestHorizontal) {
LoadAhem();
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0 }</style>
<div id="div" style="position: relative; font: 10px/10px Ahem;
top: 100px; left: 50px; width: 200px; height: 80px">
<span id="span1">ABCDE</span><span id="span2"
style="position: relative; top: 30px">XYZ</span>
</div>
)HTML");
// (50, 100) (250, 100)
// |------------------|
// |ABCDE |
// | |
// | |
// | XYZ |
// | |
// | |
// |------------------|
// (50, 180) (250, 180)
auto* div = GetDocument().getElementById("div");
auto* text1 = GetDocument().getElementById("span1")->firstChild();
auto* text2 = GetDocument().getElementById("span2")->firstChild();
HitTestResult result;
// In body, but not in any descendants.
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(1, 1)), result);
EXPECT_EQ(GetDocument().body(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream),
result.GetPosition());
// Top-left corner of div and span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 101)), result);
EXPECT_EQ(text1, result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (outside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(251, 101)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(251, 101), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 3), TextAffinity::kUpstream),
result.GetPosition());
// Top-right corner (inside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(249, 101)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(199, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 3), TextAffinity::kUpstream),
result.GetPosition());
// Top-right corner (inside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(99, 101)), result);
EXPECT_EQ(text1, result.InnerNode());
EXPECT_EQ(PhysicalOffset(49, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 5), TextAffinity::kUpstream),
result.GetPosition());
// Top-right corner (outside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(101, 101)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(51, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 0), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-left corner (outside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 181)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(51, 181), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-left corner (inside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 179)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 79), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-left corner (outside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 111)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 11), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Top-left corner of span2.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(101, 131)), result);
EXPECT_EQ(text2, result.InnerNode());
if (RuntimeEnabledFeatures::LayoutNGEnabled())
EXPECT_EQ(PhysicalOffset(51, 31), result.LocalPoint());
else
EXPECT_EQ(PhysicalOffset(51, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 0), TextAffinity::kDownstream),
result.GetPosition());
}
TEST_P(LayoutViewHitTestTest, HitTestVerticalLR) {
LoadAhem();
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0 }</style>
<div id="div" style="position: relative; font: 10px/10px Ahem;
top: 100px; left: 50px; width: 200px; height: 80px;
writing-mode: vertical-lr">
<span id="span1">ABCDE</span><span id="span2"
style="position: relative; left: 30px">XYZ</span>
</div>
)HTML");
// (50, 100) (250, 100)
// |------------------|
// |A |
// |B |
// |C |
// |D |
// |E |
// | X |
// | Y |
// | Z |
// |------------------|
// (50, 180) (250, 180)
auto* div = GetDocument().getElementById("div");
auto* text1 = GetDocument().getElementById("span1")->firstChild();
auto* text2 = GetDocument().getElementById("span2")->firstChild();
HitTestResult result;
// In body, but not in any descendants.
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(1, 1)), result);
EXPECT_EQ(GetDocument().body(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream),
result.GetPosition());
// Top-left corner of div and span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 101)), result);
EXPECT_EQ(text1, result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (outside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(251, 101)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(251, 101), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (inside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(249, 101)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(199, 1), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (inside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(59, 101)), result);
EXPECT_EQ(text1, result.InnerNode());
EXPECT_EQ(PhysicalOffset(9, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (outside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(61, 101)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(11, 1), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-left corner (outside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 181)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(51, 181), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 3), TextAffinity::kUpstream),
result.GetPosition());
// Bottom-left corner (inside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 179)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 79), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 3), TextAffinity::kUpstream),
result.GetPosition());
// Top-left corner of span2.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(81, 151)), result);
EXPECT_EQ(text2, result.InnerNode());
if (RuntimeEnabledFeatures::LayoutNGEnabled())
EXPECT_EQ(PhysicalOffset(31, 51), result.LocalPoint());
else
EXPECT_EQ(PhysicalOffset(1, 51), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 0), TextAffinity::kDownstream),
result.GetPosition());
}
TEST_P(LayoutViewHitTestTest, HitTestVerticalRL) {
LoadAhem();
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0 }</style>
<div id="div" style="position: relative; font: 10px/10px Ahem;
top: 100px; left: 50px; width: 200px; height: 80px;
writing-mode: vertical-rl">
<span id="span1">ABCDE</span><span id="span2"
style="position: relative; left: -30px">XYZ</span>
</div>
)HTML");
// (50, 100) (250, 100)
// |------------------|
// | A|
// | B|
// | C|
// | D|
// | E|
// | X |
// | Y |
// | Z |
// |------------------|
// (50, 180) (250, 180)
auto* div = GetDocument().getElementById("div");
auto* text1 = GetDocument().getElementById("span1")->firstChild();
auto* text2 = GetDocument().getElementById("span2")->firstChild();
HitTestResult result;
// In body, but not in any descendants.
// XXX1
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(1, 1)), result);
EXPECT_EQ(GetDocument().body(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 1), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Top-left corner of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 101)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 1), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (outside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(251, 101)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(251, 101), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (inside) of div and span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(249, 101)), result);
EXPECT_EQ(text1, result.InnerNode());
EXPECT_EQ(PhysicalOffset(199, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 0), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-right corner (inside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(249, 149)), result);
EXPECT_EQ(text1, result.InnerNode());
EXPECT_EQ(PhysicalOffset(199, 49), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text1, 5), TextAffinity::kUpstream),
result.GetPosition());
// Bottom-right corner (outside) of span1 but inside of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(249, 151)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(199, 51), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 0), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-left corner (outside) of div.
// XXX2
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 181)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(51, 181), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text2, 3), TextAffinity::kUpstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-left corner (inside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(51, 179)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 79), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text2, 3), TextAffinity::kUpstream)
: PositionWithAffinity(Position(text2, 3), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-left corner (outside) of span1.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(241, 151)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(191, 51), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 0), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (inside) of span2.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(219, 151)), result);
EXPECT_EQ(text2, result.InnerNode());
if (RuntimeEnabledFeatures::LayoutNGEnabled())
EXPECT_EQ(PhysicalOffset(169, 51), result.LocalPoint());
else
EXPECT_EQ(PhysicalOffset(199, 51), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text2, 0), TextAffinity::kDownstream),
result.GetPosition());
}
TEST_P(LayoutViewHitTestTest, HitTestVerticalRLRoot) {
LoadAhem();
SetBodyInnerHTML(R"HTML(
<style>
html { writing-mode: vertical-rl; }
body { margin: 0 }
</style>
<div id="div" style="font: 10px/10px Ahem; width: 200px; height: 80px">
<span id="span">ABCDE</span>
</div>
)HTML");
// (0,0) (600, 0) (800, 0)
// +----...----+---------------+
// | | A|
// | | B|
// | | C|
// | | (div) D|
// | (screen) | E|
// | | |
// | | |
// | +---------------+ (800, 80)
// | (600, 80) |
// . .
// +----...--------------------+ (800, 600)
auto* div = GetDocument().getElementById("div");
auto* text = GetDocument().getElementById("span")->firstChild();
HitTestResult result;
// Not in any element. Should fallback to documentElement.
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(1, 1)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(-599, 1), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text, 5), TextAffinity::kDownstream),
result.GetPosition());
// Top-left corner (inside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(601, 1)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(1, 1), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text, 5), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (outside) of div. Should fallback to documentElement.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(801, 1)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(201, 1), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text, 0), TextAffinity::kDownstream)
: PositionWithAffinity(Position(text, 0), TextAffinity::kDownstream),
result.GetPosition());
// Top-right corner (inside) of div and span.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(799, 1)), result);
EXPECT_EQ(text, result.InnerNode());
EXPECT_EQ(PhysicalOffset(199, 1), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text, 0), TextAffinity::kDownstream),
result.GetPosition());
// Bottom-right corner (outside) of span1 but inside of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(799, 51)), result);
EXPECT_EQ(div, result.InnerNode());
EXPECT_EQ(PhysicalOffset(199, 51), result.LocalPoint());
EXPECT_EQ(PositionWithAffinity(Position(text, 5), TextAffinity::kUpstream),
result.GetPosition());
// Bottom-left corner (outside) of div.
result = HitTestResult();
GetLayoutView().HitTest(HitTestLocation(PhysicalOffset(599, 81)), result);
EXPECT_EQ(GetDocument().documentElement(), result.InnerNode());
EXPECT_EQ(PhysicalOffset(-1, 81), result.LocalPoint());
EXPECT_EQ(
IsAndroidOrWindowsEditingBehavior()
? PositionWithAffinity(Position(text, 5), TextAffinity::kUpstream)
: PositionWithAffinity(Position(text, 5), TextAffinity::kDownstream),
result.GetPosition());
}
// http://crbug.com/1164974
TEST_P(LayoutViewHitTestTest, PseudoElementAfterBlock) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/15px Ahem; }"
"p::after { content: 'XY' }");
SetBodyInnerHTML("<div><p id=target>ab</p></div>");
const auto& text_ab = *To<Text>(GetElementById("target")->firstChild());
// In legacy layout, this position comes from |LayoutBlock::PositionBox()|
// for mac/unix, or |LayoutObject::FindPosition()| on android/windows.
const auto expected = PositionWithAffinity(
IsAndroidOrWindowsEditingBehavior() ? Position(text_ab, 2)
: Position(text_ab, 0),
LayoutNG() && IsAndroidOrWindowsEditingBehavior()
? TextAffinity::kUpstream
: TextAffinity::kDownstream);
EXPECT_EQ(expected, HitTest(20, 5)) << "after ab";
EXPECT_EQ(expected, HitTest(25, 5)) << "at X";
EXPECT_EQ(expected, HitTest(35, 5)) << "at Y";
EXPECT_EQ(expected, HitTest(40, 5)) << "after Y";
EXPECT_EQ(expected, HitTest(50, 5)) << "after XY";
}
// http://crbug.com/1043471
TEST_P(LayoutViewHitTestTest, PseudoElementAfterInline) {
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(5, 5));
// Because of hit testing at "b", position should be |kDownstream|.
EXPECT_EQ(PositionWithAffinity(Position(text_ab, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(15, 5));
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 0)), HitTest(25, 5));
// Because of hit testing at "d", position should be |kDownstream|.
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 1),
LayoutNG() ? TextAffinity::kDownstream
: TextAffinity::kUpstream),
HitTest(35, 5));
// Because of hit testing at right of <span cd>, result position should be
// |kUpstream|.
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 2),
LayoutNG() ? TextAffinity::kUpstream
: TextAffinity::kDownstream),
HitTest(45, 5));
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 2),
LayoutNG() ? TextAffinity::kUpstream
: TextAffinity::kDownstream),
HitTest(55, 5));
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 2),
LayoutNG() ? TextAffinity::kUpstream
: TextAffinity::kDownstream),
HitTest(65, 5));
}
TEST_P(LayoutViewHitTestTest, PseudoElementAfterBlockWithMargin) {
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 10px/15px Ahem; }"
"p::after { content: 'XY'; margin-left: 10px;}");
SetBodyInnerHTML("<div><p id=target>ab</p></div>");
const auto& text_ab = *To<Text>(GetElementById("target")->firstChild());
// In legacy layout, this position comes from |LayoutBlock::PositionBox()|
// for mac/unix, or |LayoutObject::FindPosition()| on android/windows.
const auto expected = PositionWithAffinity(
IsAndroidOrWindowsEditingBehavior() ? Position(text_ab, 2)
: Position(text_ab, 0),
LayoutNG() && IsAndroidOrWindowsEditingBehavior()
? TextAffinity::kUpstream
: TextAffinity::kDownstream);
EXPECT_EQ(expected, HitTest(20, 5)) << "after ab";
EXPECT_EQ(expected, HitTest(25, 5)) << "at margin-left";
EXPECT_EQ(expected, HitTest(30, 5)) << "before X";
EXPECT_EQ(expected, HitTest(35, 5)) << "at X";
EXPECT_EQ(expected, HitTest(45, 5)) << "at Y";
EXPECT_EQ(expected, HitTest(50, 5)) << "after Y";
EXPECT_EQ(expected, HitTest(55, 5)) << "after XY";
}
TEST_P(LayoutViewHitTestTest, TextAndInputsWithRtlDirection) {
LoadAhem();
InsertStyleElement(R"CSS(
body {
margin: 0 auto 0 0;
direction: rtl;
width: 200px;
font: 50px/1 Ahem;
}
input {
width: 100px;
height: 50px;
box-sizing: border-box;
vertical-align: top;
}
)CSS");
SetBodyInnerHTML("ab<input><input>cd");
Element* body = GetDocument().body();
Node* text_ab = body->firstChild();
Node* input_1 = text_ab->nextSibling();
Node* input_2 = input_1->nextSibling();
Node* text_cd = input_2->nextSibling();
Node* shadow_div_1 = input_1->GetShadowRoot()->firstChild();
Node* shadow_div_2 = input_2->GetShadowRoot()->firstChild();
TextAffinity downstream_if_ng =
LayoutNG() ? TextAffinity::kDownstream : TextAffinity::kUpstream;
// Note: This is a crash test. The expectations only reflect the current
// behavior, which may change.
for (int y : {0, 25, 49}) {
for (int x : {0, 25}) {
EXPECT_EQ(PositionWithAffinity(Position::AfterNode(*input_1),
LayoutNG() ? TextAffinity::kUpstream
: TextAffinity::kDownstream),
HitTest(x, y));
}
for (int x : {26, 50, 75}) {
EXPECT_EQ(PositionWithAffinity(Position(text_ab, 1), downstream_if_ng),
HitTest(x, y));
}
for (int x : {76, 99}) {
EXPECT_EQ(
PositionWithAffinity(Position(text_ab, 2), TextAffinity::kUpstream),
HitTest(x, y));
}
for (int x : {100, 125, 150, 175, 199}) {
EXPECT_EQ(PositionWithAffinity(Position(shadow_div_1, 0)), HitTest(x, y));
}
EXPECT_EQ(PositionWithAffinity(Position::AfterNode(*input_1)),
HitTest(200, y));
}
for (int y : {50, 75, 99}) {
for (int x : {0, 25, 50, 75, 99}) {
EXPECT_EQ(PositionWithAffinity(Position(shadow_div_2, 0)), HitTest(x, y));
}
for (int x : {100, 125}) {
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 0)), HitTest(x, y));
}
for (int x : {126, 150, 175}) {
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 1), downstream_if_ng),
HitTest(x, y));
}
for (int x : {176, 200}) {
EXPECT_EQ(PositionWithAffinity(LayoutNG() ? Position::BeforeNode(*input_2)
: Position(input_2, 0)),
HitTest(x, y));
}
}
if (IsAndroidOrWindowsEditingBehavior()) {
for (int x : {0, 25, 50, 75, 99}) {
EXPECT_EQ(PositionWithAffinity(Position::AfterNode(*input_2)),
HitTest(x, 100));
}
for (int x : {100, 125}) {
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 0)), HitTest(x, 100));
}
for (int x : {126, 150, 175}) {
EXPECT_EQ(PositionWithAffinity(Position(text_cd, 1), downstream_if_ng),
HitTest(x, 100));
}
for (int x : {176, 200}) {
EXPECT_EQ(PositionWithAffinity(LayoutNG() ? Position::BeforeNode(*input_2)
: Position(input_2, 0)),
HitTest(x, 100));
}
} else {
for (int x : {0, 25, 50, 75, 100, 125, 150, 175, 200}) {
EXPECT_EQ(PositionWithAffinity(LayoutNG() ? Position::AfterNode(*input_2)
: Position(text_cd, 2)),
HitTest(x, 100));
}
}
}
TEST_P(LayoutViewHitTestTest, TextCombineOneTextNode) {
ScopedLayoutNGTextCombineForTest enable_layout_ng_text_combine(true);
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 100px/110px Ahem; }"
"c { text-combine-upright: all; }"
"div { writing-mode: vertical-rl; }");
SetBodyInnerHTML("<div>a<c id=target>01234</c>b</div>");
// LayoutNGBlockFlow {HTML} at (0,0) size 800x600
// LayoutNGBlockFlow {BODY} at (0,0) size 800x600
// LayoutNGBlockFlow {DIV} at (0,0) size 110x300
// LayoutText {#text} at (5,0) size 100x100
// text run at (5,0) width 100: "a"
// LayoutInline {C} at (5,100) size 100x100
// LayoutNGTextCombine (anonymous) at (5,100) size 100x100
// LayoutText {#text} at (-5,0) size 110x100
// text run at (0,0) width 500: "01234"
// LayoutText {#text} at (5,200) size 100x100
// text run at (5,200) width 100: "b"
const auto& target = *GetElementById("target");
const auto& text_01234 = *To<Text>(target.firstChild());
const auto& text_a = *To<Text>(target.previousSibling());
const auto& text_b = *To<Text>(target.nextSibling());
if (LayoutNG()) {
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(0, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(10, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 1)), HitTest(20, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 1)), HitTest(30, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 2)), HitTest(40, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 2)), HitTest(50, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 3)), HitTest(60, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 3)), HitTest(70, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 4)), HitTest(80, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 4)), HitTest(90, 150));
EXPECT_EQ(
PositionWithAffinity(Position(text_01234, 5), TextAffinity::kUpstream),
HitTest(100, 150));
// TODO(yosin): should be text_01234@5
if (IsAndroidOrWindowsEditingBehavior()) {
EXPECT_EQ(PositionWithAffinity(Position(text_b, 0)), HitTest(110, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_b, 0)), HitTest(120, 150));
} else {
EXPECT_EQ(PositionWithAffinity(Position(text_a, 0)), HitTest(110, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_a, 0)), HitTest(120, 150));
}
} else {
// Note: Hit test for legacy layout is broken. This is just record of
// current behavior.
if (IsAndroidOrWindowsEditingBehavior())
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(0, 150));
else
EXPECT_EQ(PositionWithAffinity(Position(text_b, 1)), HitTest(0, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(10, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(20, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(30, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(40, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(50, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(60, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(70, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(80, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(90, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)), HitTest(100, 150));
if (IsAndroidOrWindowsEditingBehavior()) {
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)),
HitTest(110, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_01234, 0)),
HitTest(120, 150));
} else {
EXPECT_EQ(PositionWithAffinity(Position(text_a, 0)), HitTest(110, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_a, 0)), HitTest(120, 150));
}
}
}
TEST_P(LayoutViewHitTestTest, TextCombineTwoTextNodes) {
ScopedLayoutNGTextCombineForTest enable_layout_ng_text_combine(true);
LoadAhem();
InsertStyleElement(
"body { margin: 0px; font: 100px/110px Ahem; }"
"c { text-combine-upright: all; }"
"div { writing-mode: vertical-rl; }");
SetBodyInnerHTML("<div>a<c id=target>012<wbr>34</c>b</div>");
// LayoutNGBlockFlow {HTML} at (0,0) size 800x600
// LayoutNGBlockFlow {BODY} at (0,0) size 800x600
// LayoutNGBlockFlow {DIV} at (0,0) size 110x300
// LayoutText {#text} at (5,0) size 100x100
// text run at (5,0) width 100: "a"
// LayoutInline {C} at (5,100) size 100x100
// LayoutNGTextCombine (anonymous) at (5,100) size 100x100
// LayoutText {#text} at (-5,0) size 66x100
// text run at (0,0) width 300: "012"
// LayoutWordBreak {WBR} at (61,0) size 0x100
// text run at (300,0) width 0: "\x{200B}"
// LayoutText {#text} at (61,0) size 44x100
// text run at (300,0) width 200: "34"
// LayoutInline {B} at (5,200) size 100x100
// LayoutText {#text} at (5,200) size 100x100
// text run at (5,200) width 100: "b"
// const auto& target = *GetElementById("target");
const auto& target = *GetElementById("target");
const auto& text_012 = *To<Text>(target.firstChild());
const auto& text_34 = *To<Text>(target.lastChild());
const auto& text_a = *To<Text>(target.previousSibling());
const auto& text_b = *To<Text>(target.nextSibling());
if (LayoutNG()) {
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(0, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(10, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 1)), HitTest(20, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 1)), HitTest(30, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 2)), HitTest(40, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 2)), HitTest(50, 150));
EXPECT_EQ(
PositionWithAffinity(Position(text_012, 3), TextAffinity::kUpstream),
HitTest(60, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_34, 0)), HitTest(70, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_34, 1)), HitTest(80, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_34, 1)), HitTest(90, 150));
EXPECT_EQ(
PositionWithAffinity(Position(text_34, 2), TextAffinity::kUpstream),
HitTest(100, 150));
// TODO(yosin): should be text_012@5
if (IsAndroidOrWindowsEditingBehavior()) {
EXPECT_EQ(PositionWithAffinity(Position(text_b, 0)), HitTest(110, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_b, 0)), HitTest(120, 150));
} else {
EXPECT_EQ(PositionWithAffinity(Position(text_a, 0)), HitTest(110, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_a, 0)), HitTest(120, 150));
}
} else {
// Note: Hit test for legacy layout is broken. This is just record of
// current behavior.
if (IsAndroidOrWindowsEditingBehavior())
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(0, 150));
else
EXPECT_EQ(PositionWithAffinity(Position(text_b, 1)), HitTest(0, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(10, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(20, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(30, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(40, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(50, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(60, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(70, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(80, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(90, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(100, 150));
if (IsAndroidOrWindowsEditingBehavior()) {
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(110, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_012, 0)), HitTest(120, 150));
} else {
EXPECT_EQ(PositionWithAffinity(Position(text_a, 0)), HitTest(110, 150));
EXPECT_EQ(PositionWithAffinity(Position(text_a, 0)), HitTest(120, 150));
}
}
}
} // namespace blink