blob: 6aff41e7dea3a508cb49550e3914e8e16e03f9a5 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/page_animator.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
#include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
#include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
#include "ui/gfx/geometry/rect_conversions.h"
namespace blink {
namespace {
inline PaintLayerScrollableArea* GetScrollableArea(
const LayoutBlock* container) {
return To<Element>(container->GetNode())
->GetLayoutBoxForScrolling()
->GetScrollableArea();
}
} // namespace
class VisualRectMappingTest : public PaintTestConfigurations,
public RenderingTest {
public:
VisualRectMappingTest()
: RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()) {}
protected:
enum Flags { kContainsToEnclosingRect = 1 << 0 };
void SetUp() override {
EnableCompositing();
RenderingTest::SetUp();
}
LayoutView& GetLayoutView() const { return *GetDocument().GetLayoutView(); }
void CheckPaintInvalidationVisualRect(
const LayoutObject& object,
const LayoutBoxModelObject& ancestor,
const PhysicalRect& expected_visual_rect_in_ancestor) {
CheckVisualRect(object, ancestor, object.LocalVisualRect(),
expected_visual_rect_in_ancestor);
}
void CheckVisualRect(const LayoutObject& object,
const LayoutBoxModelObject& ancestor,
const PhysicalRect& local_rect,
const PhysicalRect& expected_visual_rect_in_ancestor,
unsigned flags = 0) {
auto slow_map_rect = local_rect;
object.MapToVisualRectInAncestorSpace(&ancestor, slow_map_rect);
FloatClipRect geometry_mapper_rect((gfx::RectF(local_rect)));
const FragmentData& fragment_data = object.FirstFragment();
if (fragment_data.HasLocalBorderBoxProperties()) {
auto local_rect_copy = local_rect;
object.MapToVisualRectInAncestorSpace(&ancestor, local_rect_copy,
kUseGeometryMapper);
geometry_mapper_rect.SetRect(gfx::RectF(local_rect_copy));
}
if (expected_visual_rect_in_ancestor.IsEmpty()) {
EXPECT_TRUE(slow_map_rect.IsEmpty());
if (fragment_data.HasLocalBorderBoxProperties())
EXPECT_TRUE(geometry_mapper_rect.Rect().IsEmpty());
return;
}
if (flags & kContainsToEnclosingRect) {
EXPECT_TRUE(
ToEnclosingRect(slow_map_rect)
.Contains(ToEnclosingRect(expected_visual_rect_in_ancestor)));
if (object.FirstFragment().HasLocalBorderBoxProperties()) {
EXPECT_TRUE(
gfx::ToEnclosingRect(geometry_mapper_rect.Rect())
.Contains(ToEnclosingRect(expected_visual_rect_in_ancestor)));
}
} else {
EXPECT_EQ(expected_visual_rect_in_ancestor, slow_map_rect);
if (object.FirstFragment().HasLocalBorderBoxProperties()) {
EXPECT_EQ(expected_visual_rect_in_ancestor,
PhysicalRect::EnclosingRect(geometry_mapper_rect.Rect()));
}
}
}
// Checks the result of MapToVisualRectInAncestorSpace with and without
// geometry mapper.
void CheckMapToVisualRectInAncestorSpace(const PhysicalRect& rect,
const PhysicalRect& expected,
const LayoutObject* object,
const LayoutBoxModelObject* ancestor,
VisualRectFlags flags,
bool expected_retval) {
PhysicalRect result = rect;
EXPECT_EQ(expected_retval,
object->MapToVisualRectInAncestorSpace(ancestor, result, flags));
EXPECT_EQ(result, expected);
result = rect;
EXPECT_EQ(expected_retval,
object->MapToVisualRectInAncestorSpace(
ancestor, result,
static_cast<VisualRectFlags>(flags | kUseGeometryMapper)));
EXPECT_EQ(result, expected);
}
};
INSTANTIATE_PAINT_TEST_SUITE_P(VisualRectMappingTest);
TEST_P(VisualRectMappingTest, LayoutText) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='container' style='vertical-align: bottom; overflow: scroll;
width: 50px; height: 50px'>
<span><img style='width: 20px; height: 100px'></span>
<span id='text'>text text text text text text text</span>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* text = GetLayoutObjectByElementId("text")->SlowFirstChild();
auto* scrollable_area = GetScrollableArea(container);
scrollable_area->ScrollToAbsolutePosition(
gfx::PointF(scrollable_area->ScrollPosition().x(), 50));
UpdateAllLifecyclePhasesForTest();
PhysicalRect original_rect(0, 60, 20, 80);
PhysicalRect rect = original_rect;
// For a LayoutText, the "local coordinate space" is actually the contents
// coordinate space of the containing block, so the following mappings are
// only affected by the geometry of the container, not related to where the
// text is laid out.
EXPECT_TRUE(text->MapToVisualRectInAncestorSpace(container, rect));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
EXPECT_EQ(rect, PhysicalRect(0, 10, 20, 80));
rect = original_rect;
EXPECT_TRUE(text->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(rect, PhysicalRect(0, 10, 20, 40));
rect = PhysicalRect(0, 60, 80, 0);
EXPECT_TRUE(
text->MapToVisualRectInAncestorSpace(container, rect, kEdgeInclusive));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
EXPECT_EQ(rect, PhysicalRect(0, 10, 80, 0));
}
TEST_P(VisualRectMappingTest, LayoutTextContainerFlippedWritingMode) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='container' style='vertical-align: bottom; overflow: scroll;
width: 50px; height: 50px; writing-mode: vertical-rl'>
<span><img style='width: 20px; height: 100px'></span>
<span id='text'>text text text text text text text</span>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* text = GetLayoutObjectByElementId("text")->SlowFirstChild();
auto* scrollable_area = GetScrollableArea(container);
scrollable_area->ScrollToAbsolutePosition(
gfx::PointF(scrollable_area->ScrollPosition().x(), 50));
UpdateAllLifecyclePhasesForTest();
// All results are the same as VisualRectMappingTest.LayoutText because all
// rects are in physical coordinates of the container's contents space.
PhysicalRect original_rect(0, 60, 20, 80);
PhysicalRect rect = original_rect;
EXPECT_TRUE(text->MapToVisualRectInAncestorSpace(container, rect));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
EXPECT_EQ(rect, PhysicalRect(0, 10, 20, 80));
rect = original_rect;
EXPECT_TRUE(text->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(rect, PhysicalRect(0, 10, 20, 40));
rect = PhysicalRect(0, 60, 80, 0);
EXPECT_TRUE(
text->MapToVisualRectInAncestorSpace(container, rect, kEdgeInclusive));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
EXPECT_EQ(rect, PhysicalRect(0, 10, 80, 0));
}
TEST_P(VisualRectMappingTest, LayoutInline) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='container' style='overflow: scroll; width: 50px; height: 50px'>
<span><img style='width: 20px; height: 100px'></span>
<span id='leaf'></span>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
LayoutObject* leaf = container->LastChild();
auto* scrollable_area = GetScrollableArea(container);
scrollable_area->ScrollToAbsolutePosition(
gfx::PointF(scrollable_area->ScrollPosition().x(), 50));
UpdateAllLifecyclePhasesForTest();
PhysicalRect original_rect(0, 60, 20, 80);
PhysicalRect rect = original_rect;
EXPECT_TRUE(leaf->MapToVisualRectInAncestorSpace(container, rect));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
EXPECT_EQ(rect, PhysicalRect(0, 10, 20, 80));
rect = original_rect;
EXPECT_TRUE(leaf->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(rect, PhysicalRect(0, 10, 20, 40));
// The span is empty.
CheckPaintInvalidationVisualRect(*leaf, GetLayoutView(), PhysicalRect());
rect = PhysicalRect(0, 60, 80, 0);
EXPECT_TRUE(
leaf->MapToVisualRectInAncestorSpace(container, rect, kEdgeInclusive));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
EXPECT_EQ(rect, PhysicalRect(0, 10, 80, 0));
}
TEST_P(VisualRectMappingTest, LayoutInlineContainerFlippedWritingMode) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='container' style='overflow: scroll; width: 50px; height: 50px;
writing-mode: vertical-rl'>
<span><img style='width: 20px; height: 100px'></span>
<span id='leaf'></span>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
LayoutObject* leaf = container->LastChild();
auto* scrollable_area = GetScrollableArea(container);
scrollable_area->ScrollToAbsolutePosition(
gfx::PointF(scrollable_area->ScrollPosition().x(), 50));
UpdateAllLifecyclePhasesForTest();
// All results are the same as VisualRectMappingTest.LayoutInline because all
// rects are in physical coordinates.
PhysicalRect original_rect(0, 60, 20, 80);
PhysicalRect rect = original_rect;
EXPECT_TRUE(leaf->MapToVisualRectInAncestorSpace(container, rect));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
EXPECT_EQ(rect, PhysicalRect(0, 10, 20, 80));
rect = original_rect;
EXPECT_TRUE(leaf->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(rect, PhysicalRect(0, 10, 20, 40));
// The span is empty.
CheckPaintInvalidationVisualRect(*leaf, GetLayoutView(), PhysicalRect());
rect = PhysicalRect(0, 60, 80, 0);
EXPECT_TRUE(
leaf->MapToVisualRectInAncestorSpace(container, rect, kEdgeInclusive));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
EXPECT_EQ(rect, PhysicalRect(0, 10, 80, 0));
}
TEST_P(VisualRectMappingTest, LayoutView) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id=frameContainer>
<iframe src='http://test.com' width='50' height='50'
frameBorder='0'></iframe>
</div>
)HTML");
SetChildFrameHTML(
"<style>body { margin: 0; }</style>"
"<span><img style='width: 20px; height: 100px'></span>text text text");
UpdateAllLifecyclePhasesForTest();
auto* frame_container =
To<LayoutBlock>(GetLayoutObjectByElementId("frameContainer"));
auto* frame_body = To<LayoutBlock>(ChildDocument().body()->GetLayoutObject());
auto* frame_text = To<LayoutText>(frame_body->LastChild());
// This case involves clipping: frame height is 50, y-coordinate of result
// rect is 13, so height should be clipped to (50 - 13) == 37.
ChildDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 47), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
PhysicalRect original_rect(4, 60, 20, 80);
PhysicalRect rect = original_rect;
EXPECT_TRUE(
frame_text->MapToVisualRectInAncestorSpace(frame_container, rect));
EXPECT_EQ(rect, PhysicalRect(4, 13, 20, 37));
rect = original_rect;
EXPECT_TRUE(
frame_text->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(rect, PhysicalRect(4, 13, 20, 37));
CheckPaintInvalidationVisualRect(*frame_text, GetLayoutView(),
PhysicalRect());
rect = PhysicalRect(4, 60, 0, 80);
EXPECT_TRUE(frame_text->MapToVisualRectInAncestorSpace(frame_container, rect,
kEdgeInclusive));
EXPECT_EQ(rect, PhysicalRect(4, 13, 0, 37));
}
TEST_P(VisualRectMappingTest, LayoutViewSubpixelRounding) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id=frameContainer style='position: relative; left: 0.5px'>
<iframe style='position: relative; left: 0.5px' width='200'
height='200' src='http://test.com' frameBorder='0'></iframe>
</div>
)HTML");
SetChildFrameHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='target' style='position: relative; width: 100px; height: 100px;
left: 0.5px'></div>
)HTML");
UpdateAllLifecyclePhasesForTest();
auto* frame_container =
To<LayoutBlock>(GetLayoutObjectByElementId("frameContainer"));
LayoutObject* target =
ChildDocument().getElementById(AtomicString("target"))->GetLayoutObject();
PhysicalRect rect(0, 0, 100, 100);
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(frame_container, rect));
// When passing from the iframe to the parent frame, the rect of (0.5, 0, 100,
// 100) is expanded to (0, 0, 100, 100), and then offset by the 0.5 offset of
// frameContainer.
EXPECT_EQ(PhysicalRect(LayoutUnit(0.5), LayoutUnit(), LayoutUnit(101),
LayoutUnit(100)),
rect);
}
TEST_P(VisualRectMappingTest, LayoutViewDisplayNone) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id=frameContainer>
<iframe id='frame' src='http://test.com' width='50' height='50'
frameBorder='0'></iframe>
</div>
)HTML");
SetChildFrameHTML(
"<style>body { margin: 0; }</style>"
"<div style='width:100px;height:100px;'></div>");
UpdateAllLifecyclePhasesForTest();
auto* frame_container =
To<LayoutBlock>(GetLayoutObjectByElementId("frameContainer"));
auto* frame_body = To<LayoutBlock>(ChildDocument().body()->GetLayoutObject());
auto* frame_div = To<LayoutBlock>(frame_body->LastChild());
// This part is copied from the LayoutView test, just to ensure that the
// mapped rect is valid before display:none is set on the iframe.
ChildDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 47), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
PhysicalRect original_rect(4, 60, 20, 80);
PhysicalRect rect = original_rect;
EXPECT_TRUE(frame_div->MapToVisualRectInAncestorSpace(frame_container, rect));
EXPECT_EQ(rect, PhysicalRect(4, 13, 20, 37));
Element* frame_element = GetElementById("frame");
frame_element->SetInlineStyleProperty(CSSPropertyID::kDisplay, "none");
UpdateAllLifecyclePhasesForTest();
frame_body = To<LayoutBlock>(ChildDocument().body()->GetLayoutObject());
EXPECT_EQ(nullptr, frame_body);
}
TEST_P(VisualRectMappingTest, SelfFlippedWritingMode) {
SetBodyInnerHTML(R"HTML(
<div id='target' style='writing-mode: vertical-rl;
box-shadow: 40px 20px black; width: 100px; height: 50px;
position: absolute; top: 111px; left: 222px'>
</div>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect local_visual_rect = target->LocalVisualRect();
// 140 = width(100) + box_shadow_offset_x(40)
// 70 = height(50) + box_shadow_offset_y(20)
EXPECT_EQ(PhysicalRect(0, 0, 140, 70), local_visual_rect);
PhysicalRect rect = local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(target, rect));
// This rect is in physical coordinates of target.
EXPECT_EQ(PhysicalRect(0, 0, 140, 70), rect);
CheckPaintInvalidationVisualRect(*target, GetLayoutView(),
PhysicalRect(222, 111, 140, 70));
}
TEST_P(VisualRectMappingTest, ContainerFlippedWritingMode) {
SetBodyInnerHTML(R"HTML(
<div id='container' style='writing-mode: vertical-rl;
position: absolute; top: 111px; left: 222px'>
<div id='target' style='box-shadow: 40px 20px black; width: 100px;
height: 90px'></div>
<div style='width: 100px; height: 100px'></div>
</div>
)HTML");
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect target_local_visual_rect = target->LocalVisualRect();
// 140 = width(100) + box_shadow_offset_x(40)
// 110 = height(90) + box_shadow_offset_y(20)
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), target_local_visual_rect);
PhysicalRect rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(target, rect));
// This rect is in physical coordinates of target.
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), rect);
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(container, rect));
// 100 is the physical x location of target in container.
EXPECT_EQ(PhysicalRect(100, 0, 140, 110), rect);
CheckPaintInvalidationVisualRect(*target, GetLayoutView(),
PhysicalRect(322, 111, 140, 110));
PhysicalRect container_local_visual_rect = container->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 200, 100), container_local_visual_rect);
rect = container_local_visual_rect;
EXPECT_TRUE(container->MapToVisualRectInAncestorSpace(container, rect));
EXPECT_EQ(PhysicalRect(0, 0, 200, 100), rect);
rect = container_local_visual_rect;
EXPECT_TRUE(
container->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(PhysicalRect(222, 111, 200, 100), rect);
}
TEST_P(VisualRectMappingTest, ContainerOverflowScroll) {
SetBodyInnerHTML(R"HTML(
<div id='container' style='position: absolute; top: 111px; left: 222px;
border: 10px solid red; overflow: scroll; width: 50px;
height: 80px'>
<div id='target' style='box-shadow: 40px 20px black; width: 100px;
height: 90px'></div>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* scrollable_area = GetScrollableArea(container);
EXPECT_EQ(0, scrollable_area->ScrollPosition().y());
EXPECT_EQ(0, scrollable_area->ScrollPosition().x());
scrollable_area->ScrollToAbsolutePosition(gfx::PointF(8, 7));
UpdateAllLifecyclePhasesForTest();
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect target_local_visual_rect = target->LocalVisualRect();
// 140 = width(100) + box_shadow_offset_x(40)
// 110 = height(90) + box_shadow_offset_y(20)
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), target_local_visual_rect);
PhysicalRect rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(target, rect));
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), rect);
rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(container, rect));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
// 2 = target_x(0) + container_border_left(10) - scroll_left(8)
// 3 = target_y(0) + container_border_top(10) - scroll_top(7)
// Rect is not clipped by container's overflow clip because of
// overflow:scroll.
EXPECT_EQ(PhysicalRect(2, 3, 140, 110), rect);
// (2, 3, 140, 100) is first clipped by container's overflow clip, to
// (10, 10, 50, 80), then is by added container's offset in LayoutView
// (222, 111).
CheckPaintInvalidationVisualRect(*target, GetLayoutView(),
PhysicalRect(232, 121, 50, 80));
PhysicalRect container_local_visual_rect = container->LocalVisualRect();
// Because container has overflow clip, its visual overflow doesn't include
// overflow from children.
// 70 = width(50) + border_left_width(10) + border_right_width(10)
// 100 = height(80) + border_top_width(10) + border_bottom_width(10)
EXPECT_EQ(PhysicalRect(0, 0, 70, 100), container_local_visual_rect);
rect = container_local_visual_rect;
EXPECT_TRUE(container->MapToVisualRectInAncestorSpace(container, rect));
// Container should not apply overflow clip on its own overflow rect.
EXPECT_EQ(PhysicalRect(0, 0, 70, 100), rect);
CheckPaintInvalidationVisualRect(*container, GetLayoutView(),
PhysicalRect(222, 111, 70, 100));
}
TEST_P(VisualRectMappingTest, ContainerFlippedWritingModeAndOverflowScroll) {
SetBodyInnerHTML(R"HTML(
<div id='container' style='writing-mode: vertical-rl;
position: absolute; top: 111px; left: 222px; border: solid red;
border-width: 10px 20px 30px 40px; overflow: scroll; width: 50px;
height: 80px'>
<div id='target' style='box-shadow: 40px 20px black; width: 100px;
height: 90px'></div>
<div style='width: 100px; height: 100px'></div>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* scrollable_area = GetScrollableArea(container);
EXPECT_EQ(0, scrollable_area->ScrollPosition().y());
// The initial scroll offset is to the left-most because of flipped blocks
// writing mode.
// 150 = total_scrollable_overflow(100 + 100) - width(50)
EXPECT_EQ(150, scrollable_area->ScrollPosition().x());
// Scroll to the right by 8 pixels.
scrollable_area->ScrollToAbsolutePosition(gfx::PointF(142, 7));
UpdateAllLifecyclePhasesForTest();
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect target_local_visual_rect = target->LocalVisualRect();
// 140 = width(100) + box_shadow_offset_x(40)
// 110 = height(90) + box_shadow_offset_y(20)
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), target_local_visual_rect);
PhysicalRect rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(target, rect));
// This rect is in physical coordinates of target.
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), rect);
rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(container, rect));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
// -2 = target_physical_x(100) + container_border_left(40) - scroll_left(142)
// 3 = target_y(0) + container_border_top(10) - scroll_top(7)
// Rect is clipped by container's overflow clip because of overflow:scroll.
EXPECT_EQ(PhysicalRect(-2, 3, 140, 110), rect);
// (-2, 3, 140, 100) is first clipped by container's overflow clip, to
// (40, 10, 50, 80), then is added by container's offset in LayoutView
// (222, 111).
PhysicalRect expectation(262, 121, 50, 80);
CheckPaintInvalidationVisualRect(*target, GetLayoutView(), expectation);
PhysicalRect container_local_visual_rect = container->LocalVisualRect();
// Because container has overflow clip, its visual overflow doesn't include
// overflow from children.
// 110 = width(50) + border_left_width(40) + border_right_width(20)
// 120 = height(80) + border_top_width(10) + border_bottom_width(30)
EXPECT_EQ(PhysicalRect(0, 0, 110, 120), container_local_visual_rect);
rect = container_local_visual_rect;
EXPECT_TRUE(container->MapToVisualRectInAncestorSpace(container, rect));
EXPECT_EQ(PhysicalRect(0, 0, 110, 120), rect);
expectation = PhysicalRect(222, 111, 110, 120);
CheckPaintInvalidationVisualRect(*container, GetLayoutView(), expectation);
}
TEST_P(VisualRectMappingTest, ContainerOverflowHidden) {
SetBodyInnerHTML(R"HTML(
<div id='container' style='position: absolute; top: 111px; left: 222px;
border: 10px solid red; overflow: hidden; width: 50px;
height: 80px;'>
<div id='target' style='box-shadow: 40px 20px black; width: 100px;
height: 90px'></div>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* scrollable_area = GetScrollableArea(container);
EXPECT_EQ(0, scrollable_area->ScrollPosition().y());
EXPECT_EQ(0, scrollable_area->ScrollPosition().x());
scrollable_area->ScrollToAbsolutePosition(gfx::PointF(28, 27));
UpdateAllLifecyclePhasesForTest();
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
auto target_local_visual_rect = target->LocalVisualRect();
// 140 = width(100) + box_shadow_offset_x(40)
// 110 = height(90) + box_shadow_offset_y(20)
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), target_local_visual_rect);
auto rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(target, rect));
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), rect);
rect = target_local_visual_rect;
// Rect is not clipped by container's overflow clip.
CheckVisualRect(*target, *container, rect, PhysicalRect(10, 10, 140, 110));
}
TEST_P(VisualRectMappingTest, ContainerFlippedWritingModeAndOverflowHidden) {
SetBodyInnerHTML(R"HTML(
<div id='container' style='writing-mode: vertical-rl;
position: absolute; top: 111px; left: 222px; border: solid red;
border-width: 10px 20px 30px 40px; overflow: hidden; width: 50px;
height: 80px'>
<div id='target' style='box-shadow: 40px 20px black; width: 100px;
height: 90px'></div>
<div style='width: 100px; height: 100px'></div>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* scrollable_area = GetScrollableArea(container);
EXPECT_EQ(0, scrollable_area->ScrollPosition().y());
// The initial scroll offset is to the left-most because of flipped blocks
// writing mode.
// 150 = total_scrollable_overflow(100 + 100) - width(50)
EXPECT_EQ(150, scrollable_area->ScrollPosition().x());
scrollable_area->ScrollToAbsolutePosition(gfx::PointF(82, 7));
UpdateAllLifecyclePhasesForTest();
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect target_local_visual_rect = target->LocalVisualRect();
// 140 = width(100) + box_shadow_offset_x(40)
// 110 = height(90) + box_shadow_offset_y(20)
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), target_local_visual_rect);
PhysicalRect rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(target, rect));
// This rect is in physical coordinates of target.
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), rect);
rect = target_local_visual_rect;
// 58 = target_physical_x(100) + container_border_left(40) - scroll_left(58)
CheckVisualRect(*target, *container, rect, PhysicalRect(-10, 10, 140, 110));
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(container, rect));
}
TEST_P(VisualRectMappingTest, ContainerAndTargetDifferentFlippedWritingMode) {
SetBodyInnerHTML(R"HTML(
<div id='container' style='writing-mode: vertical-rl;
position: absolute; top: 111px; left: 222px; border: solid red;
border-width: 10px 20px 30px 40px; overflow: scroll; width: 50px;
height: 80px'>
<div id='target' style='writing-mode: vertical-lr; width: 100px;
height: 90px; box-shadow: 40px 20px black'></div>
<div style='width: 100px; height: 100px'></div>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* scrollable_area = GetScrollableArea(container);
EXPECT_EQ(0, scrollable_area->ScrollPosition().y());
// The initial scroll offset is to the left-most because of flipped blocks
// writing mode.
// 150 = total_scrollable_overflow(100 + 100) - width(50)
EXPECT_EQ(150, scrollable_area->ScrollPosition().x());
// Scroll to the right by 8 pixels.
scrollable_area->ScrollToAbsolutePosition(gfx::PointF(142, 7));
UpdateAllLifecyclePhasesForTest();
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect target_local_visual_rect = target->LocalVisualRect();
// 140 = width(100) + box_shadow_offset_x(40)
// 110 = height(90) + box_shadow_offset_y(20)
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), target_local_visual_rect);
PhysicalRect rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(target, rect));
// This rect is in physical coordinates of target.
EXPECT_EQ(PhysicalRect(0, 0, 140, 110), rect);
rect = target_local_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(container, rect));
rect.Move(-PhysicalOffset(container->ScrolledContentOffset()));
// -2 = target_physical_x(100) + container_border_left(40) - scroll_left(142)
// 3 = target_y(0) + container_border_top(10) - scroll_top(7)
// Rect is not clipped by container's overflow clip.
EXPECT_EQ(PhysicalRect(-2, 3, 140, 110), rect);
}
TEST_P(VisualRectMappingTest,
DifferentPaintInvalidaitionContainerForAbsolutePosition) {
SetPreferCompositingToLCDText(true);
SetBodyInnerHTML(R"HTML(
<div id='stacking-context' style='opacity: 0.9; background: blue;
will-change: transform'>
<div id='scroller' style='overflow: scroll; width: 80px;
height: 80px'>
<div id='absolute' style='position: absolute; top: 111px;
left: 222px; width: 50px; height: 50px; background: green'>
</div>
<div id='normal-flow' style='width: 2000px; height: 2000px;
background: yellow'></div>
</div>
</div>
)HTML");
auto* scroller = To<LayoutBlock>(GetLayoutObjectByElementId("scroller"));
GetScrollableArea(scroller)->ScrollToAbsolutePosition(gfx::PointF(88, 77));
UpdateAllLifecyclePhasesForTest();
auto* normal_flow =
To<LayoutBlock>(GetLayoutObjectByElementId("normal-flow"));
PhysicalRect normal_flow_visual_rect = normal_flow->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 2000, 2000), normal_flow_visual_rect);
PhysicalRect rect = normal_flow_visual_rect;
EXPECT_TRUE(normal_flow->MapToVisualRectInAncestorSpace(scroller, rect));
EXPECT_EQ(PhysicalRect(0, 0, 2000, 2000), rect);
auto* stacking_context =
To<LayoutBlock>(GetLayoutObjectByElementId("stacking-context"));
auto* absolute = To<LayoutBlock>(GetLayoutObjectByElementId("absolute"));
EXPECT_EQ(stacking_context, absolute->Container());
EXPECT_EQ(PhysicalRect(0, 0, 50, 50), absolute->LocalVisualRect());
CheckPaintInvalidationVisualRect(*absolute, *stacking_context,
PhysicalRect(222, 111, 50, 50));
}
TEST_P(VisualRectMappingTest,
ContainerOfAbsoluteAbovePaintInvalidationContainer) {
SetPreferCompositingToLCDText(true);
SetBodyInnerHTML(
"<div id='container' style='position: absolute; top: 88px; left: 99px'>"
" <div style='height: 222px'></div>"
// This div makes stacking-context composited.
" <div style='position: absolute; width: 1px; height: 1px; "
" background:yellow; will-change: transform'></div>"
// This stacking context is paintInvalidationContainer of the absolute
// child, but not a container of it.
" <div id='stacking-context' style='opacity: 0.9'>"
" <div id='absolute' style='position: absolute; top: 50px; left: 50px;"
" width: 50px; height: 50px; background: green'></div>"
" </div>"
"</div>");
auto* stacking_context =
To<LayoutBlock>(GetLayoutObjectByElementId("stacking-context"));
auto* absolute = To<LayoutBlock>(GetLayoutObjectByElementId("absolute"));
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
EXPECT_EQ(container, absolute->Container());
PhysicalRect absolute_visual_rect = absolute->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 50, 50), absolute_visual_rect);
PhysicalRect rect = absolute_visual_rect;
EXPECT_TRUE(absolute->MapToVisualRectInAncestorSpace(stacking_context, rect));
// -172 = top(50) - y_offset_of_stacking_context(222)
EXPECT_EQ(PhysicalRect(50, -172, 50, 50), rect);
// Call checkPaintInvalidationVisualRect to deal with layer squashing.
CheckPaintInvalidationVisualRect(*absolute, GetLayoutView(),
PhysicalRect(149, 138, 50, 50));
}
TEST_P(VisualRectMappingTest, CSSClip) {
SetBodyInnerHTML(R"HTML(
<div id='container' style='position: absolute; top: 0px; left: 0px;
clip: rect(0px, 200px, 200px, 0px)'>
<div id='target' style='width: 400px; height: 400px'></div>
</div>
)HTML");
auto* target = GetLayoutBoxByElementId("target");
EXPECT_EQ(PhysicalRect(0, 0, 400, 400), target->LocalVisualRect());
CheckPaintInvalidationVisualRect(*target, GetLayoutView(),
PhysicalRect(0, 0, 200, 200));
}
TEST_P(VisualRectMappingTest, ContainPaint) {
SetBodyInnerHTML(R"HTML(
<div id='container' style='position: absolute; top: 0px; left: 0px;
width: 200px; height: 200px; contain: paint'>
<div id='target' style='width: 400px; height: 400px'></div>
</div>
)HTML");
auto* target = GetLayoutBoxByElementId("target");
EXPECT_EQ(PhysicalRect(0, 0, 400, 400), target->LocalVisualRect());
CheckPaintInvalidationVisualRect(*target, GetLayoutView(),
PhysicalRect(0, 0, 200, 200));
}
TEST_P(VisualRectMappingTest, FloatUnderInline) {
SetBodyInnerHTML(R"HTML(
<div style='position: absolute; top: 55px; left: 66px'>
<span id='span' style='position: relative; top: 100px; left: 200px'>
<div id='target' style='float: left; width: 33px; height: 44px'>
</div>
</span>
</div>
)HTML");
auto* span = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("span"));
auto* target = GetLayoutBoxByElementId("target");
PhysicalRect target_visual_rect = target->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 33, 44), target_visual_rect);
PhysicalRect rect = target_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
// Inline-level floats are children of their inline-level containers. As such
// they are positioned relative to their inline-level container, (and shifted
// by an additional 200,100 in this case).
EXPECT_EQ(PhysicalRect(266, 155, 33, 44), rect);
rect = target_visual_rect;
CheckVisualRect(*target, *span, rect, PhysicalRect(200, 100, 33, 44));
}
TEST_P(VisualRectMappingTest, FloatUnderInlineVerticalRL) {
SetBodyInnerHTML(R"HTML(
<div style='position: absolute; writing-mode: vertical-rl;
top: 55px; left: 66px; width: 600px; height: 400px'>
<span id='span' style='position: relative; top: 100px; left: -200px'>
<div id='target' style='float: left; width: 33px; height: 44px'>
</div>
</span>
</div>
)HTML");
auto* span = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("span"));
auto* target = GetLayoutBoxByElementId("target");
auto target_visual_rect = target->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 33, 44), target_visual_rect);
auto rect = target_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
// Inline-level floats are children of their inline-level containers. As such
// they are positioned relative to their inline-level container, (and shifted
// by an additional 200,100 in this case).
EXPECT_EQ(PhysicalRect(66 + 600 - 200 - 33, 55 + 100, 33, 44), rect);
// An inline object's coordinate space is its containing block's coordinate
// space shifted by the inline's relative offset. |target|'s left is 100 from
// the right edge of the coordinate space whose width is 600.
rect = target_visual_rect;
CheckVisualRect(*target, *span, rect, PhysicalRect(367, 100, 33, 44));
}
TEST_P(VisualRectMappingTest, InlineBlock) {
SetBodyInnerHTML(R"HTML(
<div style="position: absolute; top: 55px; left: 66px">
<span id="span" style="position: relative; top: 100px; left: 200px">
<div id="target"
style="display: inline-block; width: 33px; height: 44px">
</div>
</span>
</div>
)HTML");
auto* span = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("span"));
auto* target = GetLayoutBoxByElementId("target");
auto target_visual_rect = target->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 33, 44), target_visual_rect);
auto rect = target_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(PhysicalRect(266, 155, 33, 44), rect);
rect = target_visual_rect;
CheckVisualRect(*target, *span, rect, PhysicalRect(200, 100, 33, 44));
}
TEST_P(VisualRectMappingTest, InlineBlockVerticalRL) {
SetBodyInnerHTML(R"HTML(
<div style='position: absolute; writing-mode: vertical-rl;
top: 55px; left: 66px; width: 600px; height: 400px'>
<span id="span" style="position: relative; top: 100px; left: -200px">
<div id="target"
style="display: inline-block; width: 33px; height: 44px">
</div>
</span>
</div>
)HTML");
auto* span = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("span"));
auto* target = GetLayoutBoxByElementId("target");
auto target_visual_rect = target->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 33, 44), target_visual_rect);
auto rect = target_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(PhysicalRect(66 + 600 - 200 - 33, 155, 33, 44), rect);
// An inline object's coordinate space is its containing block's coordinate
// space shifted by the inline's relative offset. |target|'s left is -33 from
// the right edge of the coordinate space whose width is 600.
rect = target_visual_rect;
CheckVisualRect(*target, *span, rect, PhysicalRect(367, 100, 33, 44));
}
TEST_P(VisualRectMappingTest, AbsoluteUnderRelativeInline) {
SetBodyInnerHTML(R"HTML(
<div style='position: absolute; top: 55px; left: 66px'>
<span id='span' style='position: relative; top: 100px; left: 200px'>
<div id='target' style='position: absolute; top: 50px; left: 100px;
width: 33px; height: 44px'>
</div>
</span>
</div>
)HTML");
auto* span = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("span"));
auto* target = GetLayoutBoxByElementId("target");
auto target_visual_rect = target->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 33, 44), target_visual_rect);
auto rect = target_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(PhysicalRect(66 + 200 + 100, 55 + 100 + 50, 33, 44), rect);
rect = target_visual_rect;
CheckVisualRect(*target, *span, rect, PhysicalRect(300, 150, 33, 44));
}
TEST_P(VisualRectMappingTest, AbsoluteUnderRelativeInlineVerticalRL) {
SetBodyInnerHTML(R"HTML(
<div style='position: absolute; writing-mode: vertical-rl;
top: 55px; left: 66px; width: 600px; height: 400px'>
<span id='span' style='position: relative; top: 100px; left: -200px'>
<div id='target' style='position: absolute; top: 50px; left: 100px;
width: 33px; height: 44px'>
</div>
</span>
</div>
)HTML");
auto* span = To<LayoutBoxModelObject>(GetLayoutObjectByElementId("span"));
auto* target = GetLayoutBoxByElementId("target");
auto target_visual_rect = target->LocalVisualRect();
EXPECT_EQ(PhysicalRect(0, 0, 33, 44), target_visual_rect);
auto rect = target_visual_rect;
EXPECT_TRUE(target->MapToVisualRectInAncestorSpace(&GetLayoutView(), rect));
EXPECT_EQ(PhysicalRect(66 + 600 - 200 + 100, 55 + 100 + 50, 33, 44), rect);
// An inline object's coordinate space is its containing block's coordinate
// space shifted by the inline's relative offset. |target|'s left is 100 from
// the right edge of the coordinate space whose width is 600.
rect = target_visual_rect;
CheckVisualRect(*target, *span, rect, PhysicalRect(500, 150, 33, 44));
}
TEST_P(VisualRectMappingTest, ShouldAccountForPreserve3d) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
#container {
transform: rotateX(-45deg);
width: 100px; height: 100px;
}
#target {
transform-style: preserve-3d; transform: rotateX(45deg);
background: lightblue;
width: 100px; height: 100px;
}
</style>
<div id='container'><div id='target'></div></div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect original_rect(0, 0, 100, 100);
// Multiply both matrices together before flattening.
gfx::Transform matrix = container->Layer()->CurrentTransform();
matrix.Flatten();
matrix *= target->Layer()->CurrentTransform();
PhysicalRect output =
PhysicalRect::EnclosingRect(matrix.MapRect(gfx::RectF(original_rect)));
CheckVisualRect(*target, *target->View(), original_rect, output,
kContainsToEnclosingRect);
}
TEST_P(VisualRectMappingTest, ShouldAccountForPreserve3dNested) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
#container {
transform-style: preserve-3d;
transform: rotateX(-45deg);
width: 100px; height: 100px;
}
#target {
transform-style: preserve-3d; transform: rotateX(45deg);
background: lightblue;
width: 100px; height: 100px;
}
</style>
<div id='container'><div id='target'></div></div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect original_rect(0, 0, 100, 100);
// Multiply both matrices together before flattening.
gfx::Transform matrix = container->Layer()->CurrentTransform();
matrix *= target->Layer()->CurrentTransform();
PhysicalRect output =
PhysicalRect::EnclosingRect(matrix.MapRect(gfx::RectF(original_rect)));
CheckVisualRect(*target, *target->View(), original_rect, output);
}
TEST_P(VisualRectMappingTest, ShouldAccountForPerspective) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
#container {
transform: rotateX(-45deg); perspective: 100px;
width: 100px; height: 100px;
}
#target {
transform-style: preserve-3d; transform: rotateX(45deg);
background: lightblue;
width: 100px; height: 100px;
}
</style>
<div id='container'><div id='target'></div></div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect original_rect(0, 0, 100, 100);
gfx::Transform matrix = container->Layer()->CurrentTransform();
matrix.Flatten();
gfx::Transform target_matrix;
// GetTransformfromContainer includes transform and perspective matrix
// of the container.
target->GetTransformFromContainer(container, PhysicalOffset(), target_matrix);
matrix *= target_matrix;
PhysicalRect output =
PhysicalRect::EnclosingRect(matrix.MapRect(gfx::RectF(original_rect)));
CheckVisualRect(*target, *target->View(), original_rect, output,
kContainsToEnclosingRect);
}
TEST_P(VisualRectMappingTest, ShouldAccountForPerspectiveNested) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
#container {
transform-style: preserve-3d;
transform: rotateX(-45deg); perspective: 100px;
width: 100px; height: 100px;
}
#target {
transform-style: preserve-3d; transform: rotateX(45deg);
background: lightblue;
width: 100px; height: 100px;
}
</style>
<div id='container'><div id='target'></div></div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect original_rect(0, 0, 100, 100);
gfx::Transform matrix = container->Layer()->CurrentTransform();
gfx::Transform target_matrix;
// GetTransformfromContainer includes transform and perspective matrix
// of the container.
target->GetTransformFromContainer(container, PhysicalOffset(), target_matrix);
matrix *= target_matrix;
PhysicalRect output =
PhysicalRect::EnclosingRect(matrix.MapRect(gfx::RectF(original_rect)));
CheckVisualRect(*target, *target->View(), original_rect, output);
}
TEST_P(VisualRectMappingTest, PerspectivePlusScroll) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
#container {
perspective: 100px;
width: 100px; height: 100px;
overflow: scroll;
}
#target {
transform: rotatex(45eg);
background: lightblue;
width: 100px; height: 100px;
}
#spacer {
width: 10px; height:2000px;
}
</style>
<div id='container'>
<div id='target'></div>
<div id='spacer'></div>
</div>
)HTML");
auto* container = To<LayoutBlock>(GetLayoutObjectByElementId("container"));
To<Element>(container->GetNode())->scrollTo(0, 5);
UpdateAllLifecyclePhasesForTest();
auto* target = To<LayoutBlock>(GetLayoutObjectByElementId("target"));
PhysicalRect originalRect(0, 0, 100, 100);
gfx::Transform transform;
target->GetTransformFromContainer(
container, target->OffsetFromContainer(container), transform);
transform.Flatten();
PhysicalRect output =
PhysicalRect::EnclosingRect(transform.MapRect(gfx::RectF(originalRect)));
output.Intersect(container->ClippingRect(PhysicalOffset()));
CheckVisualRect(*target, *target->View(), originalRect, output);
}
TEST_P(VisualRectMappingTest, FixedContentsInIframe) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetPreferCompositingToLCDText(true);
SetBodyInnerHTML(R"HTML(
<style> * { margin:0; } </style>
<iframe src='http://test.com' width='500' height='500' frameBorder='0'>
</iframe>
)HTML");
SetChildFrameHTML(R"HTML(
<style>body { margin:0; } ::-webkit-scrollbar { display:none; }</style>
<div id='forcescroll' style='height:6000px;'></div>
<div id='fixed' style='
position:fixed; top:0; left:0; width:400px; height:300px;'>
</div>
)HTML");
UpdateAllLifecyclePhasesForTest();
auto* fixed =
ChildDocument().getElementById(AtomicString("fixed"))->GetLayoutObject();
auto* root_view = fixed->View();
while (root_view->GetFrame()->OwnerLayoutObject())
root_view = root_view->GetFrame()->OwnerLayoutObject()->View();
CheckMapToVisualRectInAncestorSpace(PhysicalRect(0, 0, 400, 300),
PhysicalRect(0, 0, 400, 300), fixed,
root_view, kDefaultVisualRectFlags, true);
ChildDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 50), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
// The fixed element should not scroll so the mapped visual rect should not
// have changed.
CheckMapToVisualRectInAncestorSpace(PhysicalRect(0, 0, 400, 300),
PhysicalRect(0, 0, 400, 300), fixed,
root_view, kDefaultVisualRectFlags, true);
}
TEST_P(VisualRectMappingTest, FixedContentsWithScrollOffset) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetPreferCompositingToLCDText(true);
SetBodyInnerHTML(R"HTML(
<style>body { margin:0; } ::-webkit-scrollbar { display:none; }</style>
<div id='space' style='height:10px;'></div>
<div id='ancestor'>
<div id='fixed' style='
position:fixed; top:0; left:0; width:400px; height:300px;'>
</div>
</div>
<div id='forcescroll' style='height:1000px;'></div>
)HTML");
auto* ancestor = GetLayoutBoxByElementId("ancestor");
auto* fixed = GetLayoutObjectByElementId("fixed");
CheckMapToVisualRectInAncestorSpace(PhysicalRect(0, 0, 400, 300),
PhysicalRect(0, -10, 400, 300), fixed,
ancestor, kDefaultVisualRectFlags, true);
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 50), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
// The fixed element does not scroll but the ancestor does which changes the
// visual rect.
CheckMapToVisualRectInAncestorSpace(PhysicalRect(0, 0, 400, 300),
PhysicalRect(0, 40, 400, 300), fixed,
ancestor, kDefaultVisualRectFlags, true);
}
TEST_P(VisualRectMappingTest, FixedContentsUnderViewWithScrollOffset) {
SetPreferCompositingToLCDText(true);
SetBodyInnerHTML(R"HTML(
<style>body { margin:0; } ::-webkit-scrollbar { display:none; }</style>
<div id='fixed' style='
position:fixed; top:0; left:0; width:400px; height:300px;'>
</div>
<div id='forcescroll' style='height:1000px;'></div>
)HTML");
auto* fixed = GetLayoutObjectByElementId("fixed");
CheckMapToVisualRectInAncestorSpace(
PhysicalRect(0, 0, 400, 300), PhysicalRect(0, 0, 400, 300), fixed,
fixed->View(), kDefaultVisualRectFlags, true);
GetDocument().View()->LayoutViewport()->SetScrollOffset(
ScrollOffset(0, 50), mojom::blink::ScrollType::kProgrammatic);
UpdateAllLifecyclePhasesForTest();
// Results of mapping to ancestor are in absolute coordinates of the
// ancestor. Therefore a fixed-position element is (reverse) offset by scroll.
CheckMapToVisualRectInAncestorSpace(
PhysicalRect(0, 0, 400, 300), PhysicalRect(0, 50, 400, 300), fixed,
fixed->View(), kDefaultVisualRectFlags, true);
}
TEST_P(VisualRectMappingTest, InclusiveIntersect) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin:0; }</style>
<div id='ancestor' style='position: relative'>
<div style='width: 50px; height: 50px; overflow: hidden'>
<div id='child' style='width: 10px; height: 10px; position: relative; left: 50px'></div>
</div>
</div>
)HTML");
auto* ancestor = GetLayoutBoxByElementId("ancestor");
auto* child = GetLayoutBoxByElementId("child");
CheckMapToVisualRectInAncestorSpace(PhysicalRect(0, 0, 10, 10),
PhysicalRect(50, 0, 0, 10), child,
ancestor, kEdgeInclusive, true);
CheckMapToVisualRectInAncestorSpace(PhysicalRect(1, 1, 10, 10),
PhysicalRect(), child, ancestor,
kEdgeInclusive, false);
CheckMapToVisualRectInAncestorSpace(PhysicalRect(1, 1, 10, 10),
PhysicalRect(1, 1, 10, 10), child, child,
kEdgeInclusive, true);
CheckMapToVisualRectInAncestorSpace(PhysicalRect(0, 0, 10, 10),
PhysicalRect(), child, ancestor,
kDefaultVisualRectFlags, false);
}
TEST_P(VisualRectMappingTest, Perspective) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin:0; }</style>
<div id='ancestor' style='perspective: 100px'>
<div>
<div id='child' style='width: 10px; height: 10px;
transform: rotateY(45deg); position: absolute'></div>
</div>
</div>
)HTML");
auto* ancestor = GetLayoutBoxByElementId("ancestor");
auto* child = GetLayoutBoxByElementId("child");
PhysicalRect rect(0, 0, 10, 10);
child->MapToVisualRectInAncestorSpace(ancestor, rect);
EXPECT_EQ(gfx::Rect(1, 0, 8, 10), ToEnclosingRect(rect));
}
TEST_P(VisualRectMappingTest, PerspectiveWithAnonymousTable) {
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>body { margin:0; }</style>
<div id='ancestor' style='display: table; perspective: 100px; width: 10px;
height: 10px;'>
<div id='child' style='display: table-cell; width: 10px; height: 10px;
transform: rotateY(45deg); position: absolute'></div>
</table>
)HTML");
auto* ancestor = GetLayoutBoxByElementId("ancestor");
auto* child = GetLayoutBoxByElementId("child");
PhysicalRect rect(0, 0, 10, 10);
child->MapToVisualRectInAncestorSpace(ancestor, rect);
EXPECT_EQ(gfx::Rect(1, -1, 8, 12), ToEnclosingRect(rect));
}
TEST_P(VisualRectMappingTest, AnchorPositionScroll) {
ScopedCSSAnchorPositioningForTest enabled_scope(true);
GetDocument().SetBaseURLOverride(KURL("http://test.com"));
SetBodyInnerHTML(R"HTML(
<style>
#cb {
position: relative;
overflow: hidden;
width: min-content;
height: min-content;
}
#scroller {
overflow: scroll;
width: 300px;
height: 300px;
}
#anchor {
anchor-name: --anchor;
margin-top: 100px;
margin-left: 500px;
margin-right: 500px;
width: 50px;
height: 50px;
}
#anchored {
position: absolute;
left: anchor(--anchor left);
bottom: anchor(--anchor top);
width: 50px;
height: 50px;
position-anchor: --anchor;
}
</style>
<div id=cb>
<div id=scroller>
<div id=anchor></div>
</div>
<div id=anchored></div>
</div>
)HTML");
LayoutBox& ancestor = *To<LayoutBox>(GetDocument().body()->GetLayoutObject());
LayoutBox& anchored = *GetLayoutBoxByElementId("anchored");
// #anchored is fully clipped by #cb at the initial scroll position
CheckVisualRect(anchored, ancestor, PhysicalRect(0, 0, 50, 50),
PhysicalRect());
auto* scrollable_area =
GetScrollableArea(To<LayoutBlock>(GetLayoutBoxByElementId("scroller")));
scrollable_area->ScrollToAbsolutePosition(gfx::PointF(400, 0));
// Simulates a frame to update snapshotted scroll offset.
GetPage().Animator().ServiceScriptedAnimations(
GetAnimationClock().CurrentTime() + base::Milliseconds(100));
UpdateAllLifecyclePhasesForTest();
// #anchored is moved into view and should have a non-empty visual rect
CheckVisualRect(anchored, ancestor, PhysicalRect(0, 0, 50, 50),
PhysicalRect(100, 50, 50, 50));
}
TEST_P(VisualRectMappingTest, IgnoreFilters) {
SetBodyInnerHTML(R"HTML(
<div id="parent">
<div id="filter" style="filter: blur(1px)">
<div id="child"></div>
</div>
</div>
)HTML");
auto* parent = GetLayoutBoxByElementId("parent");
auto* filter = GetLayoutBoxByElementId("filter");
auto* child = GetLayoutBoxByElementId("child");
PhysicalRect input(0, 0, 50, 50);
PhysicalRect expected_without_filter = input;
PhysicalRect expected_with_filter(-3, -3, 56, 56);
CheckMapToVisualRectInAncestorSpace(input, expected_without_filter, child,
filter, kDefaultVisualRectFlags, true);
CheckMapToVisualRectInAncestorSpace(input, expected_without_filter, child,
filter, kIgnoreFilters, true);
CheckMapToVisualRectInAncestorSpace(input, expected_with_filter, child,
parent, kDefaultVisualRectFlags, true);
CheckMapToVisualRectInAncestorSpace(input, expected_without_filter, child,
parent, kIgnoreFilters, true);
CheckMapToVisualRectInAncestorSpace(input, expected_with_filter, filter,
parent, kDefaultVisualRectFlags, true);
CheckMapToVisualRectInAncestorSpace(input, expected_without_filter, filter,
parent, kIgnoreFilters, true);
}
} // namespace blink