blob: 0dc03eaaaec6b714ec5b493ef279003f20448697 [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/layout/LayoutTestHelper.h"
#include "core/layout/LayoutTreeAsText.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/paint/ObjectPaintProperties.h"
#include "core/paint/PaintPropertyTreePrinter.h"
#include "platform/graphics/paint/GeometryMapper.h"
#include "platform/graphics/paint/ScrollPaintPropertyNode.h"
#include "platform/graphics/paint/TransformPaintPropertyNode.h"
#include "platform/testing/RuntimeEnabledFeaturesTestHelpers.h"
#include "platform/testing/UnitTestHelpers.h"
#include "platform/text/TextStream.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "wtf/HashMap.h"
#include "wtf/Vector.h"
namespace blink {
typedef bool TestParamRootLayerScrolling;
class PaintPropertyTreeBuilderTest
: public ::testing::WithParamInterface<TestParamRootLayerScrolling>,
private ScopedSlimmingPaintV2ForTest,
private ScopedRootLayerScrollingForTest,
public RenderingTest {
public:
PaintPropertyTreeBuilderTest()
: ScopedSlimmingPaintV2ForTest(true),
ScopedRootLayerScrollingForTest(GetParam()),
RenderingTest(SingleChildFrameLoaderClient::create()) {}
void loadTestData(const char* fileName) {
String fullPath = testing::blinkRootDir();
fullPath.append("/Source/core/paint/test_data/");
fullPath.append(fileName);
RefPtr<SharedBuffer> inputBuffer = testing::readFromFile(fullPath);
setBodyInnerHTML(String(inputBuffer->data(), inputBuffer->size()));
}
const TransformPaintPropertyNode* framePreTranslation() {
FrameView* frameView = document().view();
if (RuntimeEnabledFeatures::rootLayerScrollingEnabled())
return frameView->layoutView()
->objectPaintProperties()
->paintOffsetTranslation();
return frameView->preTranslation();
}
const TransformPaintPropertyNode* frameScrollTranslation() {
FrameView* frameView = document().view();
if (RuntimeEnabledFeatures::rootLayerScrollingEnabled())
return frameView->layoutView()
->objectPaintProperties()
->scrollTranslation();
return frameView->scrollTranslation();
}
const ClipPaintPropertyNode* frameContentClip() {
FrameView* frameView = document().view();
if (RuntimeEnabledFeatures::rootLayerScrollingEnabled())
return frameView->layoutView()->objectPaintProperties()->overflowClip();
return frameView->contentClip();
}
const ScrollPaintPropertyNode* frameScroll() {
FrameView* frameView = document().view();
if (RuntimeEnabledFeatures::rootLayerScrollingEnabled())
return frameView->layoutView()->objectPaintProperties()->scroll();
return frameView->scroll();
}
private:
void SetUp() override {
Settings::setMockScrollbarsEnabled(true);
RenderingTest::SetUp();
enableCompositing();
}
void TearDown() override {
RenderingTest::TearDown();
Settings::setMockScrollbarsEnabled(false);
}
};
#define CHECK_VISUAL_RECT(expected, sourceLayoutObject, ancestorLayoutObject, \
slopFactor) \
do { \
GeometryMapper geometryMapper; \
LayoutRect source( \
(sourceLayoutObject)->localOverflowRectForPaintInvalidation()); \
source.moveBy((sourceLayoutObject) \
->objectPaintProperties() \
->localBorderBoxProperties() \
->paintOffset); \
bool success = false; \
auto contentsProperties = \
(ancestorLayoutObject)->objectPaintProperties()->contentsProperties(); \
LayoutRect actual = \
LayoutRect(geometryMapper.mapToVisualRectInDestinationSpace( \
FloatRect(source), (sourceLayoutObject) \
->objectPaintProperties() \
->localBorderBoxProperties() \
->propertyTreeState, \
contentsProperties.propertyTreeState, success)); \
ASSERT_TRUE(success); \
actual.moveBy(-contentsProperties.paintOffset); \
EXPECT_EQ(expected, actual) \
<< "GeometryMapper: expected: " << expected.toString() \
<< ", actual: " << actual.toString(); \
\
if (slopFactor == LayoutUnit::max()) \
break; \
LayoutRect slowPathRect = \
(sourceLayoutObject)->localOverflowRectForPaintInvalidation(); \
(sourceLayoutObject) \
->mapToVisualRectInAncestorSpace(ancestorLayoutObject, slowPathRect); \
if (slopFactor) { \
LayoutRect inflatedActual = LayoutRect(actual); \
inflatedActual.inflate(slopFactor); \
SCOPED_TRACE( \
String::format("Old path rect: %s, Actual: %s, Inflated actual: %s", \
slowPathRect.toString().ascii().data(), \
actual.toString().ascii().data(), \
inflatedActual.toString().ascii().data())); \
EXPECT_TRUE(slowPathRect.contains(LayoutRect(actual))); \
EXPECT_TRUE(inflatedActual.contains(slowPathRect)); \
} else { \
EXPECT_EQ(expected, slowPathRect) \
<< "Slow path: expected: " << slowPathRect.toString() \
<< ", actual: " << actual.toString().ascii().data(); \
} \
} while (0)
#define CHECK_EXACT_VISUAL_RECT(expected, sourceLayoutObject, \
ancestorLayoutObject) \
CHECK_VISUAL_RECT(expected, sourceLayoutObject, ancestorLayoutObject, 0)
INSTANTIATE_TEST_CASE_P(All, PaintPropertyTreeBuilderTest, ::testing::Bool());
TEST_P(PaintPropertyTreeBuilderTest, FixedPosition) {
loadTestData("fixed-position.html");
Element* positionedScroll = document().getElementById("positionedScroll");
positionedScroll->setScrollTop(3);
Element* transformedScroll = document().getElementById("transformedScroll");
transformedScroll->setScrollTop(5);
FrameView* frameView = document().view();
frameView->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 = document().getElementById("target1");
const ObjectPaintProperties* target1Properties =
target1->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(200, 150),
target1Properties->paintOffsetTranslation()->matrix());
EXPECT_EQ(framePreTranslation(),
target1Properties->paintOffsetTranslation()->parent());
EXPECT_EQ(target1Properties->paintOffsetTranslation(),
target1Properties->overflowClip()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(0, 0, 100, 100),
target1Properties->overflowClip()->clipRect());
// Likewise, it inherits clip from the viewport, skipping overflow clip of the
// scroller.
EXPECT_EQ(frameContentClip(), target1Properties->overflowClip()->parent());
// target1 should not have it's own scroll node and instead should inherit
// positionedScroll's.
const ObjectPaintProperties* positionedScrollProperties =
positionedScroll->layoutObject()->objectPaintProperties();
EXPECT_TRUE(positionedScrollProperties->scroll()->parent()->isRoot());
EXPECT_EQ(TransformationMatrix().translate(0, -3),
positionedScrollProperties->scroll()
->scrollOffsetTranslation()
->matrix());
EXPECT_EQ(nullptr, target1Properties->scroll());
CHECK_EXACT_VISUAL_RECT(LayoutRect(200, 150, 100, 100),
target1->layoutObject(), frameView->layoutView());
// 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 = document().getElementById("target2");
const ObjectPaintProperties* target2Properties =
target2->layoutObject()->objectPaintProperties();
Element* scroller = document().getElementById("transformedScroll");
const ObjectPaintProperties* scrollerProperties =
scroller->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(200, 150),
target2Properties->paintOffsetTranslation()->matrix());
EXPECT_EQ(scrollerProperties->scrollTranslation(),
target2Properties->paintOffsetTranslation()->parent());
EXPECT_EQ(target2Properties->paintOffsetTranslation(),
target2Properties->overflowClip()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(0, 0, 100, 100),
target2Properties->overflowClip()->clipRect());
EXPECT_EQ(scrollerProperties->overflowClip(),
target2Properties->overflowClip()->parent());
// target2 should not have it's own scroll node and instead should inherit
// transformedScroll's.
const ObjectPaintProperties* transformedScrollProperties =
transformedScroll->layoutObject()->objectPaintProperties();
EXPECT_TRUE(transformedScrollProperties->scroll()->parent()->isRoot());
EXPECT_EQ(TransformationMatrix().translate(0, -5),
transformedScrollProperties->scroll()
->scrollOffsetTranslation()
->matrix());
EXPECT_EQ(nullptr, target2Properties->scroll());
CHECK_EXACT_VISUAL_RECT(LayoutRect(208, 153, 200, 100),
target2->layoutObject(), frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, PositionAndScroll) {
loadTestData("position-and-scroll.html");
Element* scroller = document().getElementById("scroller");
scroller->scrollTo(0, 100);
FrameView* frameView = document().view();
frameView->updateAllLifecyclePhases();
const ObjectPaintProperties* scrollerProperties =
scroller->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(0, -100),
scrollerProperties->scrollTranslation()->matrix());
EXPECT_EQ(frameScrollTranslation(),
scrollerProperties->scrollTranslation()->parent());
EXPECT_EQ(frameScrollTranslation(),
scrollerProperties->overflowClip()->localTransformSpace());
EXPECT_EQ(frameScroll(), scrollerProperties->scroll()->parent());
EXPECT_EQ(FloatSize(413, 317), scrollerProperties->scroll()->clip());
EXPECT_EQ(FloatSize(660, 10200), scrollerProperties->scroll()->bounds());
EXPECT_FALSE(scrollerProperties->scroll()->userScrollableHorizontal());
EXPECT_TRUE(scrollerProperties->scroll()->userScrollableVertical());
EXPECT_EQ(FloatRoundedRect(120, 340, 413, 317),
scrollerProperties->overflowClip()->clipRect());
EXPECT_EQ(frameContentClip(), scrollerProperties->overflowClip()->parent());
// http://crbug.com/638415
if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
CHECK_EXACT_VISUAL_RECT(LayoutRect(120, 340, 413, 317),
scroller->layoutObject(), frameView->layoutView());
}
// The relative-positioned element should have accumulated box offset (exclude
// scrolling), and should be affected by ancestor scroll transforms.
Element* relPos = document().getElementById("rel-pos");
const ObjectPaintProperties* relPosProperties =
relPos->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(680, 1120),
relPosProperties->paintOffsetTranslation()->matrix());
EXPECT_EQ(scrollerProperties->scrollTranslation(),
relPosProperties->paintOffsetTranslation()->parent());
EXPECT_EQ(relPosProperties->transform(),
relPosProperties->overflowClip()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(0, 0, 100, 200),
relPosProperties->overflowClip()->clipRect());
EXPECT_EQ(scrollerProperties->overflowClip(),
relPosProperties->overflowClip()->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(), relPos->layoutObject(),
frameView->layoutView());
// The absolute-positioned element should not be affected by non-positioned
// scroller at all.
Element* absPos = document().getElementById("abs-pos");
const ObjectPaintProperties* absPosProperties =
absPos->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(123, 456),
absPosProperties->paintOffsetTranslation()->matrix());
EXPECT_EQ(frameScrollTranslation(),
absPosProperties->paintOffsetTranslation()->parent());
EXPECT_EQ(absPosProperties->transform(),
absPosProperties->overflowClip()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(0, 0, 300, 400),
absPosProperties->overflowClip()->clipRect());
EXPECT_EQ(frameContentClip(), absPosProperties->overflowClip()->parent());
// http://crbug.com/638415
if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
CHECK_EXACT_VISUAL_RECT(LayoutRect(123, 456, 300, 400),
absPos->layoutObject(), frameView->layoutView());
}
}
TEST_P(PaintPropertyTreeBuilderTest, FrameScrollingTraditional) {
setBodyInnerHTML("<style> body { height: 10000px; } </style>");
document().domWindow()->scrollTo(0, 100);
FrameView* frameView = document().view();
frameView->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());
LayoutViewItem layoutViewItem = document().layoutViewItem();
const ObjectPaintProperties* layoutViewProperties =
layoutViewItem.objectPaintProperties();
// http://crbug.com/638415
if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
EXPECT_EQ(nullptr, layoutViewProperties->scrollTranslation());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 784, 10000),
document().body()->layoutObject(),
frameView->layoutView());
}
}
TEST_P(PaintPropertyTreeBuilderTest, Perspective) {
loadTestData("perspective.html");
Element* perspective = document().getElementById("perspective");
const ObjectPaintProperties* perspectiveProperties =
perspective->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().applyPerspective(100),
perspectiveProperties->perspective()->matrix());
// The perspective origin is the center of the border box plus accumulated
// paint offset.
EXPECT_EQ(FloatPoint3D(250, 250, 0),
perspectiveProperties->perspective()->origin());
EXPECT_EQ(framePreTranslation(),
perspectiveProperties->perspective()->parent());
// Adding perspective doesn't clear paint offset. The paint offset will be
// passed down to children.
Element* inner = document().getElementById("inner");
const ObjectPaintProperties* innerProperties =
inner->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(50, 100),
innerProperties->paintOffsetTranslation()->matrix());
EXPECT_EQ(perspectiveProperties->perspective(),
innerProperties->paintOffsetTranslation()->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(50, 100, 100, 200), inner->layoutObject(),
document().view()->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, Transform) {
loadTestData("transform.html");
Element* transform = document().getElementById("transform");
const ObjectPaintProperties* transformProperties =
transform->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(123, 456, 789),
transformProperties->transform()->matrix());
EXPECT_EQ(FloatPoint3D(200, 150, 0),
transformProperties->transform()->origin());
EXPECT_EQ(transformProperties->paintOffsetTranslation(),
transformProperties->transform()->parent());
EXPECT_EQ(TransformationMatrix().translate(50, 100),
transformProperties->paintOffsetTranslation()->matrix());
EXPECT_EQ(frameScrollTranslation(),
transformProperties->paintOffsetTranslation()->parent());
// http://crbug.com/638415
if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
CHECK_EXACT_VISUAL_RECT(LayoutRect(173, 556, 400, 300),
transform->layoutObject(),
document().view()->layoutView());
}
}
TEST_P(PaintPropertyTreeBuilderTest, RelativePositionInline) {
loadTestData("relative-position-inline.html");
Element* inlineBlock = document().getElementById("inline-block");
const ObjectPaintProperties* inlineBlockProperties =
inlineBlock->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(135, 490),
inlineBlockProperties->paintOffsetTranslation()->matrix());
EXPECT_EQ(framePreTranslation(),
inlineBlockProperties->paintOffsetTranslation()->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(135, 490, 10, 20),
inlineBlock->layoutObject(),
document().view()->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, NestedOpacityEffect) {
setBodyInnerHTML(
"<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>");
LayoutObject* nodeWithoutOpacity =
document().getElementById("nodeWithoutOpacity")->layoutObject();
const ObjectPaintProperties* nodeWithoutOpacityProperties =
nodeWithoutOpacity->objectPaintProperties();
EXPECT_NE(nullptr, nodeWithoutOpacityProperties);
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 100, 200), nodeWithoutOpacity,
document().view()->layoutView());
LayoutObject* childWithOpacity =
document().getElementById("childWithOpacity")->layoutObject();
const ObjectPaintProperties* childWithOpacityProperties =
childWithOpacity->objectPaintProperties();
EXPECT_EQ(0.5f, childWithOpacityProperties->effect()->opacity());
// childWithOpacity is the root effect node.
EXPECT_NE(nullptr, childWithOpacityProperties->effect()->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), childWithOpacity,
document().view()->layoutView());
LayoutObject* grandChildWithoutOpacity =
document().getElementById("grandChildWithoutOpacity")->layoutObject();
EXPECT_NE(nullptr, grandChildWithoutOpacity->objectPaintProperties());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 20, 30), grandChildWithoutOpacity,
document().view()->layoutView());
LayoutObject* greatGrandChildWithOpacity =
document().getElementById("greatGrandChildWithOpacity")->layoutObject();
const ObjectPaintProperties* greatGrandChildWithOpacityProperties =
greatGrandChildWithOpacity->objectPaintProperties();
EXPECT_EQ(0.2f, greatGrandChildWithOpacityProperties->effect()->opacity());
EXPECT_EQ(childWithOpacityProperties->effect(),
greatGrandChildWithOpacityProperties->effect()->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 15), greatGrandChildWithOpacity,
document().view()->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodeDoesNotAffectEffectNodes) {
setBodyInnerHTML(
"<div id='nodeWithOpacity' style='opacity: 0.6' style='width: 100px; "
"height: 200px'>"
" <div id='childWithTransform' style='transform: translate3d(10px, "
"10px, 0px); width: 50px; height: 60px;'>"
" <div id='grandChildWithOpacity' style='opacity: 0.4; width: 20px; "
"height: 30px'/>"
" </div>"
"</div>");
LayoutObject* nodeWithOpacity =
document().getElementById("nodeWithOpacity")->layoutObject();
const ObjectPaintProperties* nodeWithOpacityProperties =
nodeWithOpacity->objectPaintProperties();
EXPECT_EQ(0.6f, nodeWithOpacityProperties->effect()->opacity());
EXPECT_NE(nullptr, nodeWithOpacityProperties->effect()->parent());
EXPECT_EQ(nullptr, nodeWithOpacityProperties->transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 784, 60), nodeWithOpacity,
document().view()->layoutView());
LayoutObject* childWithTransform =
document().getElementById("childWithTransform")->layoutObject();
const ObjectPaintProperties* childWithTransformProperties =
childWithTransform->objectPaintProperties();
EXPECT_EQ(nullptr, childWithTransformProperties->effect());
EXPECT_EQ(TransformationMatrix().translate(10, 10),
childWithTransformProperties->transform()->matrix());
CHECK_EXACT_VISUAL_RECT(LayoutRect(18, 18, 50, 60), childWithTransform,
document().view()->layoutView());
LayoutObject* grandChildWithOpacity =
document().getElementById("grandChildWithOpacity")->layoutObject();
const ObjectPaintProperties* grandChildWithOpacityProperties =
grandChildWithOpacity->objectPaintProperties();
EXPECT_EQ(0.4f, grandChildWithOpacityProperties->effect()->opacity());
EXPECT_EQ(nodeWithOpacityProperties->effect(),
grandChildWithOpacityProperties->effect()->parent());
EXPECT_EQ(nullptr, grandChildWithOpacityProperties->transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(18, 18, 20, 30), grandChildWithOpacity,
document().view()->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossStackingContext) {
setBodyInnerHTML(
"<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>");
LayoutObject* nodeWithOpacity =
document().getElementById("nodeWithOpacity")->layoutObject();
const ObjectPaintProperties* nodeWithOpacityProperties =
nodeWithOpacity->objectPaintProperties();
EXPECT_EQ(0.6f, nodeWithOpacityProperties->effect()->opacity());
EXPECT_NE(nullptr, nodeWithOpacityProperties->effect()->parent());
EXPECT_EQ(nullptr, nodeWithOpacityProperties->transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 100, 200), nodeWithOpacity,
document().view()->layoutView());
LayoutObject* childWithStackingContext =
document().getElementById("childWithStackingContext")->layoutObject();
const ObjectPaintProperties* childWithStackingContextProperties =
childWithStackingContext->objectPaintProperties();
EXPECT_EQ(nullptr, childWithStackingContextProperties->effect());
EXPECT_EQ(nullptr, childWithStackingContextProperties->transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), childWithStackingContext,
document().view()->layoutView());
LayoutObject* grandChildWithOpacity =
document().getElementById("grandChildWithOpacity")->layoutObject();
const ObjectPaintProperties* grandChildWithOpacityProperties =
grandChildWithOpacity->objectPaintProperties();
EXPECT_EQ(0.4f, grandChildWithOpacityProperties->effect()->opacity());
EXPECT_EQ(nodeWithOpacityProperties->effect(),
grandChildWithOpacityProperties->effect()->parent());
EXPECT_EQ(nullptr, grandChildWithOpacityProperties->transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 20, 30), grandChildWithOpacity,
document().view()->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, EffectNodesInSVG) {
setBodyInnerHTML(
"<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>");
LayoutObject* groupWithOpacity =
document().getElementById("groupWithOpacity")->layoutObject();
const ObjectPaintProperties* groupWithOpacityProperties =
groupWithOpacity->objectPaintProperties();
EXPECT_EQ(0.6f, groupWithOpacityProperties->effect()->opacity());
EXPECT_NE(nullptr, groupWithOpacityProperties->effect()->parent());
LayoutObject& rectWithoutOpacity =
*document().getElementById("rectWithoutOpacity")->layoutObject();
const ObjectPaintProperties* rectWithoutOpacityProperties =
rectWithoutOpacity.objectPaintProperties();
EXPECT_EQ(nullptr, rectWithoutOpacityProperties);
LayoutObject& rectWithOpacity =
*document().getElementById("rectWithOpacity")->layoutObject();
const ObjectPaintProperties* rectWithOpacityProperties =
rectWithOpacity.objectPaintProperties();
EXPECT_EQ(0.4f, rectWithOpacityProperties->effect()->opacity());
EXPECT_EQ(groupWithOpacityProperties->effect(),
rectWithOpacityProperties->effect()->parent());
// Ensure that opacity nodes are created for LayoutSVGText which inherits from
// LayoutSVGBlock instead of LayoutSVGModelObject.
LayoutObject& textWithOpacity =
*document().getElementById("textWithOpacity")->layoutObject();
const ObjectPaintProperties* textWithOpacityProperties =
textWithOpacity.objectPaintProperties();
EXPECT_EQ(0.2f, textWithOpacityProperties->effect()->opacity());
EXPECT_EQ(groupWithOpacityProperties->effect(),
textWithOpacityProperties->effect()->parent());
// Ensure that opacity nodes are created for LayoutSVGTSpan which inherits
// from LayoutSVGInline instead of LayoutSVGModelObject.
LayoutObject& tspanWithOpacity =
*document().getElementById("tspanWithOpacity")->layoutObject();
const ObjectPaintProperties* tspanWithOpacityProperties =
tspanWithOpacity.objectPaintProperties();
EXPECT_EQ(0.1f, tspanWithOpacityProperties->effect()->opacity());
EXPECT_EQ(textWithOpacityProperties->effect(),
tspanWithOpacityProperties->effect()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossHTMLSVGBoundary) {
setBodyInnerHTML(
"<div id='divWithOpacity' style='opacity: 0.2;'>"
" <svg id='svgRootWithOpacity' style='opacity: 0.3;'>"
" <rect id='rectWithOpacity' opacity='0.4' />"
" </svg>"
"</div>");
LayoutObject& divWithOpacity =
*document().getElementById("divWithOpacity")->layoutObject();
const ObjectPaintProperties* divWithOpacityProperties =
divWithOpacity.objectPaintProperties();
EXPECT_EQ(0.2f, divWithOpacityProperties->effect()->opacity());
EXPECT_NE(nullptr, divWithOpacityProperties->effect()->parent());
LayoutObject& svgRootWithOpacity =
*document().getElementById("svgRootWithOpacity")->layoutObject();
const ObjectPaintProperties* svgRootWithOpacityProperties =
svgRootWithOpacity.objectPaintProperties();
EXPECT_EQ(0.3f, svgRootWithOpacityProperties->effect()->opacity());
EXPECT_EQ(divWithOpacityProperties->effect(),
svgRootWithOpacityProperties->effect()->parent());
LayoutObject& rectWithOpacity =
*document().getElementById("rectWithOpacity")->layoutObject();
const ObjectPaintProperties* rectWithOpacityProperties =
rectWithOpacity.objectPaintProperties();
EXPECT_EQ(0.4f, rectWithOpacityProperties->effect()->opacity());
EXPECT_EQ(svgRootWithOpacityProperties->effect(),
rectWithOpacityProperties->effect()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossSVGHTMLBoundary) {
setBodyInnerHTML(
"<svg id='svgRootWithOpacity' style='opacity: 0.3;'>"
" <foreignObject id='foreignObjectWithOpacity' opacity='0.4'>"
" <body>"
" <span id='spanWithOpacity' style='opacity: 0.5'/>"
" </body>"
" </foreignObject>"
"</svg>");
LayoutObject& svgRootWithOpacity =
*document().getElementById("svgRootWithOpacity")->layoutObject();
const ObjectPaintProperties* svgRootWithOpacityProperties =
svgRootWithOpacity.objectPaintProperties();
EXPECT_EQ(0.3f, svgRootWithOpacityProperties->effect()->opacity());
EXPECT_NE(nullptr, svgRootWithOpacityProperties->effect()->parent());
LayoutObject& foreignObjectWithOpacity =
*document().getElementById("foreignObjectWithOpacity")->layoutObject();
const ObjectPaintProperties* foreignObjectWithOpacityProperties =
foreignObjectWithOpacity.objectPaintProperties();
EXPECT_EQ(0.4f, foreignObjectWithOpacityProperties->effect()->opacity());
EXPECT_EQ(svgRootWithOpacityProperties->effect(),
foreignObjectWithOpacityProperties->effect()->parent());
LayoutObject& spanWithOpacity =
*document().getElementById("spanWithOpacity")->layoutObject();
const ObjectPaintProperties* spanWithOpacityProperties =
spanWithOpacity.objectPaintProperties();
EXPECT_EQ(0.5f, spanWithOpacityProperties->effect()->opacity());
EXPECT_EQ(foreignObjectWithOpacityProperties->effect(),
spanWithOpacityProperties->effect()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodesInSVG) {
setBodyInnerHTML(
"<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>");
LayoutObject& svgRootWith3dTransform =
*document().getElementById("svgRootWith3dTransform")->layoutObject();
const ObjectPaintProperties* svgRootWith3dTransformProperties =
svgRootWith3dTransform.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(1, 2, 3),
svgRootWith3dTransformProperties->transform()->matrix());
EXPECT_EQ(FloatPoint3D(50, 50, 0),
svgRootWith3dTransformProperties->transform()->origin());
EXPECT_EQ(svgRootWith3dTransformProperties->paintOffsetTranslation(),
svgRootWith3dTransformProperties->transform()->parent());
EXPECT_EQ(
TransformationMatrix().translate(70, 25),
svgRootWith3dTransformProperties->paintOffsetTranslation()->matrix());
EXPECT_EQ(
framePreTranslation(),
svgRootWith3dTransformProperties->paintOffsetTranslation()->parent());
LayoutObject& rectWith2dTransform =
*document().getElementById("rectWith2dTransform")->layoutObject();
const ObjectPaintProperties* rectWith2dTransformProperties =
rectWith2dTransform.objectPaintProperties();
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, rectWith2dTransformProperties->transform()->matrix());
EXPECT_EQ(FloatPoint3D(0, 0, 0),
rectWith2dTransformProperties->transform()->origin());
// SVG does not use paint offset.
EXPECT_EQ(nullptr, rectWith2dTransformProperties->paintOffsetTranslation());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGViewBoxTransform) {
setBodyInnerHTML(
"<style>"
" body {"
" margin: 0px;"
" }"
" svg {"
" transform: translate3d(1px, 2px, 3px);"
" position: absolute;"
" }"
" rect {"
" transform: translate(100px, 100px);"
" }"
"</style>"
"<svg id='svgWithViewBox' width='100px' height='100px' viewBox='50 50 "
"100 100'>"
" <rect id='rect' width='100px' height='100px' />"
"</svg>");
LayoutObject& svgWithViewBox =
*document().getElementById("svgWithViewBox")->layoutObject();
const ObjectPaintProperties* svgWithViewBoxProperties =
svgWithViewBox.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(1, 2, 3),
svgWithViewBoxProperties->transform()->matrix());
EXPECT_EQ(TransformationMatrix().translate(-50, -50),
svgWithViewBoxProperties->svgLocalToBorderBoxTransform()->matrix());
EXPECT_EQ(svgWithViewBoxProperties->svgLocalToBorderBoxTransform()->parent(),
svgWithViewBoxProperties->transform());
LayoutObject& rect = *document().getElementById("rect")->layoutObject();
const ObjectPaintProperties* rectProperties = rect.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(100, 100),
rectProperties->transform()->matrix());
EXPECT_EQ(svgWithViewBoxProperties->svgLocalToBorderBoxTransform(),
rectProperties->transform()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGRootPaintOffsetTransformNode) {
setBodyInnerHTML(
"<style>body { margin: 0px; } </style>"
"<svg id='svg' style='margin-left: 50px; margin-top: 25px; width: 100px; "
"height: 100px;' />");
LayoutObject& svg = *document().getElementById("svg")->layoutObject();
const ObjectPaintProperties* svgProperties = svg.objectPaintProperties();
// Ensure that a paint offset transform is not unnecessarily emitted.
EXPECT_EQ(nullptr, svgProperties->paintOffsetTranslation());
EXPECT_EQ(TransformationMatrix().translate(50, 25),
svgProperties->svgLocalToBorderBoxTransform()->matrix());
EXPECT_EQ(framePreTranslation(),
svgProperties->svgLocalToBorderBoxTransform()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGRootLocalToBorderBoxTransformNode) {
setBodyInnerHTML(
"<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>");
LayoutObject& svg = *document().getElementById("svg")->layoutObject();
const ObjectPaintProperties* svgProperties = svg.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(2, 3),
svgProperties->paintOffsetTranslation()->matrix());
EXPECT_EQ(TransformationMatrix().translate(5, 7),
svgProperties->transform()->matrix());
EXPECT_EQ(TransformationMatrix().translate(11, 11).scale(100.0 / 13.0),
svgProperties->svgLocalToBorderBoxTransform()->matrix());
EXPECT_EQ(svgProperties->paintOffsetTranslation(),
svgProperties->transform()->parent());
EXPECT_EQ(svgProperties->transform(),
svgProperties->svgLocalToBorderBoxTransform()->parent());
// Ensure the rect's transform is a child of the local to border box
// transform.
LayoutObject& rect = *document().getElementById("rect")->layoutObject();
const ObjectPaintProperties* rectProperties = rect.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(17, 19),
rectProperties->transform()->matrix());
EXPECT_EQ(svgProperties->svgLocalToBorderBoxTransform(),
rectProperties->transform()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGNestedViewboxTransforms) {
setBodyInnerHTML(
"<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>");
LayoutObject& svg = *document().getElementById("svg")->layoutObject();
const ObjectPaintProperties* svgProperties = svg.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(11, 11),
svgProperties->transform()->matrix());
EXPECT_EQ(TransformationMatrix().scale(2),
svgProperties->svgLocalToBorderBoxTransform()->matrix());
LayoutObject& nestedSvg =
*document().getElementById("nestedSvg")->layoutObject();
const ObjectPaintProperties* nestedSvgProperties =
nestedSvg.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().scale(10),
nestedSvgProperties->transform()->matrix());
EXPECT_EQ(nullptr, nestedSvgProperties->svgLocalToBorderBoxTransform());
EXPECT_EQ(svgProperties->svgLocalToBorderBoxTransform(),
nestedSvgProperties->transform()->parent());
LayoutObject& rect = *document().getElementById("rect")->layoutObject();
const ObjectPaintProperties* rectProperties = rect.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(13, 13),
rectProperties->transform()->matrix());
EXPECT_EQ(nestedSvgProperties->transform(),
rectProperties->transform()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodesAcrossSVGHTMLBoundary) {
setBodyInnerHTML(
"<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>");
LayoutObject& svgWithTransform =
*document().getElementById("svgWithTransform")->layoutObject();
const ObjectPaintProperties* svgWithTransformProperties =
svgWithTransform.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(1, 2, 3),
svgWithTransformProperties->transform()->matrix());
LayoutObject& divWithTransform =
*document().getElementById("divWithTransform")->layoutObject();
const ObjectPaintProperties* divWithTransformProperties =
divWithTransform.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(3, 4, 5),
divWithTransformProperties->transform()->matrix());
// Ensure the div's transform node is a child of the svg's transform node.
EXPECT_EQ(svgWithTransformProperties->transform(),
divWithTransformProperties->transform()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest,
FixedTransformAncestorAcrossSVGHTMLBoundary) {
setBodyInnerHTML(
"<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>");
LayoutObject& svg = *document().getElementById("svg")->layoutObject();
const ObjectPaintProperties* svgProperties = svg.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(1, 2, 3),
svgProperties->transform()->matrix());
LayoutObject& container =
*document().getElementById("container")->layoutObject();
const ObjectPaintProperties* containerProperties =
container.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(20, 30),
containerProperties->transform()->matrix());
EXPECT_EQ(svgProperties->transform(),
containerProperties->transform()->parent());
Element* fixed = document().getElementById("fixed");
const ObjectPaintProperties* fixedProperties =
fixed->layoutObject()->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(200, 150),
fixedProperties->paintOffsetTranslation()->matrix());
// Ensure the fixed position element is rooted at the nearest transform
// container.
EXPECT_EQ(containerProperties->transform(),
fixedProperties->paintOffsetTranslation()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, ControlClip) {
setBodyInnerHTML(
"<style>"
" body {"
" margin: 0;"
" }"
" input {"
" border-width: 5px;"
" padding: 0;"
" }"
"</style>"
"<input id='button' type='button' style='width:345px; height:123px' "
"value='some text'/>");
LayoutObject& button = *document().getElementById("button")->layoutObject();
const ObjectPaintProperties* buttonProperties =
button.objectPaintProperties();
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!frameScrollTranslation());
EXPECT_EQ(framePreTranslation(),
buttonProperties->overflowClip()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(5, 5, 335, 113),
buttonProperties->overflowClip()->clipRect());
EXPECT_EQ(frameContentClip(), buttonProperties->overflowClip()->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 345, 123), &button,
document().view()->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, BorderRadiusClip) {
setBodyInnerHTML(
"<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>");
LayoutObject& div = *document().getElementById("div")->layoutObject();
const ObjectPaintProperties* divProperties = div.objectPaintProperties();
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!frameScrollTranslation());
EXPECT_EQ(framePreTranslation(),
divProperties->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),
divProperties->overflowClip()->clipRect());
const ClipPaintPropertyNode* borderRadiusClip =
divProperties->overflowClip()->parent();
EXPECT_EQ(framePreTranslation(), borderRadiusClip->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.
EXPECT_EQ(
FloatRoundedRect(
FloatRect(60, 45, 500,
400), // = border box(610, 500) - border outset(110, 100)
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), // (bottom left) = max((78, 78) - (60, 55), (0, 0))
FloatSize(6,
1)), // (bottom right) = max((56, 56) - (50, 55), (0, 0))
borderRadiusClip->clipRect());
EXPECT_EQ(frameContentClip(), borderRadiusClip->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 610, 500), &div,
document().view()->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodesAcrossSubframes) {
setBodyInnerHTML(
"<style>body { margin: 0; }</style>"
"<div id='divWithTransform' style='transform: translate3d(1px, 2px, "
"3px);'>"
" <iframe style='border: 7px solid black' id='frame'></iframe>"
"</div>");
Document& frameDocument = setupChildIframe(
"frame",
"<style>body { margin: 0; }</style><div id='transform' style='transform: "
"translate3d(4px, 5px, 6px); width: 100px; height: 200px'></div>");
FrameView* frameView = document().view();
frameView->updateAllLifecyclePhases();
LayoutObject* divWithTransform =
document().getElementById("divWithTransform")->layoutObject();
const ObjectPaintProperties* divWithTransformProperties =
divWithTransform->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(1, 2, 3),
divWithTransformProperties->transform()->matrix());
// http://crbug.com/638415
if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
CHECK_EXACT_VISUAL_RECT(LayoutRect(1, 2, 800, 164), divWithTransform,
frameView->layoutView());
}
LayoutObject* innerDivWithTransform =
frameDocument.getElementById("transform")->layoutObject();
const ObjectPaintProperties* innerDivWithTransformProperties =
innerDivWithTransform->objectPaintProperties();
auto* innerDivTransform = innerDivWithTransformProperties->transform();
EXPECT_EQ(TransformationMatrix().translate3d(4, 5, 6),
innerDivTransform->matrix());
CHECK_EXACT_VISUAL_RECT(LayoutRect(12, 14, 100, 145), innerDivWithTransform,
frameView->layoutView());
// 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
// ScrollTranslation transform=translation=0.000000,0.000000,0.000000
// Transform transform=translation=4.000000,5.000000,6.000000
auto* innerDocumentScrollTranslation = innerDivTransform->parent();
EXPECT_EQ(TransformationMatrix().translate3d(0, 0, 0),
innerDocumentScrollTranslation->matrix());
auto* iframePreTranslation = innerDocumentScrollTranslation->parent();
EXPECT_EQ(TransformationMatrix().translate3d(7, 7, 0),
iframePreTranslation->matrix());
EXPECT_EQ(divWithTransformProperties->transform(),
iframePreTranslation->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, TransformNodesInTransformedSubframes) {
setBodyInnerHTML(
"<style>body { margin: 0; }</style>"
"<div id='divWithTransform' style='transform: translate3d(1px, 2px, "
"3px);'>"
" <iframe id='frame' style='transform: translate3d(4px, 5px, 6px); "
"border: 42px solid; margin: 7px;'></iframe>"
"</div>");
Document& frameDocument =
setupChildIframe("frame",
"<style>body { margin: 31px; }</style><div "
"id='transform' style='transform: translate3d(7px, 8px, "
"9px); width: 100px; height: 200px'></div>");
FrameView* frameView = document().view();
frameView->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* innerDivWithTransform =
frameDocument.getElementById("transform")->layoutObject();
auto* innerDivTransform =
innerDivWithTransform->objectPaintProperties()->transform();
EXPECT_EQ(TransformationMatrix().translate3d(7, 8, 9),
innerDivTransform->matrix());
CHECK_EXACT_VISUAL_RECT(LayoutRect(92, 95, 100, 111), innerDivWithTransform,
frameView->layoutView());
auto* innerDocumentPaintOffsetTranslation = innerDivTransform->parent();
EXPECT_EQ(TransformationMatrix().translate3d(31, 31, 0),
innerDocumentPaintOffsetTranslation->matrix());
auto* innerDocumentScrollTranslation =
innerDocumentPaintOffsetTranslation->parent();
EXPECT_EQ(TransformationMatrix().translate3d(0, 0, 0),
innerDocumentScrollTranslation->matrix());
auto* iframePreTranslation = innerDocumentScrollTranslation->parent();
EXPECT_EQ(TransformationMatrix().translate3d(42, 42, 0),
iframePreTranslation->matrix());
auto* iframeTransform = iframePreTranslation->parent();
EXPECT_EQ(TransformationMatrix().translate3d(4, 5, 6),
iframeTransform->matrix());
auto* iframePaintOffsetTranslation = iframeTransform->parent();
EXPECT_EQ(TransformationMatrix().translate3d(7, 7, 0),
iframePaintOffsetTranslation->matrix());
auto* divWithTransformTransform = iframePaintOffsetTranslation->parent();
EXPECT_EQ(TransformationMatrix().translate3d(1, 2, 3),
divWithTransformTransform->matrix());
LayoutObject* divWithTransform =
document().getElementById("divWithTransform")->layoutObject();
EXPECT_EQ(divWithTransformTransform,
divWithTransform->objectPaintProperties()->transform());
// http://crbug.com/638415
if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
CHECK_EXACT_VISUAL_RECT(LayoutRect(1, 2, 800, 248), divWithTransform,
frameView->layoutView());
}
}
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(
"<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>");
FrameView* frameView = document().view();
LayoutObject* scroller =
document().getElementById("scroller")->layoutObject();
const ObjectPaintProperties* scrollerProperties =
scroller->objectPaintProperties();
LayoutObject* child = document().getElementById("child")->layoutObject();
const ObjectPaintProperties* childProperties = child->objectPaintProperties();
EXPECT_EQ(
scrollerProperties->overflowClip(),
childProperties->localBorderBoxProperties()->propertyTreeState.clip());
EXPECT_EQ(scrollerProperties->scrollTranslation(),
childProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_NE(
nullptr,
childProperties->localBorderBoxProperties()->propertyTreeState.effect());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 400, 300), scroller,
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 100, 200), child,
frameView->layoutView());
}
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(
"<style>body { margin: 0; }</style>"
"<div id='scroller' style='overflow:scroll; opacity:0.5;'>"
" <div id='child' style='position:absolute; left:0; top:0; width: "
"100px; height: 200px'></div>"
" <div style='height:10000px;'></div>"
"</div>");
LayoutObject& scroller =
*document().getElementById("scroller")->layoutObject();
const ObjectPaintProperties* scrollerProperties =
scroller.objectPaintProperties();
LayoutObject& child = *document().getElementById("child")->layoutObject();
const ObjectPaintProperties* childProperties = child.objectPaintProperties();
EXPECT_EQ(
frameContentClip(),
childProperties->localBorderBoxProperties()->propertyTreeState.clip());
EXPECT_EQ(frameScrollTranslation(),
childProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_EQ(
scrollerProperties->effect(),
childProperties->localBorderBoxProperties()->propertyTreeState.effect());
if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled()) {
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 800, 10000), &scroller,
document().view()->layoutView());
}
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 100, 200), &child,
document().view()->layoutView());
}
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(
"<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>");
LayoutObject& target = *document().getElementById("target")->layoutObject();
const ObjectPaintProperties* targetProperties =
target.objectPaintProperties();
EXPECT_EQ(LayoutPoint(170, 170),
targetProperties->localBorderBoxProperties()->paintOffset);
EXPECT_EQ(framePreTranslation(), targetProperties->localBorderBoxProperties()
->propertyTreeState.transform());
CHECK_EXACT_VISUAL_RECT(LayoutRect(170, 170, 100, 100), &target,
document().view()->layoutView());
}
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(
"<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>");
LayoutRect localClipRect(40, 10, 40, 60);
LayoutRect absoluteClipRect = localClipRect;
absoluteClipRect.move(123, 456);
LayoutObject& clip = *document().getElementById("clip")->layoutObject();
const ObjectPaintProperties* clipProperties = clip.objectPaintProperties();
EXPECT_EQ(frameContentClip(), clipProperties->cssClip()->parent());
EXPECT_EQ(framePreTranslation(),
clipProperties->cssClip()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(FloatRect(absoluteClipRect)),
clipProperties->cssClip()->clipRect());
CHECK_VISUAL_RECT(absoluteClipRect, &clip, document().view()->layoutView(),
// TODO(crbug.com/599939): mapToVisualRectInAncestorSpace()
// doesn't apply css clip on the object itself.
LayoutUnit::max());
LayoutObject* fixed = document().getElementById("fixed")->layoutObject();
const ObjectPaintProperties* fixedProperties = fixed->objectPaintProperties();
EXPECT_EQ(
clipProperties->cssClip(),
fixedProperties->localBorderBoxProperties()->propertyTreeState.clip());
EXPECT_EQ(framePreTranslation(), fixedProperties->localBorderBoxProperties()
->propertyTreeState.transform()
->parent());
EXPECT_EQ(TransformationMatrix().translate(654, 321),
fixedProperties->localBorderBoxProperties()
->propertyTreeState.transform()
->matrix());
EXPECT_EQ(LayoutPoint(),
fixedProperties->localBorderBoxProperties()->paintOffset);
CHECK_VISUAL_RECT(LayoutRect(), fixed, document().view()->layoutView(),
// 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(
"<style>"
" #clip {"
" position: absolute;"
" left: 123px;"
" top: 456px;"
" clip: rect(10px, 80px, 70px, 40px);"
" width: 100px;"
" height: 100px;"
" }"
" #abs {"
" position: absolute;"
" left: 654px;"
" top: 321px;"
" width: 10px;"
" heght: 20px"
" }"
"</style>"
"<div id='clip'><div id='absolute'></div></div>");
LayoutRect localClipRect(40, 10, 40, 60);
LayoutRect absoluteClipRect = localClipRect;
absoluteClipRect.move(123, 456);
LayoutObject* clip = document().getElementById("clip")->layoutObject();
const ObjectPaintProperties* clipProperties = clip->objectPaintProperties();
EXPECT_EQ(frameContentClip(), clipProperties->cssClip()->parent());
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!frameScrollTranslation());
EXPECT_EQ(framePreTranslation(),
clipProperties->cssClip()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(FloatRect(absoluteClipRect)),
clipProperties->cssClip()->clipRect());
CHECK_VISUAL_RECT(absoluteClipRect, clip, document().view()->layoutView(),
// TODO(crbug.com/599939): mapToVisualRectInAncestorSpace()
// doesn't apply css clip on the object itself.
LayoutUnit::max());
LayoutObject* absolute =
document().getElementById("absolute")->layoutObject();
const ObjectPaintProperties* absPosProperties =
absolute->objectPaintProperties();
EXPECT_EQ(
clipProperties->cssClip(),
absPosProperties->localBorderBoxProperties()->propertyTreeState.clip());
EXPECT_EQ(framePreTranslation(), absPosProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_EQ(LayoutPoint(123, 456),
absPosProperties->localBorderBoxProperties()->paintOffset);
CHECK_VISUAL_RECT(LayoutRect(), absolute, document().view()->layoutView(),
// 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(
"<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>");
LayoutRect localClipRect(40, 10, 40, 60);
LayoutRect absoluteClipRect = localClipRect;
absoluteClipRect.move(123, 456);
LayoutObject& overflow =
*document().getElementById("overflow")->layoutObject();
const ObjectPaintProperties* overflowProperties =
overflow.objectPaintProperties();
EXPECT_EQ(frameContentClip(), overflowProperties->overflowClip()->parent());
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!frameScrollTranslation());
EXPECT_EQ(framePreTranslation(),
overflowProperties->scrollTranslation()->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 50, 50), &overflow,
document().view()->layoutView());
LayoutObject* clip = document().getElementById("clip")->layoutObject();
const ObjectPaintProperties* clipProperties = clip->objectPaintProperties();
EXPECT_EQ(overflowProperties->overflowClip(),
clipProperties->cssClip()->parent());
EXPECT_EQ(overflowProperties->scrollTranslation(),
clipProperties->cssClip()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(FloatRect(absoluteClipRect)),
clipProperties->cssClip()->clipRect());
EXPECT_EQ(frameContentClip(),
clipProperties->cssClipFixedPosition()->parent());
EXPECT_EQ(overflowProperties->scrollTranslation(),
clipProperties->cssClipFixedPosition()->localTransformSpace());
EXPECT_EQ(FloatRoundedRect(FloatRect(absoluteClipRect)),
clipProperties->cssClipFixedPosition()->clipRect());
CHECK_EXACT_VISUAL_RECT(LayoutRect(), clip, document().view()->layoutView());
LayoutObject* fixed = document().getElementById("fixed")->layoutObject();
const ObjectPaintProperties* fixedProperties = fixed->objectPaintProperties();
EXPECT_EQ(
clipProperties->cssClipFixedPosition(),
fixedProperties->localBorderBoxProperties()->propertyTreeState.clip());
EXPECT_EQ(framePreTranslation(), fixedProperties->localBorderBoxProperties()
->propertyTreeState.transform()
->parent());
EXPECT_EQ(TransformationMatrix().translate(654, 321),
fixedProperties->localBorderBoxProperties()
->propertyTreeState.transform()
->matrix());
EXPECT_EQ(LayoutPoint(),
fixedProperties->localBorderBoxProperties()->paintOffset);
CHECK_VISUAL_RECT(LayoutRect(), fixed, document().view()->layoutView(),
// TODO(crbug.com/599939): CSS clip of fixed-position
// descendants is broken in geometry mapping.
LayoutUnit::max());
}
TEST_P(PaintPropertyTreeBuilderTest, ColumnSpannerUnderRelativePositioned) {
setBodyInnerHTML(
"<div style='columns: 3; position: absolute; top: 44px; left: 55px;'>"
" <div style='position: relative; top: 100px; left: 100px'>"
" <div id='spanner' style='column-span: all; opacity: 0.5; width: "
"100px; height: 100px;'></div>"
" </div>"
"</div>");
LayoutObject* spanner = getLayoutObjectByElementId("spanner");
EXPECT_EQ(LayoutPoint(55, 44), spanner->objectPaintProperties()
->localBorderBoxProperties()
->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(55, 44, 100, 100), spanner,
document().view()->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, FractionalPaintOffset) {
setBodyInnerHTML(
"<style>"
" * { margin: 0; }"
" div { position: absolute; }"
"</style>"
"<div id='a' style='width: 70px; height: 70px; left: 0.1px; top: 0.3px;'>"
" <div id='b' style='width: 40px; height: 40px; left: 0.5px; top: "
"11.1px;'></div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* a = document().getElementById("a")->layoutObject();
const ObjectPaintProperties* aProperties = a->objectPaintProperties();
LayoutPoint aPaintOffset = LayoutPoint(FloatPoint(0.1, 0.3));
EXPECT_EQ(aPaintOffset, aProperties->localBorderBoxProperties()->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.1), LayoutUnit(0.3),
LayoutUnit(70), LayoutUnit(70)),
a, frameView->layoutView());
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
LayoutPoint bPaintOffset = aPaintOffset + LayoutPoint(FloatPoint(0.5, 11.1));
EXPECT_EQ(bPaintOffset, bProperties->localBorderBoxProperties()->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.1), LayoutUnit(0.3),
LayoutUnit(70), LayoutUnit(70)),
a, frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetWithBasicPixelSnapping) {
setBodyInnerHTML(
"<style>"
" * { margin: 0; }"
" div { position: relative; }"
"</style>"
"<div id='a' style='width: 70px; height: 70px; left: 0.3px; top: 0.3px;'>"
" <div id='b' style='width: 40px; height: 40px; transform: "
"translateZ(0);'>"
" <div id='c' style='width: 40px; height: 40px; left: 0.1px; top: "
"0.1px;'></div>"
" </div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(0, 0, 0),
bProperties->transform()->matrix());
// The paint offset transform should be snapped from (0.3,0.3) to (0,0).
EXPECT_EQ(TransformationMatrix().translate(0, 0),
bProperties->transform()->parent()->matrix());
// The residual subpixel adjustment should be (0.3,0.3) - (0,0) = (0.3,0.3).
LayoutPoint subpixelAccumulation = LayoutPoint(FloatPoint(0.3, 0.3));
EXPECT_EQ(subpixelAccumulation,
bProperties->localBorderBoxProperties()->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(FloatRect(0.3, 0.3, 40, 40)), b,
frameView->layoutView());
// c should be painted starting at subpixelAccumulation + (0.1,0.1) =
// (0.4,0.4).
LayoutObject* c = document().getElementById("c")->layoutObject();
LayoutPoint cPaintOffset =
subpixelAccumulation + LayoutPoint(FloatPoint(0.1, 0.1));
const ObjectPaintProperties* cProperties = c->objectPaintProperties();
EXPECT_EQ(cPaintOffset, cProperties->localBorderBoxProperties()->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,
frameView->layoutView(), 1);
}
TEST_P(PaintPropertyTreeBuilderTest,
PaintOffsetWithPixelSnappingThroughTransform) {
setBodyInnerHTML(
"<style>"
" * { margin: 0; }"
" div { position: relative; }"
"</style>"
"<div id='a' style='width: 70px; height: 70px; left: 0.7px; top: 0.7px;'>"
" <div id='b' style='width: 40px; height: 40px; transform: "
"translateZ(0);'>"
" <div id='c' style='width: 40px; height: 40px; left: 0.7px; top: "
"0.7px;'></div>"
" </div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(0, 0, 0),
bProperties->transform()->matrix());
// The paint offset transform should be snapped from (0.7,0.7) to (1,1).
EXPECT_EQ(TransformationMatrix().translate(1, 1),
bProperties->transform()->parent()->matrix());
// The residual subpixel adjustment should be (0.7,0.7) - (1,1) = (-0.3,-0.3).
LayoutPoint subpixelAccumulation =
LayoutPoint(LayoutPoint(FloatPoint(0.7, 0.7)) - LayoutPoint(1, 1));
EXPECT_EQ(subpixelAccumulation,
bProperties->localBorderBoxProperties()->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0.7),
LayoutUnit(40), LayoutUnit(40)),
b, frameView->layoutView());
// c should be painted starting at subpixelAccumulation + (0.7,0.7) =
// (0.4,0.4).
LayoutObject* c = document().getElementById("c")->layoutObject();
LayoutPoint cPaintOffset =
subpixelAccumulation + LayoutPoint(FloatPoint(0.7, 0.7));
const ObjectPaintProperties* cProperties = c->objectPaintProperties();
EXPECT_EQ(cPaintOffset, cProperties->localBorderBoxProperties()->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, frameView->layoutView(), 1);
}
TEST_P(PaintPropertyTreeBuilderTest,
PaintOffsetWithPixelSnappingThroughMultipleTransforms) {
setBodyInnerHTML(
"<style>"
" * { margin: 0; }"
" div { position: relative; }"
"</style>"
"<div id='a' style='width: 70px; height: 70px; left: 0.7px; top: 0.7px;'>"
" <div id='b' style='width: 40px; height: 40px; transform: "
"translate3d(5px, 7px, 0);'>"
" <div id='c' style='width: 40px; height: 40px; transform: "
"translate3d(11px, 13px, 0);'>"
" <div id='d' style='width: 40px; height: 40px; left: 0.7px; top: "
"0.7px;'></div>"
" </div>"
" </div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(5, 7, 0),
bProperties->transform()->matrix());
// The paint offset transform should be snapped from (0.7,0.7) to (1,1).
EXPECT_EQ(TransformationMatrix().translate(1, 1),
bProperties->transform()->parent()->matrix());
// The residual subpixel adjustment should be (0.7,0.7) - (1,1) = (-0.3,-0.3).
LayoutPoint subpixelAccumulation =
LayoutPoint(LayoutPoint(FloatPoint(0.7, 0.7)) - LayoutPoint(1, 1));
EXPECT_EQ(subpixelAccumulation,
bProperties->localBorderBoxProperties()->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(5.7), LayoutUnit(7.7),
LayoutUnit(40), LayoutUnit(40)),
b, frameView->layoutView());
LayoutObject* c = document().getElementById("c")->layoutObject();
const ObjectPaintProperties* cProperties = c->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate3d(11, 13, 0),
cProperties->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),
cProperties->transform()->parent()->matrix());
// The residual subpixel adjustment should still be (-0.3,-0.3).
EXPECT_EQ(subpixelAccumulation,
cProperties->localBorderBoxProperties()->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(16.7), LayoutUnit(20.7),
LayoutUnit(40), LayoutUnit(40)),
c, frameView->layoutView());
// d should be painted starting at subpixelAccumulation + (0.7,0.7) =
// (0.4,0.4).
LayoutObject* d = document().getElementById("d")->layoutObject();
LayoutPoint dPaintOffset =
subpixelAccumulation + LayoutPoint(FloatPoint(0.7, 0.7));
const ObjectPaintProperties* dProperties = d->objectPaintProperties();
EXPECT_EQ(dPaintOffset, dProperties->localBorderBoxProperties()->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, frameView->layoutView(), 1);
}
TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetWithPixelSnappingWithFixedPos) {
setBodyInnerHTML(
"<style>"
" * { margin: 0; }"
"</style>"
"<div id='a' style='width: 70px; height: 70px; left: 0.7px; position: "
"relative;'>"
" <div id='b' style='width: 40px; height: 40px; transform: "
"translateZ(0); position: relative;'>"
" <div id='fixed' style='width: 40px; height: 40px; position: fixed;'>"
" <div id='d' style='width: 40px; height: 40px; left: 0.7px; "
"position: relative;'></div>"
" </div>"
" </div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(0, 0),
bProperties->transform()->matrix());
// The paint offset transform should be snapped from (0.7,0) to (1,0).
EXPECT_EQ(TransformationMatrix().translate(1, 0),
bProperties->transform()->parent()->matrix());
// The residual subpixel adjustment should be (0.7,0) - (1,0) = (-0.3,0).
LayoutPoint subpixelAccumulation =
LayoutPoint(LayoutPoint(FloatPoint(0.7, 0)) - LayoutPoint(1, 0));
EXPECT_EQ(subpixelAccumulation,
bProperties->localBorderBoxProperties()->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0),
LayoutUnit(40), LayoutUnit(40)),
b, frameView->layoutView());
LayoutObject* fixed = document().getElementById("fixed")->layoutObject();
const ObjectPaintProperties* fixedProperties = fixed->objectPaintProperties();
// The residual subpixel adjustment should still be (-0.3,0).
EXPECT_EQ(subpixelAccumulation,
fixedProperties->localBorderBoxProperties()->paintOffset);
CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0),
LayoutUnit(40), LayoutUnit(40)),
fixed, frameView->layoutView());
// d should be painted starting at subpixelAccumulation + (0.7,0) = (0.4,0).
LayoutObject* d = document().getElementById("d")->layoutObject();
LayoutPoint dPaintOffset =
subpixelAccumulation + LayoutPoint(FloatPoint(0.7, 0));
const ObjectPaintProperties* dProperties = d->objectPaintProperties();
EXPECT_EQ(dPaintOffset, dProperties->localBorderBoxProperties()->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, frameView->layoutView(), 1);
}
TEST_P(PaintPropertyTreeBuilderTest, SvgPixelSnappingShouldResetPaintOffset) {
setBodyInnerHTML(
"<svg id='svg' style='position: relative; left: 0.1px; transform: "
"matrix(1, 0, 0, 1, 0, 0);'>"
" <rect id='rect' transform='translate(1, 1)'/>"
"</svg>");
LayoutObject& svgWithTransform =
*document().getElementById("svg")->layoutObject();
const ObjectPaintProperties* svgWithTransformProperties =
svgWithTransform.objectPaintProperties();
EXPECT_EQ(TransformationMatrix(),
svgWithTransformProperties->transform()->matrix());
EXPECT_EQ(
LayoutPoint(FloatPoint(0.1, 0)),
svgWithTransformProperties->localBorderBoxProperties()->paintOffset);
EXPECT_EQ(nullptr,
svgWithTransformProperties->svgLocalToBorderBoxTransform());
LayoutObject& rectWithTransform =
*document().getElementById("rect")->layoutObject();
const ObjectPaintProperties* rectWithTransformProperties =
rectWithTransform.objectPaintProperties();
EXPECT_EQ(TransformationMatrix().translate(1, 1),
rectWithTransformProperties->transform()->matrix());
// Ensure there is no PaintOffset transform between the rect and the svg's
// transform.
EXPECT_EQ(svgWithTransformProperties->transform(),
rectWithTransformProperties->transform()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, NoRenderingContextByDefault) {
setBodyInnerHTML("<div style='transform: translateZ(0)'></div>");
const ObjectPaintProperties* properties =
document().body()->firstChild()->layoutObject()->objectPaintProperties();
ASSERT_TRUE(properties->transform());
EXPECT_FALSE(properties->transform()->hasRenderingContext());
}
TEST_P(PaintPropertyTreeBuilderTest, Preserve3DCreatesSharedRenderingContext) {
setBodyInnerHTML(
"<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>");
FrameView* frameView = document().view();
LayoutObject* a = document().getElementById("a")->layoutObject();
const ObjectPaintProperties* aProperties = a->objectPaintProperties();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
ASSERT_TRUE(aProperties->transform() && bProperties->transform());
EXPECT_NE(aProperties->transform(), bProperties->transform());
EXPECT_TRUE(aProperties->transform()->hasRenderingContext());
EXPECT_TRUE(bProperties->transform()->hasRenderingContext());
EXPECT_EQ(aProperties->transform()->renderingContextID(),
bProperties->transform()->renderingContextID());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 48, 20, 10), b,
frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, FlatTransformStyleEndsRenderingContext) {
setBodyInnerHTML(
"<div style='transform-style: preserve-3d'>"
" <div id='a' style='transform: translateZ(0); width: 30px; height: "
"40px'>"
" <div id='b' style='transform: translateZ(0); width: 10px; height: "
"20px'></div>"
" </div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* a = document().getElementById("a")->layoutObject();
const ObjectPaintProperties* aProperties = a->objectPaintProperties();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
ASSERT_FALSE(a->styleRef().preserves3D());
ASSERT_TRUE(aProperties->transform() && bProperties->transform());
// #a should participate in a rendering context (due to its parent), but its
// child #b should not.
EXPECT_TRUE(aProperties->transform()->hasRenderingContext());
EXPECT_FALSE(bProperties->transform()->hasRenderingContext());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 20), b, frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, NestedRenderingContexts) {
setBodyInnerHTML(
"<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>");
FrameView* frameView = document().view();
LayoutObject* a = document().getElementById("a")->layoutObject();
const ObjectPaintProperties* aProperties = a->objectPaintProperties();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
ASSERT_FALSE(a->styleRef().preserves3D());
ASSERT_TRUE(aProperties->transform() && bProperties->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(aProperties->transform()->hasRenderingContext());
EXPECT_TRUE(bProperties->transform()->hasRenderingContext());
EXPECT_NE(aProperties->transform()->renderingContextID(),
bProperties->transform()->renderingContextID());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), a, frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 20), b, frameView->layoutView());
}
// 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(
"<div id='a' style='transform: translateZ(0); transform-style: flat; "
"width: 30px; height: 40px'>"
" <div id='b' style='transform: translateZ(0); width: 10px; height: "
"10px'></div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* a = document().getElementById("a")->layoutObject();
LayoutObject* b = document().getElementById("b")->layoutObject();
const auto* aTransform = a->objectPaintProperties()->transform();
ASSERT_TRUE(aTransform);
const auto* bTransform = b->objectPaintProperties()->transform();
ASSERT_TRUE(bTransform);
ASSERT_TRUE(nodeHasAncestor(bTransform, aTransform));
// Some node must flatten the inherited transform from #a before it reaches
// #b's transform.
EXPECT_TRUE(someNodeFlattensTransform(bTransform, aTransform));
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 10), b, frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest,
Preserve3DTransformStylePropagatesToChildren) {
setBodyInnerHTML(
"<div id='a' style='transform: translateZ(0); transform-style: "
"preserve-3d; width: 30px; height: 40px'>"
" <div id='b' style='transform: translateZ(0); width: 10px; height: "
"10px'></div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* a = document().getElementById("a")->layoutObject();
LayoutObject* b = document().getElementById("b")->layoutObject();
const auto* aTransform = a->objectPaintProperties()->transform();
ASSERT_TRUE(aTransform);
const auto* bTransform = b->objectPaintProperties()->transform();
ASSERT_TRUE(bTransform);
ASSERT_TRUE(nodeHasAncestor(bTransform, aTransform));
// No node may flatten the inherited transform from #a before it reaches
// #b's transform.
EXPECT_FALSE(someNodeFlattensTransform(bTransform, aTransform));
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 10), b, frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, PerspectiveIsNotFlattened) {
// It's necessary to make nodes from the one that applies perspective to
// ones that combine with it preserve 3D. Otherwise, the perspective doesn't
// do anything.
setBodyInnerHTML(
"<div id='a' style='perspective: 800px; width: 30px; height: 40px'>"
" <div id='b' style='transform: translateZ(0); width: 10px; height: "
"20px'></div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* a = document().getElementById("a")->layoutObject();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* aProperties = a->objectPaintProperties();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
const TransformPaintPropertyNode* aPerspective = aProperties->perspective();
ASSERT_TRUE(aPerspective);
const TransformPaintPropertyNode* bTransform = bProperties->transform();
ASSERT_TRUE(bTransform);
ASSERT_TRUE(nodeHasAncestor(bTransform, aPerspective));
EXPECT_FALSE(someNodeFlattensTransform(bTransform, aPerspective));
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 20), b, frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest,
PerspectiveDoesNotEstablishRenderingContext) {
// It's necessary to make nodes from the one that applies perspective to
// ones that combine with it preserve 3D. Otherwise, the perspective doesn't
// do anything.
setBodyInnerHTML(
"<div id='a' style='perspective: 800px; width: 30px; height: 40px'>"
" <div id='b' style='transform: translateZ(0); width: 10px; height: "
"20px'></div>"
"</div>");
FrameView* frameView = document().view();
LayoutObject* a = document().getElementById("a")->layoutObject();
LayoutObject* b = document().getElementById("b")->layoutObject();
const ObjectPaintProperties* aProperties = a->objectPaintProperties();
const ObjectPaintProperties* bProperties = b->objectPaintProperties();
const TransformPaintPropertyNode* aPerspective = aProperties->perspective();
ASSERT_TRUE(aPerspective);
EXPECT_FALSE(aPerspective->hasRenderingContext());
const TransformPaintPropertyNode* bTransform = bProperties->transform();
ASSERT_TRUE(bTransform);
EXPECT_FALSE(bTransform->hasRenderingContext());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 20), b, frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, CachedProperties) {
setBodyInnerHTML(
"<style>body { margin: 0 }</style>"
"<div id='a' style='transform: translate(33px, 44px); width: 50px; "
"height: 60px'>"
" <div id='b' style='transform: translate(55px, 66px); width: 30px; "
"height: 40px'>"
" <div id='c' style='transform: translate(77px, 88px); width: 10px; "
"height: 20px'>C<div>"
" </div>"
"</div>");
FrameView* frameView = document().view();
Element* a = document().getElementById("a");
const ObjectPaintProperties* aProperties =
a->layoutObject()->objectPaintProperties();
const TransformPaintPropertyNode* aTransformNode = aProperties->transform();
EXPECT_EQ(TransformationMatrix().translate(33, 44), aTransformNode->matrix());
Element* b = document().getElementById("b");
const ObjectPaintProperties* bProperties =
b->layoutObject()->objectPaintProperties();
const TransformPaintPropertyNode* bTransformNode = bProperties->transform();
EXPECT_EQ(TransformationMatrix().translate(55, 66), bTransformNode->matrix());
Element* c = document().getElementById("c");
const ObjectPaintProperties* cProperties =
c->layoutObject()->objectPaintProperties();
const TransformPaintPropertyNode* cTransformNode = cProperties->transform();
EXPECT_EQ(TransformationMatrix().translate(77, 88), cTransformNode->matrix());
CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 60), a->layoutObject(),
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(88, 110, 30, 40), b->layoutObject(),
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(165, 198, 10, 20), c->layoutObject(),
frameView->layoutView());
// Change transform of b. B's transform node should be a new node with the new
// value, and a and c's transform nodes should be unchanged (with c's parent
// adjusted).
b->setAttribute(HTMLNames::styleAttr, "transform: translate(111px, 222px)");
document().view()->updateAllLifecyclePhases();
EXPECT_EQ(aProperties, a->layoutObject()->objectPaintProperties());
EXPECT_EQ(aTransformNode, aProperties->transform());
EXPECT_EQ(bProperties, b->layoutObject()->objectPaintProperties());
bTransformNode = bProperties->transform();
EXPECT_EQ(TransformationMatrix().translate(111, 222),
bTransformNode->matrix());
EXPECT_EQ(aTransformNode, bTransformNode->parent());
EXPECT_EQ(cProperties, c->layoutObject()->objectPaintProperties());
EXPECT_EQ(cTransformNode, cProperties->transform());
EXPECT_EQ(bTransformNode, cTransformNode->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 60), a->layoutObject(),
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(144, 266, 50, 20), b->layoutObject(),
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(221, 354, 10, 20), c->layoutObject(),
frameView->layoutView());
// Remove transform from b. B's transform node should be removed from the
// tree, and a and c's transform nodes should be unchanged (with c's parent
// adjusted).
b->setAttribute(HTMLNames::styleAttr, "");
document().view()->updateAllLifecyclePhases();
EXPECT_EQ(aProperties, a->layoutObject()->objectPaintProperties());
EXPECT_EQ(aTransformNode, aProperties->transform());
EXPECT_EQ(bProperties, b->layoutObject()->objectPaintProperties());
EXPECT_EQ(nullptr, bProperties->transform());
EXPECT_EQ(cProperties, c->layoutObject()->objectPaintProperties());
EXPECT_EQ(cTransformNode, cProperties->transform());
EXPECT_EQ(aTransformNode, cTransformNode->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 60), a->layoutObject(),
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 20), b->layoutObject(),
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(110, 132, 10, 20), c->layoutObject(),
frameView->layoutView());
// Re-add transform to b. B's transform node should be inserted into the tree,
// and a and c's transform nodes should be unchanged (with c's parent
// adjusted).
b->setAttribute(HTMLNames::styleAttr, "transform: translate(4px, 5px)");
document().view()->updateAllLifecyclePhases();
EXPECT_EQ(aProperties, a->layoutObject()->objectPaintProperties());
EXPECT_EQ(aTransformNode, aProperties->transform());
EXPECT_EQ(bProperties, b->layoutObject()->objectPaintProperties());
bTransformNode = bProperties->transform();
EXPECT_EQ(TransformationMatrix().translate(4, 5), bTransformNode->matrix());
EXPECT_EQ(aTransformNode, bTransformNode->parent());
EXPECT_EQ(cProperties, c->layoutObject()->objectPaintProperties());
EXPECT_EQ(cTransformNode, cProperties->transform());
EXPECT_EQ(bTransformNode, cTransformNode->parent());
CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 60), a->layoutObject(),
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(37, 49, 50, 20), b->layoutObject(),
frameView->layoutView());
CHECK_EXACT_VISUAL_RECT(LayoutRect(114, 137, 10, 20), c->layoutObject(),
frameView->layoutView());
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowClipContentsTreeState) {
// 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(
"<style>body { margin: 20px 30px; }</style>"
"<div id='clipper' style='overflow:hidden; width:400px; height:300px;'>"
" <div id='child' style='position:relative; width:500px; height: "
"600px;'></div>"
"</div>");
LayoutBoxModelObject* clipper = toLayoutBoxModelObject(
document().getElementById("clipper")->layoutObject());
const ObjectPaintProperties* clipProperties =
clipper->objectPaintProperties();
LayoutObject* child = document().getElementById("child")->layoutObject();
const ObjectPaintProperties* childProperties = child->objectPaintProperties();
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!frameScrollTranslation());
EXPECT_EQ(framePreTranslation(), clipProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_EQ(
frameContentClip(),
clipProperties->localBorderBoxProperties()->propertyTreeState.clip());
auto contentsProperties = clipProperties->contentsProperties();
EXPECT_EQ(LayoutPoint(30, 20), contentsProperties.paintOffset);
EXPECT_EQ(framePreTranslation(),
contentsProperties.propertyTreeState.transform());
EXPECT_EQ(clipProperties->overflowClip(),
contentsProperties.propertyTreeState.clip());
EXPECT_EQ(framePreTranslation(), childProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_EQ(
clipProperties->overflowClip(),
childProperties->localBorderBoxProperties()->propertyTreeState.clip());
EXPECT_NE(
nullptr,
childProperties->localBorderBoxProperties()->propertyTreeState.effect());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 500, 600), child, clipper);
}
TEST_P(PaintPropertyTreeBuilderTest, ContainsPaintContentsTreeState) {
setBodyInnerHTML(
"<style>body { margin: 20px 30px; }</style>"
"<div id='clipper' style='contain:paint; width:300px; height:200px;'>"
" <div id='child' style='position:relative; width:400px; height: "
"500px;'></div>"
"</div>");
LayoutBoxModelObject* clipper = toLayoutBoxModelObject(
document().getElementById("clipper")->layoutObject());
const ObjectPaintProperties* clipProperties =
clipper->objectPaintProperties();
LayoutObject* child = document().getElementById("child")->layoutObject();
const ObjectPaintProperties* childProperties = child->objectPaintProperties();
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!frameScrollTranslation());
EXPECT_EQ(framePreTranslation(), clipProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_EQ(
frameContentClip(),
clipProperties->localBorderBoxProperties()->propertyTreeState.clip());
auto contentsProperties = clipProperties->contentsProperties();
EXPECT_EQ(LayoutPoint(30, 20), contentsProperties.paintOffset);
EXPECT_EQ(framePreTranslation(),
contentsProperties.propertyTreeState.transform());
EXPECT_EQ(clipProperties->overflowClip(),
contentsProperties.propertyTreeState.clip());
EXPECT_EQ(framePreTranslation(), childProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_EQ(
clipProperties->overflowClip(),
childProperties->localBorderBoxProperties()->propertyTreeState.clip());
EXPECT_NE(
nullptr,
childProperties->localBorderBoxProperties()->propertyTreeState.effect());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 400, 500), child, clipper);
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollContentsTreeState) {
// 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(
"<style>body { margin: 20px 30px; }</style>"
"<div id='clipper' style='overflow:scroll; width:400px; height:300px;'>"
" <div id='child' style='position:relative; width:500px; height: "
"600px;'></div>"
" <div style='width: 200px; height: 10000px'></div>"
"</div>"
"<div id='forceScroll' style='height: 4000px;'></div>");
Element* clipperElement = document().getElementById("clipper");
clipperElement->scrollTo(1, 2);
LayoutBoxModelObject* clipper =
toLayoutBoxModelObject(clipperElement->layoutObject());
const ObjectPaintProperties* clipProperties =
clipper->objectPaintProperties();
LayoutObject* child = document().getElementById("child")->layoutObject();
const ObjectPaintProperties* childProperties = child->objectPaintProperties();
EXPECT_EQ(frameScrollTranslation(), clipProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_EQ(
frameContentClip(),
clipProperties->localBorderBoxProperties()->propertyTreeState.clip());
auto contentsProperties = clipProperties->contentsProperties();
EXPECT_EQ(LayoutPoint(30, 20), contentsProperties.paintOffset);
EXPECT_EQ(clipProperties->scrollTranslation(),
contentsProperties.propertyTreeState.transform());
EXPECT_EQ(clipProperties->overflowClip(),
contentsProperties.propertyTreeState.clip());
EXPECT_EQ(clipProperties->scrollTranslation(),
childProperties->localBorderBoxProperties()
->propertyTreeState.transform());
EXPECT_EQ(
clipProperties->overflowClip(),
childProperties->localBorderBoxProperties()->propertyTreeState.clip());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 500, 600), child, clipper);
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollWithRoundedRect) {
setBodyInnerHTML(
"<style>"
" * { margin: 0; }"
" ::-webkit-scrollbar {"
" width: 13px;"
" height: 13px;"
" }"
" #roundedBox {"
" width: 200px;"
" height: 200px;"
" border-radius: 100px;"
" background-color: red;"
" border: 50px solid green;"
" overflow: scroll;"
" }"
" #roundedBoxChild {"
" width: 200px;"
" height: 200px;"
" background-color: orange;"
" }"
"</style>"
"<div id='roundedBox'>"
" <div id='roundedBoxChild'></div>"
"</div>");
LayoutObject& roundedBox =
*document().getElementById("roundedBox")->layoutObject();
const ObjectPaintProperties* roundedBoxProperties =
roundedBox.objectPaintProperties();
EXPECT_EQ(
FloatRoundedRect(FloatRect(50, 50, 200, 200), FloatSize(50, 50),
FloatSize(50, 50), FloatSize(50, 50), FloatSize(50, 50)),
roundedBoxProperties->innerBorderRadiusClip()->clipRect());
// Unlike the inner border radius clip, the overflow clip is inset by the
// scrollbars (13px).
EXPECT_EQ(FloatRoundedRect(50, 50, 187, 187),
roundedBoxProperties->overflowClip()->clipRect());
EXPECT_EQ(frameContentClip(),
roundedBoxProperties->innerBorderRadiusClip()->parent());
EXPECT_EQ(roundedBoxProperties->innerBorderRadiusClip(),
roundedBoxProperties->overflowClip()->parent());
}
TEST_P(PaintPropertyTreeBuilderTest, CssClipContentsTreeState) {
// 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(
"<style>body { margin: 20px 30px; }</style>"
"<div id='clipper' style='position: absolute; clip: rect(10px, 80px, "
"70px, 40px); width:300px; height:200px;'>"
" <div id='child' style='position:relative; width:400px; height: "
"500px;'></div>"
"</div>");
LayoutBoxModelObject* clipper = toLayoutBoxModelObject(
document().getElementById("clipper")->layoutObject());
const ObjectPaintProperties* clipProperties =
clipper->objectPaintProperties();
LayoutObject* child = document().getElementById("child")->layoutObject();
// No scroll translation because the document does not scroll (not enough
// content).
EXPECT_TRUE(!frameScrollTranslation());
EXPECT_EQ(framePreTranslation(), clipProperties->localBorderBoxProperties()
->propertyTreeState.transform());
// CSS clip on an element causes it to clip itself, not just descendants.
EXPECT_EQ(
clipProperties->cssClip(),
clipProperties->localBorderBoxProperties()->propertyTreeState.clip());
auto contentsProperties = clipProperties->contentsProperties();
EXPECT_EQ(LayoutPoint(30, 20), contentsProperties.paintOffset);
EXPECT_EQ(framePreTranslation(),
contentsProperties.propertyTreeState.transform());
EXPECT_EQ(clipProperties->cssClip(),
contentsProperties.propertyTreeState.clip());
CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 400, 500), child, clipper);
}
TEST_P(PaintPropertyTreeBuilderTest,
SvgLocalToBorderBoxTransformContentsTreeState) {
setBodyInnerHTML(
"<style>"
" body {"
" margin: 20px 30px;"
" }"
" svg {"
" position: absolute;"
" }"
" rect {"
" transform: translate(100px, 100px);"
" }"
"</style>"
"<svg id='svgWithViewBox' width='100px' height='100px' viewBox='50 50 "
"100 100'>"
" <rect id='rect' width='100px' height='100px' />"
"</svg>");
LayoutObject& svgWithViewBox =
*document().getElementById("svgWithViewBox")->layoutObject();
const ObjectPaintProperties* svgWithViewBoxProperties =
svgWithViewBox.objectPaintProperties();
EXPECT_EQ(framePreTranslation(),
svgWithViewBoxProperties->localBorderBoxProperties()
->propertyTreeState.transform());
auto contentsProperties = svgWithViewBoxProperties->contentsProperties();
EXPECT_EQ(LayoutPoint(), contentsProperties.paintOffset);
EXPECT_EQ(svgWithViewBoxProperties->svgLocalToBorderBoxTransform(),
contentsProperties.propertyTreeState.transform());
}
TEST_P(PaintPropertyTreeBuilderTest, OverflowHiddenScrollProperties) {
setBodyInnerHTML(
"<style>"
" body {"
" margin: 0px;"
" }"
" #overflowHidden {"
" overflow: hidden;"
" width: 5px;"
" height: 3px;"
" }"
" .forceScroll {"
" height: 79px;"
" }"
"</style>"
"<div id='overflowHidden'>"
" <div class='forceScroll'></div>"
"</div>");
Element* overflowHidden = document().getElementById("overflowHidden");
overflowHidden->setScrollTop(37);
document().view()->updateAllLifecyclePhases();
const ObjectPaintProperties* overflowHiddenScrollProperties =
overflowHidden->layoutObject()->objectPaintProperties();
// Because the frameView is does not scroll, overflowHidden's scroll should be
// under the root.
EXPECT_TRUE(overflowHiddenScrollProperties->scroll()->parent()->isRoot());
EXPECT_EQ(TransformationMatrix().translate(0, -37),
overflowHiddenScrollProperties->scroll()
->scrollOffsetTranslation()
->matrix());
// This should match the overflow's dimensions.
EXPECT_EQ(IntSize(5, 3), overflowHiddenScrollProperties->scroll()->clip());
// The scrolling content's bounds should include both the overflow's
// dimensions (5x3) and the 0x79 "forceScroll" object.
EXPECT_EQ(IntSize(5, 79), overflowHiddenScrollProperties->scroll()->bounds());
// Although overflow: hidden is programmatically scrollable, it is not user
// scrollable.
EXPECT_FALSE(
overflowHiddenScrollProperties->scroll()->userScrollableHorizontal());
EXPECT_FALSE(
overflowHiddenScrollProperties->scroll()->userScrollableVertical());
}
TEST_P(PaintPropertyTreeBuilderTest, NestedScrollProperties) {
setBodyInnerHTML(
"<style>"
" * {"
" margin: 0px;"
" }"
" #overflowA {"
" overflow: scroll;"
" width: 5px;"
" height: 3px;"
" }"
" #overflowB {"
" overflow: scroll;"
" width: 9px;"
" height: 7px;"
" }"
" .forceScroll {"
" height: 100px;"
" }"
"</style>"
"<div id='overflowA'>"
" <div id='overflowB'>"
" <div class='forceScroll'></div>"
" </div>"
" <div class='forceScroll'></div>"
"</div>");
Element* overflowA = document().getElementById("overflowA");
overflowA->setScrollTop(37);
Element* overflowB = document().getElementById("overflowB");
overflowB->setScrollTop(41);
document().view()->updateAllLifecyclePhases();
const ObjectPaintProperties* overflowAScrollProperties =
overflowA->layoutObject()->objectPaintProperties();
// Because the frameView is does not scroll, overflowA's scroll should be
// under the root.
EXPECT_TRUE(overflowAScrollProperties->scroll()->parent()->isRoot());
EXPECT_EQ(
TransformationMatrix().translate(0, -37),
overflowAScrollProperties->scroll()->scrollOffsetTranslation()->matrix());
EXPECT_EQ(IntSize(5, 3), overflowAScrollProperties->scroll()->clip());
// 107 is the forceScroll element plus the height of the overflow scroll child
// (overflowB).
EXPECT_EQ(IntSize(9, 107), overflowAScrollProperties->scroll()->bounds());
EXPECT_TRUE(overflowAScrollProperties->scroll()->userScrollableHorizontal());
EXPECT_TRUE(overflowAScrollProperties->scroll()->userScrollableVertical());
const ObjectPaintProperties* overflowBScrollProperties =
overflowB->layoutObject()->objectPaintProperties();
// The overflow child's scroll node should be a child of the parent's
// (overflowA) scroll node.
EXPECT_EQ(overflowAScrollProperties->scroll(),
overflowBScrollProperties->scroll()->parent());
EXPECT_EQ(
TransformationMatrix().translate(0, -41),
overflowBScrollProperties->scroll()->scrollOffsetTranslation()->matrix());
EXPECT_EQ(IntSize(9, 7), overflowBScrollProperties->scroll()->clip());
EXPECT_EQ(IntSize(9, 100), overflowBScrollProperties->scroll()->bounds());
EXPECT_TRUE(overflowBScrollProperties->scroll()->userScrollableHorizontal());
EXPECT_TRUE(overflowBScrollProperties->scroll()->userScrollableVertical());
}
TEST_P(PaintPropertyTreeBuilderTest, PositionedScrollerIsNotNested) {
setBodyInnerHTML(
"<style>"
" * {"
" margin: 0px;"
" }"
" #overflow {"
" overflow: scroll;"
" width: 5px;"
" height: 3px;"
" }"
" #absposOverflow {"
" position: absolute;"
" top: 0;"
" left: 0;"
" overflow: scroll;"
" width: 9px;"
" height: 7px;"
" }"
" #fixedOverflow {"
" position: fixed;"
" top: 0;"
" left: 0;"
" overflow: scroll;"
" width: 13px;"
" height: 11px;"
" }"
" .forceScroll {"
" height: 4000px;"
" }"
"</style>"
"<div id='overflow'>"
" <div id='absposOverflow'>"
" <div class='forceScroll'></div>"
" </div>"
" <div id='fixedOverflow'>"
" <div class='forceScroll'></div>"
" </div>"
" <div class='forceScroll'></div>"
"</div>"
"<div class='forceScroll'></div>");
Element* overflow = document().getElementById("overflow");
overflow->setScrollTop(37);
Element* absposOverflow = document().getElementById("absposOverflow");
absposOverflow->setScrollTop(41);
Element* fixedOverflow = document().getElementById("fixedOverflow");
fixedOverflow->setScrollTop(43);
document().view()->updateAllLifecyclePhases();
// The frame should scroll due to the "forceScroll" element.
EXPECT_NE(nullptr, frameScroll());
const ObjectPaintProperties* overflowScrollProperties =
overflow->layoutObject()->objectPaintProperties();
EXPECT_EQ(frameScroll(), overflowScrollProperties->scroll()->parent());
EXPECT_EQ(
TransformationMatrix().translate(0, -37),
overflowScrollProperties->scroll()->scrollOffsetTranslation()->matrix());
EXPECT_EQ(IntSize(5, 3), overflowScrollProperties->scroll()->clip());
// The height should be 4000px because the (dom-order) overflow children are
// positioned and do not contribute to the height. Only the 4000px
// "forceScroll" height is present.
EXPECT_EQ(IntSize(5, 4000), overflowScrollProperties->scroll()->bounds());
const ObjectPaintProperties* absposOverflowScrollProperties =
absposOverflow->layoutObject()->objectPaintProperties();
// The absolute position overflow scroll node is parented under the frame, not
// the dom-order parent.
EXPECT_EQ(frameScroll(), absposOverflowScrollProperties->scroll()->parent());
EXPECT_EQ(TransformationMatrix().translate(0, -41),
absposOverflowScrollProperties->scroll()
->scrollOffsetTranslation()
->matrix());
EXPECT_EQ(IntSize(9, 7), absposOverflowScrollProperties->scroll()->clip());
EXPECT_EQ(IntSize(9, 4000),
absposOverflowScrollProperties->scroll()->bounds());
const ObjectPaintProperties* fixedOverflowScrollProperties =
fixedOverflow->layoutObject()->objectPaintProperties();
// The fixed position overflow scroll node is parented under the root, not the
// dom-order parent or frame's scroll.
EXPECT_TRUE(fixedOverflowScrollProperties->scroll()->parent()->isRoot());
EXPECT_EQ(TransformationMatrix().translate(0, -43),
fixedOverflowScrollProperties->scroll()
->scrollOffsetTranslation()
->matrix());
EXPECT_EQ(IntSize(13, 11), fixedOverflowScrollProperties->scroll()->clip());
EXPECT_EQ(IntSize(13, 4000),
fixedOverflowScrollProperties->scroll()->bounds());
}
TEST_P(PaintPropertyTreeBuilderTest, NestedPositionedScrollProperties) {
setBodyInnerHTML(
"<style>"
" * {"
" margin: 0px;"
" }"
" #overflowA {"
" position: absolute;"
" top: 7px;"
" left: 11px;"
" overflow: scroll;"
" width: 20px;"
" height: 20px;"
" }"
" #overflowB {"
" position: absolute;"
" top: 1px;"
" left: 3px;"
" overflow: scroll;"
" width: 5px;"
" height: 3px;"
" }"
" .forceScroll {"
" height: 100px;"
" }"
"</style>"
"<div id='overflowA'>"
" <div id='overflowB'>"
" <div class='forceScroll'></div>"
" </div>"
" <div class='forceScroll'></div>"
"</div>");
Element* overflowA = document().getElementById("overflowA");
overflowA->setScrollTop(37);
Element* overflowB = document().getElementById("overflowB");
overflowB->setScrollTop(41);
document().view()->updateAllLifecyclePhases();
const ObjectPaintProperties* overflowAScrollProperties =
overflowA->layoutObject()->objectPaintProperties();
// Because the frameView is does not scroll, overflowA's scroll should be
// under the root.
EXPECT_TRUE(overflowAScrollProperties->scroll()->parent()->isRoot());
EXPECT_EQ(
TransformationMatrix().translate(0, -37),
overflowAScrollProperties->scroll()->scrollOffsetTranslation()->matrix());
EXPECT_EQ(IntSize(20, 20), overflowAScrollProperties->scroll()->clip());
// 100 is the forceScroll element's height because the overflow child does not
// contribute to the height.
EXPECT_EQ(IntSize(20, 100), overflowAScrollProperties->scroll()->bounds());
EXPECT_TRUE(overflowAScrollProperties->scroll()->userScrollableHorizontal());
EXPECT_TRUE(overflowAScrollProperties->scroll()->userScrollableVertical());
const ObjectPaintProperties* overflowBScrollProperties =
overflowB->layoutObject()->objectPaintProperties();
// The overflow child's scroll node should be a child of the parent's
// (overflowA) scroll node.
EXPECT_EQ(overflowAScrollProperties->scroll(),
overflowBScrollProperties->scroll()->parent());
EXPECT_EQ(
TransformationMatrix().translate(0, -41),
overflowBScrollProperties->scroll()->scrollOffsetTranslation()->matrix());
EXPECT_EQ(IntSize(5, 3), overflowBScrollProperties->scroll()->clip());
EXPECT_EQ(IntSize(5, 100), overflowBScrollProperties->scroll()->bounds());
EXPECT_TRUE(overflowBScrollProperties->scroll()->userScrollableHorizontal());
EXPECT_TRUE(overflowBScrollProperties->scroll()->userScrollableVertical());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGRootClip) {
setBodyInnerHTML(
"<svg id='svg' xmlns='http://www.w3.org/2000/svg' width='100px' "
"height='100px'>"
" <rect width='200' height='200' fill='red' />"
"</svg>");
const ClipPaintPropertyNode* clip = getLayoutObjectByElementId("svg")
->objectPaintProperties()
->overflowClip();
EXPECT_EQ(frameContentClip(), clip->parent());
EXPECT_EQ(FloatRoundedRect(8, 8, 100, 100), clip->clipRect());
}
TEST_P(PaintPropertyTreeBuilderTest, SVGRootNoClip) {
setBodyInnerHTML(
"<svg id='svg' xmlns='http://www.w3.org/2000/svg' width='100px' "
"height='100px' style='overflow: visible'>"
" <rect width='200' height='200' fill='red' />"
"</svg>");
EXPECT_FALSE(getLayoutObjectByElementId("svg")
->objectPaintProperties()
->overflowClip());
}
TEST_P(PaintPropertyTreeBuilderTest,
ThreadedScrollingDisabledMainThreadScrollReason) {
setBodyInnerHTML(
"<style>"
" #overflowA {"
" position: absolute;"
" overflow: scroll;"
" width: 20px;"
" height: 20px;"
" }"
" .forceScroll {"
" height: 4000px;"
" }"
"</style>"
"<div id='overflowA'>"
" <div class='forceScroll'></div>"
"</div>"
"<div class='forceScroll'></div>");
Element* overflowA = document().getElementById("overflowA");
EXPECT_FALSE(frameScroll()->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kThreadedScrollingDisabled));
EXPECT_FALSE(overflowA->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kThreadedScrollingDisabled));
document().settings()->setThreadedScrollingEnabled(false);
document().view()->updateAllLifecyclePhases();
EXPECT_TRUE(frameScroll()->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kThreadedScrollingDisabled));
EXPECT_TRUE(overflowA->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kThreadedScrollingDisabled));
}
TEST_P(PaintPropertyTreeBuilderTest,
BackgroundAttachmentFixedMainThreadScrollReasonsWithNestedScrollers) {
setBodyInnerHTML(
"<style>"
" #overflowA {"
" position: absolute;"
" overflow: scroll;"
" width: 20px;"
" height: 20px;"
" }"
" #overflowB {"
" position: absolute;"
" overflow: scroll;"
" width: 5px;"
" height: 3px;"
" }"
" .backgroundAttachmentFixed {"
" background-image: url('foo');"
" background-attachment: fixed;"
" }"
" .forceScroll {"
" height: 4000px;"
" }"
"</style>"
"<div id='overflowA'>"
" <div id='overflowB' class='backgroundAttachmentFixed'>"
" <div class='forceScroll'></div>"
" </div>"
" <div class='forceScroll'></div>"
"</div>"
"<div class='forceScroll'></div>");
Element* overflowA = document().getElementById("overflowA");
Element* overflowB = document().getElementById("overflowB");
EXPECT_TRUE(frameScroll()->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_TRUE(
overflowA->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_FALSE(
overflowB->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
// Removing a main thread scrolling reason should update the entire tree.
overflowB->removeAttribute("class");
document().view()->updateAllLifecyclePhases();
EXPECT_FALSE(frameScroll()->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_FALSE(
overflowA->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_FALSE(
overflowB->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
// Adding a main thread scrolling reason should update the entire tree.
overflowB->setAttribute(HTMLNames::classAttr, "backgroundAttachmentFixed");
document().view()->updateAllLifecyclePhases();
EXPECT_TRUE(frameScroll()->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_TRUE(
overflowA->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_FALSE(
overflowB->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
}
TEST_P(PaintPropertyTreeBuilderTest,
BackgroundAttachmentFixedMainThreadScrollReasonsWithFixedScroller) {
setBodyInnerHTML(
"<style>"
" #overflowA {"
" position: absolute;"
" overflow: scroll;"
" width: 20px;"
" height: 20px;"
" }"
" #overflowB {"
" position: fixed;"
" overflow: scroll;"
" width: 5px;"
" height: 3px;"
" }"
" .backgroundAttachmentFixed {"
" background-image: url('foo');"
" background-attachment: fixed;"
" }"
" .forceScroll {"
" height: 4000px;"
" }"
"</style>"
"<div id='overflowA'>"
" <div id='overflowB' class='backgroundAttachmentFixed'>"
" <div class='forceScroll'></div>"
" </div>"
" <div class='forceScroll'></div>"
"</div>"
"<div class='forceScroll'></div>");
Element* overflowA = document().getElementById("overflowA");
Element* overflowB = document().getElementById("overflowB");
EXPECT_FALSE(
overflowA->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_FALSE(
overflowB->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_TRUE(
overflowB->layoutObject()
->objectPaintProperties()
->scroll()
->parent()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
// Removing a main thread scrolling reason should update the entire tree.
overflowB->removeAttribute("class");
document().view()->updateAllLifecyclePhases();
EXPECT_FALSE(
overflowA->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_FALSE(
overflowB->layoutObject()
->objectPaintProperties()
->scroll()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
EXPECT_FALSE(
overflowB->layoutObject()
->objectPaintProperties()
->scroll()
->parent()
->hasMainThreadScrollingReasons(
MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects));
}
} // namespace blink