blob: 69b19f58c75339323dd02129f1f78048c9e84742 [file] [log] [blame]
// Copyright 2015 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/paint/paint_property_tree_builder_test.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/layout/layout_flow_thread.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/layout_table_cell.h"
#include "third_party/blink/renderer/core/layout/layout_table_section.h"
#include "third_party/blink/renderer/core/layout/layout_tree_as_text.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_root.h"
#include "third_party/blink/renderer/core/paint/object_paint_properties.h"
#include "third_party/blink/renderer/core/paint/paint_property_tree_printer.h"
#include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
namespace blink {
void PaintPropertyTreeBuilderTest::LoadTestData(const char* file_name) {
String full_path = test::BlinkRootDir();
full_path.append("/renderer/core/paint/test_data/");
full_path.append(file_name);
const Vector<char> input_buffer =
test::ReadFromFile(full_path)->CopyAs<Vector<char>>();
SetBodyInnerHTML(String(input_buffer.data(), input_buffer.size()));
}
const TransformPaintPropertyNode*
PaintPropertyTreeBuilderTest::DocPreTranslation(const Document* document) {
if (!document)
document = &GetDocument();
return document->GetLayoutView()
->FirstFragment()
.PaintProperties()
->PaintOffsetTranslation();
}
const TransformPaintPropertyNode*
PaintPropertyTreeBuilderTest::DocScrollTranslation(const Document* document) {
if (!document)
document = &GetDocument();
return document->GetLayoutView()
->FirstFragment()
.PaintProperties()
->ScrollTranslation();
}
const ClipPaintPropertyNode* PaintPropertyTreeBuilderTest::DocContentClip(
const Document* document) {
if (!document)
document = &GetDocument();
return document->GetLayoutView()
->FirstFragment()
.PaintProperties()
->OverflowClip();
}
const ScrollPaintPropertyNode* PaintPropertyTreeBuilderTest::DocScroll(
const Document* document) {
if (!document)
document = &GetDocument();
return document->GetLayoutView()->FirstFragment().PaintProperties()->Scroll();
}
const ObjectPaintProperties*
PaintPropertyTreeBuilderTest::PaintPropertiesForElement(const char* name) {
return GetDocument()
.getElementById(name)
->GetLayoutObject()
->FirstFragment()
.PaintProperties();
}
void PaintPropertyTreeBuilderTest::SetUp() {
RenderingTest::SetUp();
EnableCompositing();
}
#define CHECK_VISUAL_RECT(expected, source_object, ancestor, slop_factor) \
do { \
if ((source_object)->HasLayer() && (ancestor)->HasLayer()) { \
LayoutRect actual((source_object)->LocalVisualRect()); \
(source_object) \
->MapToVisualRectInAncestorSpace(ancestor, actual, \
kUseGeometryMapper); \
SCOPED_TRACE("GeometryMapper: "); \
EXPECT_EQ(expected, actual); \
} \
\
if (slop_factor == LayoutUnit::Max()) \
break; \
LayoutRect slow_path_rect = (source_object)->LocalVisualRect(); \
(source_object)->MapToVisualRectInAncestorSpace(ancestor, slow_path_rect); \
if (slop_factor) { \
LayoutRect inflated_expected = LayoutRect(expected); \
inflated_expected.Inflate(slop_factor); \
SCOPED_TRACE(String::Format( \
"Slow path rect: %s, Expected: %s, Inflated expected: %s", \
slow_path_rect.ToString().Ascii().data(), \
expected.ToString().Ascii().data(), \
inflated_expected.ToString().Ascii().data())); \
EXPECT_TRUE(LayoutRect(EnclosingIntRect(slow_path_rect)) \
.Contains(LayoutRect(expected))); \
EXPECT_TRUE(inflated_expected.Contains(slow_path_rect)); \
} else { \
SCOPED_TRACE("Slow path: "); \
EXPECT_EQ(expected, slow_path_rect); \
} \
} while (0)
#define CHECK_EXACT_VISUAL_RECT(expected, source_object, ancestor) \
CHECK_VISUAL_RECT(expected, source_object, ancestor, 0)
INSTANTIATE_PAINT_TEST_CASE_P(PaintPropertyTreeBuilderTest);
TEST_P(PaintPropertyTreeBuilderTest, FixedPosition) {
LoadTestData("fixed-position.html");
Element* positioned_scroll = GetDocument().getElementById("positionedScroll");
positioned_scroll->setScrollTop(3);
Element* transformed_scroll =
GetDocument().getElementById("transformedScroll");
transformed_scroll->setScrollTop(5);
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
// target1 is a fixed-position element inside an absolute-position scrolling
// element. It should be attached under the viewport to skip scrolling and
// offset of the parent.
Element* target1 = GetDocument().getElementById("target1");
const ObjectPaintProperties* target1_properties =
target1->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(FloatRoundedRect(200, 150, 100, 100),
target1_properties->OverflowClip()->ClipRect());
// Likewise, it inherits clip from the viewport, skipping overflow clip of the
// scroller.
EXPECT_EQ(DocContentClip(), target1_properties->OverflowClip()->Parent());
// target1 should not have its own scroll node and instead should inherit
// positionedScroll's.
const ObjectPaintProperties* positioned_scroll_properties =
positioned_scroll->GetLayoutObject()->FirstFragment().PaintProperties();
auto* positioned_scroll_translation =
positioned_scroll_properties->ScrollTranslation();
auto* positioned_scroll_node = positioned_scroll_translation->ScrollNode();
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(GetDocument()
.GetPage()
->GetVisualViewport()
.GetScrollTranslationNode()
->ScrollNode(),
positioned_scroll_node->Parent());
} else {
EXPECT_EQ(DocScroll(), positioned_scroll_node->Parent());
}
EXPECT_EQ(TransformationMatrix().Translate(0, -3),
positioned_scroll_translation->Matrix());
EXPECT_EQ(nullptr, target1_properties->ScrollTranslation());
CHECK_EXACT_VISUAL_RECT(LayoutRect(200, 150, 100, 100),
target1->GetLayoutObject(),
frame_view->GetLayoutView());
// target2 is a fixed-position element inside a transformed scrolling element.
// It should be attached under the scrolled box of the transformed element.
Element* target2 = GetDocument().getElementById("target2");
const ObjectPaintProperties* target2_properties =
target2->GetLayoutObject()->FirstFragment().PaintProperties();
Element* scroller = GetDocument().getElementById("transformedScroll");
const ObjectPaintProperties* scroller_properties =
scroller->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(FloatRoundedRect(200, 150, 100, 100),
target2_properties->OverflowClip()->ClipRect());
EXPECT_EQ(scroller_properties->OverflowClip(),
target2_properties->OverflowClip()->Parent());
// target2 should not have it's own scroll node and instead should inherit
// transformedScroll's.
const ObjectPaintProperties* transformed_scroll_properties =
transformed_scroll->GetLayoutObject()->FirstFragment().PaintProperties();
auto* transformed_scroll_translation =
transformed_scroll_properties->ScrollTranslation();
auto* transformed_scroll_node = transformed_scroll_translation->ScrollNode();
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(GetDocument()
.GetPage()
->GetVisualViewport()
.GetScrollTranslationNode()
->ScrollNode(),
positioned_scroll_node->Parent());
} else {
EXPECT_EQ(DocScroll(), transformed_scroll_node->Parent());
}
EXPECT_EQ(TransformationMatrix().Translate(0, -5),
transformed_scroll_translation->Matrix());
EXPECT_EQ(nullptr, target2_properties->ScrollTranslation());
CHECK_EXACT_VISUAL_RECT(LayoutRect(208, 153, 200, 100),
target2->GetLayoutObject(),
frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, PositionAndScroll) {
GetDocument().SetCompatibilityMode(Document::kQuirksMode);
LoadTestData("position-and-scroll.html");
Element* scroller = GetDocument().getElementById("scroller");
scroller->scrollTo(0, 100);
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
const ObjectPaintProperties* scroller_properties =
scroller->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(0, -100),
scroller_properties->ScrollTranslation()->Matrix());
EXPECT_EQ(scroller_properties->PaintOffsetTranslation(),
scroller_properties->ScrollTranslation()->Parent());
EXPECT_EQ(DocScrollTranslation(),
scroller_properties->PaintOffsetTranslation()->Parent());
EXPECT_EQ(scroller_properties->PaintOffsetTranslation(),
scroller_properties->OverflowClip()->LocalTransformSpace());
const auto* scroll = scroller_properties->ScrollTranslation()->ScrollNode();
EXPECT_EQ(DocScroll(), scroll->Parent());
EXPECT_EQ(IntRect(0, 0, 413, 317), scroll->ContainerRect());
EXPECT_EQ(IntSize(660, 10200), scroll->ContentsSize());
EXPECT_FALSE(scroll->UserScrollableHorizontal());
EXPECT_TRUE(scroll->UserScrollableVertical());
EXPECT_EQ(FloatSize(120, 340), scroller_properties->PaintOffsetTranslation()
->Matrix()
.To2DTranslation());
EXPECT_EQ(FloatRoundedRect(0, 0, 413, 317),
scroller_properties->OverflowClip()->ClipRect());
EXPECT_EQ(DocContentClip(), scroller_properties->OverflowClip()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(120, 340, 413, 317),
scroller->GetLayoutObject(),
frame_view->GetLayoutView());
// The relative-positioned element should have accumulated box offset (exclude
// scrolling), and should be affected by ancestor scroll transforms.
Element* rel_pos = GetDocument().getElementById("rel-pos");
const ObjectPaintProperties* rel_pos_properties =
rel_pos->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(560, 780),
rel_pos_properties->PaintOffsetTranslation()->Matrix());
EXPECT_EQ(scroller_properties->ScrollTranslation(),
rel_pos_properties->PaintOffsetTranslation()->Parent());
EXPECT_EQ(rel_pos_properties->Transform(),
rel_pos_properties->OverflowClip()->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(0, 0, 100, 200),
rel_pos_properties->OverflowClip()->ClipRect());
EXPECT_EQ(scroller_properties->OverflowClip(),
rel_pos_properties->OverflowClip()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(), rel_pos->GetLayoutObject(),
frame_view->GetLayoutView());
// The absolute-positioned element should not be affected by non-positioned
// scroller at all.
Element* abs_pos = GetDocument().getElementById("abs-pos");
const ObjectPaintProperties* abs_pos_properties =
abs_pos->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(123, 456),
abs_pos_properties->PaintOffsetTranslation()->Matrix());
EXPECT_EQ(DocScrollTranslation(),
abs_pos_properties->PaintOffsetTranslation()->Parent());
EXPECT_EQ(abs_pos_properties->Transform(),
abs_pos_properties->OverflowClip()->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(0, 0, 300, 400),
abs_pos_properties->OverflowClip()->ClipRect());
EXPECT_EQ(DocContentClip(), abs_pos_properties->OverflowClip()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(123, 456, 300, 400),
abs_pos->GetLayoutObject(),
frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollExcludeScrollbars) {
SetBodyInnerHTML(R"HTML(
<div id='scroller'
style='width: 100px; height: 100px; overflow: scroll;
border: 10px solid blue'>
<div style='width: 400px; height: 400px'></div>
</div>
)HTML");
CHECK(GetDocument().GetPage()->GetScrollbarTheme().UsesOverlayScrollbars());
const auto* properties = PaintPropertiesForElement("scroller");
const auto* overflow_clip = properties->OverflowClip();
EXPECT_EQ(DocContentClip(), overflow_clip->Parent());
EXPECT_EQ(properties->PaintOffsetTranslation(),
overflow_clip->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(10, 10, 100, 100), overflow_clip->ClipRect());
PaintLayer* paint_layer =
ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"))->Layer();
EXPECT_TRUE(paint_layer->GetScrollableArea()
->VerticalScrollbar()
->IsOverlayScrollbar());
EXPECT_EQ(FloatRoundedRect(10, 10, 93, 93),
overflow_clip->ClipRectExcludingOverlayScrollbars());
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollExcludeScrollbarsSubpixel) {
SetBodyInnerHTML(R"HTML(
<div id='scroller'
style='width: 100.5px; height: 100px; overflow: scroll;
border: 10px solid blue'>
<div style='width: 400px; height: 400px'></div>
</div>
)HTML");
CHECK(GetDocument().GetPage()->GetScrollbarTheme().UsesOverlayScrollbars());
const auto* scroller = GetLayoutObjectByElementId("scroller");
const auto* properties = scroller->FirstFragment().PaintProperties();
const auto* overflow_clip = properties->OverflowClip();
EXPECT_EQ(DocContentClip(), overflow_clip->Parent());
EXPECT_EQ(properties->PaintOffsetTranslation(),
overflow_clip->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(10, 10, 101, 100), overflow_clip->ClipRect());
EXPECT_TRUE(ToLayoutBox(scroller)
->GetScrollableArea()
->VerticalScrollbar()
->IsOverlayScrollbar());
EXPECT_EQ(FloatRoundedRect(10, 10, 94, 93),
overflow_clip->ClipRectExcludingOverlayScrollbars());
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollVerticalRL) {
SetBodyInnerHTML(R"HTML(
<style>::-webkit-scrollbar {width: 15px; height: 15px}</style>
<div id='scroller'
style='width: 100px; height: 100px; overflow: scroll;
writing-mode: vertical-rl; border: 10px solid blue'>
<div id="content" style='width: 400px; height: 400px'></div>
</div>
)HTML");
const auto* scroller = ToLayoutBox(GetLayoutObjectByElementId("scroller"));
const auto* content = GetLayoutObjectByElementId("content");
const auto* properties = scroller->FirstFragment().PaintProperties();
const auto* overflow_clip = properties->OverflowClip();
const auto* scroll_translation = properties->ScrollTranslation();
const auto* scroll = properties->Scroll();
// -315: container_width (100) - contents_width (400) - scrollber_width
EXPECT_EQ(TransformationMatrix().Translate(-315, 0),
scroll_translation->Matrix());
EXPECT_EQ(scroll, scroll_translation->ScrollNode());
// 10: border width. 85: container client size (== 100 - scrollbar width).
EXPECT_EQ(IntRect(10, 10, 85, 85), scroll->ContainerRect());
EXPECT_EQ(IntSize(400, 400), scroll->ContentsSize());
EXPECT_EQ(LayoutPoint(), scroller->FirstFragment().PaintOffset());
EXPECT_EQ(IntPoint(315, 0), scroller->ScrollOrigin());
EXPECT_EQ(LayoutPoint(10, 10), content->FirstFragment().PaintOffset());
EXPECT_EQ(DocContentClip(), overflow_clip->Parent());
EXPECT_EQ(properties->PaintOffsetTranslation(),
overflow_clip->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(10, 10, 85, 85), overflow_clip->ClipRect());
scroller->GetScrollableArea()->ScrollBy(ScrollOffset(-100, 0), kUserScroll);
UpdateAllLifecyclePhasesForTest();
// Only scroll_translation is affected by scrolling.
EXPECT_EQ(TransformationMatrix().Translate(-215, 0),
scroll_translation->Matrix());
// Other properties are the same as before.
EXPECT_EQ(scroll, scroll_translation->ScrollNode());
EXPECT_EQ(IntRect(10, 10, 85, 85), scroll->ContainerRect());
EXPECT_EQ(IntSize(400, 400), scroll->ContentsSize());
EXPECT_EQ(LayoutPoint(), scroller->FirstFragment().PaintOffset());
EXPECT_EQ(IntPoint(315, 0), scroller->ScrollOrigin());
EXPECT_EQ(LayoutPoint(10, 10), content->FirstFragment().PaintOffset());
EXPECT_EQ(DocContentClip(), overflow_clip->Parent());
EXPECT_EQ(properties->PaintOffsetTranslation(),
overflow_clip->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(10, 10, 85, 85), overflow_clip->ClipRect());
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollRTL) {
SetBodyInnerHTML(R"HTML(
<style>::-webkit-scrollbar {width: 15px; height: 15px}</style>
<div id='scroller'
style='width: 100px; height: 100px; overflow: scroll;
direction: rtl; border: 10px solid blue'>
<div id='content' style='width: 400px; height: 400px'></div>
</div>
)HTML");
const auto* scroller = ToLayoutBox(GetLayoutObjectByElementId("scroller"));
const auto* content = GetLayoutObjectByElementId("content");
const auto* properties = scroller->FirstFragment().PaintProperties();
const auto* overflow_clip = properties->OverflowClip();
const auto* scroll_translation = properties->ScrollTranslation();
const auto* scroll = properties->Scroll();
// -315: container_width (100) - contents_width (400) - scrollbar width (15).
EXPECT_EQ(TransformationMatrix().Translate(-315, 0),
scroll_translation->Matrix());
EXPECT_EQ(scroll, scroll_translation->ScrollNode());
// 25: border width (10) + scrollbar (on the left) width (15).
// 85: container client size (== 100 - scrollbar width).
EXPECT_EQ(IntRect(25, 10, 85, 85), scroll->ContainerRect());
EXPECT_EQ(IntSize(400, 400), scroll->ContentsSize());
EXPECT_EQ(LayoutPoint(), scroller->FirstFragment().PaintOffset());
EXPECT_EQ(IntPoint(315, 0), scroller->ScrollOrigin());
EXPECT_EQ(LayoutPoint(25, 10), content->FirstFragment().PaintOffset());
EXPECT_EQ(DocContentClip(), overflow_clip->Parent());
EXPECT_EQ(properties->PaintOffsetTranslation(),
overflow_clip->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(25, 10, 85, 85), overflow_clip->ClipRect());
scroller->GetScrollableArea()->ScrollBy(ScrollOffset(-100, 0), kUserScroll);
UpdateAllLifecyclePhasesForTest();
// Only scroll_translation is affected by scrolling.
EXPECT_EQ(TransformationMatrix().Translate(-215, 0),
scroll_translation->Matrix());
// Other properties are the same as before.
EXPECT_EQ(scroll, scroll_translation->ScrollNode());
EXPECT_EQ(IntRect(25, 10, 85, 85), scroll->ContainerRect());
EXPECT_EQ(IntSize(400, 400), scroll->ContentsSize());
EXPECT_EQ(LayoutPoint(), scroller->FirstFragment().PaintOffset());
EXPECT_EQ(IntPoint(315, 0), scroller->ScrollOrigin());
EXPECT_EQ(LayoutPoint(25, 10), content->FirstFragment().PaintOffset());
EXPECT_EQ(DocContentClip(), overflow_clip->Parent());
EXPECT_EQ(properties->PaintOffsetTranslation(),
overflow_clip->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(25, 10, 85, 85), overflow_clip->ClipRect());
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollVerticalRLMulticol) {
SetBodyInnerHTML(R"HTML(
<style>::-webkit-scrollbar {width: 15px; height: 15px}</style>
<div id='scroller'
style='width: 100px; height: 100px; overflow: scroll;
writing-mode: vertical-rl; border: 10px solid blue'>
<div id="multicol"
style="width: 50px; height: 400px; columns: 2; column-gap: 0">
<div style="width: 100px"></div>
</div>
<div style='width: 400px; height: 400px'></div>
</div>
)HTML");
const auto* flow_thread =
GetLayoutObjectByElementId("multicol")->SlowFirstChild();
auto check_fragments = [flow_thread]() {
ASSERT_EQ(2u, NumFragments(flow_thread));
EXPECT_EQ(410, FragmentAt(flow_thread, 0)
.PaintProperties()
->FragmentClip()
->ClipRect()
.Rect()
.X());
EXPECT_EQ(LayoutPoint(360, 10), FragmentAt(flow_thread, 0).PaintOffset());
EXPECT_EQ(460, FragmentAt(flow_thread, 1)
.PaintProperties()
->FragmentClip()
->ClipRect()
.Rect()
.MaxX());
EXPECT_EQ(LayoutPoint(410, 210), FragmentAt(flow_thread, 1).PaintOffset());
};
check_fragments();
// Fragment geometries are not affected by parent scrolling.
ToLayoutBox(GetLayoutObjectByElementId("scroller"))
->GetScrollableArea()
->ScrollBy(ScrollOffset(-100, 200), kUserScroll);
UpdateAllLifecyclePhasesForTest();
check_fragments();
}
TEST_P(PaintPropertyTreeBuilderTest, DocScrollingTraditional) {
SetBodyInnerHTML("<style> body { height: 10000px; } </style>");
GetDocument().domWindow()->scrollTo(0, 100);
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
EXPECT_EQ(TransformationMatrix(), DocPreTranslation()->Matrix());
EXPECT_EQ(
GetDocument().GetPage()->GetVisualViewport().GetScrollTranslationNode(),
DocPreTranslation()->Parent());
EXPECT_EQ(TransformationMatrix().Translate(0, -100),
DocScrollTranslation()->Matrix());
EXPECT_EQ(DocPreTranslation(), DocScrollTranslation()->Parent());
EXPECT_EQ(DocPreTranslation(), DocContentClip()->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), DocContentClip()->ClipRect());
EXPECT_TRUE(DocContentClip()->Parent()->IsRoot());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 784, 10000),
GetDocument().body()->GetLayoutObject(),
frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, Perspective) {
SetBodyInnerHTML(R"HTML(
<style>
#perspective {
position: absolute;
left: 50px;
top: 100px;
width: 400px;
height: 300px;
perspective: 100px;
}
#inner {
transform: translateZ(0);
width: 100px;
height: 200px;
}
</style>
<div id='perspective'>
<div id='inner'></div>
</div>
)HTML");
Element* perspective = GetDocument().getElementById("perspective");
const ObjectPaintProperties* perspective_properties =
perspective->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().ApplyPerspective(100),
perspective_properties->Perspective()->Matrix());
// The perspective origin is the center of the border box plus accumulated
// paint offset.
EXPECT_EQ(FloatPoint3D(250, 250, 0),
perspective_properties->Perspective()->Origin());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(),
perspective_properties->Perspective()->Parent());
} else {
EXPECT_EQ(DocScrollTranslation(),
perspective_properties->Perspective()->Parent());
}
// Adding perspective doesn't clear paint offset. The paint offset will be
// passed down to children.
Element* inner = GetDocument().getElementById("inner");
const ObjectPaintProperties* inner_properties =
inner->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(50, 100),
inner_properties->PaintOffsetTranslation()->Matrix());
EXPECT_EQ(perspective_properties->Perspective(),
inner_properties->PaintOffsetTranslation()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(50, 100, 100, 200),
inner->GetLayoutObject(),
GetDocument().View()->GetLayoutView());
perspective->setAttribute(html_names::kStyleAttr, "perspective: 200px");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(TransformationMatrix().ApplyPerspective(200),
perspective_properties->Perspective()->Matrix());
EXPECT_EQ(FloatPoint3D(250, 250, 0),
perspective_properties->Perspective()->Origin());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(),
perspective_properties->Perspective()->Parent());
} else {
EXPECT_EQ(DocScrollTranslation(),
perspective_properties->Perspective()->Parent());
}
perspective->setAttribute(html_names::kStyleAttr,
"perspective-origin: 5% 20%");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(TransformationMatrix().ApplyPerspective(100),
perspective_properties->Perspective()->Matrix());
EXPECT_EQ(FloatPoint3D(70, 160, 0),
perspective_properties->Perspective()->Origin());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(),
perspective_properties->Perspective()->Parent());
} else {
EXPECT_EQ(DocScrollTranslation(),
perspective_properties->Perspective()->Parent());
}
}
TEST_P(PaintPropertyTreeBuilderTest, Transform) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0 } </style>
<div id='transform' style='margin-left: 50px; margin-top: 100px;
width: 400px; height: 300px;
transform: translate3d(123px, 456px, 789px)'>
</div>
)HTML");
Element* transform = GetDocument().getElementById("transform");
const ObjectPaintProperties* transform_properties =
transform->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(123, 456, 789),
transform_properties->Transform()->Matrix());
EXPECT_EQ(FloatPoint3D(200, 150, 0),
transform_properties->Transform()->Origin());
EXPECT_EQ(transform_properties->PaintOffsetTranslation(),
transform_properties->Transform()->Parent());
EXPECT_EQ(TransformationMatrix().Translate(50, 100),
transform_properties->PaintOffsetTranslation()->Matrix());
EXPECT_EQ(DocScrollTranslation(),
transform_properties->PaintOffsetTranslation()->Parent());
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(
transform_properties->Transform()->HasDirectCompositingReasons());
}
CHECK_EXACT_VISUAL_RECT(LayoutRect(173, 556, 400, 300),
transform->GetLayoutObject(),
GetDocument().View()->GetLayoutView());
transform->setAttribute(
html_names::kStyleAttr,
"margin-left: 50px; margin-top: 100px; width: 400px; height: 300px;");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(nullptr,
transform->GetLayoutObject()->FirstFragment().PaintProperties());
transform->setAttribute(
html_names::kStyleAttr,
"margin-left: 50px; margin-top: 100px; width: 400px; height: 300px; "
"transform: translate3d(123px, 456px, 789px)");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(TransformationMatrix().Translate3d(123, 456, 789),
transform->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->Transform()
->Matrix());
}
TEST_P(PaintPropertyTreeBuilderTest, Preserve3D3DTransformedDescendant) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0 } </style>
<div id='preserve' style='transform-style: preserve-3d'>
<div id='transform' style='margin-left: 50px; margin-top: 100px;
width: 400px; height: 300px;
transform: translate3d(123px, 456px, 789px)'>
</div>
</div>
)HTML");
Element* preserve = GetDocument().getElementById("preserve");
const ObjectPaintProperties* preserve_properties =
preserve->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_TRUE(preserve_properties->Transform());
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(
preserve_properties->Transform()->HasDirectCompositingReasons());
}
}
TEST_P(PaintPropertyTreeBuilderTest, Perspective3DTransformedDescendant) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0 } </style>
<div id='perspective' style='perspective: 800px;'>
<div id='transform' style='margin-left: 50px; margin-top: 100px;
width: 400px; height: 300px;
transform: translate3d(123px, 456px, 789px)'>
</div>
</div>
)HTML");
Element* perspective = GetDocument().getElementById("perspective");
const ObjectPaintProperties* perspective_properties =
perspective->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_TRUE(perspective_properties->Transform());
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(
perspective_properties->Transform()->HasDirectCompositingReasons());
}
}
TEST_P(PaintPropertyTreeBuilderTest,
TransformNodeWithActiveAnimationHasDirectCompositingReason) {
if (!RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
LoadTestData("transform-animation.html");
EXPECT_TRUE(PaintPropertiesForElement("target")
->Transform()
->HasDirectCompositingReasons());
}
TEST_P(PaintPropertyTreeBuilderTest,
OpacityAnimationCreatesTransformAndFilterNodes) {
LoadTestData("opacity-animation.html");
// TODO(flackr): Verify that after https://crbug.com/900241 is fixed we no
// longer create transform or filter nodes for opacity animations.
EXPECT_NE(nullptr, PaintPropertiesForElement("target")->Transform());
EXPECT_NE(nullptr, PaintPropertiesForElement("target")->Filter());
}
TEST_P(PaintPropertyTreeBuilderTest,
EffectNodeWithActiveAnimationHasDirectCompositingReason) {
if (!RuntimeEnabledFeatures::SlimmingPaintV2Enabled())
return;
LoadTestData("opacity-animation.html");
EXPECT_TRUE(PaintPropertiesForElement("target")
->Effect()
->HasDirectCompositingReasons());
}
TEST_P(PaintPropertyTreeBuilderTest, WillChangeTransform) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0 } </style>
<div id='transform' style='margin-left: 50px; margin-top: 100px;
width: 400px; height: 300px;
will-change: transform'>
</div>
)HTML");
Element* transform = GetDocument().getElementById("transform");
const ObjectPaintProperties* transform_properties =
transform->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix(),
transform_properties->Transform()->Matrix());
EXPECT_EQ(TransformationMatrix().Translate(0, 0),
transform_properties->Transform()->Matrix());
// The value is zero without a transform property that needs transform-offset.
EXPECT_EQ(FloatPoint3D(0, 0, 0), transform_properties->Transform()->Origin());
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(nullptr, transform_properties->PaintOffsetTranslation());
} else {
// SPv1 creates PaintOffsetTranslation for composited layers.
EXPECT_EQ(TransformationMatrix().Translate(50, 100),
transform_properties->PaintOffsetTranslation()->Matrix());
}
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(
transform_properties->Transform()->HasDirectCompositingReasons());
}
CHECK_EXACT_VISUAL_RECT(LayoutRect(50, 100, 400, 300),
transform->GetLayoutObject(),
GetDocument().View()->GetLayoutView());
transform->setAttribute(
html_names::kStyleAttr,
"margin-left: 50px; margin-top: 100px; width: 400px; height: 300px;");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(nullptr,
transform->GetLayoutObject()->FirstFragment().PaintProperties());
transform->setAttribute(
html_names::kStyleAttr,
"margin-left: 50px; margin-top: 100px; width: 400px; height: 300px; "
"will-change: transform");
UpdateAllLifecyclePhasesForTest();
EXPECT_EQ(TransformationMatrix(), transform->GetLayoutObject()
->FirstFragment()
.PaintProperties()
->Transform()
->Matrix());
}
TEST_P(PaintPropertyTreeBuilderTest, WillChangeContents) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0 } </style>
<div id='transform' style='margin-left: 50px; margin-top: 100px;
width: 400px; height: 300px;
will-change: transform, contents'>
</div>
)HTML");
Element* transform = GetDocument().getElementById("transform");
EXPECT_EQ(nullptr,
transform->GetLayoutObject()->FirstFragment().PaintProperties());
CHECK_EXACT_VISUAL_RECT(LayoutRect(50, 100, 400, 300),
transform->GetLayoutObject(),
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, RelativePositionInline) {
LoadTestData("relative-position-inline.html");
Element* inline_block = GetDocument().getElementById("inline-block");
const ObjectPaintProperties* inline_block_properties =
inline_block->GetLayoutObject()->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(135, 490),
inline_block_properties->PaintOffsetTranslation()->Matrix());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(),
inline_block_properties->PaintOffsetTranslation()->Parent());
} else {
EXPECT_EQ(DocScrollTranslation(),
inline_block_properties->PaintOffsetTranslation()->Parent());
}
CHECK_EXACT_VISUAL_RECT(LayoutRect(135, 490, 10, 20),
inline_block->GetLayoutObject(),
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, NestedOpacityEffect) {
SetBodyInnerHTML(R"HTML(
<div id='nodeWithoutOpacity' style='width: 100px; height: 200px'>
<div id='childWithOpacity'
style='opacity: 0.5; width: 50px; height: 60px;'>
<div id='grandChildWithoutOpacity'
style='width: 20px; height: 30px'>
<div id='greatGrandChildWithOpacity'
style='opacity: 0.2; width: 10px; height: 15px'></div>
</div>
</div>
</div>
)HTML");
LayoutObject* node_without_opacity =
GetLayoutObjectByElementId("nodeWithoutOpacity");
const auto* data_without_opacity_properties =
node_without_opacity->FirstFragment().PaintProperties();
EXPECT_EQ(nullptr, data_without_opacity_properties);
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 100, 200), node_without_opacity,
GetDocument().View()->GetLayoutView());
LayoutObject* child_with_opacity =
GetLayoutObjectByElementId("childWithOpacity");
const ObjectPaintProperties* child_with_opacity_properties =
child_with_opacity->FirstFragment().PaintProperties();
EXPECT_EQ(0.5f, child_with_opacity_properties->Effect()->Opacity());
// childWithOpacity is the root effect node.
EXPECT_NE(nullptr, child_with_opacity_properties->Effect()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), child_with_opacity,
GetDocument().View()->GetLayoutView());
LayoutObject* grand_child_without_opacity =
GetDocument()
.getElementById("grandChildWithoutOpacity")
->GetLayoutObject();
EXPECT_EQ(nullptr,
grand_child_without_opacity->FirstFragment().PaintProperties());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 20, 30), grand_child_without_opacity,
GetDocument().View()->GetLayoutView());
LayoutObject* great_grand_child_with_opacity =
GetDocument()
.getElementById("greatGrandChildWithOpacity")
->GetLayoutObject();
const ObjectPaintProperties* great_grand_child_with_opacity_properties =
great_grand_child_with_opacity->FirstFragment().PaintProperties();
EXPECT_EQ(0.2f,
great_grand_child_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(child_with_opacity_properties->Effect(),
great_grand_child_with_opacity_properties->Effect()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 15),
great_grand_child_with_opacity,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodeDoesNotAffectEffectNodes) {
SetBodyInnerHTML(R"HTML(
<style>
#nodeWithOpacity {
opacity: 0.6;
width: 100px;
height: 200px;
}
#childWithTransform {
transform: translate3d(10px, 10px, 0px);
width: 50px;
height: 60px;
}
#grandChildWithOpacity {
opacity: 0.4;
width: 20px;
height: 30px;
}
</style>
<div id='nodeWithOpacity'>
<div id='childWithTransform'>
<div id='grandChildWithOpacity'></div>
</div>
</div>
)HTML");
LayoutObject* node_with_opacity =
GetLayoutObjectByElementId("nodeWithOpacity");
const ObjectPaintProperties* node_with_opacity_properties =
node_with_opacity->FirstFragment().PaintProperties();
EXPECT_EQ(0.6f, node_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(DocContentClip(),
node_with_opacity_properties->Effect()->OutputClip());
EXPECT_NE(nullptr, node_with_opacity_properties->Effect()->Parent());
EXPECT_EQ(nullptr, node_with_opacity_properties->Transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 100, 200), node_with_opacity,
GetDocument().View()->GetLayoutView());
LayoutObject* child_with_transform =
GetLayoutObjectByElementId("childWithTransform");
const ObjectPaintProperties* child_with_transform_properties =
child_with_transform->FirstFragment().PaintProperties();
EXPECT_EQ(nullptr, child_with_transform_properties->Effect());
EXPECT_EQ(TransformationMatrix().Translate(10, 10),
child_with_transform_properties->Transform()->Matrix());
CHECK_EXACT_VISUAL_RECT(LayoutRect(18, 18, 50, 60), child_with_transform,
GetDocument().View()->GetLayoutView());
LayoutObject* grand_child_with_opacity =
GetLayoutObjectByElementId("grandChildWithOpacity");
const ObjectPaintProperties* grand_child_with_opacity_properties =
grand_child_with_opacity->FirstFragment().PaintProperties();
EXPECT_EQ(0.4f, grand_child_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(DocContentClip(),
grand_child_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(node_with_opacity_properties->Effect(),
grand_child_with_opacity_properties->Effect()->Parent());
EXPECT_EQ(nullptr, grand_child_with_opacity_properties->Transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(18, 18, 20, 30), grand_child_with_opacity,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossStackingContext) {
SetBodyInnerHTML(R"HTML(
<div id='nodeWithOpacity'
style='opacity: 0.6; width: 100px; height: 200px'>
<div id='childWithStackingContext'
style='position:absolute; width: 50px; height: 60px;'>
<div id='grandChildWithOpacity'
style='opacity: 0.4; width: 20px; height: 30px'></div>
</div>
</div>
)HTML");
LayoutObject* node_with_opacity =
GetLayoutObjectByElementId("nodeWithOpacity");
const ObjectPaintProperties* node_with_opacity_properties =
node_with_opacity->FirstFragment().PaintProperties();
EXPECT_EQ(0.6f, node_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(DocContentClip(),
node_with_opacity_properties->Effect()->OutputClip());
EXPECT_NE(nullptr, node_with_opacity_properties->Effect()->Parent());
EXPECT_EQ(nullptr, node_with_opacity_properties->Transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 100, 200), node_with_opacity,
GetDocument().View()->GetLayoutView());
LayoutObject* child_with_stacking_context =
GetDocument()
.getElementById("childWithStackingContext")
->GetLayoutObject();
const ObjectPaintProperties* child_with_stacking_context_properties =
child_with_stacking_context->FirstFragment().PaintProperties();
EXPECT_EQ(nullptr, child_with_stacking_context_properties);
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), child_with_stacking_context,
GetDocument().View()->GetLayoutView());
LayoutObject* grand_child_with_opacity =
GetLayoutObjectByElementId("grandChildWithOpacity");
const ObjectPaintProperties* grand_child_with_opacity_properties =
grand_child_with_opacity->FirstFragment().PaintProperties();
EXPECT_EQ(0.4f, grand_child_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(DocContentClip(),
grand_child_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(node_with_opacity_properties->Effect(),
grand_child_with_opacity_properties->Effect()->Parent());
EXPECT_EQ(nullptr, grand_child_with_opacity_properties->Transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 20, 30), grand_child_with_opacity,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, EffectNodesInSVG) {
SetBodyInnerHTML(R"HTML(
<svg id='svgRoot'>
<g id='groupWithOpacity' opacity='0.6'>
<rect id='rectWithoutOpacity' />
<rect id='rectWithOpacity' opacity='0.4' />
<text id='textWithOpacity' opacity='0.2'>
<tspan id='tspanWithOpacity' opacity='0.1' />
</text>
</g>
</svg>
)HTML");
const auto* svg_clip = PaintPropertiesForElement("svgRoot")->OverflowClip();
const auto* group_with_opacity_properties =
PaintPropertiesForElement("groupWithOpacity");
EXPECT_EQ(0.6f, group_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(svg_clip, group_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(&EffectPaintPropertyNode::Root(),
group_with_opacity_properties->Effect()->Parent());
EXPECT_EQ(nullptr, PaintPropertiesForElement("rectWithoutOpacity"));
const auto* rect_with_opacity_properties =
PaintPropertiesForElement("rectWithOpacity");
EXPECT_EQ(0.4f, rect_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(svg_clip, rect_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(group_with_opacity_properties->Effect(),
rect_with_opacity_properties->Effect()->Parent());
// Ensure that opacity nodes are created for LayoutSVGText which inherits from
// LayoutSVGBlock instead of LayoutSVGModelObject.
const auto* text_with_opacity_properties =
PaintPropertiesForElement("textWithOpacity");
EXPECT_EQ(0.2f, text_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(svg_clip, text_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(group_with_opacity_properties->Effect(),
text_with_opacity_properties->Effect()->Parent());
// Ensure that opacity nodes are created for LayoutSVGTSpan which inherits
// from LayoutSVGInline instead of LayoutSVGModelObject.
const auto* tspan_with_opacity_properties =
PaintPropertiesForElement("tspanWithOpacity");
EXPECT_EQ(0.1f, tspan_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(svg_clip, tspan_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(text_with_opacity_properties->Effect(),
tspan_with_opacity_properties->Effect()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossHTMLSVGBoundary) {
SetBodyInnerHTML(R"HTML(
<div id='divWithOpacity' style='opacity: 0.2;'>
<svg id='svgRootWithOpacity' style='opacity: 0.3;'>
<rect id='rectWithOpacity' opacity='0.4' />
</svg>
</div>
)HTML");
const auto* div_with_opacity_properties =
PaintPropertiesForElement("divWithOpacity");
EXPECT_EQ(0.2f, div_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(DocContentClip(),
div_with_opacity_properties->Effect()->OutputClip());
EXPECT_NE(nullptr, div_with_opacity_properties->Effect()->Parent());
const auto* svg_root_with_opacity_properties =
PaintPropertiesForElement("svgRootWithOpacity");
EXPECT_EQ(0.3f, svg_root_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(DocContentClip(),
svg_root_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(div_with_opacity_properties->Effect(),
svg_root_with_opacity_properties->Effect()->Parent());
const auto* rect_with_opacity_properties =
PaintPropertiesForElement("rectWithOpacity");
EXPECT_EQ(0.4f, rect_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(svg_root_with_opacity_properties->OverflowClip(),
rect_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(svg_root_with_opacity_properties->Effect(),
rect_with_opacity_properties->Effect()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossSVGHTMLBoundary) {
SetBodyInnerHTML(R"HTML(
<svg id='svgRootWithOpacity' style='opacity: 0.3;'>
<foreignObject id='foreignObjectWithOpacity' opacity='0.4' style='overflow: visible;'>
<body>
<span id='spanWithOpacity' style='opacity: 0.5'/>
</body>
</foreignObject>
</svg>
)HTML");
const auto* svg_root_with_opacity_properties =
PaintPropertiesForElement("svgRootWithOpacity");
EXPECT_EQ(0.3f, svg_root_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(DocContentClip(),
svg_root_with_opacity_properties->Effect()->OutputClip());
EXPECT_NE(nullptr, svg_root_with_opacity_properties->Effect()->Parent());
const auto* foreign_object_with_opacity_properties =
PaintPropertiesForElement("foreignObjectWithOpacity");
EXPECT_EQ(0.4f, foreign_object_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(svg_root_with_opacity_properties->OverflowClip(),
foreign_object_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(svg_root_with_opacity_properties->Effect(),
foreign_object_with_opacity_properties->Effect()->Parent());
const auto* span_with_opacity_properties =
PaintPropertiesForElement("spanWithOpacity");
EXPECT_EQ(0.5f, span_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(svg_root_with_opacity_properties->OverflowClip(),
span_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(foreign_object_with_opacity_properties->Effect(),
span_with_opacity_properties->Effect()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodesInSVG) {
SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0px;
}
svg {
margin-left: 50px;
transform: translate3d(1px, 2px, 3px);
position: absolute;
left: 20px;
top: 25px;
}
rect {
transform: translate(100px, 100px) rotate(45deg);
transform-origin: 50px 25px;
}
</style>
<svg id='svgRootWith3dTransform' width='100px' height='100px'>
<rect id='rectWith2dTransform' width='100px' height='100px' />
</svg>
)HTML");
LayoutObject& svg_root_with3d_transform =
*GetDocument()
.getElementById("svgRootWith3dTransform")
->GetLayoutObject();
const ObjectPaintProperties* svg_root_with3d_transform_properties =
svg_root_with3d_transform.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3),
svg_root_with3d_transform_properties->Transform()->Matrix());
EXPECT_EQ(FloatPoint3D(50, 50, 0),
svg_root_with3d_transform_properties->Transform()->Origin());
EXPECT_EQ(svg_root_with3d_transform_properties->PaintOffsetTranslation(),
svg_root_with3d_transform_properties->Transform()->Parent());
EXPECT_EQ(
TransformationMatrix().Translate(70, 25),
svg_root_with3d_transform_properties->PaintOffsetTranslation()->Matrix());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(),
svg_root_with3d_transform_properties->PaintOffsetTranslation()
->Parent());
} else {
EXPECT_EQ(DocScrollTranslation(),
svg_root_with3d_transform_properties->PaintOffsetTranslation()
->Parent());
}
LayoutObject& rect_with2d_transform =
*GetLayoutObjectByElementId("rectWith2dTransform");
const ObjectPaintProperties* rect_with2d_transform_properties =
rect_with2d_transform.FirstFragment().PaintProperties();
TransformationMatrix matrix;
matrix.Translate(100, 100);
matrix.Rotate(45);
// SVG's transform origin is baked into the transform.
matrix.ApplyTransformOrigin(50, 25, 0);
EXPECT_EQ(matrix, rect_with2d_transform_properties->Transform()->Matrix());
EXPECT_EQ(FloatPoint3D(0, 0, 0),
rect_with2d_transform_properties->Transform()->Origin());
// SVG does not use paint offset.
EXPECT_EQ(nullptr,
rect_with2d_transform_properties->PaintOffsetTranslation());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGViewBoxTransform) {
SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0px;
}
#svgWithViewBox {
transform: translate3d(1px, 2px, 3px);
position: absolute;
width: 100px;
height: 100px;
}
#rect {
transform: translate(100px, 100px);
width: 100px;
height: 100px;
}
</style>
<svg id='svgWithViewBox' viewBox='50 50 100 100'>
<rect id='rect' />
</svg>
)HTML");
LayoutObject& svg_with_view_box =
*GetLayoutObjectByElementId("svgWithViewBox");
const ObjectPaintProperties* svg_with_view_box_properties =
svg_with_view_box.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3),
svg_with_view_box_properties->Transform()->Matrix());
EXPECT_EQ(TransformationMatrix().Translate(-50, -50),
svg_with_view_box_properties->ReplacedContentTransform()->Matrix());
EXPECT_EQ(svg_with_view_box_properties->ReplacedContentTransform()->Parent(),
svg_with_view_box_properties->Transform());
LayoutObject& rect = *GetLayoutObjectByElementId("rect");
const ObjectPaintProperties* rect_properties =
rect.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(100, 100),
rect_properties->Transform()->Matrix());
EXPECT_EQ(svg_with_view_box_properties->ReplacedContentTransform(),
rect_properties->Transform()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGRootPaintOffsetTransformNode) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0px; }
#svg {
margin-left: 50px;
margin-top: 25px;
width: 100px;
height: 100px;
}
</style>
<svg id='svg' />
)HTML");
LayoutObject& svg = *GetLayoutObjectByElementId("svg");
const ObjectPaintProperties* svg_properties =
svg.FirstFragment().PaintProperties();
EXPECT_TRUE(svg_properties->PaintOffsetTranslation());
EXPECT_EQ(
FloatSize(50, 25),
svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation());
EXPECT_EQ(nullptr, svg_properties->ReplacedContentTransform());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(),
svg_properties->PaintOffsetTranslation()->Parent());
} else {
EXPECT_EQ(DocScrollTranslation(),
svg_properties->PaintOffsetTranslation()->Parent());
}
}
TEST_P(PaintPropertyTreeBuilderTest, SVGRootLocalToBorderBoxTransformNode) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0px; }
svg {
margin-left: 2px;
margin-top: 3px;
transform: translate(5px, 7px);
border: 11px solid green;
}
</style>
<svg id='svg' width='100px' height='100px' viewBox='0 0 13 13'>
<rect id='rect' transform='translate(17 19)' />
</svg>
)HTML");
LayoutObject& svg = *GetLayoutObjectByElementId("svg");
const ObjectPaintProperties* svg_properties =
svg.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(2, 3),
svg_properties->PaintOffsetTranslation()->Matrix());
EXPECT_EQ(TransformationMatrix().Translate(5, 7),
svg_properties->Transform()->Matrix());
EXPECT_EQ(TransformationMatrix().Translate(11, 11).Scale(100.0 / 13.0),
svg_properties->ReplacedContentTransform()->Matrix());
EXPECT_EQ(svg_properties->PaintOffsetTranslation(),
svg_properties->Transform()->Parent());
EXPECT_EQ(svg_properties->Transform(),
svg_properties->ReplacedContentTransform()->Parent());
// Ensure the rect's transform is a child of the local to border box
// transform.
LayoutObject& rect = *GetLayoutObjectByElementId("rect");
const ObjectPaintProperties* rect_properties =
rect.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(17, 19),
rect_properties->Transform()->Matrix());
EXPECT_EQ(svg_properties->ReplacedContentTransform(),
rect_properties->Transform()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGNestedViewboxTransforms) {
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0px; } </style>
<svg id='svg' width='100px' height='100px' viewBox='0 0 50 50'
style='transform: translate(11px, 11px);'>
<svg id='nestedSvg' width='50px' height='50px' viewBox='0 0 5 5'>
<rect id='rect' transform='translate(13 13)' />
</svg>
</svg>
)HTML");
LayoutObject& svg = *GetLayoutObjectByElementId("svg");
const ObjectPaintProperties* svg_properties =
svg.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(11, 11),
svg_properties->Transform()->Matrix());
EXPECT_EQ(TransformationMatrix().Scale(2),
svg_properties->ReplacedContentTransform()->Matrix());
LayoutObject& nested_svg = *GetLayoutObjectByElementId("nestedSvg");
const ObjectPaintProperties* nested_svg_properties =
nested_svg.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Scale(10),
nested_svg_properties->Transform()->Matrix());
EXPECT_EQ(nullptr, nested_svg_properties->ReplacedContentTransform());
EXPECT_EQ(svg_properties->ReplacedContentTransform(),
nested_svg_properties->Transform()->Parent());
LayoutObject& rect = *GetLayoutObjectByElementId("rect");
const ObjectPaintProperties* rect_properties =
rect.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(13, 13),
rect_properties->Transform()->Matrix());
EXPECT_EQ(nested_svg_properties->Transform(),
rect_properties->Transform()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodesAcrossSVGHTMLBoundary) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0px; } </style>
<svg id='svgWithTransform'
style='transform: translate3d(1px, 2px, 3px);'>
<foreignObject>
<body>
<div id='divWithTransform'
style='transform: translate3d(3px, 4px, 5px);'></div>
</body>
</foreignObject>
</svg>
)HTML");
LayoutObject& svg_with_transform =
*GetLayoutObjectByElementId("svgWithTransform");
const ObjectPaintProperties* svg_with_transform_properties =
svg_with_transform.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3),
svg_with_transform_properties->Transform()->Matrix());
LayoutObject& div_with_transform =
*GetLayoutObjectByElementId("divWithTransform");
const ObjectPaintProperties* div_with_transform_properties =
div_with_transform.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(3, 4, 5),
div_with_transform_properties->Transform()->Matrix());
// Ensure the div's transform node is a child of the svg's transform node.
EXPECT_EQ(svg_with_transform_properties->Transform(),
div_with_transform_properties->Transform()->Parent()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, ForeignObjectWithTransformAndOffset) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0px; } </style>
<svg id='svgWithTransform'>
<foreignObject id="foreignObject"
x="10" y="10" width="50" height="40" transform="scale(5)">
<div id='div'></div>
</foreignObject>
</svg>
)HTML");
LayoutObject& foreign_object = *GetLayoutObjectByElementId("foreignObject");
const ObjectPaintProperties* foreign_object_properties =
foreign_object.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Scale(5),
foreign_object_properties->Transform()->Matrix());
EXPECT_EQ(LayoutPoint(10, 10), foreign_object.FirstFragment().PaintOffset());
EXPECT_EQ(nullptr, foreign_object_properties->PaintOffsetTranslation());
LayoutObject& div = *GetLayoutObjectByElementId("div");
EXPECT_EQ(LayoutPoint(10, 10), div.FirstFragment().PaintOffset());
}
TEST_P(PaintPropertyTreeBuilderTest, ForeignObjectWithMask) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0px; } </style>
<svg id='svg' style='position; relative'>
<foreignObject id="foreignObject"
x="10" y="10" width="50" height="40"
style="-webkit-mask:linear-gradient(red,red)">
<div id='div'></div>
</foreignObject>
</svg>
)HTML");
LayoutObject& svg = *GetLayoutObjectByElementId("svg");
LayoutObject& foreign_object = *GetLayoutObjectByElementId("foreignObject");
const ObjectPaintProperties* foreign_object_properties =
foreign_object.FirstFragment().PaintProperties();
EXPECT_TRUE(foreign_object_properties->Mask());
EXPECT_EQ(foreign_object_properties->MaskClip(),
foreign_object_properties->Mask()->OutputClip());
EXPECT_EQ(svg.FirstFragment().LocalBorderBoxProperties().Transform(),
foreign_object_properties->Mask()->LocalTransformSpace());
}
TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetTranslationSVGHTMLBoundary) {
SetBodyInnerHTML(R"HTML(
<svg id='svg'
<foreignObject>
<body>
<div id='divWithTransform'
style='transform: translate3d(3px, 4px, 5px);'></div>
</body>
</foreignObject>
</svg>
)HTML");
LayoutObject& svg = *GetLayoutObjectByElementId("svg");
const ObjectPaintProperties* svg_properties =
svg.FirstFragment().PaintProperties();
EXPECT_EQ(
FloatSize(8, 8),
svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation());
LayoutObject& div_with_transform =
*GetLayoutObjectByElementId("divWithTransform");
const ObjectPaintProperties* div_with_transform_properties =
div_with_transform.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(3, 4, 5),
div_with_transform_properties->Transform()->Matrix());
EXPECT_EQ(FloatSize(8, 158),
div_with_transform_properties->PaintOffsetTranslation()
->Matrix()
.To2DTranslation());
EXPECT_EQ(div_with_transform_properties->PaintOffsetTranslation(),
div_with_transform_properties->Transform()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGViewportContainer) {
SetBodyInnerHTML(R"HTML(
<!-- border radius of inner svg elemnents should be ignored. -->
<style>svg { border-radius: 10px }</style>
<svg id='svg'>
<svg id='container1' width='30' height='30'></svg>
<svg id='container2'
width='30' height='30' x='40' y='50' viewBox='0 0 60 60'></svg>
<svg id='container3' overflow='visible' width='30' height='30'></svg>
<svg id='container4' overflow='visible'
width='30' height='30' x='20' y='30'></svg>
</svg>
)HTML");
const auto* svg_properties = PaintPropertiesForElement("svg");
ASSERT_NE(nullptr, svg_properties);
const auto* parent_transform = svg_properties->PaintOffsetTranslation();
const auto* parent_clip = svg_properties->OverflowClip();
// overflow: hidden and zero offset: OverflowClip only.
const auto* properties1 = PaintPropertiesForElement("container1");
ASSERT_NE(nullptr, properties1);
const auto* clip = properties1->OverflowClip();
const auto* transform = properties1->Transform();
ASSERT_NE(nullptr, clip);
EXPECT_EQ(nullptr, transform);
EXPECT_EQ(parent_clip, clip->Parent());
EXPECT_EQ(FloatRect(0, 0, 30, 30), clip->ClipRect().Rect());
EXPECT_EQ(parent_transform, clip->LocalTransformSpace());
// overflow: hidden and non-zero offset and viewport scale:
// both Transform and OverflowClip.
const auto* properties2 = PaintPropertiesForElement("container2");
ASSERT_NE(nullptr, properties2);
clip = properties2->OverflowClip();
transform = properties2->Transform();
ASSERT_NE(nullptr, clip);
ASSERT_NE(nullptr, transform);
EXPECT_EQ(parent_clip, clip->Parent());
EXPECT_EQ(FloatRect(0, 0, 60, 60), clip->ClipRect().Rect());
EXPECT_EQ(transform, clip->LocalTransformSpace());
EXPECT_EQ(TransformationMatrix().Translate(40, 50).Scale(0.5),
transform->Matrix());
EXPECT_EQ(parent_transform, transform->Parent());
// overflow: visible and zero offset: no paint properties.
const auto* properties3 = PaintPropertiesForElement("container3");
EXPECT_EQ(nullptr, properties3);
// overflow: visible and non-zero offset: Transform only.
const auto* properties4 = PaintPropertiesForElement("container4");
ASSERT_NE(nullptr, properties4);
clip = properties4->OverflowClip();
transform = properties4->Transform();
EXPECT_EQ(nullptr, clip);
ASSERT_NE(nullptr, transform);
EXPECT_EQ(TransformationMatrix().Translate(20, 30), transform->Matrix());
EXPECT_EQ(parent_transform, transform->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGForeignObjectOverflowClip) {
SetBodyInnerHTML(R"HTML(
<svg id='svg'>
<foreignObject id='object1' x='10' y='20' width='30' height='40'
overflow='hidden'>
</foreignObject>
<foreignObject id='object2' x='50' y='60' width='30' height='40'
overflow='visible'>
</foreignObject>
</svg>
)HTML");
const auto* svg_properties = PaintPropertiesForElement("svg");
ASSERT_NE(nullptr, svg_properties);
const auto* parent_transform = svg_properties->PaintOffsetTranslation();
const auto* parent_clip = svg_properties->OverflowClip();
const auto* properties1 = PaintPropertiesForElement("object1");
ASSERT_NE(nullptr, properties1);
const auto* clip = properties1->OverflowClip();
ASSERT_NE(nullptr, clip);
EXPECT_EQ(parent_clip, clip->Parent());
EXPECT_EQ(FloatRect(10, 20, 30, 40), clip->ClipRect().Rect());
EXPECT_EQ(parent_transform, clip->LocalTransformSpace());
const auto* properties2 = PaintPropertiesForElement("object2");
EXPECT_EQ(nullptr, properties2);
}
TEST_P(PaintPropertyTreeBuilderTest,
PaintOffsetTranslationSVGHTMLBoundaryMulticol) {
SetBodyInnerHTML(R"HTML(
<svg id='svg'>
<foreignObject>
<body>
<div id='divWithColumns' style='columns: 2'>
<div style='width: 5px; height: 5px; background: blue'>
</div>
</body>
</foreignObject>
</svg>
)HTML");
LayoutObject& svg = *GetLayoutObjectByElementId("svg");
const ObjectPaintProperties* svg_properties =
svg.FirstFragment().PaintProperties();
EXPECT_EQ(
FloatSize(8, 8),
svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation());
LayoutObject& div_with_columns =
*GetLayoutObjectByElementId("divWithColumns")->SlowFirstChild();
EXPECT_EQ(LayoutPoint(0, 0), div_with_columns.FirstFragment().PaintOffset());
}
TEST_P(PaintPropertyTreeBuilderTest,
FixedTransformAncestorAcrossSVGHTMLBoundary) {
SetBodyInnerHTML(R"HTML(
<style> body { margin: 0px; } </style>
<svg id='svg' style='transform: translate3d(1px, 2px, 3px);'>
<g id='container' transform='translate(20 30)'>
<foreignObject>
<body>
<div id='fixed'
style='position: fixed; left: 200px; top: 150px;'></div>
</body>
</foreignObject>
</g>
</svg>
)HTML");
LayoutObject& svg = *GetLayoutObjectByElementId("svg");
const ObjectPaintProperties* svg_properties =
svg.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3),
svg_properties->Transform()->Matrix());
LayoutObject& container = *GetLayoutObjectByElementId("container");
const ObjectPaintProperties* container_properties =
container.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(20, 30),
container_properties->Transform()->Matrix());
EXPECT_EQ(svg_properties->Transform(),
container_properties->Transform()->Parent());
Element* fixed = GetDocument().getElementById("fixed");
// Ensure the fixed position element is rooted at the nearest transform
// container.
EXPECT_EQ(container_properties->Transform(), fixed->GetLayoutObject()
->FirstFragment()
.LocalBorderBoxProperties()
.Transform());
}
TEST_P(PaintPropertyTreeBuilderTest, ControlClip) {
SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0;
}
input {
border-radius: 0;
border-width: 5px;
padding: 0;
}
</style>
<input id='button' type='button'
style='width:345px; height:123px' value='some text'/>
)HTML");
LayoutObject& button = *GetLayoutObjectByElementId("button");
const ObjectPaintProperties* button_properties =
button.FirstFragment().PaintProperties();
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(DocPreTranslation());
EXPECT_FALSE(DocScrollTranslation());
EXPECT_EQ(DocPreTranslation(),
button_properties->OverflowClip()->LocalTransformSpace());
} else {
// Always create scroll translation for layout view even the document does
// not scroll (not enough content).
EXPECT_TRUE(DocScrollTranslation());
EXPECT_EQ(DocScrollTranslation(),
button_properties->OverflowClip()->LocalTransformSpace());
}
EXPECT_EQ(FloatRoundedRect(5, 5, 335, 113),
button_properties->OverflowClip()->ClipRect());
EXPECT_EQ(DocContentClip(), button_properties->OverflowClip()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 345, 123), &button,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, ControlClipInsideForeignObject) {
GetDocument().SetCompatibilityMode(Document::kQuirksMode);
SetBodyInnerHTML(R"HTML(
<div style='column-count:2;'>
<div style='columns: 2'>
<svg style='width: 500px; height: 500px;'>
<foreignObject style='overflow: visible;'>
<input id='button' style='width:345px; height:123px'
value='some text'/>
</foreignObject>
</svg>
</div>
</div>
)HTML");
LayoutObject& button = *GetLayoutObjectByElementId("button");
const ObjectPaintProperties* button_properties =
button.FirstFragment().PaintProperties();
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_FALSE(DocScrollTranslation());
} else {
// Always create scroll translation for layout view even the document does
// not scroll (not enough content).
EXPECT_TRUE(DocScrollTranslation());
}
EXPECT_EQ(FloatRoundedRect(2, 2, 341, 119),
button_properties->OverflowClip()->ClipRect());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 345, 123), &button,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, BorderRadiusClip) {
SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0px;
}
#div {
border-radius: 12px 34px 56px 78px;
border-top: 45px solid;
border-right: 50px solid;
border-bottom: 55px solid;
border-left: 60px solid;
width: 500px;
height: 400px;
overflow: scroll;
}
</style>
<div id='div'></div>
)HTML");
LayoutObject& div = *GetLayoutObjectByElementId("div");
const ObjectPaintProperties* div_properties =
div.FirstFragment().PaintProperties();
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_FALSE(DocScrollTranslation());
EXPECT_EQ(DocPreTranslation(),
div_properties->OverflowClip()->LocalTransformSpace());
} else {
// Always create scroll translation for layout view even the document does
// not scroll (not enough content).
EXPECT_TRUE(DocScrollTranslation());
EXPECT_EQ(DocScrollTranslation(),
div_properties->OverflowClip()->LocalTransformSpace());
}
// The overflow clip rect includes only the padding box.
// padding box = border box(500+60+50, 400+45+55) - border outset(60+50,
// 45+55) - scrollbars(15, 15)
EXPECT_EQ(FloatRoundedRect(60, 45, 500, 400),
div_properties->OverflowClip()->ClipRect());
const ClipPaintPropertyNode* border_radius_clip =
div_properties->OverflowClip()->Parent();
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(), border_radius_clip->LocalTransformSpace());
} else {
EXPECT_EQ(DocScrollTranslation(),
border_radius_clip->LocalTransformSpace());
}
// The border radius clip is the area enclosed by inner border edge, including
// the scrollbars. As the border-radius is specified in outer radius, the
// inner radius is calculated by:
// inner radius = max(outer radius - border width, 0)
// In the case that two adjacent borders have different width, the inner
// radius of the corner may transition from one value to the other. i.e. being
// an ellipse.
// The following is border box(610, 500) - border outset(110, 100).
FloatRect border_box_minus_border_outset(60, 45, 500, 400);
EXPECT_EQ(
FloatRoundedRect(
border_box_minus_border_outset,
FloatSize(0, 0), // (top left) = max((12, 12) - (60, 45), (0, 0))
FloatSize(0, 0), // (top right) = max((34, 34) - (50, 45), (0, 0))
FloatSize(18, 23), // (bot left) = max((78, 78) - (60, 55), (0, 0))
FloatSize(6, 1)), // (bot right) = max((56, 56) - (50, 55), (0, 0))
border_radius_clip->ClipRect());
EXPECT_EQ(DocContentClip(), border_radius_clip->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 610, 500), &div,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodesAcrossSubframes) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0; }
#divWithTransform {
transform: translate3d(1px, 2px, 3px);
}
</style>
<div id='divWithTransform'>
<iframe id='iframe' style='border: 7px solid black'></iframe>
</div>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
body { margin: 0; }
#innerDivWithTransform {
transform: translate3d(4px, 5px, 6px);
width: 100px;
height: 200px;
}
</style>
<div id='innerDivWithTransform'></div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
LayoutObject* div_with_transform =
GetLayoutObjectByElementId("divWithTransform");
const ObjectPaintProperties* div_with_transform_properties =
div_with_transform->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3),
div_with_transform_properties->Transform()->Matrix());
CHECK_EXACT_VISUAL_RECT(LayoutRect(1, 2, 800, 164), div_with_transform,
frame_view->GetLayoutView());
LayoutObject* inner_div_with_transform =
ChildDocument()
.getElementById("innerDivWithTransform")
->GetLayoutObject();
const ObjectPaintProperties* inner_div_with_transform_properties =
inner_div_with_transform->FirstFragment().PaintProperties();
auto* inner_div_transform = inner_div_with_transform_properties->Transform();
EXPECT_EQ(TransformationMatrix().Translate3d(4, 5, 6),
inner_div_transform->Matrix());
CHECK_EXACT_VISUAL_RECT(LayoutRect(12, 14, 100, 145),
inner_div_with_transform,
frame_view->GetLayoutView());
// Ensure that the inner div's transform is correctly rooted in the root
// frame's transform tree.
// This asserts that we have the following tree structure:
// Transform transform=translation=1.000000,2.000000,3.000000
// PaintOffsetTranslation transform=Identity
// PreTranslation transform=translation=7.000000,7.000000,0.000000
// PaintOffsetTranslation transform=Identity
// ScrollTranslation transform=translation=0.000000,0.000000,0.000000
// Transform transform=translation=4.000000,5.000000,6.000000
auto* inner_document_scroll_translation = inner_div_transform->Parent();
EXPECT_EQ(TransformationMatrix().Translate3d(0, 0, 0),
inner_document_scroll_translation->Matrix());
auto* paint_offset_translation = inner_document_scroll_translation->Parent();
auto* iframe_pre_translation =
inner_document_scroll_translation->Parent()->Unalias()->Parent();
EXPECT_EQ(FloatSize(), paint_offset_translation->Matrix().To2DTranslation());
EXPECT_EQ(TransformationMatrix().Translate3d(7, 7, 0),
iframe_pre_translation->Matrix());
// SPv1 composited elements always create paint offset translation,
// where in SPv2 they don't.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(div_with_transform_properties->Transform(),
iframe_pre_translation->Parent());
} else {
LayoutObject* iframe_element = GetLayoutObjectByElementId("iframe");
const ObjectPaintProperties* iframe_element_properties =
iframe_element->FirstFragment().PaintProperties();
EXPECT_EQ(iframe_element_properties->PaintOffsetTranslation(),
iframe_pre_translation->Parent());
EXPECT_EQ(TransformationMatrix(),
iframe_element_properties->PaintOffsetTranslation()->Matrix());
EXPECT_EQ(div_with_transform_properties->Transform(),
iframe_element_properties->PaintOffsetTranslation()->Parent());
}
}
TEST_P(PaintPropertyTreeBuilderTest, FramesEstablishIsolation) {
if (!RuntimeEnabledFeatures::LayoutViewIsolationNodesEnabled())
return;
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0; }
.transformed {
transform: translateX(1px);
}
#parent {
width: 100px;
height: 100px;
overflow: hidden;
}
</style>
<div id='parent'>
<iframe id='iframe'></iframe>
</div>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
body { margin: 0; }
#child {
transform: translateX(50px);
width: 50px;
height: 50px;
overflow: hidden;
}
</style>
<div id='child'></div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
LayoutObject* frame = ChildFrame().View()->GetLayoutView();
const auto& frame_contents_properties =
frame->FirstFragment().ContentsProperties();
LayoutObject* child =
ChildDocument().getElementById("child")->GetLayoutObject();
const auto& child_local_border_box_properties =
child->FirstFragment().LocalBorderBoxProperties();
auto* child_properties =
child->GetMutableForPainting().FirstFragment().PaintProperties();
// From the frame content's properties, we have:
// - transform isolation node
// - paint offset translation
// - transform
EXPECT_EQ(TransformationMatrix().Translate(50, 0),
child_local_border_box_properties.Transform()->Matrix());
EXPECT_EQ(child_local_border_box_properties.Transform()->Parent(),
child_properties->PaintOffsetTranslation());
EXPECT_EQ(child_local_border_box_properties.Transform()->Parent()->Parent(),
frame_contents_properties.Transform());
// Verify it's a true isolation node (i.e. it has a parent and it is a parent
// alias).
EXPECT_TRUE(frame_contents_properties.Transform()->Parent());
EXPECT_TRUE(frame_contents_properties.Transform()->IsParentAlias());
// Do similar checks for clip and effect, although the child local border box
// properties directly reference the alias, since they do not have their own
// clip and effect.
EXPECT_EQ(child_local_border_box_properties.Clip(),
frame_contents_properties.Clip());
EXPECT_TRUE(frame_contents_properties.Clip()->Parent());
EXPECT_TRUE(frame_contents_properties.Clip()->IsParentAlias());
EXPECT_EQ(child_local_border_box_properties.Effect(),
frame_contents_properties.Effect());
EXPECT_TRUE(frame_contents_properties.Effect()->Parent());
EXPECT_TRUE(frame_contents_properties.Effect()->IsParentAlias());
// The following part of the code would cause a DCHECK, but we want to see if
// the pre-paint iteration doesn't touch child's state, due to isolation. Hence,
// this only runs if we don't have DCHECKs enabled.
#if !DCHECK_IS_ON()
// Now clobber the child transform to something identifiable.
TransformPaintPropertyNode::State state{
TransformationMatrix().Translate(123, 321)};
child_properties->UpdateTransform(
*child_local_border_box_properties.Transform()->Parent(),
std::move(state));
// Verify that we clobbered it correctly.
EXPECT_EQ(TransformationMatrix().Translate(123, 321),
child_local_border_box_properties.Transform()->Matrix());
// This causes a tree topology change which forces the subtree to be updated.
// However, isolation stops this recursion.
GetDocument().getElementById("parent")->setAttribute(html_names::kClassAttr,
"transformed");
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
// Verify that our clobbered state is still clobbered.
EXPECT_EQ(TransformationMatrix().Translate(123, 321),
child_local_border_box_properties.Transform()->Matrix());
#endif // !DCHECK_IS_ON()
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodesInTransformedSubframes) {
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0; }
#divWithTransform {
transform: translate3d(1px, 2px, 3px);
}
iframe {
transform: translate3d(4px, 5px, 6px);
border: 42px solid;
margin: 7px;
}
</style>
<div id='divWithTransform'>
<iframe></iframe>
</div>
)HTML");
SetChildFrameHTML(R"HTML(
<style>
body { margin: 31px; }
#transform {
transform: translate3d(7px, 8px, 9px);
width: 100px;
height: 200px;
}
</style>
<div id='transform'></div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases(
DocumentLifecycle::LifecycleUpdateReason::kTest);
// Assert that we have the following tree structure:
// ...
// Transform transform=translation=1.000000,2.000000,3.000000
// PaintOffsetTranslation transform=translation=7.000000,7.000000,0.000000
// Transform transform=translation=4.000000,5.000000,6.000000
// PreTranslation transform=translation=42.000000,42.000000,0.000000
// ScrollTranslation transform=translation=0.000000,0.000000,0.00000
// PaintOffsetTranslation transform=translation=31.00,31.00,0.00
// Transform transform=translation=7.000000,8.000000,9.000000
LayoutObject* inner_div_with_transform =
ChildDocument().getElementById("transform")->GetLayoutObject();
auto* inner_div_transform =
inner_div_with_transform->FirstFragment().PaintProperties()->Transform();
EXPECT_EQ(TransformationMatrix().Translate3d(7, 8, 9),
inner_div_transform->Matrix());
CHECK_EXACT_VISUAL_RECT(LayoutRect(92, 95, 100, 111),
inner_div_with_transform,
frame_view->GetLayoutView());
auto* inner_document_paint_offset_translation = inner_div_transform->Parent();
EXPECT_EQ(TransformationMatrix().Translate3d(31, 31, 0),
inner_document_paint_offset_translation->Matrix());
auto* inner_document_scroll_translation =
inner_document_paint_offset_translation->Parent()->Unalias();
EXPECT_EQ(TransformationMatrix().Translate3d(0, 0, 0),
inner_document_scroll_translation->Matrix());
auto* iframe_pre_translation = inner_document_scroll_translation->Parent();
EXPECT_EQ(TransformationMatrix().Translate3d(42, 42, 0),
iframe_pre_translation->Matrix());
auto* iframe_transform = iframe_pre_translation->Parent();
EXPECT_EQ(TransformationMatrix().Translate3d(4, 5, 6),
iframe_transform->Matrix());
auto* iframe_paint_offset_translation = iframe_transform->Parent();
EXPECT_EQ(TransformationMatrix().Translate3d(7, 7, 0),
iframe_paint_offset_translation->Matrix());
auto* div_with_transform_transform =
iframe_paint_offset_translation->Parent();
EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3),
div_with_transform_transform->Matrix());
LayoutObject* div_with_transform =
GetLayoutObjectByElementId("divWithTransform");
EXPECT_EQ(div_with_transform_transform,
div_with_transform->FirstFragment().PaintProperties()->Transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(1, 2, 800, 248), div_with_transform,
frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, TreeContextClipByNonStackingContext) {
// This test verifies the tree builder correctly computes and records the
// property tree context for a (pseudo) stacking context that is scrolled by a
// containing block that is not one of the painting ancestors.
SetBodyInnerHTML(R"HTML(
<style>body { margin: 0; }</style>
<div id='scroller' style='overflow:scroll; width:400px; height:300px;'>
<div id='child'
style='position:relative; width:100px; height: 200px;'></div>
<div style='height:10000px;'></div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* scroller = GetLayoutObjectByElementId("scroller");
const ObjectPaintProperties* scroller_properties =
scroller->FirstFragment().PaintProperties();
LayoutObject* child = GetLayoutObjectByElementId("child");
EXPECT_EQ(scroller_properties->OverflowClip(),
child->FirstFragment().LocalBorderBoxProperties().Clip());
EXPECT_EQ(scroller_properties->ScrollTranslation(),
child->FirstFragment().LocalBorderBoxProperties().Transform());
EXPECT_NE(nullptr,
child->FirstFragment().LocalBorderBoxProperties().Effect());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 400, 300), scroller,
frame_view->GetLayoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 100, 200), child,
frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest,
TreeContextUnclipFromParentStackingContext) {
// This test verifies the tree builder correctly computes and records the
// property tree context for a (pseudo) stacking context that has a scrolling
// painting ancestor that is not its containing block (thus should not be
// scrolled by it).
SetBodyInnerHTML(R"HTML(
<style>
body { margin: 0; }
#scroller {
overflow:scroll;
opacity:0.5;
}
#child {
position:absolute;
left:0;
top:0;
width: 100px;
height: 200px;
}
</style>
<div id='scroller'>
<div id='child'></div>
<div id='forceScroll' style='height:10000px;'></div>
</div>
)HTML");
auto& scroller = *GetLayoutObjectByElementId("scroller");
const ObjectPaintProperties* scroller_properties =
scroller.FirstFragment().PaintProperties();
LayoutObject& child = *GetLayoutObjectByElementId("child");
EXPECT_EQ(DocContentClip(),
child.FirstFragment().LocalBorderBoxProperties().Clip());
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocScrollTranslation(),
child.FirstFragment().LocalBorderBoxProperties().Transform());
} else {
// For SPv1, |child| is composited so we created PaintOffsetTranslation.
EXPECT_EQ(child.FirstFragment().PaintProperties()->PaintOffsetTranslation(),
child.FirstFragment().LocalBorderBoxProperties().Transform());
}
EXPECT_EQ(scroller_properties->Effect(),
child.FirstFragment().LocalBorderBoxProperties().Effect());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 800, 10000), &scroller,
GetDocument().View()->GetLayoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 100, 200), &child,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, TableCellLayoutLocation) {
// This test verifies that the border box space of a table cell is being
// correctly computed. Table cells have weird location adjustment in our
// layout/paint implementation.
SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0;
}
table {
border-spacing: 0;
margin: 20px;
padding: 40px;
border: 10px solid black;
}
td {
width: 100px;
height: 100px;
padding: 0;
}
#target {
position: relative;
width: 100px;
height: 100px;
}
</style>
<table>
<tr><td></td><td></td></tr>
<tr><td></td><td><div id='target'></div></td></tr>
</table>
)HTML");
LayoutObject& target = *GetLayoutObjectByElementId("target");
EXPECT_EQ(LayoutPoint(170, 170), target.FirstFragment().PaintOffset());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(),
target.FirstFragment().LocalBorderBoxProperties().Transform());
} else {
EXPECT_EQ(DocScrollTranslation(),
target.FirstFragment().LocalBorderBoxProperties().Transform());
}
CHECK_EXACT_VISUAL_RECT(LayoutRect(170, 170, 100, 100), &target,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, CSSClipFixedPositionDescendant) {
// This test verifies that clip tree hierarchy being generated correctly for
// the hard case such that a fixed position element getting clipped by an
// absolute position CSS clip.
SetBodyInnerHTML(R"HTML(
<style>
#clip {
position: absolute;
left: 123px;
top: 456px;
clip: rect(10px, 80px, 70px, 40px);
width: 100px;
height: 100px;
}
#fixed {
position: fixed;
left: 654px;
top: 321px;
width: 10px;
height: 20px
}
</style>
<div id='clip'><div id='fixed'></div></div>
)HTML");
LayoutRect local_clip_rect(40, 10, 40, 60);
LayoutRect absolute_clip_rect = local_clip_rect;
absolute_clip_rect.Move(123, 456);
LayoutObject& clip = *GetLayoutObjectByElementId("clip");
const ObjectPaintProperties* clip_properties =
clip.FirstFragment().PaintProperties();
EXPECT_EQ(DocContentClip(), clip_properties->CssClip()->Parent());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(DocPreTranslation(),
clip_properties->CssClip()->LocalTransformSpace());
} else {
EXPECT_EQ(DocScrollTranslation(),
clip_properties->CssClip()->LocalTransformSpace());
}
EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)),
clip_properties->CssClip()->ClipRect());
CHECK_VISUAL_RECT(absolute_clip_rect, &clip,
GetDocument().View()->GetLayoutView(),
// TODO(crbug.com/599939): mapToVisualRectInAncestorSpace()
// doesn't apply css clip on the object itself.
LayoutUnit::Max());
LayoutObject* fixed = GetLayoutObjectByElementId("fixed");
EXPECT_EQ(clip_properties->CssClip(),
fixed->FirstFragment().LocalBorderBoxProperties().Clip());
EXPECT_EQ(DocPreTranslation(),
fixed->FirstFragment().LocalBorderBoxProperties().Transform());
EXPECT_EQ(LayoutPoint(654, 321), fixed->FirstFragment().PaintOffset());
CHECK_VISUAL_RECT(LayoutRect(), fixed, GetDocument().View()->GetLayoutView(),
// TODO(crbug.com/599939): CSS clip of fixed-position
// descendants is broken in
// mapToVisualRectInAncestorSpace().
LayoutUnit::Max());
}
TEST_P(PaintPropertyTreeBuilderTest, CSSClipAbsPositionDescendant) {
// This test verifies that clip tree hierarchy being generated correctly for
// the hard case such that a fixed position element getting clipped by an
// absolute position CSS clip.
SetBodyInnerHTML(R"HTML(
<style>
#clip {
position: absolute;
left: 123px;
top: 456px;
clip: rect(10px, 80px, 70px, 40px);
width: 100px;
height: 100px;
}
#absolute {
position: absolute;
left: 654px;
top: 321px;
width: 10px;
heght: 20px
}
</style>
<div id='clip'><div id='absolute'></div></div>
)HTML");
LayoutRect local_clip_rect(40, 10, 40, 60);
LayoutRect absolute_clip_rect = local_clip_rect;
absolute_clip_rect.Move(123, 456);
auto* clip = GetLayoutObjectByElementId("clip");
const ObjectPaintProperties* clip_properties =
clip->FirstFragment().PaintProperties();
EXPECT_EQ(DocContentClip(), clip_properties->CssClip()->Parent());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_FALSE(DocScrollTranslation());
EXPECT_EQ(DocPreTranslation(),
clip_properties->CssClip()->LocalTransformSpace());
} else {
// Always create scroll translation for layout view even the document does
// not scroll (not enough content).
EXPECT_TRUE(DocScrollTranslation());
EXPECT_EQ(DocScrollTranslation(),
clip_properties->CssClip()->LocalTransformSpace());
}
EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)),
clip_properties->CssClip()->ClipRect());
CHECK_VISUAL_RECT(absolute_clip_rect, clip,
GetDocument().View()->GetLayoutView(),
// TODO(crbug.com/599939): mapToVisualRectInAncestorSpace()
// doesn't apply css clip on the object itself.
LayoutUnit::Max());
auto* absolute = GetLayoutObjectByElementId("absolute");
EXPECT_EQ(clip_properties->CssClip(),
absolute->FirstFragment().LocalBorderBoxProperties().Clip());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_FALSE(DocScrollTranslation());
EXPECT_EQ(DocPreTranslation(),
absolute->FirstFragment().LocalBorderBoxProperties().Transform());
} else {
EXPECT_TRUE(DocScrollTranslation());
EXPECT_EQ(DocScrollTranslation(),
absolute->FirstFragment().LocalBorderBoxProperties().Transform());
}
EXPECT_EQ(LayoutPoint(777, 777), absolute->FirstFragment().PaintOffset());
CHECK_VISUAL_RECT(LayoutRect(), absolute,
GetDocument().View()->GetLayoutView(),
// TODO(crbug.com/599939): CSS clip of fixed-position
// descendants is broken in
// mapToVisualRectInAncestorSpace().
LayoutUnit::Max());
}
TEST_P(PaintPropertyTreeBuilderTest, CSSClipSubpixel) {
// This test verifies that clip tree hierarchy being generated correctly for
// a subpixel-positioned element with CSS clip.
SetBodyInnerHTML(R"HTML(
<style>
#clip {
position: absolute;
left: 123.5px;
top: 456px;
clip: rect(10px, 80px, 70px, 40px);
width: 100px;
height: 100px;
}
</style>
<div id='clip'></div>
)HTML");
LayoutRect local_clip_rect(40, 10, 40, 60);
LayoutRect absolute_clip_rect = local_clip_rect;
// Moved by 124 pixels due to pixel-snapping.
absolute_clip_rect.Move(124, 456);
auto* clip = GetLayoutObjectByElementId("clip");
const ObjectPaintProperties* clip_properties =
clip->FirstFragment().PaintProperties();
EXPECT_EQ(DocContentClip(), clip_properties->CssClip()->Parent());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(DocPreTranslation());
EXPECT_EQ(DocPreTranslation(),
clip_properties->CssClip()->LocalTransformSpace());
} else {
// Always create scroll translation for layout view even the document does
// not scroll (not enough content).
EXPECT_TRUE(DocScrollTranslation());
EXPECT_EQ(DocScrollTranslation(),
clip_properties->CssClip()->LocalTransformSpace());
}
EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)),
clip_properties->CssClip()->ClipRect());
}
TEST_P(PaintPropertyTreeBuilderTest, CSSClipFixedPositionDescendantNonShared) {
// This test is similar to CSSClipFixedPositionDescendant above, except that
// now we have a parent overflow clip that should be escaped by the fixed
// descendant.
SetBodyInnerHTML(R"HTML(
<style>
body {
margin: 0;
}
#overflow {
position: relative;
width: 50px;
height: 50px;
overflow: scroll;
}
#clip {
position: absolute;
left: 123px;
top: 456px;
clip: rect(10px, 80px, 70px, 40px);
width: 100px;
height: 100px;
}
#fixed {
position: fixed;
left: 654px;
top: 321px;
}
</style>
<div id='overflow'><div id='clip'><div id='fixed'></div></div></div>
)HTML");
LayoutRect local_clip_rect(40, 10, 40, 60);
LayoutRect absolute_clip_rect = local_clip_rect;
absolute_clip_rect.Move(123, 456);
LayoutObject& overflow = *GetLayoutObjectByElementId("overflow");
const ObjectPaintProperties* overflow_properties =
overflow.FirstFragment().PaintProperties();
EXPECT_EQ(DocContentClip(), overflow_properties->OverflowClip()->Parent());
// TODO(crbug.com/732611): SPv2 invalidations are incorrect if there is
// scrolling.
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_TRUE(DocPreTranslation());
EXPECT_EQ(DocPreTranslation(),
overflow_properties->ScrollTranslation()->Parent()->Parent());
} else {
// Always create scroll translation for layout view even the document does
// not scroll (not enough content).
EXPECT_TRUE(DocScrollTranslation());
EXPECT_EQ(DocScrollTranslation(),
overflow_properties->ScrollTranslation()->Parent()->Parent());
}
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 50, 50), &overflow,
GetDocument().View()->GetLayoutView());
LayoutObject* clip = GetLayoutObjectByElementId("clip");
const ObjectPaintProperties* clip_properties =
clip->FirstFragment().PaintProperties();
EXPECT_EQ(overflow_properties->OverflowClip(),
clip_properties->CssClip()->Parent());
EXPECT_EQ(overflow_properties->ScrollTranslation(),
clip_properties->CssClip()->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)),
clip_properties->CssClip()->ClipRect());
EXPECT_EQ(DocContentClip(),
clip_properties->CssClipFixedPosition()->Parent());
EXPECT_EQ(overflow_properties->ScrollTranslation(),
clip_properties->CssClipFixedPosition()->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)),
clip_properties->CssClipFixedPosition()->ClipRect());
CHECK_EXACT_VISUAL_RECT(LayoutRect(), clip,
GetDocument().View()->GetLayoutView());
LayoutObject* fixed = GetLayoutObjectByElementId("fixed");
EXPECT_EQ(clip_properties->CssClipFixedPosition(),
fixed->FirstFragment().LocalBorderBoxProperties().Clip());
EXPECT_EQ(DocPreTranslation(),
fixed->FirstFragment().LocalBorderBoxProperties().Transform());
EXPECT_EQ(LayoutPoint(654, 321), fixed->FirstFragment().PaintOffset());
CHECK_VISUAL_RECT(LayoutRect(), fixed, GetDocument().View()->GetLayoutView(),
// TODO(crbug.com/599939): CSS clip of fixed-position
// descendants is broken in geometry mapping.
LayoutUnit::Max());
}
TEST_P(PaintPropertyTreeBuilderTest, ColumnSpannerUnderRelativePositioned) {
SetBodyInnerHTML(R"HTML(
<style>
#spanner {
column-span: all;
opacity: 0.5;
width: 100px;
height: 100px;
}
</style>
<div style='columns: 3; position: absolute; top: 44px; left: 55px;'>
<div style='position: relative; top: 100px; left: 100px'>
<div id='spanner'></div>
</div>
</div>
)HTML");
LayoutObject* spanner = GetLayoutObjectByElementId("spanner");
EXPECT_EQ(LayoutPoint(55, 44), spanner->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(55, 44, 100, 100), spanner,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, FractionalPaintOffset) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
div { position: absolute; }
#a {
width: 70px;
height: 70px;
left: 0.1px;
top: 0.3px;
}
#b {
width: 40px;
height: 40px;
left: 0.5px;
top: 11.1px;
}
</style>
<div id='a'>
<div id='b'></div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* a = GetLayoutObjectByElementId("a");
LayoutPoint a_paint_offset = LayoutPoint(FloatPoint(0.1, 0.3));
EXPECT_EQ(a_paint_offset, a->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.1), LayoutUnit(0.3),
LayoutUnit(70), LayoutUnit(70)),
a, frame_view->GetLayoutView());
LayoutObject* b = GetLayoutObjectByElementId("b");
LayoutPoint b_paint_offset =
a_paint_offset + LayoutPoint(FloatPoint(0.5, 11.1));
EXPECT_EQ(b_paint_offset, b->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.1), LayoutUnit(0.3),
LayoutUnit(70), LayoutUnit(70)),
a, frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetWithBasicPixelSnapping) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
div { position: relative; }
#a {
width: 70px;
height: 70px;
left: 0.3px;
top: 0.3px;
}
#b {
width: 40px;
height: 40px;
transform: translateZ(0);
}
#c {
width: 40px;
height: 40px;
left: 0.1px;
top: 0.1px;
}
</style>
<div id='a'>
<div id='b'>
<div id='c'></div>
</div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* b = GetLayoutObjectByElementId("b");
const ObjectPaintProperties* b_properties =
b->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(0, 0, 0),
b_properties->Transform()->Matrix());
// The paint offset transform should be snapped from (0.3,0.3) to (0,0).
EXPECT_EQ(TransformationMatrix().Translate(0, 0),
b_properties->Transform()->Parent()->Matrix());
// The residual subpixel adjustment should be (0.3,0.3) - (0,0) = (0.3,0.3).
LayoutPoint subpixel_accumulation = LayoutPoint(FloatPoint(0.3, 0.3));
EXPECT_EQ(subpixel_accumulation, b->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(FloatRect(0.3, 0.3, 40, 40)), b,
frame_view->GetLayoutView());