| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "core/paint/PaintPropertyTreeBuilderTest.h" |
| |
| #include "core/html/HTMLIFrameElement.h" |
| #include "core/layout/LayoutTreeAsText.h" |
| #include "core/paint/ObjectPaintProperties.h" |
| #include "core/paint/PaintPropertyTreePrinter.h" |
| #include "platform/graphics/paint/GeometryMapper.h" |
| |
| namespace blink { |
| |
| void PaintPropertyTreeBuilderTest::LoadTestData(const char* file_name) { |
| String full_path = testing::BlinkRootDir(); |
| full_path.append("/Source/core/paint/test_data/"); |
| full_path.append(file_name); |
| const Vector<char> input_buffer = testing::ReadFromFile(full_path)->Copy(); |
| SetBodyInnerHTML(String(input_buffer.data(), input_buffer.size())); |
| } |
| |
| const TransformPaintPropertyNode* |
| PaintPropertyTreeBuilderTest::FramePreTranslation() { |
| LocalFrameView* frame_view = GetDocument().View(); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| return frame_view->GetLayoutView() |
| ->FirstFragment() |
| .PaintProperties() |
| ->PaintOffsetTranslation(); |
| } |
| return frame_view->PreTranslation(); |
| } |
| |
| const TransformPaintPropertyNode* |
| PaintPropertyTreeBuilderTest::FrameScrollTranslation() { |
| LocalFrameView* frame_view = GetDocument().View(); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| return frame_view->GetLayoutView() |
| ->FirstFragment() |
| .PaintProperties() |
| ->ScrollTranslation(); |
| } |
| return frame_view->ScrollTranslation(); |
| } |
| |
| const ClipPaintPropertyNode* PaintPropertyTreeBuilderTest::FrameContentClip() { |
| LocalFrameView* frame_view = GetDocument().View(); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| return frame_view->GetLayoutView() |
| ->FirstFragment() |
| .PaintProperties() |
| ->OverflowClip(); |
| } |
| return frame_view->ContentClip(); |
| } |
| |
| const ScrollPaintPropertyNode* PaintPropertyTreeBuilderTest::FrameScroll( |
| LocalFrameView* frame_view) { |
| if (!frame_view) |
| frame_view = GetDocument().View(); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| return frame_view->GetLayoutView() |
| ->FirstFragment() |
| .PaintProperties() |
| ->Scroll(); |
| } |
| return frame_view->ScrollNode(); |
| } |
| |
| const ObjectPaintProperties* |
| PaintPropertyTreeBuilderTest::PaintPropertiesForElement(const char* name) { |
| return GetDocument() |
| .getElementById(name) |
| ->GetLayoutObject() |
| ->FirstFragment() |
| .PaintProperties(); |
| } |
| |
| void PaintPropertyTreeBuilderTest::SetUp() { |
| RenderingTest::SetUp(); |
| EnableCompositing(); |
| } |
| |
| #define CHECK_VISUAL_RECT(expected, source_object, ancestor, slop_factor) \ |
| do { \ |
| if ((source_object)->HasLayer() && (ancestor)->HasLayer()) { \ |
| LayoutRect source((source_object)->LocalVisualRect()); \ |
| source.MoveBy((source_object)->FirstFragment().PaintOffset()); \ |
| auto contents_properties = \ |
| (ancestor)->FirstFragment().ContentsProperties(); \ |
| FloatClipRect actual_float_rect((FloatRect(source))); \ |
| GeometryMapper::LocalToAncestorVisualRect( \ |
| *(source_object)->FirstFragment().LocalBorderBoxProperties(), \ |
| contents_properties, actual_float_rect); \ |
| LayoutRect actual(actual_float_rect.Rect()); \ |
| actual.MoveBy(-(ancestor)->FirstFragment().PaintOffset()); \ |
| SCOPED_TRACE("GeometryMapper: "); \ |
| EXPECT_EQ(expected, actual); \ |
| } \ |
| \ |
| if (slop_factor == LayoutUnit::Max()) \ |
| break; \ |
| LayoutRect slow_path_rect = (source_object)->LocalVisualRect(); \ |
| (source_object)->MapToVisualRectInAncestorSpace(ancestor, slow_path_rect); \ |
| if (slop_factor) { \ |
| LayoutRect inflated_expected = LayoutRect(expected); \ |
| inflated_expected.Inflate(slop_factor); \ |
| SCOPED_TRACE(String::Format( \ |
| "Slow path rect: %s, Expected: %s, Inflated expected: %s", \ |
| slow_path_rect.ToString().Ascii().data(), \ |
| expected.ToString().Ascii().data(), \ |
| inflated_expected.ToString().Ascii().data())); \ |
| EXPECT_TRUE(LayoutRect(EnclosingIntRect(slow_path_rect)) \ |
| .Contains(LayoutRect(expected))); \ |
| EXPECT_TRUE(inflated_expected.Contains(slow_path_rect)); \ |
| } else { \ |
| SCOPED_TRACE("Slow path: "); \ |
| EXPECT_EQ(expected, slow_path_rect); \ |
| } \ |
| } while (0) |
| |
| #define CHECK_EXACT_VISUAL_RECT(expected, source_object, ancestor) \ |
| CHECK_VISUAL_RECT(expected, source_object, ancestor, 0) |
| |
| INSTANTIATE_TEST_CASE_P( |
| All, |
| PaintPropertyTreeBuilderTest, |
| ::testing::ValuesIn(kSlimmingPaintNonV1TestConfigurations)); |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FixedPosition) { |
| LoadTestData("fixed-position.html"); |
| |
| Element* positioned_scroll = GetDocument().getElementById("positionedScroll"); |
| positioned_scroll->setScrollTop(3); |
| Element* transformed_scroll = |
| GetDocument().getElementById("transformedScroll"); |
| transformed_scroll->setScrollTop(5); |
| |
| LocalFrameView* frame_view = GetDocument().View(); |
| frame_view->UpdateAllLifecyclePhases(); |
| |
| // target1 is a fixed-position element inside an absolute-position scrolling |
| // element. It should be attached under the viewport to skip scrolling and |
| // offset of the parent. |
| Element* target1 = GetDocument().getElementById("target1"); |
| const ObjectPaintProperties* target1_properties = |
| target1->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(FloatRoundedRect(200, 150, 100, 100), |
| target1_properties->OverflowClip()->ClipRect()); |
| // Likewise, it inherits clip from the viewport, skipping overflow clip of the |
| // scroller. |
| EXPECT_EQ(FrameContentClip(), target1_properties->OverflowClip()->Parent()); |
| // target1 should not have its own scroll node and instead should inherit |
| // positionedScroll's. |
| const ObjectPaintProperties* positioned_scroll_properties = |
| positioned_scroll->GetLayoutObject()->FirstFragment().PaintProperties(); |
| auto* positioned_scroll_translation = |
| positioned_scroll_properties->ScrollTranslation(); |
| auto* positioned_scroll_node = positioned_scroll_translation->ScrollNode(); |
| EXPECT_TRUE(positioned_scroll_node->Parent()->IsRoot()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -3), |
| positioned_scroll_translation->Matrix()); |
| EXPECT_EQ(nullptr, target1_properties->ScrollTranslation()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(200, 150, 100, 100), |
| target1->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| |
| // target2 is a fixed-position element inside a transformed scrolling element. |
| // It should be attached under the scrolled box of the transformed element. |
| Element* target2 = GetDocument().getElementById("target2"); |
| const ObjectPaintProperties* target2_properties = |
| target2->GetLayoutObject()->FirstFragment().PaintProperties(); |
| Element* scroller = GetDocument().getElementById("transformedScroll"); |
| const ObjectPaintProperties* scroller_properties = |
| scroller->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(FloatRoundedRect(200, 150, 100, 100), |
| target2_properties->OverflowClip()->ClipRect()); |
| EXPECT_EQ(scroller_properties->OverflowClip(), |
| target2_properties->OverflowClip()->Parent()); |
| // target2 should not have it's own scroll node and instead should inherit |
| // transformedScroll's. |
| const ObjectPaintProperties* transformed_scroll_properties = |
| transformed_scroll->GetLayoutObject()->FirstFragment().PaintProperties(); |
| auto* transformed_scroll_translation = |
| transformed_scroll_properties->ScrollTranslation(); |
| auto* transformed_scroll_node = transformed_scroll_translation->ScrollNode(); |
| EXPECT_TRUE(transformed_scroll_node->Parent()->IsRoot()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -5), |
| transformed_scroll_translation->Matrix()); |
| EXPECT_EQ(nullptr, target2_properties->ScrollTranslation()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(208, 153, 200, 100), |
| target2->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, PositionAndScroll) { |
| LoadTestData("position-and-scroll.html"); |
| |
| Element* scroller = GetDocument().getElementById("scroller"); |
| scroller->scrollTo(0, 100); |
| LocalFrameView* frame_view = GetDocument().View(); |
| frame_view->UpdateAllLifecyclePhases(); |
| const ObjectPaintProperties* scroller_properties = |
| scroller->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -100), |
| scroller_properties->ScrollTranslation()->Matrix()); |
| EXPECT_EQ(scroller_properties->PaintOffsetTranslation(), |
| scroller_properties->ScrollTranslation()->Parent()); |
| EXPECT_EQ(FrameScrollTranslation(), |
| scroller_properties->PaintOffsetTranslation()->Parent()); |
| EXPECT_EQ(scroller_properties->PaintOffsetTranslation(), |
| scroller_properties->OverflowClip()->LocalTransformSpace()); |
| const auto* scroll = scroller_properties->ScrollTranslation()->ScrollNode(); |
| EXPECT_EQ(FrameScroll(), scroll->Parent()); |
| EXPECT_EQ(IntRect(0, 0, 413, 317), scroll->ContainerRect()); |
| EXPECT_EQ(IntRect(0, 0, 660, 10200), scroll->ContentsRect()); |
| EXPECT_FALSE(scroll->UserScrollableHorizontal()); |
| EXPECT_TRUE(scroll->UserScrollableVertical()); |
| EXPECT_EQ(FloatSize(120, 340), scroller_properties->PaintOffsetTranslation() |
| ->Matrix() |
| .To2DTranslation()); |
| EXPECT_EQ(FloatRoundedRect(0, 0, 413, 317), |
| scroller_properties->OverflowClip()->ClipRect()); |
| EXPECT_EQ(FrameContentClip(), scroller_properties->OverflowClip()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(120, 340, 413, 317), |
| scroller->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| |
| // The relative-positioned element should have accumulated box offset (exclude |
| // scrolling), and should be affected by ancestor scroll transforms. |
| Element* rel_pos = GetDocument().getElementById("rel-pos"); |
| const ObjectPaintProperties* rel_pos_properties = |
| rel_pos->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(560, 780), |
| rel_pos_properties->PaintOffsetTranslation()->Matrix()); |
| EXPECT_EQ(scroller_properties->ScrollTranslation(), |
| rel_pos_properties->PaintOffsetTranslation()->Parent()); |
| EXPECT_EQ(rel_pos_properties->Transform(), |
| rel_pos_properties->OverflowClip()->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(0, 0, 100, 200), |
| rel_pos_properties->OverflowClip()->ClipRect()); |
| EXPECT_EQ(scroller_properties->OverflowClip(), |
| rel_pos_properties->OverflowClip()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(), rel_pos->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| |
| // The absolute-positioned element should not be affected by non-positioned |
| // scroller at all. |
| Element* abs_pos = GetDocument().getElementById("abs-pos"); |
| const ObjectPaintProperties* abs_pos_properties = |
| abs_pos->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(123, 456), |
| abs_pos_properties->PaintOffsetTranslation()->Matrix()); |
| EXPECT_EQ(FrameScrollTranslation(), |
| abs_pos_properties->PaintOffsetTranslation()->Parent()); |
| EXPECT_EQ(abs_pos_properties->Transform(), |
| abs_pos_properties->OverflowClip()->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(0, 0, 300, 400), |
| abs_pos_properties->OverflowClip()->ClipRect()); |
| EXPECT_EQ(FrameContentClip(), abs_pos_properties->OverflowClip()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(123, 456, 300, 400), |
| abs_pos->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollVerticalRL) { |
| SetBodyInnerHTML(R"HTML( |
| <style>::-webkit-scrollbar {width: 15px; height: 15px}</style> |
| <div id='scroller' |
| style='width: 100px; height: 100px; overflow: scroll; |
| writing-mode: vertical-rl; border: 10px solid blue'> |
| <div style='width: 400px; height: 400px'></div> |
| </div> |
| )HTML"); |
| |
| const auto* properties = PaintPropertiesForElement("scroller"); |
| const auto* overflow_clip = properties->OverflowClip(); |
| const auto* scroll_translation = properties->ScrollTranslation(); |
| const auto* scroll = properties->Scroll(); |
| |
| EXPECT_EQ(TransformationMatrix().Translate(-15, 0), |
| scroll_translation->Matrix()); |
| EXPECT_EQ(scroll, scroll_translation->ScrollNode()); |
| // 10: border width. 85: container client size (== 100 - scrollbar width). |
| EXPECT_EQ(IntRect(10, 10, 85, 85), scroll->ContainerRect()); |
| // The content is placed at (-290, 10) so that its right edge aligns with the |
| // right edge of the container's client box, with the initial |
| // ScrollTranslation applied. |
| EXPECT_EQ(IntRect(-290, 10, 400, 400), scroll->ContentsRect()); |
| |
| EXPECT_EQ(FrameContentClip(), overflow_clip->Parent()); |
| EXPECT_EQ(properties->PaintOffsetTranslation(), |
| overflow_clip->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(10, 10, 85, 85), overflow_clip->ClipRect()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollRTL) { |
| SetBodyInnerHTML(R"HTML( |
| <style>::-webkit-scrollbar {width: 15px; height: 15px}</style> |
| <div id='scroller' |
| style='width: 100px; height: 100px; overflow: scroll; |
| direction: rtl; border: 10px solid blue'> |
| <div style='width: 400px; height: 400px'></div> |
| </div> |
| )HTML"); |
| |
| const auto* properties = PaintPropertiesForElement("scroller"); |
| const auto* overflow_clip = properties->OverflowClip(); |
| const auto* scroll_translation = properties->ScrollTranslation(); |
| const auto* scroll = properties->Scroll(); |
| |
| EXPECT_EQ(TransformationMatrix(), scroll_translation->Matrix()); |
| EXPECT_EQ(scroll, scroll_translation->ScrollNode()); |
| // 25: border width (10) + scrollbar (on the left) width (15). |
| // 85: container client size (== 100 - scrollbar width). |
| EXPECT_EQ(IntRect(25, 10, 85, 85), scroll->ContainerRect()); |
| EXPECT_EQ(IntRect(-290, 10, 400, 400), scroll->ContentsRect()); |
| |
| EXPECT_EQ(FrameContentClip(), overflow_clip->Parent()); |
| EXPECT_EQ(properties->PaintOffsetTranslation(), |
| overflow_clip->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(25, 10, 85, 85), overflow_clip->ClipRect()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FrameScrollingTraditional) { |
| SetBodyInnerHTML("<style> body { height: 10000px; } </style>"); |
| |
| GetDocument().domWindow()->scrollTo(0, 100); |
| |
| LocalFrameView* frame_view = GetDocument().View(); |
| frame_view->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(TransformationMatrix(), FramePreTranslation()->Matrix()); |
| EXPECT_TRUE(FramePreTranslation()->Parent()->IsRoot()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -100), |
| FrameScrollTranslation()->Matrix()); |
| EXPECT_EQ(FramePreTranslation(), FrameScrollTranslation()->Parent()); |
| EXPECT_EQ(FramePreTranslation(), FrameContentClip()->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(0, 0, 800, 600), FrameContentClip()->ClipRect()); |
| EXPECT_TRUE(FrameContentClip()->Parent()->IsRoot()); |
| |
| if (!RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| // No scroll properties should be present. |
| EXPECT_EQ(nullptr, |
| frame_view->GetLayoutView()->FirstFragment().PaintProperties()); |
| } |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 784, 10000), |
| GetDocument().body()->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, Perspective) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #perspective { |
| position: absolute; |
| left: 50px; |
| top: 100px; |
| width: 400px; |
| height: 300px; |
| perspective: 100px; |
| } |
| #inner { |
| transform: translateZ(0); |
| width: 100px; |
| height: 200px; |
| } |
| </style> |
| <div id='perspective'> |
| <div id='inner'></div> |
| </div> |
| )HTML"); |
| Element* perspective = GetDocument().getElementById("perspective"); |
| const ObjectPaintProperties* perspective_properties = |
| perspective->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().ApplyPerspective(100), |
| perspective_properties->Perspective()->Matrix()); |
| // The perspective origin is the center of the border box plus accumulated |
| // paint offset. |
| EXPECT_EQ(FloatPoint3D(250, 250, 0), |
| perspective_properties->Perspective()->Origin()); |
| EXPECT_EQ(FramePreTranslation(), |
| perspective_properties->Perspective()->Parent()); |
| |
| // Adding perspective doesn't clear paint offset. The paint offset will be |
| // passed down to children. |
| Element* inner = GetDocument().getElementById("inner"); |
| const ObjectPaintProperties* inner_properties = |
| inner->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(50, 100), |
| inner_properties->PaintOffsetTranslation()->Matrix()); |
| EXPECT_EQ(perspective_properties->Perspective(), |
| inner_properties->PaintOffsetTranslation()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(50, 100, 100, 200), |
| inner->GetLayoutObject(), |
| GetDocument().View()->GetLayoutView()); |
| |
| perspective->setAttribute(HTMLNames::styleAttr, "perspective: 200px"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(TransformationMatrix().ApplyPerspective(200), |
| perspective_properties->Perspective()->Matrix()); |
| EXPECT_EQ(FloatPoint3D(250, 250, 0), |
| perspective_properties->Perspective()->Origin()); |
| EXPECT_EQ(FramePreTranslation(), |
| perspective_properties->Perspective()->Parent()); |
| |
| perspective->setAttribute(HTMLNames::styleAttr, "perspective-origin: 5% 20%"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(TransformationMatrix().ApplyPerspective(100), |
| perspective_properties->Perspective()->Matrix()); |
| EXPECT_EQ(FloatPoint3D(70, 160, 0), |
| perspective_properties->Perspective()->Origin()); |
| EXPECT_EQ(FramePreTranslation(), |
| perspective_properties->Perspective()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, Transform) { |
| SetBodyInnerHTML(R"HTML( |
| <style> body { margin: 0 } </style> |
| <div id='transform' style='margin-left: 50px; margin-top: 100px; |
| width: 400px; height: 300px; |
| transform: translate3d(123px, 456px, 789px)'> |
| </div> |
| )HTML"); |
| |
| Element* transform = GetDocument().getElementById("transform"); |
| const ObjectPaintProperties* transform_properties = |
| transform->GetLayoutObject()->FirstFragment().PaintProperties(); |
| |
| EXPECT_EQ(TransformationMatrix().Translate3d(123, 456, 789), |
| transform_properties->Transform()->Matrix()); |
| EXPECT_EQ(FloatPoint3D(200, 150, 0), |
| transform_properties->Transform()->Origin()); |
| EXPECT_EQ(transform_properties->PaintOffsetTranslation(), |
| transform_properties->Transform()->Parent()); |
| EXPECT_EQ(TransformationMatrix().Translate(50, 100), |
| transform_properties->PaintOffsetTranslation()->Matrix()); |
| EXPECT_EQ(FrameScrollTranslation(), |
| transform_properties->PaintOffsetTranslation()->Parent()); |
| |
| EXPECT_TRUE(transform_properties->Transform()->HasDirectCompositingReasons()); |
| EXPECT_FALSE(FrameScrollTranslation()->HasDirectCompositingReasons()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(173, 556, 400, 300), |
| transform->GetLayoutObject(), |
| GetDocument().View()->GetLayoutView()); |
| |
| transform->setAttribute( |
| HTMLNames::styleAttr, |
| "margin-left: 50px; margin-top: 100px; width: 400px; height: 300px;"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(nullptr, |
| transform->GetLayoutObject()->FirstFragment().PaintProperties()); |
| |
| transform->setAttribute( |
| HTMLNames::styleAttr, |
| "margin-left: 50px; margin-top: 100px; width: 400px; height: 300px; " |
| "transform: translate3d(123px, 456px, 789px)"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(123, 456, 789), |
| transform->GetLayoutObject() |
| ->FirstFragment() |
| .PaintProperties() |
| ->Transform() |
| ->Matrix()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, Preserve3D3DTransformedDescendant) { |
| SetBodyInnerHTML(R"HTML( |
| <style> body { margin: 0 } </style> |
| <div id='preserve' style='transform-style: preserve-3d'> |
| <div id='transform' style='margin-left: 50px; margin-top: 100px; |
| width: 400px; height: 300px; |
| transform: translate3d(123px, 456px, 789px)'> |
| </div> |
| </div> |
| )HTML"); |
| |
| Element* preserve = GetDocument().getElementById("preserve"); |
| const ObjectPaintProperties* preserve_properties = |
| preserve->GetLayoutObject()->FirstFragment().PaintProperties(); |
| |
| EXPECT_TRUE(preserve_properties->Transform()); |
| EXPECT_TRUE(preserve_properties->Transform()->HasDirectCompositingReasons()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, Perspective3DTransformedDescendant) { |
| SetBodyInnerHTML(R"HTML( |
| <style> body { margin: 0 } </style> |
| <div id='perspective' style='perspective: 800px;'> |
| <div id='transform' style='margin-left: 50px; margin-top: 100px; |
| width: 400px; height: 300px; |
| transform: translate3d(123px, 456px, 789px)'> |
| </div> |
| </div> |
| )HTML"); |
| |
| Element* perspective = GetDocument().getElementById("perspective"); |
| const ObjectPaintProperties* perspective_properties = |
| perspective->GetLayoutObject()->FirstFragment().PaintProperties(); |
| |
| EXPECT_TRUE(perspective_properties->Transform()); |
| EXPECT_TRUE( |
| perspective_properties->Transform()->HasDirectCompositingReasons()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| TransformNodeWithActiveAnimationHasDirectCompositingReason) { |
| LoadTestData("transform-animation.html"); |
| EXPECT_TRUE(PaintPropertiesForElement("target") |
| ->Transform() |
| ->HasDirectCompositingReasons()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| OpacityAnimationDoesNotCreateTransformNode) { |
| LoadTestData("opacity-animation.html"); |
| EXPECT_EQ(nullptr, PaintPropertiesForElement("target")->Transform()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| EffectNodeWithActiveAnimationHasDirectCompositingReason) { |
| LoadTestData("opacity-animation.html"); |
| EXPECT_TRUE(PaintPropertiesForElement("target") |
| ->Effect() |
| ->HasDirectCompositingReasons()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, WillChangeTransform) { |
| SetBodyInnerHTML(R"HTML( |
| <style> body { margin: 0 } </style> |
| <div id='transform' style='margin-left: 50px; margin-top: 100px; |
| width: 400px; height: 300px; |
| will-change: transform'> |
| </div> |
| )HTML"); |
| |
| Element* transform = GetDocument().getElementById("transform"); |
| const ObjectPaintProperties* transform_properties = |
| transform->GetLayoutObject()->FirstFragment().PaintProperties(); |
| |
| EXPECT_EQ(TransformationMatrix(), |
| transform_properties->Transform()->Matrix()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, 0), |
| transform_properties->Transform()->Matrix()); |
| // The value is zero without a transform property that needs transform-offset. |
| EXPECT_EQ(FloatPoint3D(0, 0, 0), transform_properties->Transform()->Origin()); |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| EXPECT_EQ(nullptr, transform_properties->PaintOffsetTranslation()); |
| } else { |
| // SPv175 creates PaintOffsetTranslation for composited layers. |
| EXPECT_EQ(TransformationMatrix().Translate(50, 100), |
| transform_properties->PaintOffsetTranslation()->Matrix()); |
| } |
| EXPECT_TRUE(transform_properties->Transform()->HasDirectCompositingReasons()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(50, 100, 400, 300), |
| transform->GetLayoutObject(), |
| GetDocument().View()->GetLayoutView()); |
| |
| transform->setAttribute( |
| HTMLNames::styleAttr, |
| "margin-left: 50px; margin-top: 100px; width: 400px; height: 300px;"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(nullptr, |
| transform->GetLayoutObject()->FirstFragment().PaintProperties()); |
| |
| transform->setAttribute( |
| HTMLNames::styleAttr, |
| "margin-left: 50px; margin-top: 100px; width: 400px; height: 300px; " |
| "will-change: transform"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(TransformationMatrix(), transform->GetLayoutObject() |
| ->FirstFragment() |
| .PaintProperties() |
| ->Transform() |
| ->Matrix()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, WillChangeContents) { |
| SetBodyInnerHTML(R"HTML( |
| <style> body { margin: 0 } </style> |
| <div id='transform' style='margin-left: 50px; margin-top: 100px; |
| width: 400px; height: 300px; |
| will-change: transform, contents'> |
| </div> |
| )HTML"); |
| |
| Element* transform = GetDocument().getElementById("transform"); |
| EXPECT_EQ(nullptr, |
| transform->GetLayoutObject()->FirstFragment().PaintProperties()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(50, 100, 400, 300), |
| transform->GetLayoutObject(), |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, RelativePositionInline) { |
| LoadTestData("relative-position-inline.html"); |
| |
| Element* inline_block = GetDocument().getElementById("inline-block"); |
| const ObjectPaintProperties* inline_block_properties = |
| inline_block->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(135, 490), |
| inline_block_properties->PaintOffsetTranslation()->Matrix()); |
| EXPECT_EQ(FramePreTranslation(), |
| inline_block_properties->PaintOffsetTranslation()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(135, 490, 10, 20), |
| inline_block->GetLayoutObject(), |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, NestedOpacityEffect) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='nodeWithoutOpacity' style='width: 100px; height: 200px'> |
| <div id='childWithOpacity' |
| style='opacity: 0.5; width: 50px; height: 60px;'> |
| <div id='grandChildWithoutOpacity' |
| style='width: 20px; height: 30px'> |
| <div id='greatGrandChildWithOpacity' |
| style='opacity: 0.2; width: 10px; height: 15px'></div> |
| </div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* node_without_opacity = |
| GetLayoutObjectByElementId("nodeWithoutOpacity"); |
| const auto* data_without_opacity_properties = |
| node_without_opacity->FirstFragment().PaintProperties(); |
| EXPECT_EQ(nullptr, data_without_opacity_properties); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 100, 200), node_without_opacity, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* child_with_opacity = |
| GetLayoutObjectByElementId("childWithOpacity"); |
| const ObjectPaintProperties* child_with_opacity_properties = |
| child_with_opacity->FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.5f, child_with_opacity_properties->Effect()->Opacity()); |
| // childWithOpacity is the root effect node. |
| EXPECT_NE(nullptr, child_with_opacity_properties->Effect()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), child_with_opacity, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* grand_child_without_opacity = |
| GetDocument() |
| .getElementById("grandChildWithoutOpacity") |
| ->GetLayoutObject(); |
| EXPECT_EQ(nullptr, |
| grand_child_without_opacity->FirstFragment().PaintProperties()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 20, 30), grand_child_without_opacity, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* great_grand_child_with_opacity = |
| GetDocument() |
| .getElementById("greatGrandChildWithOpacity") |
| ->GetLayoutObject(); |
| const ObjectPaintProperties* great_grand_child_with_opacity_properties = |
| great_grand_child_with_opacity->FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.2f, |
| great_grand_child_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(child_with_opacity_properties->Effect(), |
| great_grand_child_with_opacity_properties->Effect()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 15), |
| great_grand_child_with_opacity, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TransformNodeDoesNotAffectEffectNodes) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #nodeWithOpacity { |
| opacity: 0.6; |
| width: 100px; |
| height: 200px; |
| } |
| #childWithTransform { |
| transform: translate3d(10px, 10px, 0px); |
| width: 50px; |
| height: 60px; |
| } |
| #grandChildWithOpacity { |
| opacity: 0.4; |
| width: 20px; |
| height: 30px; |
| } |
| </style> |
| <div id='nodeWithOpacity'> |
| <div id='childWithTransform'> |
| <div id='grandChildWithOpacity'></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* node_with_opacity = |
| GetLayoutObjectByElementId("nodeWithOpacity"); |
| const ObjectPaintProperties* node_with_opacity_properties = |
| node_with_opacity->FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.6f, node_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, node_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_NE(nullptr, node_with_opacity_properties->Effect()->Parent()); |
| EXPECT_EQ(nullptr, node_with_opacity_properties->Transform()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 100, 200), node_with_opacity, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* child_with_transform = |
| GetLayoutObjectByElementId("childWithTransform"); |
| const ObjectPaintProperties* child_with_transform_properties = |
| child_with_transform->FirstFragment().PaintProperties(); |
| EXPECT_EQ(nullptr, child_with_transform_properties->Effect()); |
| EXPECT_EQ(TransformationMatrix().Translate(10, 10), |
| child_with_transform_properties->Transform()->Matrix()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(18, 18, 50, 60), child_with_transform, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* grand_child_with_opacity = |
| GetLayoutObjectByElementId("grandChildWithOpacity"); |
| const ObjectPaintProperties* grand_child_with_opacity_properties = |
| grand_child_with_opacity->FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.4f, grand_child_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, |
| grand_child_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(node_with_opacity_properties->Effect(), |
| grand_child_with_opacity_properties->Effect()->Parent()); |
| EXPECT_EQ(nullptr, grand_child_with_opacity_properties->Transform()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(18, 18, 20, 30), grand_child_with_opacity, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossStackingContext) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='nodeWithOpacity' |
| style='opacity: 0.6; width: 100px; height: 200px'> |
| <div id='childWithStackingContext' |
| style='position:absolute; width: 50px; height: 60px;'> |
| <div id='grandChildWithOpacity' |
| style='opacity: 0.4; width: 20px; height: 30px'></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* node_with_opacity = |
| GetLayoutObjectByElementId("nodeWithOpacity"); |
| const ObjectPaintProperties* node_with_opacity_properties = |
| node_with_opacity->FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.6f, node_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, node_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_NE(nullptr, node_with_opacity_properties->Effect()->Parent()); |
| EXPECT_EQ(nullptr, node_with_opacity_properties->Transform()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 100, 200), node_with_opacity, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* child_with_stacking_context = |
| GetDocument() |
| .getElementById("childWithStackingContext") |
| ->GetLayoutObject(); |
| const ObjectPaintProperties* child_with_stacking_context_properties = |
| child_with_stacking_context->FirstFragment().PaintProperties(); |
| EXPECT_EQ(nullptr, child_with_stacking_context_properties); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), child_with_stacking_context, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* grand_child_with_opacity = |
| GetLayoutObjectByElementId("grandChildWithOpacity"); |
| const ObjectPaintProperties* grand_child_with_opacity_properties = |
| grand_child_with_opacity->FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.4f, grand_child_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, |
| grand_child_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(node_with_opacity_properties->Effect(), |
| grand_child_with_opacity_properties->Effect()->Parent()); |
| EXPECT_EQ(nullptr, grand_child_with_opacity_properties->Transform()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 20, 30), grand_child_with_opacity, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, EffectNodesInSVG) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svgRoot'> |
| <g id='groupWithOpacity' opacity='0.6'> |
| <rect id='rectWithoutOpacity' /> |
| <rect id='rectWithOpacity' opacity='0.4' /> |
| <text id='textWithOpacity' opacity='0.2'> |
| <tspan id='tspanWithOpacity' opacity='0.1' /> |
| </text> |
| </g> |
| </svg> |
| )HTML"); |
| LayoutObject* group_with_opacity = |
| GetLayoutObjectByElementId("groupWithOpacity"); |
| const ObjectPaintProperties* group_with_opacity_properties = |
| group_with_opacity->FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.6f, group_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, group_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_NE(nullptr, group_with_opacity_properties->Effect()->Parent()); |
| |
| LayoutObject& rect_without_opacity = |
| *GetLayoutObjectByElementId("rectWithoutOpacity"); |
| const auto* rect_without_opacity_properties = |
| rect_without_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(nullptr, rect_without_opacity_properties); |
| |
| LayoutObject& rect_with_opacity = |
| *GetLayoutObjectByElementId("rectWithOpacity"); |
| const ObjectPaintProperties* rect_with_opacity_properties = |
| rect_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.4f, rect_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, rect_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(group_with_opacity_properties->Effect(), |
| rect_with_opacity_properties->Effect()->Parent()); |
| |
| // Ensure that opacity nodes are created for LayoutSVGText which inherits from |
| // LayoutSVGBlock instead of LayoutSVGModelObject. |
| LayoutObject& text_with_opacity = |
| *GetLayoutObjectByElementId("textWithOpacity"); |
| const ObjectPaintProperties* text_with_opacity_properties = |
| text_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.2f, text_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, text_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(group_with_opacity_properties->Effect(), |
| text_with_opacity_properties->Effect()->Parent()); |
| |
| // Ensure that opacity nodes are created for LayoutSVGTSpan which inherits |
| // from LayoutSVGInline instead of LayoutSVGModelObject. |
| LayoutObject& tspan_with_opacity = |
| *GetLayoutObjectByElementId("tspanWithOpacity"); |
| const ObjectPaintProperties* tspan_with_opacity_properties = |
| tspan_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.1f, tspan_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, tspan_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(text_with_opacity_properties->Effect(), |
| tspan_with_opacity_properties->Effect()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossHTMLSVGBoundary) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='divWithOpacity' style='opacity: 0.2;'> |
| <svg id='svgRootWithOpacity' style='opacity: 0.3;'> |
| <rect id='rectWithOpacity' opacity='0.4' /> |
| </svg> |
| </div> |
| )HTML"); |
| |
| LayoutObject& div_with_opacity = |
| *GetLayoutObjectByElementId("divWithOpacity"); |
| const ObjectPaintProperties* div_with_opacity_properties = |
| div_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.2f, div_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, div_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_NE(nullptr, div_with_opacity_properties->Effect()->Parent()); |
| |
| LayoutObject& svg_root_with_opacity = |
| *GetLayoutObjectByElementId("svgRootWithOpacity"); |
| const ObjectPaintProperties* svg_root_with_opacity_properties = |
| svg_root_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.3f, svg_root_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, svg_root_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(div_with_opacity_properties->Effect(), |
| svg_root_with_opacity_properties->Effect()->Parent()); |
| |
| LayoutObject& rect_with_opacity = |
| *GetLayoutObjectByElementId("rectWithOpacity"); |
| const ObjectPaintProperties* rect_with_opacity_properties = |
| rect_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.4f, rect_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, rect_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(svg_root_with_opacity_properties->Effect(), |
| rect_with_opacity_properties->Effect()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, EffectNodesAcrossSVGHTMLBoundary) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svgRootWithOpacity' style='opacity: 0.3;'> |
| <foreignObject id='foreignObjectWithOpacity' opacity='0.4'> |
| <body> |
| <span id='spanWithOpacity' style='opacity: 0.5'/> |
| </body> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg_root_with_opacity = |
| *GetLayoutObjectByElementId("svgRootWithOpacity"); |
| const ObjectPaintProperties* svg_root_with_opacity_properties = |
| svg_root_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.3f, svg_root_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, svg_root_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_NE(nullptr, svg_root_with_opacity_properties->Effect()->Parent()); |
| |
| LayoutObject& foreign_object_with_opacity = |
| *GetDocument() |
| .getElementById("foreignObjectWithOpacity") |
| ->GetLayoutObject(); |
| const ObjectPaintProperties* foreign_object_with_opacity_properties = |
| foreign_object_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.4f, foreign_object_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, |
| foreign_object_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(svg_root_with_opacity_properties->Effect(), |
| foreign_object_with_opacity_properties->Effect()->Parent()); |
| |
| LayoutObject& span_with_opacity = |
| *GetLayoutObjectByElementId("spanWithOpacity"); |
| const ObjectPaintProperties* span_with_opacity_properties = |
| span_with_opacity.FirstFragment().PaintProperties(); |
| EXPECT_EQ(0.5f, span_with_opacity_properties->Effect()->Opacity()); |
| EXPECT_EQ(nullptr, span_with_opacity_properties->Effect()->OutputClip()); |
| EXPECT_EQ(foreign_object_with_opacity_properties->Effect(), |
| span_with_opacity_properties->Effect()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TransformNodesInSVG) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0px; |
| } |
| svg { |
| margin-left: 50px; |
| transform: translate3d(1px, 2px, 3px); |
| position: absolute; |
| left: 20px; |
| top: 25px; |
| } |
| rect { |
| transform: translate(100px, 100px) rotate(45deg); |
| transform-origin: 50px 25px; |
| } |
| </style> |
| <svg id='svgRootWith3dTransform' width='100px' height='100px'> |
| <rect id='rectWith2dTransform' width='100px' height='100px' /> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg_root_with3d_transform = |
| *GetDocument() |
| .getElementById("svgRootWith3dTransform") |
| ->GetLayoutObject(); |
| const ObjectPaintProperties* svg_root_with3d_transform_properties = |
| svg_root_with3d_transform.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3), |
| svg_root_with3d_transform_properties->Transform()->Matrix()); |
| EXPECT_EQ(FloatPoint3D(50, 50, 0), |
| svg_root_with3d_transform_properties->Transform()->Origin()); |
| EXPECT_EQ(svg_root_with3d_transform_properties->PaintOffsetTranslation(), |
| svg_root_with3d_transform_properties->Transform()->Parent()); |
| EXPECT_EQ( |
| TransformationMatrix().Translate(70, 25), |
| svg_root_with3d_transform_properties->PaintOffsetTranslation()->Matrix()); |
| EXPECT_EQ( |
| FramePreTranslation(), |
| svg_root_with3d_transform_properties->PaintOffsetTranslation()->Parent()); |
| |
| LayoutObject& rect_with2d_transform = |
| *GetLayoutObjectByElementId("rectWith2dTransform"); |
| const ObjectPaintProperties* rect_with2d_transform_properties = |
| rect_with2d_transform.FirstFragment().PaintProperties(); |
| TransformationMatrix matrix; |
| matrix.Translate(100, 100); |
| matrix.Rotate(45); |
| // SVG's transform origin is baked into the transform. |
| matrix.ApplyTransformOrigin(50, 25, 0); |
| EXPECT_EQ(matrix, rect_with2d_transform_properties->Transform()->Matrix()); |
| EXPECT_EQ(FloatPoint3D(0, 0, 0), |
| rect_with2d_transform_properties->Transform()->Origin()); |
| // SVG does not use paint offset. |
| EXPECT_EQ(nullptr, |
| rect_with2d_transform_properties->PaintOffsetTranslation()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGViewBoxTransform) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0px; |
| } |
| #svgWithViewBox { |
| transform: translate3d(1px, 2px, 3px); |
| position: absolute; |
| width: 100px; |
| height: 100px; |
| } |
| #rect { |
| transform: translate(100px, 100px); |
| width: 100px; |
| height: 100px; |
| } |
| </style> |
| <svg id='svgWithViewBox' viewBox='50 50 100 100'> |
| <rect id='rect' /> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg_with_view_box = |
| *GetLayoutObjectByElementId("svgWithViewBox"); |
| const ObjectPaintProperties* svg_with_view_box_properties = |
| svg_with_view_box.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3), |
| svg_with_view_box_properties->Transform()->Matrix()); |
| EXPECT_EQ( |
| TransformationMatrix().Translate(-50, -50), |
| svg_with_view_box_properties->SvgLocalToBorderBoxTransform()->Matrix()); |
| EXPECT_EQ( |
| svg_with_view_box_properties->SvgLocalToBorderBoxTransform()->Parent(), |
| svg_with_view_box_properties->Transform()); |
| |
| LayoutObject& rect = *GetLayoutObjectByElementId("rect"); |
| const ObjectPaintProperties* rect_properties = |
| rect.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(100, 100), |
| rect_properties->Transform()->Matrix()); |
| EXPECT_EQ(svg_with_view_box_properties->SvgLocalToBorderBoxTransform(), |
| rect_properties->Transform()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGRootPaintOffsetTransformNode) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { margin: 0px; } |
| #svg { |
| margin-left: 50px; |
| margin-top: 25px; |
| width: 100px; |
| height: 100px; |
| } |
| </style> |
| <svg id='svg' /> |
| )HTML"); |
| |
| LayoutObject& svg = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_properties = |
| svg.FirstFragment().PaintProperties(); |
| EXPECT_TRUE(svg_properties->PaintOffsetTranslation()); |
| EXPECT_EQ( |
| FloatSize(50, 25), |
| svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation()); |
| EXPECT_EQ(nullptr, svg_properties->SvgLocalToBorderBoxTransform()); |
| EXPECT_EQ(FramePreTranslation(), |
| svg_properties->PaintOffsetTranslation()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGRootLocalToBorderBoxTransformNode) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { margin: 0px; } |
| svg { |
| margin-left: 2px; |
| margin-top: 3px; |
| transform: translate(5px, 7px); |
| border: 11px solid green; |
| } |
| </style> |
| <svg id='svg' width='100px' height='100px' viewBox='0 0 13 13'> |
| <rect id='rect' transform='translate(17 19)' /> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_properties = |
| svg.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(2, 3), |
| svg_properties->PaintOffsetTranslation()->Matrix()); |
| EXPECT_EQ(TransformationMatrix().Translate(5, 7), |
| svg_properties->Transform()->Matrix()); |
| EXPECT_EQ(TransformationMatrix().Translate(11, 11).Scale(100.0 / 13.0), |
| svg_properties->SvgLocalToBorderBoxTransform()->Matrix()); |
| EXPECT_EQ(svg_properties->PaintOffsetTranslation(), |
| svg_properties->Transform()->Parent()); |
| EXPECT_EQ(svg_properties->Transform(), |
| svg_properties->SvgLocalToBorderBoxTransform()->Parent()); |
| |
| // Ensure the rect's transform is a child of the local to border box |
| // transform. |
| LayoutObject& rect = *GetLayoutObjectByElementId("rect"); |
| const ObjectPaintProperties* rect_properties = |
| rect.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(17, 19), |
| rect_properties->Transform()->Matrix()); |
| EXPECT_EQ(svg_properties->SvgLocalToBorderBoxTransform(), |
| rect_properties->Transform()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGNestedViewboxTransforms) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0px; } </style> |
| <svg id='svg' width='100px' height='100px' viewBox='0 0 50 50' |
| style='transform: translate(11px, 11px);'> |
| <svg id='nestedSvg' width='50px' height='50px' viewBox='0 0 5 5'> |
| <rect id='rect' transform='translate(13 13)' /> |
| </svg> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_properties = |
| svg.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(11, 11), |
| svg_properties->Transform()->Matrix()); |
| EXPECT_EQ(TransformationMatrix().Scale(2), |
| svg_properties->SvgLocalToBorderBoxTransform()->Matrix()); |
| |
| LayoutObject& nested_svg = *GetLayoutObjectByElementId("nestedSvg"); |
| const ObjectPaintProperties* nested_svg_properties = |
| nested_svg.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Scale(10), |
| nested_svg_properties->Transform()->Matrix()); |
| EXPECT_EQ(nullptr, nested_svg_properties->SvgLocalToBorderBoxTransform()); |
| EXPECT_EQ(svg_properties->SvgLocalToBorderBoxTransform(), |
| nested_svg_properties->Transform()->Parent()); |
| |
| LayoutObject& rect = *GetLayoutObjectByElementId("rect"); |
| const ObjectPaintProperties* rect_properties = |
| rect.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(13, 13), |
| rect_properties->Transform()->Matrix()); |
| EXPECT_EQ(nested_svg_properties->Transform(), |
| rect_properties->Transform()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TransformNodesAcrossSVGHTMLBoundary) { |
| SetBodyInnerHTML(R"HTML( |
| <style> body { margin: 0px; } </style> |
| <svg id='svgWithTransform' |
| style='transform: translate3d(1px, 2px, 3px);'> |
| <foreignObject> |
| <body> |
| <div id='divWithTransform' |
| style='transform: translate3d(3px, 4px, 5px);'></div> |
| </body> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg_with_transform = |
| *GetLayoutObjectByElementId("svgWithTransform"); |
| const ObjectPaintProperties* svg_with_transform_properties = |
| svg_with_transform.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3), |
| svg_with_transform_properties->Transform()->Matrix()); |
| |
| LayoutObject& div_with_transform = |
| *GetLayoutObjectByElementId("divWithTransform"); |
| const ObjectPaintProperties* div_with_transform_properties = |
| div_with_transform.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(3, 4, 5), |
| div_with_transform_properties->Transform()->Matrix()); |
| // Ensure the div's transform node is a child of the svg's transform node. |
| EXPECT_EQ(svg_with_transform_properties->Transform(), |
| div_with_transform_properties->Transform()->Parent()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetTranslationSVGHTMLBoundary) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg' |
| <foreignObject> |
| <body> |
| <div id='divWithTransform' |
| style='transform: translate3d(3px, 4px, 5px);'></div> |
| </body> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_properties = |
| svg.FirstFragment().PaintProperties(); |
| EXPECT_EQ( |
| FloatSize(8, 8), |
| svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation()); |
| |
| LayoutObject& div_with_transform = |
| *GetLayoutObjectByElementId("divWithTransform"); |
| const ObjectPaintProperties* div_with_transform_properties = |
| div_with_transform.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(3, 4, 5), |
| div_with_transform_properties->Transform()->Matrix()); |
| EXPECT_EQ(FloatSize(8, 158), |
| div_with_transform_properties->PaintOffsetTranslation() |
| ->Matrix() |
| .To2DTranslation()); |
| EXPECT_EQ(div_with_transform_properties->PaintOffsetTranslation(), |
| div_with_transform_properties->Transform()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| PaintOffsetTranslationSVGHTMLBoundaryMulticol) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg'> |
| <foreignObject> |
| <body> |
| <div id='divWithColumns' style='columns: 2'> |
| <div style='width: 5px; height: 5px; background: blue'> |
| </div> |
| </body> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_properties = |
| svg.FirstFragment().PaintProperties(); |
| EXPECT_EQ( |
| FloatSize(8, 8), |
| svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation()); |
| LayoutObject& div_with_columns = |
| *GetLayoutObjectByElementId("divWithColumns")->SlowFirstChild(); |
| EXPECT_EQ(LayoutPoint(0, 0), div_with_columns.FirstFragment().PaintOffset()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FixedTransformAncestorAcrossSVGHTMLBoundary) { |
| SetBodyInnerHTML(R"HTML( |
| <style> body { margin: 0px; } </style> |
| <svg id='svg' style='transform: translate3d(1px, 2px, 3px);'> |
| <g id='container' transform='translate(20 30)'> |
| <foreignObject> |
| <body> |
| <div id='fixed' |
| style='position: fixed; left: 200px; top: 150px;'></div> |
| </body> |
| </foreignObject> |
| </g> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_properties = |
| svg.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3), |
| svg_properties->Transform()->Matrix()); |
| |
| LayoutObject& container = *GetLayoutObjectByElementId("container"); |
| const ObjectPaintProperties* container_properties = |
| container.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(20, 30), |
| container_properties->Transform()->Matrix()); |
| EXPECT_EQ(svg_properties->Transform(), |
| container_properties->Transform()->Parent()); |
| |
| Element* fixed = GetDocument().getElementById("fixed"); |
| // Ensure the fixed position element is rooted at the nearest transform |
| // container. |
| EXPECT_EQ(container_properties->Transform(), fixed->GetLayoutObject() |
| ->FirstFragment() |
| .LocalBorderBoxProperties() |
| ->Transform()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ControlClip) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0; |
| } |
| input { |
| border-radius: 0; |
| border-width: 5px; |
| padding: 0; |
| } |
| </style> |
| <input id='button' type='button' |
| style='width:345px; height:123px' value='some text'/> |
| )HTML"); |
| |
| LayoutObject& button = *GetLayoutObjectByElementId("button"); |
| const ObjectPaintProperties* button_properties = |
| button.FirstFragment().PaintProperties(); |
| // No scroll translation because the document does not scroll (not enough |
| // content). |
| EXPECT_TRUE(!FrameScrollTranslation()); |
| EXPECT_EQ(FramePreTranslation(), |
| button_properties->OverflowClip()->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(5, 5, 335, 113), |
| button_properties->OverflowClip()->ClipRect()); |
| EXPECT_EQ(FrameContentClip(), button_properties->OverflowClip()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 345, 123), &button, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ControlClipInsideForeignObject) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='column-count:2;'> |
| <div style='columns: 2'> |
| <svg style='width: 500px; height: 500px;'> |
| <foreignObject> |
| <input id='button' style='width:345px; height:123px' |
| value='some text'/> |
| </foreignObject> |
| </svg> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject& button = *GetLayoutObjectByElementId("button"); |
| const ObjectPaintProperties* button_properties = |
| button.FirstFragment().PaintProperties(); |
| // No scroll translation because the document does not scroll (not enough |
| // content). |
| EXPECT_TRUE(!FrameScrollTranslation()); |
| EXPECT_EQ(FloatRoundedRect(2, 2, 341, 119), |
| button_properties->OverflowClip()->ClipRect()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 345, 123), &button, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, BorderRadiusClip) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0px; |
| } |
| #div { |
| border-radius: 12px 34px 56px 78px; |
| border-top: 45px solid; |
| border-right: 50px solid; |
| border-bottom: 55px solid; |
| border-left: 60px solid; |
| width: 500px; |
| height: 400px; |
| overflow: scroll; |
| } |
| </style> |
| <div id='div'></div> |
| )HTML"); |
| |
| LayoutObject& div = *GetLayoutObjectByElementId("div"); |
| const ObjectPaintProperties* div_properties = |
| div.FirstFragment().PaintProperties(); |
| // No scroll translation because the document does not scroll (not enough |
| // content). |
| EXPECT_TRUE(!FrameScrollTranslation()); |
| EXPECT_EQ(FramePreTranslation(), |
| div_properties->OverflowClip()->LocalTransformSpace()); |
| // The overflow clip rect includes only the padding box. |
| // padding box = border box(500+60+50, 400+45+55) - border outset(60+50, |
| // 45+55) - scrollbars(15, 15) |
| EXPECT_EQ(FloatRoundedRect(60, 45, 500, 400), |
| div_properties->OverflowClip()->ClipRect()); |
| const ClipPaintPropertyNode* border_radius_clip = |
| div_properties->OverflowClip()->Parent(); |
| EXPECT_EQ(FramePreTranslation(), border_radius_clip->LocalTransformSpace()); |
| // The border radius clip is the area enclosed by inner border edge, including |
| // the scrollbars. As the border-radius is specified in outer radius, the |
| // inner radius is calculated by: |
| // inner radius = max(outer radius - border width, 0) |
| // In the case that two adjacent borders have different width, the inner |
| // radius of the corner may transition from one value to the other. i.e. being |
| // an ellipse. |
| // The following is border box(610, 500) - border outset(110, 100). |
| FloatRect border_box_minus_border_outset(60, 45, 500, 400); |
| EXPECT_EQ( |
| FloatRoundedRect( |
| border_box_minus_border_outset, |
| FloatSize(0, 0), // (top left) = max((12, 12) - (60, 45), (0, 0)) |
| FloatSize(0, 0), // (top right) = max((34, 34) - (50, 45), (0, 0)) |
| FloatSize(18, 23), // (bot left) = max((78, 78) - (60, 55), (0, 0)) |
| FloatSize(6, 1)), // (bot right) = max((56, 56) - (50, 55), (0, 0)) |
| border_radius_clip->ClipRect()); |
| EXPECT_EQ(FrameContentClip(), border_radius_clip->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 610, 500), &div, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TransformNodesAcrossSubframes) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { margin: 0; } |
| #divWithTransform { |
| transform: translate3d(1px, 2px, 3px); |
| } |
| </style> |
| <div id='divWithTransform'> |
| <iframe style='border: 7px solid black'></iframe> |
| </div> |
| )HTML"); |
| SetChildFrameHTML(R"HTML( |
| <style> |
| body { margin: 0; } |
| #innerDivWithTransform { |
| transform: translate3d(4px, 5px, 6px); |
| width: 100px; |
| height: 200px; |
| } |
| </style> |
| <div id='innerDivWithTransform'></div> |
| )HTML"); |
| |
| LocalFrameView* frame_view = GetDocument().View(); |
| frame_view->UpdateAllLifecyclePhases(); |
| |
| LayoutObject* div_with_transform = |
| GetLayoutObjectByElementId("divWithTransform"); |
| const ObjectPaintProperties* div_with_transform_properties = |
| div_with_transform->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3), |
| div_with_transform_properties->Transform()->Matrix()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(1, 2, 800, 164), div_with_transform, |
| frame_view->GetLayoutView()); |
| |
| LayoutObject* inner_div_with_transform = |
| ChildDocument() |
| .getElementById("innerDivWithTransform") |
| ->GetLayoutObject(); |
| const ObjectPaintProperties* inner_div_with_transform_properties = |
| inner_div_with_transform->FirstFragment().PaintProperties(); |
| auto* inner_div_transform = inner_div_with_transform_properties->Transform(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(4, 5, 6), |
| inner_div_transform->Matrix()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(12, 14, 100, 145), |
| inner_div_with_transform, |
| frame_view->GetLayoutView()); |
| |
| // Ensure that the inner div's transform is correctly rooted in the root |
| // frame's transform tree. |
| // This asserts that we have the following tree structure: |
| // ... |
| // Transform transform=translation=1.000000,2.000000,3.000000 |
| // PreTranslation transform=translation=7.000000,7.000000,0.000000 |
| // PaintOffsetTranslation transform=Identity |
| // ScrollTranslation transform=translation=0.000000,0.000000,0.000000 |
| // Transform transform=translation=4.000000,5.000000,6.000000 |
| auto* inner_document_scroll_translation = inner_div_transform->Parent(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(0, 0, 0), |
| inner_document_scroll_translation->Matrix()); |
| auto* paint_offset_translation = inner_document_scroll_translation->Parent(); |
| auto* iframe_pre_translation = |
| inner_document_scroll_translation->Parent()->Parent(); |
| EXPECT_EQ(FloatSize(), paint_offset_translation->Matrix().To2DTranslation()); |
| EXPECT_EQ(TransformationMatrix().Translate3d(7, 7, 0), |
| iframe_pre_translation->Matrix()); |
| EXPECT_EQ(div_with_transform_properties->Transform(), |
| iframe_pre_translation->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TransformNodesInTransformedSubframes) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { margin: 0; } |
| #divWithTransform { |
| transform: translate3d(1px, 2px, 3px); |
| } |
| iframe { |
| transform: translate3d(4px, 5px, 6px); |
| border: 42px solid; |
| margin: 7px; |
| } |
| </style> |
| <div id='divWithTransform'> |
| <iframe></iframe> |
| </div> |
| )HTML"); |
| SetChildFrameHTML(R"HTML( |
| <style> |
| body { margin: 31px; } |
| #transform { |
| transform: translate3d(7px, 8px, 9px); |
| width: 100px; |
| height: 200px; |
| } |
| </style> |
| <div id='transform'></div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| frame_view->UpdateAllLifecyclePhases(); |
| |
| // Assert that we have the following tree structure: |
| // ... |
| // Transform transform=translation=1.000000,2.000000,3.000000 |
| // PaintOffsetTranslation transform=translation=7.000000,7.000000,0.000000 |
| // Transform transform=translation=4.000000,5.000000,6.000000 |
| // PreTranslation transform=translation=42.000000,42.000000,0.000000 |
| // ScrollTranslation transform=translation=0.000000,0.000000,0.00000 |
| // PaintOffsetTranslation transform=translation=31.00,31.00,0.00 |
| // Transform transform=translation=7.000000,8.000000,9.000000 |
| |
| LayoutObject* inner_div_with_transform = |
| ChildDocument().getElementById("transform")->GetLayoutObject(); |
| auto* inner_div_transform = |
| inner_div_with_transform->FirstFragment().PaintProperties()->Transform(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(7, 8, 9), |
| inner_div_transform->Matrix()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(92, 95, 100, 111), |
| inner_div_with_transform, |
| frame_view->GetLayoutView()); |
| |
| auto* inner_document_paint_offset_translation = inner_div_transform->Parent(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(31, 31, 0), |
| inner_document_paint_offset_translation->Matrix()); |
| auto* inner_document_scroll_translation = |
| inner_document_paint_offset_translation->Parent(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(0, 0, 0), |
| inner_document_scroll_translation->Matrix()); |
| auto* iframe_pre_translation = inner_document_scroll_translation->Parent(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(42, 42, 0), |
| iframe_pre_translation->Matrix()); |
| auto* iframe_transform = iframe_pre_translation->Parent(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(4, 5, 6), |
| iframe_transform->Matrix()); |
| auto* iframe_paint_offset_translation = iframe_transform->Parent(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(7, 7, 0), |
| iframe_paint_offset_translation->Matrix()); |
| auto* div_with_transform_transform = |
| iframe_paint_offset_translation->Parent(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3), |
| div_with_transform_transform->Matrix()); |
| |
| LayoutObject* div_with_transform = |
| GetLayoutObjectByElementId("divWithTransform"); |
| EXPECT_EQ(div_with_transform_transform, |
| div_with_transform->FirstFragment().PaintProperties()->Transform()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(1, 2, 800, 248), div_with_transform, |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TreeContextClipByNonStackingContext) { |
| // This test verifies the tree builder correctly computes and records the |
| // property tree context for a (pseudo) stacking context that is scrolled by a |
| // containing block that is not one of the painting ancestors. |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0; }</style> |
| <div id='scroller' style='overflow:scroll; width:400px; height:300px;'> |
| <div id='child' |
| style='position:relative; width:100px; height: 200px;'></div> |
| <div style='height:10000px;'></div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* scroller = GetLayoutObjectByElementId("scroller"); |
| const ObjectPaintProperties* scroller_properties = |
| scroller->FirstFragment().PaintProperties(); |
| LayoutObject* child = GetLayoutObjectByElementId("child"); |
| |
| EXPECT_EQ(scroller_properties->OverflowClip(), |
| child->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(scroller_properties->ScrollTranslation(), |
| child->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_NE(nullptr, |
| child->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 400, 300), scroller, |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 100, 200), child, |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| TreeContextUnclipFromParentStackingContext) { |
| // This test verifies the tree builder correctly computes and records the |
| // property tree context for a (pseudo) stacking context that has a scrolling |
| // painting ancestor that is not its containing block (thus should not be |
| // scrolled by it). |
| |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { margin: 0; } |
| #scroller { |
| overflow:scroll; |
| opacity:0.5; |
| } |
| #child { |
| position:absolute; |
| left:0; |
| top:0; |
| width: 100px; |
| height: 200px; |
| } |
| </style> |
| <div id='scroller'> |
| <div id='child'></div> |
| <div id='forceScroll' style='height:10000px;'></div> |
| </div> |
| )HTML"); |
| |
| auto& scroller = *GetLayoutObjectByElementId("scroller"); |
| const ObjectPaintProperties* scroller_properties = |
| scroller.FirstFragment().PaintProperties(); |
| LayoutObject& child = *GetLayoutObjectByElementId("child"); |
| |
| EXPECT_EQ(FrameContentClip(), |
| child.FirstFragment().LocalBorderBoxProperties()->Clip()); |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| EXPECT_EQ(FrameScrollTranslation(), |
| child.FirstFragment().LocalBorderBoxProperties()->Transform()); |
| } else { |
| // For SPv1*, |child| is composited so we created PaintOffsetTranslation. |
| EXPECT_EQ(child.FirstFragment().PaintProperties()->PaintOffsetTranslation(), |
| child.FirstFragment().LocalBorderBoxProperties()->Transform()); |
| } |
| EXPECT_EQ(scroller_properties->Effect(), |
| child.FirstFragment().LocalBorderBoxProperties()->Effect()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 800, 10000), &scroller, |
| GetDocument().View()->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 100, 200), &child, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TableCellLayoutLocation) { |
| // This test verifies that the border box space of a table cell is being |
| // correctly computed. Table cells have weird location adjustment in our |
| // layout/paint implementation. |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0; |
| } |
| table { |
| border-spacing: 0; |
| margin: 20px; |
| padding: 40px; |
| border: 10px solid black; |
| } |
| td { |
| width: 100px; |
| height: 100px; |
| padding: 0; |
| } |
| #target { |
| position: relative; |
| width: 100px; |
| height: 100px; |
| } |
| </style> |
| <table> |
| <tr><td></td><td></td></tr> |
| <tr><td></td><td><div id='target'></div></td></tr> |
| </table> |
| )HTML"); |
| |
| LayoutObject& target = *GetLayoutObjectByElementId("target"); |
| EXPECT_EQ(LayoutPoint(170, 170), target.FirstFragment().PaintOffset()); |
| EXPECT_EQ(FramePreTranslation(), |
| target.FirstFragment().LocalBorderBoxProperties()->Transform()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(170, 170, 100, 100), &target, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, CSSClipFixedPositionDescendant) { |
| // This test verifies that clip tree hierarchy being generated correctly for |
| // the hard case such that a fixed position element getting clipped by an |
| // absolute position CSS clip. |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #clip { |
| position: absolute; |
| left: 123px; |
| top: 456px; |
| clip: rect(10px, 80px, 70px, 40px); |
| width: 100px; |
| height: 100px; |
| } |
| #fixed { |
| position: fixed; |
| left: 654px; |
| top: 321px; |
| width: 10px; |
| height: 20px |
| } |
| </style> |
| <div id='clip'><div id='fixed'></div></div> |
| )HTML"); |
| LayoutRect local_clip_rect(40, 10, 40, 60); |
| LayoutRect absolute_clip_rect = local_clip_rect; |
| absolute_clip_rect.Move(123, 456); |
| |
| LayoutObject& clip = *GetLayoutObjectByElementId("clip"); |
| const ObjectPaintProperties* clip_properties = |
| clip.FirstFragment().PaintProperties(); |
| EXPECT_EQ(FrameContentClip(), clip_properties->CssClip()->Parent()); |
| EXPECT_EQ(FramePreTranslation(), |
| clip_properties->CssClip()->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)), |
| clip_properties->CssClip()->ClipRect()); |
| CHECK_VISUAL_RECT(absolute_clip_rect, &clip, |
| GetDocument().View()->GetLayoutView(), |
| // TODO(crbug.com/599939): mapToVisualRectInAncestorSpace() |
| // doesn't apply css clip on the object itself. |
| LayoutUnit::Max()); |
| |
| LayoutObject* fixed = GetLayoutObjectByElementId("fixed"); |
| EXPECT_EQ(clip_properties->CssClip(), |
| fixed->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(FramePreTranslation(), |
| fixed->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(LayoutPoint(654, 321), fixed->FirstFragment().PaintOffset()); |
| CHECK_VISUAL_RECT(LayoutRect(), fixed, GetDocument().View()->GetLayoutView(), |
| // TODO(crbug.com/599939): CSS clip of fixed-position |
| // descendants is broken in |
| // mapToVisualRectInAncestorSpace(). |
| LayoutUnit::Max()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, CSSClipAbsPositionDescendant) { |
| // This test verifies that clip tree hierarchy being generated correctly for |
| // the hard case such that a fixed position element getting clipped by an |
| // absolute position CSS clip. |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #clip { |
| position: absolute; |
| left: 123px; |
| top: 456px; |
| clip: rect(10px, 80px, 70px, 40px); |
| width: 100px; |
| height: 100px; |
| } |
| #absolute { |
| position: absolute; |
| left: 654px; |
| top: 321px; |
| width: 10px; |
| heght: 20px |
| } |
| </style> |
| <div id='clip'><div id='absolute'></div></div> |
| )HTML"); |
| |
| LayoutRect local_clip_rect(40, 10, 40, 60); |
| LayoutRect absolute_clip_rect = local_clip_rect; |
| absolute_clip_rect.Move(123, 456); |
| |
| auto* clip = GetLayoutObjectByElementId("clip"); |
| const ObjectPaintProperties* clip_properties = |
| clip->FirstFragment().PaintProperties(); |
| EXPECT_EQ(FrameContentClip(), clip_properties->CssClip()->Parent()); |
| // No scroll translation because the document does not scroll (not enough |
| // content). |
| EXPECT_TRUE(!FrameScrollTranslation()); |
| EXPECT_EQ(FramePreTranslation(), |
| clip_properties->CssClip()->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)), |
| clip_properties->CssClip()->ClipRect()); |
| CHECK_VISUAL_RECT(absolute_clip_rect, clip, |
| GetDocument().View()->GetLayoutView(), |
| // TODO(crbug.com/599939): mapToVisualRectInAncestorSpace() |
| // doesn't apply css clip on the object itself. |
| LayoutUnit::Max()); |
| |
| auto* absolute = GetLayoutObjectByElementId("absolute"); |
| EXPECT_EQ(clip_properties->CssClip(), |
| absolute->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(FramePreTranslation(), |
| absolute->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(LayoutPoint(777, 777), absolute->FirstFragment().PaintOffset()); |
| CHECK_VISUAL_RECT(LayoutRect(), absolute, |
| GetDocument().View()->GetLayoutView(), |
| // TODO(crbug.com/599939): CSS clip of fixed-position |
| // descendants is broken in |
| // mapToVisualRectInAncestorSpace(). |
| LayoutUnit::Max()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, CSSClipFixedPositionDescendantNonShared) { |
| // This test is similar to CSSClipFixedPositionDescendant above, except that |
| // now we have a parent overflow clip that should be escaped by the fixed |
| // descendant. |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0; |
| } |
| #overflow { |
| position: relative; |
| width: 50px; |
| height: 50px; |
| overflow: scroll; |
| } |
| #clip { |
| position: absolute; |
| left: 123px; |
| top: 456px; |
| clip: rect(10px, 80px, 70px, 40px); |
| width: 100px; |
| height: 100px; |
| } |
| #fixed { |
| position: fixed; |
| left: 654px; |
| top: 321px; |
| } |
| </style> |
| <div id='overflow'><div id='clip'><div id='fixed'></div></div></div> |
| )HTML"); |
| LayoutRect local_clip_rect(40, 10, 40, 60); |
| LayoutRect absolute_clip_rect = local_clip_rect; |
| absolute_clip_rect.Move(123, 456); |
| |
| LayoutObject& overflow = *GetLayoutObjectByElementId("overflow"); |
| const ObjectPaintProperties* overflow_properties = |
| overflow.FirstFragment().PaintProperties(); |
| EXPECT_EQ(FrameContentClip(), overflow_properties->OverflowClip()->Parent()); |
| // No scroll translation because the document does not scroll (not enough |
| // content). |
| EXPECT_TRUE(!FrameScrollTranslation()); |
| EXPECT_EQ(FramePreTranslation(), |
| overflow_properties->ScrollTranslation()->Parent()->Parent()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 50, 50), &overflow, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* clip = GetLayoutObjectByElementId("clip"); |
| const ObjectPaintProperties* clip_properties = |
| clip->FirstFragment().PaintProperties(); |
| EXPECT_EQ(overflow_properties->OverflowClip(), |
| clip_properties->CssClip()->Parent()); |
| EXPECT_EQ(overflow_properties->ScrollTranslation(), |
| clip_properties->CssClip()->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)), |
| clip_properties->CssClip()->ClipRect()); |
| EXPECT_EQ(FrameContentClip(), |
| clip_properties->CssClipFixedPosition()->Parent()); |
| EXPECT_EQ(overflow_properties->ScrollTranslation(), |
| clip_properties->CssClipFixedPosition()->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(FloatRect(absolute_clip_rect)), |
| clip_properties->CssClipFixedPosition()->ClipRect()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(), clip, |
| GetDocument().View()->GetLayoutView()); |
| |
| LayoutObject* fixed = GetLayoutObjectByElementId("fixed"); |
| EXPECT_EQ(clip_properties->CssClipFixedPosition(), |
| fixed->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(FramePreTranslation(), |
| fixed->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(LayoutPoint(654, 321), fixed->FirstFragment().PaintOffset()); |
| CHECK_VISUAL_RECT(LayoutRect(), fixed, GetDocument().View()->GetLayoutView(), |
| // TODO(crbug.com/599939): CSS clip of fixed-position |
| // descendants is broken in geometry mapping. |
| LayoutUnit::Max()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ColumnSpannerUnderRelativePositioned) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #spanner { |
| column-span: all; |
| opacity: 0.5; |
| width: 100px; |
| height: 100px; |
| } |
| </style> |
| <div style='columns: 3; position: absolute; top: 44px; left: 55px;'> |
| <div style='position: relative; top: 100px; left: 100px'> |
| <div id='spanner'></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* spanner = GetLayoutObjectByElementId("spanner"); |
| EXPECT_EQ(LayoutPoint(55, 44), spanner->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(55, 44, 100, 100), spanner, |
| GetDocument().View()->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FractionalPaintOffset) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { margin: 0; } |
| div { position: absolute; } |
| #a { |
| width: 70px; |
| height: 70px; |
| left: 0.1px; |
| top: 0.3px; |
| } |
| #b { |
| width: 40px; |
| height: 40px; |
| left: 0.5px; |
| top: 11.1px; |
| } |
| </style> |
| <div id='a'> |
| <div id='b'></div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* a = GetLayoutObjectByElementId("a"); |
| LayoutPoint a_paint_offset = LayoutPoint(FloatPoint(0.1, 0.3)); |
| EXPECT_EQ(a_paint_offset, a->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.1), LayoutUnit(0.3), |
| LayoutUnit(70), LayoutUnit(70)), |
| a, frame_view->GetLayoutView()); |
| |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| LayoutPoint b_paint_offset = |
| a_paint_offset + LayoutPoint(FloatPoint(0.5, 11.1)); |
| EXPECT_EQ(b_paint_offset, b->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.1), LayoutUnit(0.3), |
| LayoutUnit(70), LayoutUnit(70)), |
| a, frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetWithBasicPixelSnapping) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { margin: 0; } |
| div { position: relative; } |
| #a { |
| width: 70px; |
| height: 70px; |
| left: 0.3px; |
| top: 0.3px; |
| } |
| #b { |
| width: 40px; |
| height: 40px; |
| transform: translateZ(0); |
| } |
| #c { |
| width: 40px; |
| height: 40px; |
| left: 0.1px; |
| top: 0.1px; |
| } |
| </style> |
| <div id='a'> |
| <div id='b'> |
| <div id='c'></div> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(0, 0, 0), |
| b_properties->Transform()->Matrix()); |
| // The paint offset transform should be snapped from (0.3,0.3) to (0,0). |
| EXPECT_EQ(TransformationMatrix().Translate(0, 0), |
| b_properties->Transform()->Parent()->Matrix()); |
| // The residual subpixel adjustment should be (0.3,0.3) - (0,0) = (0.3,0.3). |
| LayoutPoint subpixel_accumulation = LayoutPoint(FloatPoint(0.3, 0.3)); |
| EXPECT_EQ(subpixel_accumulation, b->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(FloatRect(0.3, 0.3, 40, 40)), b, |
| frame_view->GetLayoutView()); |
| |
| // c's painted should start at subpixelAccumulation + (0.1,0.1) = (0.4,0.4). |
| LayoutObject* c = GetLayoutObjectByElementId("c"); |
| LayoutPoint c_paint_offset = |
| subpixel_accumulation + LayoutPoint(FloatPoint(0.1, 0.1)); |
| EXPECT_EQ(c_paint_offset, c->FirstFragment().PaintOffset()); |
| // Visual rects via the non-paint properties system use enclosingIntRect |
| // before applying transforms, because they are computed bottom-up and |
| // therefore can't apply pixel snapping. Therefore apply a slop of 1px. |
| CHECK_VISUAL_RECT(LayoutRect(FloatRect(0.4, 0.4, 40, 40)), c, |
| frame_view->GetLayoutView(), 1); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| PaintOffsetWithPixelSnappingThroughTransform) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { margin: 0; } |
| div { position: relative; } |
| #a { |
| width: 70px; |
| height: 70px; |
| left: 0.7px; |
| top: 0.7px; |
| } |
| #b { |
| width: 40px; |
| height: 40px; |
| transform: translateZ(0); |
| } |
| #c { |
| width: 40px; |
| height: 40px; |
| left: 0.7px; |
| top: 0.7px; |
| } |
| </style> |
| <div id='a'> |
| <div id='b'> |
| <div id='c'></div> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(0, 0, 0), |
| b_properties->Transform()->Matrix()); |
| // The paint offset transform should be snapped from (0.7,0.7) to (1,1). |
| EXPECT_EQ(TransformationMatrix().Translate(1, 1), |
| b_properties->Transform()->Parent()->Matrix()); |
| // The residual subpixel adjustment should be (0.7,0.7) - (1,1) = (-0.3,-0.3). |
| LayoutPoint subpixel_accumulation = |
| LayoutPoint(LayoutPoint(FloatPoint(0.7, 0.7)) - LayoutPoint(1, 1)); |
| EXPECT_EQ(subpixel_accumulation, b->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0.7), |
| LayoutUnit(40), LayoutUnit(40)), |
| b, frame_view->GetLayoutView()); |
| |
| // c's painting should start at subpixelAccumulation + (0.7,0.7) = (0.4,0.4). |
| LayoutObject* c = GetLayoutObjectByElementId("c"); |
| LayoutPoint c_paint_offset = |
| subpixel_accumulation + LayoutPoint(FloatPoint(0.7, 0.7)); |
| EXPECT_EQ(c_paint_offset, c->FirstFragment().PaintOffset()); |
| // Visual rects via the non-paint properties system use enclosingIntRect |
| // before applying transforms, because they are computed bottom-up and |
| // therefore can't apply pixel snapping. Therefore apply a slop of 1px. |
| CHECK_VISUAL_RECT(LayoutRect(LayoutUnit(0.7) + LayoutUnit(0.7), |
| LayoutUnit(0.7) + LayoutUnit(0.7), |
| LayoutUnit(40), LayoutUnit(40)), |
| c, frame_view->GetLayoutView(), 1); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| NonTranslationTransformShouldResetSubpixelPaintOffset) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { margin: 0; } |
| div { position: relative; } |
| #a { |
| width: 70px; |
| height: 70px; |
| left: 0.9px; |
| top: 0.9px; |
| } |
| #b { |
| width: 40px; |
| height: 40px; |
| transform: scale(10); |
| transform-origin: 0 0; |
| } |
| #c { |
| width: 40px; |
| height: 40px; |
| left: 0.6px; |
| top: 0.6px; |
| } |
| </style> |
| <div id='a'> |
| <div id='b'> |
| <div id='c'></div> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Scale(10), |
| b_properties->Transform()->Matrix()); |
| // The paint offset transform should not be snapped. |
| EXPECT_EQ(TransformationMatrix().Translate(1, 1), |
| b_properties->Transform()->Parent()->Matrix()); |
| EXPECT_EQ(LayoutPoint(), b->FirstFragment().PaintOffset()); |
| // Visual rects via the non-paint properties system use enclosingIntRect |
| // before applying transforms, because they are computed bottom-up and |
| // therefore can't apply pixel snapping. Therefore apply a slop of 1px. |
| CHECK_VISUAL_RECT(LayoutRect(LayoutUnit(1), LayoutUnit(1), LayoutUnit(400), |
| LayoutUnit(400)), |
| b, frame_view->GetLayoutView(), 1); |
| |
| // c's painting should start at c_offset. |
| LayoutObject* c = GetLayoutObjectByElementId("c"); |
| LayoutUnit c_offset = LayoutUnit(0.6); |
| EXPECT_EQ(LayoutPoint(c_offset, c_offset), c->FirstFragment().PaintOffset()); |
| // Visual rects via the non-paint properties system use enclosingIntRect |
| // before applying transforms, because they are computed bottom-up and |
| // therefore can't apply pixel snapping. Therefore apply a slop of 1px |
| // in the transformed space (c_offset * 10 in view space) and 1px in the |
| // view space. |
| CHECK_VISUAL_RECT(LayoutRect(c_offset * 10 + 1, c_offset * 10 + 1, |
| LayoutUnit(400), LayoutUnit(400)), |
| c, frame_view->GetLayoutView(), c_offset * 10 + 1); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| PaintOffsetWithPixelSnappingThroughMultipleTransforms) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { margin: 0; } |
| div { position: relative; } |
| #a { |
| width: 70px; |
| height: 70px; |
| left: 0.7px; |
| top: 0.7px; |
| } |
| #b { |
| width: 40px; |
| height: 40px; |
| transform: translate3d(5px, 7px, 0); |
| } |
| #c { |
| width: 40px; |
| height: 40px; |
| transform: translate3d(11px, 13px, 0); |
| } |
| #d { |
| width: 40px; |
| height: 40px; |
| left: 0.7px; |
| top: 0.7px; |
| } |
| </style> |
| <div id='a'> |
| <div id='b'> |
| <div id='c'> |
| <div id='d'></div> |
| </div> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(5, 7, 0), |
| b_properties->Transform()->Matrix()); |
| // The paint offset transform should be snapped from (0.7,0.7) to (1,1). |
| EXPECT_EQ(TransformationMatrix().Translate(1, 1), |
| b_properties->Transform()->Parent()->Matrix()); |
| // The residual subpixel adjustment should be (0.7,0.7) - (1,1) = (-0.3,-0.3). |
| LayoutPoint subpixel_accumulation = |
| LayoutPoint(LayoutPoint(FloatPoint(0.7, 0.7)) - LayoutPoint(1, 1)); |
| EXPECT_EQ(subpixel_accumulation, b->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(5.7), LayoutUnit(7.7), |
| LayoutUnit(40), LayoutUnit(40)), |
| b, frame_view->GetLayoutView()); |
| |
| LayoutObject* c = GetLayoutObjectByElementId("c"); |
| const ObjectPaintProperties* c_properties = |
| c->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(11, 13, 0), |
| c_properties->Transform()->Matrix()); |
| // The paint offset should be (-0.3,-0.3) but the paint offset transform |
| // should still be at (0,0) because it should be snapped. |
| EXPECT_EQ(TransformationMatrix().Translate(0, 0), |
| c_properties->Transform()->Parent()->Matrix()); |
| // The residual subpixel adjustment should still be (-0.3,-0.3). |
| EXPECT_EQ(subpixel_accumulation, c->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(16.7), LayoutUnit(20.7), |
| LayoutUnit(40), LayoutUnit(40)), |
| c, frame_view->GetLayoutView()); |
| |
| // d should be painted starting at subpixelAccumulation + (0.7,0.7) = |
| // (0.4,0.4). |
| LayoutObject* d = GetLayoutObjectByElementId("d"); |
| LayoutPoint d_paint_offset = |
| subpixel_accumulation + LayoutPoint(FloatPoint(0.7, 0.7)); |
| EXPECT_EQ(d_paint_offset, d->FirstFragment().PaintOffset()); |
| // Visual rects via the non-paint properties system use enclosingIntRect |
| // before applying transforms, because they are computed bottom-up and |
| // therefore can't apply pixel snapping. Therefore apply a slop of 1px. |
| CHECK_VISUAL_RECT(LayoutRect(LayoutUnit(16.7) + LayoutUnit(0.7), |
| LayoutUnit(20.7) + LayoutUnit(0.7), |
| LayoutUnit(40), LayoutUnit(40)), |
| d, frame_view->GetLayoutView(), 1); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetWithPixelSnappingWithFixedPos) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { margin: 0; } |
| #a { |
| width: 70px; |
| height: 70px; |
| left: 0.7px; |
| position: relative; |
| } |
| #b { |
| width: 40px; |
| height: 40px; |
| transform: translateZ(0); |
| position: relative; |
| } |
| #fixed { |
| width: 40px; |
| height: 40px; |
| position: fixed; |
| } |
| #d { |
| width: 40px; |
| height: 40px; |
| left: 0.7px; |
| position: relative; |
| } |
| </style> |
| <div id='a'> |
| <div id='b'> |
| <div id='fixed'> |
| <div id='d'></div> |
| </div> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(0, 0), |
| b_properties->Transform()->Matrix()); |
| // The paint offset transform should be snapped from (0.7,0) to (1,0). |
| EXPECT_EQ(TransformationMatrix().Translate(1, 0), |
| b_properties->Transform()->Parent()->Matrix()); |
| // The residual subpixel adjustment should be (0.7,0) - (1,0) = (-0.3,0). |
| LayoutPoint subpixel_accumulation = |
| LayoutPoint(LayoutPoint(FloatPoint(0.7, 0)) - LayoutPoint(1, 0)); |
| EXPECT_EQ(subpixel_accumulation, b->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0), |
| LayoutUnit(40), LayoutUnit(40)), |
| b, frame_view->GetLayoutView()); |
| |
| LayoutObject* fixed = GetLayoutObjectByElementId("fixed"); |
| // The residual subpixel adjustment should still be (-0.3,0). |
| EXPECT_EQ(subpixel_accumulation, fixed->FirstFragment().PaintOffset()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(LayoutUnit(0.7), LayoutUnit(0), |
| LayoutUnit(40), LayoutUnit(40)), |
| fixed, frame_view->GetLayoutView()); |
| |
| // d should be painted starting at subpixelAccumulation + (0.7,0) = (0.4,0). |
| LayoutObject* d = GetLayoutObjectByElementId("d"); |
| LayoutPoint d_paint_offset = |
| subpixel_accumulation + LayoutPoint(FloatPoint(0.7, 0)); |
| EXPECT_EQ(d_paint_offset, d->FirstFragment().PaintOffset()); |
| // Visual rects via the non-paint properties system use enclosingIntRect |
| // before applying transforms, because they are computed bottom-up and |
| // therefore can't apply pixel snapping. Therefore apply a slop of 1px. |
| CHECK_VISUAL_RECT(LayoutRect(LayoutUnit(0.7) + LayoutUnit(0.7), LayoutUnit(), |
| LayoutUnit(40), LayoutUnit(40)), |
| d, frame_view->GetLayoutView(), 1); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SvgPixelSnappingShouldResetPaintOffset) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #svg { |
| position: relative; |
| left: 0.1px; |
| transform: matrix(1, 0, 0, 1, 0, 0); |
| } |
| </style> |
| <svg id='svg'> |
| <rect id='rect' transform='translate(1, 1)'/> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg_with_transform = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_with_transform_properties = |
| svg_with_transform.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix(), |
| svg_with_transform_properties->Transform()->Matrix()); |
| EXPECT_EQ(LayoutPoint(FloatPoint(0.1, 0)), |
| svg_with_transform.FirstFragment().PaintOffset()); |
| EXPECT_TRUE(svg_with_transform_properties->SvgLocalToBorderBoxTransform() == |
| nullptr); |
| |
| LayoutObject& rect_with_transform = *GetLayoutObjectByElementId("rect"); |
| const ObjectPaintProperties* rect_with_transform_properties = |
| rect_with_transform.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(1, 1), |
| rect_with_transform_properties->Transform()->Matrix()); |
| |
| // Ensure there is no PaintOffset transform between the rect and the svg's |
| // transform. |
| EXPECT_EQ(svg_with_transform_properties->Transform(), |
| rect_with_transform_properties->Transform()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SvgRootAndForeignObjectPixelSnapping) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id=svg style='position: relative; left: 0.6px; top: 0.3px'> |
| <foreignObject id=foreign x='3.5' y='5.4' transform='translate(1, 1)'> |
| <div id=div style='position: absolute; left: 5.6px; top: 7.3px'> |
| </div> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| const auto* svg = GetLayoutObjectByElementId("svg"); |
| const auto* svg_properties = svg->FirstFragment().PaintProperties(); |
| // The paint offset of (8.6, 8.3) is rounded off here. The fractional part |
| // remains PaintOffset. |
| EXPECT_EQ( |
| FloatSize(9, 8), |
| svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation()); |
| EXPECT_EQ(LayoutPoint(LayoutUnit(-0.40625), LayoutUnit(0.3)), |
| svg->FirstFragment().PaintOffset()); |
| EXPECT_EQ(nullptr, svg_properties->SvgLocalToBorderBoxTransform()); |
| const auto* foreign_object = GetLayoutObjectByElementId("foreign"); |
| const auto* foreign_object_properties = |
| foreign_object->FirstFragment().PaintProperties(); |
| EXPECT_EQ(nullptr, foreign_object_properties->PaintOffsetTranslation()); |
| // Paint offset of foreignObject should be originated from SVG root and |
| // snapped to pixels. |
| EXPECT_EQ(LayoutPoint(4, 5), foreign_object->FirstFragment().PaintOffset()); |
| |
| const auto* div = GetLayoutObjectByElementId("div"); |
| // Paint offset of descendant of foreignObject accumulates on paint offset of |
| // foreignObject. |
| EXPECT_EQ(LayoutPoint(LayoutUnit(4 + 5.6), LayoutUnit(5 + 7.3)), |
| div->FirstFragment().PaintOffset()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, NoRenderingContextByDefault) { |
| SetBodyInnerHTML("<div style='transform: translateZ(0)'></div>"); |
| |
| const ObjectPaintProperties* properties = GetDocument() |
| .body() |
| ->firstChild() |
| ->GetLayoutObject() |
| ->FirstFragment() |
| .PaintProperties(); |
| ASSERT_TRUE(properties->Transform()); |
| EXPECT_FALSE(properties->Transform()->HasRenderingContext()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, Preserve3DCreatesSharedRenderingContext) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='transform-style: preserve-3d'> |
| <div id='a' style='transform: translateZ(0); width: 30px; height: 40px'> |
| </div> |
| <div id='b' style='transform: translateZ(0); width: 20px; height: 10px'> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* a = GetLayoutObjectByElementId("a"); |
| const ObjectPaintProperties* a_properties = |
| a->FirstFragment().PaintProperties(); |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| ASSERT_TRUE(a_properties->Transform() && b_properties->Transform()); |
| EXPECT_NE(a_properties->Transform(), b_properties->Transform()); |
| EXPECT_TRUE(a_properties->Transform()->HasRenderingContext()); |
| EXPECT_TRUE(b_properties->Transform()->HasRenderingContext()); |
| EXPECT_EQ(a_properties->Transform()->RenderingContextId(), |
| b_properties->Transform()->RenderingContextId()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 48, 20, 10), b, |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FlatTransformStyleEndsRenderingContext) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #a { |
| transform: translateZ(0); |
| width: 30px; |
| height: 40px; |
| } |
| #b { |
| transform: translateZ(0); |
| width: 10px; |
| height: 20px; |
| } |
| </style> |
| <div style='transform-style: preserve-3d'> |
| <div id='a'> |
| <div id='b'></div> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* a = GetLayoutObjectByElementId("a"); |
| const ObjectPaintProperties* a_properties = |
| a->FirstFragment().PaintProperties(); |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| ASSERT_FALSE(a->StyleRef().Preserves3D()); |
| |
| ASSERT_TRUE(a_properties->Transform() && b_properties->Transform()); |
| |
| // #a should participate in a rendering context (due to its parent), but its |
| // child #b should not. |
| EXPECT_TRUE(a_properties->Transform()->HasRenderingContext()); |
| EXPECT_FALSE(b_properties->Transform()->HasRenderingContext()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 20), b, |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, NestedRenderingContexts) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='transform-style: preserve-3d'> |
| <div id='a' style='transform: translateZ(0); width: 50px; height: 60px'> |
| <div style='transform-style: preserve-3d; width: 30px; height: 40px'> |
| <div id='b' |
| style='transform: translateZ(0); width: 10px; height: 20px'> |
| </div> |
| </div> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* a = GetLayoutObjectByElementId("a"); |
| const ObjectPaintProperties* a_properties = |
| a->FirstFragment().PaintProperties(); |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| ASSERT_FALSE(a->StyleRef().Preserves3D()); |
| ASSERT_TRUE(a_properties->Transform() && b_properties->Transform()); |
| |
| // #a should participate in a rendering context (due to its parent). Its |
| // child does preserve 3D, but since #a does not, #a's rendering context is |
| // not passed on to its children. Thus #b ends up in a separate rendering |
| // context rooted at its parent. |
| EXPECT_TRUE(a_properties->Transform()->HasRenderingContext()); |
| EXPECT_TRUE(b_properties->Transform()->HasRenderingContext()); |
| EXPECT_NE(a_properties->Transform()->RenderingContextId(), |
| b_properties->Transform()->RenderingContextId()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 50, 60), a, |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 20), b, |
| frame_view->GetLayoutView()); |
| } |
| |
| // Returns true if the first node has the second as an ancestor. |
| static bool NodeHasAncestor(const TransformPaintPropertyNode* node, |
| const TransformPaintPropertyNode* ancestor) { |
| while (node) { |
| if (node == ancestor) |
| return true; |
| node = node->Parent(); |
| } |
| return false; |
| } |
| |
| // Returns true if some node will flatten the transform due to |node| before it |
| // is inherited by |node| (including if node->flattensInheritedTransform()). |
| static bool SomeNodeFlattensTransform( |
| const TransformPaintPropertyNode* node, |
| const TransformPaintPropertyNode* ancestor) { |
| while (node != ancestor) { |
| if (node->FlattensInheritedTransform()) |
| return true; |
| node = node->Parent(); |
| } |
| return false; |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FlatTransformStylePropagatesToChildren) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #a { |
| transform: translateZ(0); |
| transform-style: flat; |
| width: 30px; |
| height: 40px; |
| } |
| #b { |
| transform: translateZ(0); |
| width: 10px; |
| height: 10px; |
| } |
| </style> |
| <div id='a'> |
| <div id='b'></div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* a = GetLayoutObjectByElementId("a"); |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const auto* a_transform = a->FirstFragment().PaintProperties()->Transform(); |
| ASSERT_TRUE(a_transform); |
| const auto* b_transform = b->FirstFragment().PaintProperties()->Transform(); |
| ASSERT_TRUE(b_transform); |
| ASSERT_TRUE(NodeHasAncestor(b_transform, a_transform)); |
| |
| // Some node must flatten the inherited transform from #a before it reaches |
| // #b's transform. |
| EXPECT_TRUE(SomeNodeFlattensTransform(b_transform, a_transform)); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 30, 40), a, |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(8, 8, 10, 10), b, |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| Preserve3DTransformStylePropagatesToChildren) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #a { |
| transform: translateZ(0); |
| transform-style: preserve-3d; |
| width: 30px; |
| height: 40px; |
| } |
| #b { |
| transform: translateZ(0); |
| width: 10px; |
| height: 10px; |
| } |
| </style> |
| <div id='a'> |
| <div id='b'></div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
|