blob: f3458ebbc768e0befab7a961aa752ca35c301f4b [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 "core/paint/PaintPropertyTreeBuilderTest.h"
#include "core/html/HTMLIFrameElement.h"
#include "core/layout/LayoutTreeAsText.h"
#include "core/paint/ObjectPaintProperties.h"
#include "core/paint/PaintPropertyTreePrinter.h"
#include "platform/graphics/paint/GeometryMapper.h"
namespace blink {
void PaintPropertyTreeBuilderTest::LoadTestData(const char* file_name) {
String full_path = testing::BlinkRootDir();
full_path.append("/Source/core/paint/test_data/");
full_path.append(file_name);
const Vector<char> input_buffer = testing::ReadFromFile(full_path)->Copy();
SetBodyInnerHTML(String(input_buffer.data(), input_buffer.size()));
}
const TransformPaintPropertyNode*
PaintPropertyTreeBuilderTest::FramePreTranslation() {
LocalFrameView* frame_view = GetDocument().View();
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
return frame_view->GetLayoutView()
->FirstFragment()
.PaintProperties()
->PaintOffsetTranslation();
}
return frame_view->PreTranslation();
}
const TransformPaintPropertyNode*
PaintPropertyTreeBuilderTest::FrameScrollTranslation() {
LocalFrameView* frame_view = GetDocument().View();
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
return frame_view->GetLayoutView()
->FirstFragment()
.PaintProperties()
->ScrollTranslation();
}
return frame_view->ScrollTranslation();
}
const ClipPaintPropertyNode* PaintPropertyTreeBuilderTest::FrameContentClip() {
LocalFrameView* frame_view = GetDocument().View();
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
return frame_view->GetLayoutView()
->FirstFragment()
.PaintProperties()
->OverflowClip();
}
return frame_view->ContentClip();
}
const ScrollPaintPropertyNode* PaintPropertyTreeBuilderTest::FrameScroll(
LocalFrameView* frame_view) {
if (!frame_view)
frame_view = GetDocument().View();
if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
return frame_view->GetLayoutView()
->FirstFragment()
.PaintProperties()
->Scroll();
}
return frame_view->ScrollNode();
}
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 source((source_object)->LocalVisualRect()); \
source.MoveBy((source_object)->FirstFragment().PaintOffset()); \
auto contents_properties = \
(ancestor)->FirstFragment().ContentsProperties(); \
FloatClipRect actual_float_rect((FloatRect(source))); \
GeometryMapper::LocalToAncestorVisualRect( \
*(source_object)->FirstFragment().LocalBorderBoxProperties(), \
contents_properties, actual_float_rect); \
LayoutRect actual(actual_float_rect.Rect()); \
actual.MoveBy(-(ancestor)->FirstFragment().PaintOffset()); \
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_TEST_CASE_P(
All,
PaintPropertyTreeBuilderTest,
::testing::ValuesIn(kSlimmingPaintNonV1TestConfigurations));
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();
// 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(FrameContentClip(), 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();
EXPECT_TRUE(positioned_scroll_node->Parent()->IsRoot());
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();
EXPECT_TRUE(transformed_scroll_node->Parent()->IsRoot());
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) {
LoadTestData("position-and-scroll.html");
Element* scroller = GetDocument().getElementById("scroller");
scroller->scrollTo(0, 100);
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases();
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(FrameScrollTranslation(),
scroller_properties->PaintOffsetTranslation()->Parent());
EXPECT_EQ(scroller_properties->PaintOffsetTranslation(),
scroller_properties->OverflowClip()->LocalTransformSpace());
const auto* scroll = scroller_properties->ScrollTranslation()->ScrollNode();
EXPECT_EQ(FrameScroll(), scroll->Parent());
EXPECT_EQ(IntRect(0, 0, 413, 317), scroll->ContainerRect());
EXPECT_EQ(IntRect(0, 0, 660, 10200), scroll->ContentsRect());
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(FrameContentClip(), 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(FrameScrollTranslation(),
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(FrameContentClip(), abs_pos_properties->OverflowClip()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(123, 456, 300, 400),
abs_pos->GetLayoutObject(),
frame_view->GetLayoutView());
}
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 style='width: 400px; height: 400px'></div>
</div>
)HTML");
const auto* properties = PaintPropertiesForElement("scroller");
const auto* overflow_clip = properties->OverflowClip();
const auto* scroll_translation = properties->ScrollTranslation();
const auto* scroll = properties->Scroll();
EXPECT_EQ(TransformationMatrix().Translate(-15, 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());
// The content is placed at (-290, 10) so that its right edge aligns with the
// right edge of the container's client box, with the initial
// ScrollTranslation applied.
EXPECT_EQ(IntRect(-290, 10, 400, 400), scroll->ContentsRect());
EXPECT_EQ(FrameContentClip(), 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 style='width: 400px; height: 400px'></div>
</div>
)HTML");
const auto* properties = PaintPropertiesForElement("scroller");
const auto* overflow_clip = properties->OverflowClip();
const auto* scroll_translation = properties->ScrollTranslation();
const auto* scroll = properties->Scroll();
EXPECT_EQ(TransformationMatrix(), 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(IntRect(-290, 10, 400, 400), scroll->ContentsRect());
EXPECT_EQ(FrameContentClip(), overflow_clip->Parent());
EXPECT_EQ(properties->PaintOffsetTranslation(),
overflow_clip->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(25, 10, 85, 85), overflow_clip->ClipRect());
}
TEST_P(PaintPropertyTreeBuilderTest, FrameScrollingTraditional) {
SetBodyInnerHTML("<style> body { height: 10000px; } </style>");
GetDocument().domWindow()->scrollTo(0, 100);
LocalFrameView* frame_view = GetDocument().View();
frame_view->UpdateAllLifecyclePhases();
EXPECT_EQ(TransformationMatrix(), FramePreTranslation()->Matrix());
EXPECT_TRUE(FramePreTranslation()->Parent()->IsRoot());
EXPECT_EQ(TransformationMatrix().Translate(0, -100),
FrameScrollTranslation()->Matrix());
EXPECT_EQ(FramePreTranslation(), FrameScrollTranslation()->Parent());
EXPECT_EQ(FramePreTranslation(), FrameContentClip()->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), FrameContentClip()->ClipRect());
EXPECT_TRUE(FrameContentClip()->Parent()->IsRoot());
if (!RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
// No scroll properties should be present.
EXPECT_EQ(nullptr,
frame_view->GetLayoutView()->FirstFragment().PaintProperties());
}
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());
EXPECT_EQ(FramePreTranslation(),
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(HTMLNames::styleAttr, "perspective: 200px");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(TransformationMatrix().ApplyPerspective(200),
perspective_properties->Perspective()->Matrix());
EXPECT_EQ(FloatPoint3D(250, 250, 0),
perspective_properties->Perspective()->Origin());
EXPECT_EQ(FramePreTranslation(),
perspective_properties->Perspective()->Parent());
perspective->setAttribute(HTMLNames::styleAttr, "perspective-origin: 5% 20%");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(TransformationMatrix().ApplyPerspective(100),
perspective_properties->Perspective()->Matrix());
EXPECT_EQ(FloatPoint3D(70, 160, 0),
perspective_properties->Perspective()->Origin());
EXPECT_EQ(FramePreTranslation(),
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(FrameScrollTranslation(),
transform_properties->PaintOffsetTranslation()->Parent());
EXPECT_TRUE(transform_properties->Transform()->HasDirectCompositingReasons());
EXPECT_FALSE(FrameScrollTranslation()->HasDirectCompositingReasons());
CHECK_EXACT_VISUAL_RECT(LayoutRect(173, 556, 400, 300),
transform->GetLayoutObject(),
GetDocument().View()->GetLayoutView());
transform->setAttribute(
HTMLNames::styleAttr,
"margin-left: 50px; margin-top: 100px; width: 400px; height: 300px;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr,
transform->GetLayoutObject()->FirstFragment().PaintProperties());
transform->setAttribute(
HTMLNames::styleAttr,
"margin-left: 50px; margin-top: 100px; width: 400px; height: 300px; "
"transform: translate3d(123px, 456px, 789px)");
GetDocument().View()->UpdateAllLifecyclePhases();
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());
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());
EXPECT_TRUE(
perspective_properties->Transform()->HasDirectCompositingReasons());
}
TEST_P(PaintPropertyTreeBuilderTest,
TransformNodeWithActiveAnimationHasDirectCompositingReason) {
LoadTestData("transform-animation.html");
EXPECT_TRUE(PaintPropertiesForElement("target")
->Transform()
->HasDirectCompositingReasons());
}
TEST_P(PaintPropertyTreeBuilderTest,
OpacityAnimationDoesNotCreateTransformNode) {
LoadTestData("opacity-animation.html");
EXPECT_EQ(nullptr, PaintPropertiesForElement("target")->Transform());
}
TEST_P(PaintPropertyTreeBuilderTest,
EffectNodeWithActiveAnimationHasDirectCompositingReason) {
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 {
// SPv175 creates PaintOffsetTranslation for composited layers.
EXPECT_EQ(TransformationMatrix().Translate(50, 100),
transform_properties->PaintOffsetTranslation()->Matrix());
}
EXPECT_TRUE(transform_properties->Transform()->HasDirectCompositingReasons());
CHECK_EXACT_VISUAL_RECT(LayoutRect(50, 100, 400, 300),
transform->GetLayoutObject(),
GetDocument().View()->GetLayoutView());
transform->setAttribute(
HTMLNames::styleAttr,
"margin-left: 50px; margin-top: 100px; width: 400px; height: 300px;");
GetDocument().View()->UpdateAllLifecyclePhases();
EXPECT_EQ(nullptr,
transform->GetLayoutObject()->FirstFragment().PaintProperties());
transform->setAttribute(
HTMLNames::styleAttr,
"margin-left: 50px; margin-top: 100px; width: 400px; height: 300px; "
"will-change: transform");
GetDocument().View()->UpdateAllLifecyclePhases();
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());
EXPECT_EQ(FramePreTranslation(),
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(nullptr, 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(nullptr,
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(nullptr, 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(nullptr,
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");
LayoutObject* group_with_opacity =
GetLayoutObjectByElementId("groupWithOpacity");
const ObjectPaintProperties* group_with_opacity_properties =
group_with_opacity->FirstFragment().PaintProperties();
EXPECT_EQ(0.6f, group_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, group_with_opacity_properties->Effect()->OutputClip());
EXPECT_NE(nullptr, group_with_opacity_properties->Effect()->Parent());
LayoutObject& rect_without_opacity =
*GetLayoutObjectByElementId("rectWithoutOpacity");
const auto* rect_without_opacity_properties =
rect_without_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(nullptr, rect_without_opacity_properties);
LayoutObject& rect_with_opacity =
*GetLayoutObjectByElementId("rectWithOpacity");
const ObjectPaintProperties* rect_with_opacity_properties =
rect_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.4f, rect_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, 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.
LayoutObject& text_with_opacity =
*GetLayoutObjectByElementId("textWithOpacity");
const ObjectPaintProperties* text_with_opacity_properties =
text_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.2f, text_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, 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.
LayoutObject& tspan_with_opacity =
*GetLayoutObjectByElementId("tspanWithOpacity");
const ObjectPaintProperties* tspan_with_opacity_properties =
tspan_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.1f, tspan_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, 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");
LayoutObject& div_with_opacity =
*GetLayoutObjectByElementId("divWithOpacity");
const ObjectPaintProperties* div_with_opacity_properties =
div_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.2f, div_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, div_with_opacity_properties->Effect()->OutputClip());
EXPECT_NE(nullptr, div_with_opacity_properties->Effect()->Parent());
LayoutObject& svg_root_with_opacity =
*GetLayoutObjectByElementId("svgRootWithOpacity");
const ObjectPaintProperties* svg_root_with_opacity_properties =
svg_root_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.3f, svg_root_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, svg_root_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(div_with_opacity_properties->Effect(),
svg_root_with_opacity_properties->Effect()->Parent());
LayoutObject& rect_with_opacity =
*GetLayoutObjectByElementId("rectWithOpacity");
const ObjectPaintProperties* rect_with_opacity_properties =
rect_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.4f, rect_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, 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'>
<body>
<span id='spanWithOpacity' style='opacity: 0.5'/>
</body>
</foreignObject>
</svg>
)HTML");
LayoutObject& svg_root_with_opacity =
*GetLayoutObjectByElementId("svgRootWithOpacity");
const ObjectPaintProperties* svg_root_with_opacity_properties =
svg_root_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.3f, svg_root_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, svg_root_with_opacity_properties->Effect()->OutputClip());
EXPECT_NE(nullptr, svg_root_with_opacity_properties->Effect()->Parent());
LayoutObject& foreign_object_with_opacity =
*GetDocument()
.getElementById("foreignObjectWithOpacity")
->GetLayoutObject();
const ObjectPaintProperties* foreign_object_with_opacity_properties =
foreign_object_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.4f, foreign_object_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr,
foreign_object_with_opacity_properties->Effect()->OutputClip());
EXPECT_EQ(svg_root_with_opacity_properties->Effect(),
foreign_object_with_opacity_properties->Effect()->Parent());
LayoutObject& span_with_opacity =
*GetLayoutObjectByElementId("spanWithOpacity");
const ObjectPaintProperties* span_with_opacity_properties =
span_with_opacity.FirstFragment().PaintProperties();
EXPECT_EQ(0.5f, span_with_opacity_properties->Effect()->Opacity());
EXPECT_EQ(nullptr, 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());
EXPECT_EQ(
FramePreTranslation(),
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->SvgLocalToBorderBoxTransform()->Matrix());
EXPECT_EQ(
svg_with_view_box_properties->SvgLocalToBorderBoxTransform()->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->SvgLocalToBorderBoxTransform(),
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->SvgLocalToBorderBoxTransform());
EXPECT_EQ(FramePreTranslation(),
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->SvgLocalToBorderBoxTransform()->Matrix());
EXPECT_EQ(svg_properties->PaintOffsetTranslation(),
svg_properties->Transform()->Parent());
EXPECT_EQ(svg_properties->Transform(),
svg_properties->SvgLocalToBorderBoxTransform()->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->SvgLocalToBorderBoxTransform(),
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->SvgLocalToBorderBoxTransform()->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->SvgLocalToBorderBoxTransform());
EXPECT_EQ(svg_properties->SvgLocalToBorderBoxTransform(),
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, 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,
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();
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!FrameScrollTranslation());
EXPECT_EQ(FramePreTranslation(),
button_properties->OverflowClip()->LocalTransformSpace());
EXPECT_EQ(FloatRoundedRect(5, 5, 335, 113),
button_properties->OverflowClip()->ClipRect());
EXPECT_EQ(FrameContentClip(), button_properties->OverflowClip()->Parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 345, 123), &button,
GetDocument().View()->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, ControlClipInsideForeignObject) {
SetBodyInnerHTML(R"HTML(
<div style='column-count:2;'>
<div style='columns: 2'>
<svg style='width: 500px; height: 500px;'>
<foreignObject>
<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();
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!FrameScrollTranslation());
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();
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!FrameScrollTranslation());
EXPECT_EQ(FramePreTranslation(),
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();
EXPECT_EQ(FramePreTranslation(), 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(FrameContentClip(), 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 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();
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
// 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()->Parent();
EXPECT_EQ(FloatSize(), paint_offset_translation->Matrix().To2DTranslation());
EXPECT_EQ(TransformationMatrix().Translate3d(7, 7, 0),
iframe_pre_translation->Matrix());
EXPECT_EQ(div_with_transform_properties->Transform(),
iframe_pre_translation->Parent());
}
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();
// 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();
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(FrameContentClip(),
child.FirstFragment().LocalBorderBoxProperties()->Clip());
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
EXPECT_EQ(FrameScrollTranslation(),
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());
EXPECT_EQ(FramePreTranslation(),
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(FrameContentClip(), clip_properties->CssClip()->Parent());
EXPECT_EQ(FramePreTranslation(),
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(FramePreTranslation(),
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(FrameContentClip(), clip_properties->CssClip()->Parent());
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!FrameScrollTranslation());
EXPECT_EQ(FramePreTranslation(),
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());
EXPECT_EQ(FramePreTranslation(),
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, 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(FrameContentClip(), overflow_properties->OverflowClip()->Parent());
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!FrameScrollTranslation());
EXPECT_EQ(FramePreTranslation(),
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(FrameContentClip(),
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(FramePreTranslation(),
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());
// c's painted should start at subpixelAccumulation + (0.1,0.1) = (0.4,0.4).
LayoutObject* c = GetLayoutObjectByElementId("c");
LayoutPoint c_paint_offset =
subpixel_accumulation + LayoutPoint(FloatPoint(0.1, 0.1));
EXPECT_EQ(c_paint_offset, c->FirstFragment().PaintOffset());
// Visual rects via the non-paint properties system use enclosingIntRect
// before applying transforms, because they are computed bottom-up and
// therefore can't apply pixel snapping. Therefore apply a slop of 1px.
CHECK_VISUAL_RECT(LayoutRect(FloatRect(0.4, 0.4, 40, 40)), c,
frame_view->GetLayoutView(), 1);
}
TEST_P(PaintPropertyTreeBuilderTest,
PaintOffsetWithPixelSnappingThroughTransform) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
div { position: relative; }
#a {
width: 70px;
height: 70px;
left: 0.7px;
top: 0.7px;
}
#b {
width: 40px;
height: 40px;
transform: translateZ(0);
}
#c {
width: 40px;
height: 40px;
left: 0.7px;
top: 0.7px;
}
</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.7,0.7) to (1,1).
EXPECT_EQ(TransformationMatrix().Translate(1, 1),
b_properties->Transform()->Parent()->Matrix());
// The residual subpixel adjustment should be (0.7,0.7) - (1,1) = (-0.3,-0.3).
LayoutPoint subpixel_accumulation =
LayoutPoint(LayoutPoint(FloatPoint(0.7, 0.7)) - LayoutPoint(1, 1));
EXPECT_EQ(subpixel_accumulation, b->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0.7),
LayoutUnit(40), LayoutUnit(40)),
b, frame_view->GetLayoutView());
// c's painting should start at subpixelAccumulation + (0.7,0.7) = (0.4,0.4).
LayoutObject* c = GetLayoutObjectByElementId("c");
LayoutPoint c_paint_offset =
subpixel_accumulation + LayoutPoint(FloatPoint(0.7, 0.7));
EXPECT_EQ(c_paint_offset, c->FirstFragment().PaintOffset());
// Visual rects via the non-paint properties system use enclosingIntRect
// before applying transforms, because they are computed bottom-up and
// therefore can't apply pixel snapping. Therefore apply a slop of 1px.
CHECK_VISUAL_RECT(LayoutRect(LayoutUnit(0.7) + LayoutUnit(0.7),
LayoutUnit(0.7) + LayoutUnit(0.7),
LayoutUnit(40), LayoutUnit(40)),
c, frame_view->GetLayoutView(), 1);
}
TEST_P(PaintPropertyTreeBuilderTest,
NonTranslationTransformShouldResetSubpixelPaintOffset) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
div { position: relative; }
#a {
width: 70px;
height: 70px;
left: 0.9px;
top: 0.9px;
}
#b {
width: 40px;
height: 40px;
transform: scale(10);
transform-origin: 0 0;
}
#c {
width: 40px;
height: 40px;
left: 0.6px;
top: 0.6px;
}
</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().Scale(10),
b_properties->Transform()->Matrix());
// The paint offset transform should not be snapped.
EXPECT_EQ(TransformationMatrix().Translate(1, 1),
b_properties->Transform()->Parent()->Matrix());
EXPECT_EQ(LayoutPoint(), b->FirstFragment().PaintOffset());
// Visual rects via the non-paint properties system use enclosingIntRect
// before applying transforms, because they are computed bottom-up and
// therefore can't apply pixel snapping. Therefore apply a slop of 1px.
CHECK_VISUAL_RECT(LayoutRect(LayoutUnit(1), LayoutUnit(1), LayoutUnit(400),
LayoutUnit(400)),
b, frame_view->GetLayoutView(), 1);
// c's painting should start at c_offset.
LayoutObject* c = GetLayoutObjectByElementId("c");
LayoutUnit c_offset = LayoutUnit(0.6);
EXPECT_EQ(LayoutPoint(c_offset, c_offset), c->FirstFragment().PaintOffset());
// Visual rects via the non-paint properties system use enclosingIntRect
// before applying transforms, because they are computed bottom-up and
// therefore can't apply pixel snapping. Therefore apply a slop of 1px
// in the transformed space (c_offset * 10 in view space) and 1px in the
// view space.
CHECK_VISUAL_RECT(LayoutRect(c_offset * 10 + 1, c_offset * 10 + 1,
LayoutUnit(400), LayoutUnit(400)),
c, frame_view->GetLayoutView(), c_offset * 10 + 1);
}
TEST_P(PaintPropertyTreeBuilderTest,
PaintOffsetWithPixelSnappingThroughMultipleTransforms) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
div { position: relative; }
#a {
width: 70px;
height: 70px;
left: 0.7px;
top: 0.7px;
}
#b {
width: 40px;
height: 40px;
transform: translate3d(5px, 7px, 0);
}
#c {
width: 40px;
height: 40px;
transform: translate3d(11px, 13px, 0);
}
#d {
width: 40px;
height: 40px;
left: 0.7px;
top: 0.7px;
}
</style>
<div id='a'>
<div id='b'>
<div id='c'>
<div id='d'></div>
</div>
</div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* b = GetLayoutObjectByElementId("b");
const ObjectPaintProperties* b_properties =
b->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(5, 7, 0),
b_properties->Transform()->Matrix());
// The paint offset transform should be snapped from (0.7,0.7) to (1,1).
EXPECT_EQ(TransformationMatrix().Translate(1, 1),
b_properties->Transform()->Parent()->Matrix());
// The residual subpixel adjustment should be (0.7,0.7) - (1,1) = (-0.3,-0.3).
LayoutPoint subpixel_accumulation =
LayoutPoint(LayoutPoint(FloatPoint(0.7, 0.7)) - LayoutPoint(1, 1));
EXPECT_EQ(subpixel_accumulation, b->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(5.7), LayoutUnit(7.7),
LayoutUnit(40), LayoutUnit(40)),
b, frame_view->GetLayoutView());
LayoutObject* c = GetLayoutObjectByElementId("c");
const ObjectPaintProperties* c_properties =
c->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate3d(11, 13, 0),
c_properties->Transform()->Matrix());
// The paint offset should be (-0.3,-0.3) but the paint offset transform
// should still be at (0,0) because it should be snapped.
EXPECT_EQ(TransformationMatrix().Translate(0, 0),
c_properties->Transform()->Parent()->Matrix());
// The residual subpixel adjustment should still be (-0.3,-0.3).
EXPECT_EQ(subpixel_accumulation, c->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(16.7), LayoutUnit(20.7),
LayoutUnit(40), LayoutUnit(40)),
c, frame_view->GetLayoutView());
// d should be painted starting at subpixelAccumulation + (0.7,0.7) =
// (0.4,0.4).
LayoutObject* d = GetLayoutObjectByElementId("d");
LayoutPoint d_paint_offset =
subpixel_accumulation + LayoutPoint(FloatPoint(0.7, 0.7));
EXPECT_EQ(d_paint_offset, d->FirstFragment().PaintOffset());
// Visual rects via the non-paint properties system use enclosingIntRect
// before applying transforms, because they are computed bottom-up and
// therefore can't apply pixel snapping. Therefore apply a slop of 1px.
CHECK_VISUAL_RECT(LayoutRect(LayoutUnit(16.7) + LayoutUnit(0.7),
LayoutUnit(20.7) + LayoutUnit(0.7),
LayoutUnit(40), LayoutUnit(40)),
d, frame_view->GetLayoutView(), 1);
}
TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetWithPixelSnappingWithFixedPos) {
SetBodyInnerHTML(R"HTML(
<style>
* { margin: 0; }
#a {
width: 70px;
height: 70px;
left: 0.7px;
position: relative;
}
#b {
width: 40px;
height: 40px;
transform: translateZ(0);
position: relative;
}
#fixed {
width: 40px;
height: 40px;
position: fixed;
}
#d {
width: 40px;
height: 40px;
left: 0.7px;
position: relative;
}
</style>
<div id='a'>
<div id='b'>
<div id='fixed'>
<div id='d'></div>
</div>
</div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* b = GetLayoutObjectByElementId("b");
const ObjectPaintProperties* b_properties =
b->FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(0, 0),
b_properties->Transform()->Matrix());
// The paint offset transform should be snapped from (0.7,0) to (1,0).
EXPECT_EQ(TransformationMatrix().Translate(1, 0),
b_properties->Transform()->Parent()->Matrix());
// The residual subpixel adjustment should be (0.7,0) - (1,0) = (-0.3,0).
LayoutPoint subpixel_accumulation =
LayoutPoint(LayoutPoint(FloatPoint(0.7, 0)) - LayoutPoint(1, 0));
EXPECT_EQ(subpixel_accumulation, b->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0),
LayoutUnit(40), LayoutUnit(40)),
b, frame_view->GetLayoutView());
LayoutObject* fixed = GetLayoutObjectByElementId("fixed");
// The residual subpixel adjustment should still be (-0.3,0).
EXPECT_EQ(subpixel_accumulation, fixed->FirstFragment().PaintOffset());
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0),
LayoutUnit(40), LayoutUnit(40)),
fixed, frame_view->GetLayoutView());
// d should be painted starting at subpixelAccumulation + (0.7,0) = (0.4,0).
LayoutObject* d = GetLayoutObjectByElementId("d");
LayoutPoint d_paint_offset =
subpixel_accumulation + LayoutPoint(FloatPoint(0.7, 0));
EXPECT_EQ(d_paint_offset, d->FirstFragment().PaintOffset());
// Visual rects via the non-paint properties system use enclosingIntRect
// before applying transforms, because they are computed bottom-up and
// therefore can't apply pixel snapping. Therefore apply a slop of 1px.
CHECK_VISUAL_RECT(LayoutRect(LayoutUnit(0.7) + LayoutUnit(0.7), LayoutUnit(),
LayoutUnit(40), LayoutUnit(40)),
d, frame_view->GetLayoutView(), 1);
}
TEST_P(PaintPropertyTreeBuilderTest, SvgPixelSnappingShouldResetPaintOffset) {
SetBodyInnerHTML(R"HTML(
<style>
#svg {
position: relative;
left: 0.1px;
transform: matrix(1, 0, 0, 1, 0, 0);
}
</style>
<svg id='svg'>
<rect id='rect' transform='translate(1, 1)'/>
</svg>
)HTML");
LayoutObject& svg_with_transform = *GetLayoutObjectByElementId("svg");
const ObjectPaintProperties* svg_with_transform_properties =
svg_with_transform.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix(),
svg_with_transform_properties->Transform()->Matrix());
EXPECT_EQ(LayoutPoint(FloatPoint(0.1, 0)),
svg_with_transform.FirstFragment().PaintOffset());
EXPECT_TRUE(svg_with_transform_properties->SvgLocalToBorderBoxTransform() ==
nullptr);
LayoutObject& rect_with_transform = *GetLayoutObjectByElementId("rect");
const ObjectPaintProperties* rect_with_transform_properties =
rect_with_transform.FirstFragment().PaintProperties();
EXPECT_EQ(TransformationMatrix().Translate(1, 1),
rect_with_transform_properties->Transform()->Matrix());
// Ensure there is no PaintOffset transform between the rect and the svg's
// transform.
EXPECT_EQ(svg_with_transform_properties->Transform(),
rect_with_transform_properties->Transform()->Parent());
}
TEST_P(PaintPropertyTreeBuilderTest, SvgRootAndForeignObjectPixelSnapping) {
SetBodyInnerHTML(R"HTML(
<svg id=svg style='position: relative; left: 0.6px; top: 0.3px'>
<foreignObject id=foreign x='3.5' y='5.4' transform='translate(1, 1)'>
<div id=div style='position: absolute; left: 5.6px; top: 7.3px'>
</div>
</foreignObject>
</svg>
)HTML");
const auto* svg = GetLayoutObjectByElementId("svg");
const auto* svg_properties = svg->FirstFragment().PaintProperties();
// The paint offset of (8.6, 8.3) is rounded off here. The fractional part
// remains PaintOffset.
EXPECT_EQ(
FloatSize(9, 8),
svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation());
EXPECT_EQ(LayoutPoint(LayoutUnit(-0.40625), LayoutUnit(0.3)),
svg->FirstFragment().PaintOffset());
EXPECT_EQ(nullptr, svg_properties->SvgLocalToBorderBoxTransform());
const auto* foreign_object = GetLayoutObjectByElementId("foreign");
const auto* foreign_object_properties =
foreign_object->FirstFragment().PaintProperties();
EXPECT_EQ(nullptr, foreign_object_properties->PaintOffsetTranslation());
// Paint offset of foreignObject should be originated from SVG root and
// snapped to pixels.
EXPECT_EQ(LayoutPoint(4, 5), foreign_object->FirstFragment().PaintOffset());
const auto* div = GetLayoutObjectByElementId("div");
// Paint offset of descendant of foreignObject accumulates on paint offset of
// foreignObject.
EXPECT_EQ(LayoutPoint(LayoutUnit(4 + 5.6), LayoutUnit(5 + 7.3)),
div->FirstFragment().PaintOffset());
}
TEST_P(PaintPropertyTreeBuilderTest, NoRenderingContextByDefault) {
SetBodyInnerHTML("<div style='transform: translateZ(0)'></div>");
const ObjectPaintProperties* properties = GetDocument()
.body()
->firstChild()
->GetLayoutObject()
->FirstFragment()
.PaintProperties();
ASSERT_TRUE(properties->Transform());
EXPECT_FALSE(properties->Transform()->HasRenderingContext());
}
TEST_P(PaintPropertyTreeBuilderTest, Preserve3DCreatesSharedRenderingContext) {
SetBodyInnerHTML(R"HTML(
<div style='transform-style: preserve-3d'>
<div id='a' style='transform: translateZ(0); width: 30px; height: 40px'>
</div>
<div id='b' style='transform: translateZ(0); width: 20px; height: 10px'>
</div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* a = GetLayoutObjectByElementId("a");
const ObjectPaintProperties* a_properties =
a->FirstFragment().PaintProperties();
LayoutObject* b = GetLayoutObjectByElementId("b");
const ObjectPaintProperties* b_properties =
b->FirstFragment().PaintProperties();
ASSERT_TRUE(a_properties->Transform() && b_properties->Transform());
EXPECT_NE(a_properties->Transform(), b_properties->Transform());
EXPECT_TRUE(a_properties->Transform()->HasRenderingContext());
EXPECT_TRUE(b_properties->Transform()->HasRenderingContext());
EXPECT_EQ(a_properties->Transform()->RenderingContextId(),
b_properties->Transform()->RenderingContextId());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a,
frame_view->GetLayoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 48, 20, 10), b,
frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, FlatTransformStyleEndsRenderingContext) {
SetBodyInnerHTML(R"HTML(
<style>
#a {
transform: translateZ(0);
width: 30px;
height: 40px;
}
#b {
transform: translateZ(0);
width: 10px;
height: 20px;
}
</style>
<div style='transform-style: preserve-3d'>
<div id='a'>
<div id='b'></div>
</div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* a = GetLayoutObjectByElementId("a");
const ObjectPaintProperties* a_properties =
a->FirstFragment().PaintProperties();
LayoutObject* b = GetLayoutObjectByElementId("b");
const ObjectPaintProperties* b_properties =
b->FirstFragment().PaintProperties();
ASSERT_FALSE(a->StyleRef().Preserves3D());
ASSERT_TRUE(a_properties->Transform() && b_properties->Transform());
// #a should participate in a rendering context (due to its parent), but its
// child #b should not.
EXPECT_TRUE(a_properties->Transform()->HasRenderingContext());
EXPECT_FALSE(b_properties->Transform()->HasRenderingContext());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a,
frame_view->GetLayoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 20), b,
frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, NestedRenderingContexts) {
SetBodyInnerHTML(R"HTML(
<div style='transform-style: preserve-3d'>
<div id='a' style='transform: translateZ(0); width: 50px; height: 60px'>
<div style='transform-style: preserve-3d; width: 30px; height: 40px'>
<div id='b'
style='transform: translateZ(0); width: 10px; height: 20px'>
</div>
</div>
</div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* a = GetLayoutObjectByElementId("a");
const ObjectPaintProperties* a_properties =
a->FirstFragment().PaintProperties();
LayoutObject* b = GetLayoutObjectByElementId("b");
const ObjectPaintProperties* b_properties =
b->FirstFragment().PaintProperties();
ASSERT_FALSE(a->StyleRef().Preserves3D());
ASSERT_TRUE(a_properties->Transform() && b_properties->Transform());
// #a should participate in a rendering context (due to its parent). Its
// child does preserve 3D, but since #a does not, #a's rendering context is
// not passed on to its children. Thus #b ends up in a separate rendering
// context rooted at its parent.
EXPECT_TRUE(a_properties->Transform()->HasRenderingContext());
EXPECT_TRUE(b_properties->Transform()->HasRenderingContext());
EXPECT_NE(a_properties->Transform()->RenderingContextId(),
b_properties->Transform()->RenderingContextId());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), a,
frame_view->GetLayoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 20), b,
frame_view->GetLayoutView());
}
// Returns true if the first node has the second as an ancestor.
static bool NodeHasAncestor(const TransformPaintPropertyNode* node,
const TransformPaintPropertyNode* ancestor) {
while (node) {
if (node == ancestor)
return true;
node = node->Parent();
}
return false;
}
// Returns true if some node will flatten the transform due to |node| before it
// is inherited by |node| (including if node->flattensInheritedTransform()).
static bool SomeNodeFlattensTransform(
const TransformPaintPropertyNode* node,
const TransformPaintPropertyNode* ancestor) {
while (node != ancestor) {
if (node->FlattensInheritedTransform())
return true;
node = node->Parent();
}
return false;
}
TEST_P(PaintPropertyTreeBuilderTest, FlatTransformStylePropagatesToChildren) {
SetBodyInnerHTML(R"HTML(
<style>
#a {
transform: translateZ(0);
transform-style: flat;
width: 30px;
height: 40px;
}
#b {
transform: translateZ(0);
width: 10px;
height: 10px;
}
</style>
<div id='a'>
<div id='b'></div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();
LayoutObject* a = GetLayoutObjectByElementId("a");
LayoutObject* b = GetLayoutObjectByElementId("b");
const auto* a_transform = a->FirstFragment().PaintProperties()->Transform();
ASSERT_TRUE(a_transform);
const auto* b_transform = b->FirstFragment().PaintProperties()->Transform();
ASSERT_TRUE(b_transform);
ASSERT_TRUE(NodeHasAncestor(b_transform, a_transform));
// Some node must flatten the inherited transform from #a before it reaches
// #b's transform.
EXPECT_TRUE(SomeNodeFlattensTransform(b_transform, a_transform));
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a,
frame_view->GetLayoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 10), b,
frame_view->GetLayoutView());
}
TEST_P(PaintPropertyTreeBuilderTest,
Preserve3DTransformStylePropagatesToChildren) {
SetBodyInnerHTML(R"HTML(
<style>
#a {
transform: translateZ(0);
transform-style: preserve-3d;
width: 30px;
height: 40px;
}
#b {
transform: translateZ(0);
width: 10px;
height: 10px;
}
</style>
<div id='a'>
<div id='b'></div>
</div>
)HTML");
LocalFrameView* frame_view = GetDocument().View();