| // 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( |
| const LocalFrameView* frame_view) { |
| if (!frame_view) |
| frame_view = GetDocument().View(); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| return frame_view->GetLayoutView() |
| ->FirstFragment() |
| .PaintProperties() |
| ->PaintOffsetTranslation(); |
| } |
| return frame_view->PreTranslation(); |
| } |
| |
| const TransformPaintPropertyNode* |
| PaintPropertyTreeBuilderTest::FrameScrollTranslation( |
| const LocalFrameView* frame_view) { |
| if (!frame_view) |
| frame_view = GetDocument().View(); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| return frame_view->GetLayoutView() |
| ->FirstFragment() |
| .PaintProperties() |
| ->ScrollTranslation(); |
| } |
| return frame_view->ScrollTranslation(); |
| } |
| |
| const ClipPaintPropertyNode* PaintPropertyTreeBuilderTest::FrameContentClip( |
| const LocalFrameView* frame_view) { |
| if (!frame_view) |
| frame_view = GetDocument().View(); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| return frame_view->GetLayoutView() |
| ->FirstFragment() |
| .PaintProperties() |
| ->OverflowClip(); |
| } |
| return frame_view->ContentClip(); |
| } |
| |
| const ScrollPaintPropertyNode* PaintPropertyTreeBuilderTest::FrameScroll( |
| const 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) { |
| GetDocument().SetCompatibilityMode(Document::kQuirksMode); |
| LoadTestData("position-and-scroll.html"); |
| |
| Element* scroller = GetDocument().getElementById("scroller"); |
| scroller->scrollTo(0, 100); |
| LocalFrameView* frame_view = GetDocument().View(); |
| frame_view->UpdateAllLifecyclePhases(); |
| 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, OverflowScrollExcludeScrollbars) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='scroller' |
| style='width: 100px; height: 100px; overflow: scroll; |
| border: 10px solid blue'> |
| <div style='width: 400px; height: 400px'></div> |
| </div> |
| )HTML"); |
| CHECK(GetDocument().GetPage()->GetScrollbarTheme().UsesOverlayScrollbars()); |
| |
| const auto* properties = PaintPropertiesForElement("scroller"); |
| const auto* overflow_clip = properties->OverflowClip(); |
| |
| EXPECT_EQ(FrameContentClip(), overflow_clip->Parent()); |
| EXPECT_EQ(properties->PaintOffsetTranslation(), |
| overflow_clip->LocalTransformSpace()); |
| EXPECT_EQ(FloatRoundedRect(10, 10, 100, 100), overflow_clip->ClipRect()); |
| |
| PaintLayer* paint_layer = |
| ToLayoutBoxModelObject(GetLayoutObjectByElementId("scroller"))->Layer(); |
| EXPECT_TRUE(paint_layer->GetScrollableArea() |
| ->VerticalScrollbar() |
| ->IsOverlayScrollbar()); |
| |
| EXPECT_EQ(FloatRoundedRect(10, 10, 93, 93), |
| overflow_clip->ClipRectExcludingOverlayScrollbars()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, 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, SVGViewportContainer) { |
| if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) |
| return; |
| |
| SetBodyInnerHTML(R"HTML( |
| <!-- border radius of inner svg elemnents should be ignored. --> |
| <style>svg { border-radius: 10px }</style> |
| <svg id='svg'> |
| <svg id='container1' width='30' height='30'></svg> |
| <svg id='container2' |
| width='30' height='30' x='40' y='50' viewBox='0 0 60 60'></svg> |
| <svg id='container3' overflow='visible' width='30' height='30'></svg> |
| <svg id='container4' overflow='visible' |
| width='30' height='30' x='20' y='30'></svg> |
| </svg> |
| )HTML"); |
| |
| const auto* svg_properties = PaintPropertiesForElement("svg"); |
| ASSERT_NE(nullptr, svg_properties); |
| const auto* parent_transform = svg_properties->PaintOffsetTranslation(); |
| const auto* parent_clip = svg_properties->OverflowClip(); |
| |
| // overflow: hidden and zero offset: OverflowClip only. |
| const auto* properties1 = PaintPropertiesForElement("container1"); |
| ASSERT_NE(nullptr, properties1); |
| const auto* clip = properties1->OverflowClip(); |
| const auto* transform = properties1->Transform(); |
| ASSERT_NE(nullptr, clip); |
| EXPECT_EQ(nullptr, transform); |
| EXPECT_EQ(parent_clip, clip->Parent()); |
| EXPECT_EQ(FloatRect(0, 0, 30, 30), clip->ClipRect().Rect()); |
| EXPECT_EQ(parent_transform, clip->LocalTransformSpace()); |
| |
| // overflow: hidden and non-zero offset and viewport scale: |
| // both Transform and OverflowClip. |
| const auto* properties2 = PaintPropertiesForElement("container2"); |
| ASSERT_NE(nullptr, properties2); |
| clip = properties2->OverflowClip(); |
| transform = properties2->Transform(); |
| ASSERT_NE(nullptr, clip); |
| ASSERT_NE(nullptr, transform); |
| EXPECT_EQ(parent_clip, clip->Parent()); |
| EXPECT_EQ(FloatRect(0, 0, 60, 60), clip->ClipRect().Rect()); |
| EXPECT_EQ(transform, clip->LocalTransformSpace()); |
| EXPECT_EQ(TransformationMatrix().Translate(40, 50).Scale(0.5), |
| transform->Matrix()); |
| EXPECT_EQ(parent_transform, transform->Parent()); |
| |
| // overflow: visible and zero offset: no paint properties. |
| const auto* properties3 = PaintPropertiesForElement("container3"); |
| EXPECT_EQ(nullptr, properties3); |
| |
| // overflow: visible and non-zero offset: Transform only. |
| const auto* properties4 = PaintPropertiesForElement("container4"); |
| ASSERT_NE(nullptr, properties4); |
| clip = properties4->OverflowClip(); |
| transform = properties4->Transform(); |
| EXPECT_EQ(nullptr, clip); |
| ASSERT_NE(nullptr, transform); |
| EXPECT_EQ(TransformationMatrix().Translate(20, 30), transform->Matrix()); |
| EXPECT_EQ(parent_transform, transform->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGForeignObjectOverflowClip) { |
| if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) |
| return; |
| |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg'> |
| <foreignObject id='object1' x='10' y='20' width='30' height='40' |
| overflow='hidden'> |
| </foreignObject> |
| <foreignObject id='object2' x='50' y='60' width='30' height='40' |
| overflow='visible'> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| const auto* svg_properties = PaintPropertiesForElement("svg"); |
| ASSERT_NE(nullptr, svg_properties); |
| const auto* parent_transform = svg_properties->PaintOffsetTranslation(); |
| const auto* parent_clip = svg_properties->OverflowClip(); |
| |
| const auto* properties1 = PaintPropertiesForElement("object1"); |
| ASSERT_NE(nullptr, properties1); |
| const auto* clip = properties1->OverflowClip(); |
| ASSERT_NE(nullptr, clip); |
| EXPECT_EQ(parent_clip, clip->Parent()); |
| EXPECT_EQ(FloatRect(10, 20, 30, 40), clip->ClipRect().Rect()); |
| EXPECT_EQ(parent_transform, clip->LocalTransformSpace()); |
| |
| const auto* properties2 = PaintPropertiesForElement("object2"); |
| EXPECT_EQ(nullptr, properties2); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| PaintOffsetTranslationSVGHTMLBoundaryMulticol) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg'> |
| <foreignObject> |
| <body> |
| <div id='divWithColumns' style='columns: 2'> |
| <div style='width: 5px; height: 5px; background: blue'> |
| </div> |
| </body> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_properties = |
| svg.FirstFragment().PaintProperties(); |
| EXPECT_EQ( |
| FloatSize(8, 8), |
| svg_properties->PaintOffsetTranslation()->Matrix().To2DTranslation()); |
| LayoutObject& div_with_columns = |
| *GetLayoutObjectByElementId("divWithColumns")->SlowFirstChild(); |
| EXPECT_EQ(LayoutPoint(0, 0), div_with_columns.FirstFragment().PaintOffset()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FixedTransformAncestorAcrossSVGHTMLBoundary) { |
| SetBodyInnerHTML(R"HTML( |
| <style> body { margin: 0px; } </style> |
| <svg id='svg' style='transform: translate3d(1px, 2px, 3px);'> |
| <g id='container' transform='translate(20 30)'> |
| <foreignObject> |
| <body> |
| <div id='fixed' |
| style='position: fixed; left: 200px; top: 150px;'></div> |
| </body> |
| </foreignObject> |
| </g> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg = *GetLayoutObjectByElementId("svg"); |
| const ObjectPaintProperties* svg_properties = |
| svg.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate3d(1, 2, 3), |
| svg_properties->Transform()->Matrix()); |
| |
| LayoutObject& container = *GetLayoutObjectByElementId("container"); |
| const ObjectPaintProperties* container_properties = |
| container.FirstFragment().PaintProperties(); |
| EXPECT_EQ(TransformationMatrix().Translate(20, 30), |
| container_properties->Transform()->Matrix()); |
| EXPECT_EQ(svg_properties->Transform(), |
| container_properties->Transform()->Parent()); |
| |
| Element* fixed = GetDocument().getElementById("fixed"); |
| // Ensure the fixed position element is rooted at the nearest transform |
| // container. |
| EXPECT_EQ(container_properties->Transform(), fixed->GetLayoutObject() |
| ->FirstFragment() |
| .LocalBorderBoxProperties() |
| ->Transform()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ControlClip) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0; |
| } |
| input { |
| border-radius: 0; |
| border-width: 5px; |
| padding: 0; |
| } |
| </style> |
| <input id='button' type='button' |
| style='width:345px; height:123px' value='some text'/> |
| )HTML"); |
| |
| LayoutObject& button = *GetLayoutObjectByElementId("button"); |
| const ObjectPaintProperties* button_properties = |
| button.FirstFragment().PaintProperties(); |
| // 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) { |
| GetDocument().SetCompatibilityMode(Document::kQuirksMode); |
| 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(); |
| |
| 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)); |
| |
| // No node may flatten the inherited transform from #a before it reaches |
| // #b's transform. |
| EXPECT_FALSE(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, PerspectiveIsNotFlattened) { |
| // It's necessary to make nodes from the one that applies perspective to |
| // ones that combine with it preserve 3D. Otherwise, the perspective doesn't |
| // do anything. |
| SetBodyInnerHTML(R"HTML( |
| <div id='a' style='perspective: 800px; width: 30px; height: 40px'> |
| <div id='b' |
| style='transform: translateZ(0); width: 10px; height: 20px'></div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* a = GetLayoutObjectByElementId("a"); |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* a_properties = |
| a->FirstFragment().PaintProperties(); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| const TransformPaintPropertyNode* a_perspective = a_properties->Perspective(); |
| ASSERT_TRUE(a_perspective); |
| const TransformPaintPropertyNode* b_transform = b_properties->Transform(); |
| ASSERT_TRUE(b_transform); |
| ASSERT_TRUE(NodeHasAncestor(b_transform, a_perspective)); |
| EXPECT_FALSE(SomeNodeFlattensTransform(b_transform, a_perspective)); |
| 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, |
| PerspectiveDoesNotEstablishRenderingContext) { |
| // It's necessary to make nodes from the one that applies perspective to |
| // ones that combine with it preserve 3D. Otherwise, the perspective doesn't |
| // do anything. |
| SetBodyInnerHTML(R"HTML( |
| <div id='a' style='perspective: 800px; width: 30px; height: 40px'> |
| <div id='b' |
| style='transform: translateZ(0); width: 10px; height: 20px'></div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| LayoutObject* a = GetLayoutObjectByElementId("a"); |
| LayoutObject* b = GetLayoutObjectByElementId("b"); |
| const ObjectPaintProperties* a_properties = |
| a->FirstFragment().PaintProperties(); |
| const ObjectPaintProperties* b_properties = |
| b->FirstFragment().PaintProperties(); |
| const TransformPaintPropertyNode* a_perspective = a_properties->Perspective(); |
| ASSERT_TRUE(a_perspective); |
| EXPECT_FALSE(a_perspective->HasRenderingContext()); |
| const TransformPaintPropertyNode* b_transform = b_properties->Transform(); |
| ASSERT_TRUE(b_transform); |
| EXPECT_FALSE(b_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, CachedProperties) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0 }</style> |
| <div id='a' style='transform: translate(33px, 44px); width: 50px; |
| height: 60px'> |
| <div id='b' style='transform: translate(55px, 66px); width: 30px; |
| height: 40px'> |
| <div id='c' style='transform: translate(77px, 88px); width: 10px; |
| height: 20px'>C<div> |
| </div> |
| </div> |
| )HTML"); |
| LocalFrameView* frame_view = GetDocument().View(); |
| |
| Element* a = GetDocument().getElementById("a"); |
| const ObjectPaintProperties* a_properties = |
| a->GetLayoutObject()->FirstFragment().PaintProperties(); |
| const TransformPaintPropertyNode* a_transform_node = |
| a_properties->Transform(); |
| EXPECT_EQ(TransformationMatrix().Translate(33, 44), |
| a_transform_node->Matrix()); |
| |
| Element* b = GetDocument().getElementById("b"); |
| const ObjectPaintProperties* b_properties = |
| b->GetLayoutObject()->FirstFragment().PaintProperties(); |
| const TransformPaintPropertyNode* b_transform_node = |
| b_properties->Transform(); |
| EXPECT_EQ(TransformationMatrix().Translate(55, 66), |
| b_transform_node->Matrix()); |
| |
| Element* c = GetDocument().getElementById("c"); |
| const ObjectPaintProperties* c_properties = |
| c->GetLayoutObject()->FirstFragment().PaintProperties(); |
| const TransformPaintPropertyNode* c_transform_node = |
| c_properties->Transform(); |
| EXPECT_EQ(TransformationMatrix().Translate(77, 88), |
| c_transform_node->Matrix()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 60), a->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(88, 110, 30, 40), b->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(165, 198, 10, 20), c->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| |
| // Change transform of b. B's transform node should be a new node with the new |
| // value, and a and c's transform nodes should be unchanged (with c's parent |
| // adjusted). |
| b->setAttribute(HTMLNames::styleAttr, "transform: translate(111px, 222px)"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| EXPECT_EQ(a_properties, |
| a->GetLayoutObject()->FirstFragment().PaintProperties()); |
| EXPECT_EQ(a_transform_node, a_properties->Transform()); |
| |
| EXPECT_EQ(b_properties, |
| b->GetLayoutObject()->FirstFragment().PaintProperties()); |
| b_transform_node = b_properties->Transform(); |
| EXPECT_EQ(TransformationMatrix().Translate(111, 222), |
| b_transform_node->Matrix()); |
| EXPECT_EQ(a_transform_node, b_transform_node->Parent()->Parent()); |
| |
| EXPECT_EQ(c_properties, |
| c->GetLayoutObject()->FirstFragment().PaintProperties()); |
| EXPECT_EQ(c_transform_node, c_properties->Transform()); |
| EXPECT_EQ(b_transform_node, c_transform_node->Parent()->Parent()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 60), a->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(144, 266, 50, 20), b->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(221, 354, 10, 20), c->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| |
| // Remove transform from b. B's transform node should be removed from the |
| // tree, and a and c's transform nodes should be unchanged (with c's parent |
| // adjusted). |
| b->setAttribute(HTMLNames::styleAttr, ""); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| EXPECT_EQ(a_properties, |
| a->GetLayoutObject()->FirstFragment().PaintProperties()); |
| EXPECT_EQ(a_transform_node, a_properties->Transform()); |
| |
| EXPECT_EQ(nullptr, b->GetLayoutObject()->FirstFragment().PaintProperties()); |
| |
| EXPECT_EQ(c_properties, |
| c->GetLayoutObject()->FirstFragment().PaintProperties()); |
| EXPECT_EQ(c_transform_node, c_properties->Transform()); |
| EXPECT_EQ(a_transform_node, c_transform_node->Parent()->Parent()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 60), a->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 20), b->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(110, 132, 10, 20), c->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| |
| // Re-add transform to b. B's transform node should be inserted into the tree, |
| // and a and c's transform nodes should be unchanged (with c's parent |
| // adjusted). |
| b->setAttribute(HTMLNames::styleAttr, "transform: translate(4px, 5px)"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| EXPECT_EQ(a_properties, |
| a->GetLayoutObject()->FirstFragment().PaintProperties()); |
| EXPECT_EQ(a_transform_node, a_properties->Transform()); |
| |
| b_properties = b->GetLayoutObject()->FirstFragment().PaintProperties(); |
| EXPECT_EQ(b_properties, |
| b->GetLayoutObject()->FirstFragment().PaintProperties()); |
| b_transform_node = b_properties->Transform(); |
| EXPECT_EQ(TransformationMatrix().Translate(4, 5), b_transform_node->Matrix()); |
| EXPECT_EQ(a_transform_node, b_transform_node->Parent()->Parent()); |
| |
| EXPECT_EQ(c_properties, |
| c->GetLayoutObject()->FirstFragment().PaintProperties()); |
| EXPECT_EQ(c_transform_node, c_properties->Transform()); |
| EXPECT_EQ(b_transform_node, c_transform_node->Parent()->Parent()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(33, 44, 50, 60), a->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(37, 49, 50, 20), b->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(114, 137, 10, 20), c->GetLayoutObject(), |
| frame_view->GetLayoutView()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, OverflowClipContentsTreeState) { |
| // This test verifies the tree builder correctly computes and records the |
| // property tree context for a (pseudo) stacking context that is scrolled by a |
| // containing block that is not one of the painting ancestors. |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 20px 30px; }</style> |
| <div id='clipper' |
| style='overflow: hidden; width: 400px; height: 300px;'> |
| <div id='child' |
| style='position: relative; width: 500px; height: 600px;'></div> |
| </div> |
| )HTML"); |
| |
| LayoutBoxModelObject* clipper = |
| ToLayoutBoxModelObject(GetLayoutObjectByElementId("clipper")); |
| const ObjectPaintProperties* clip_properties = |
| clipper->FirstFragment().PaintProperties(); |
| LayoutObject* child = GetLayoutObjectByElementId("child"); |
| |
| // No scroll translation because the document does not scroll (not enough |
| // content). |
| EXPECT_TRUE(!FrameScrollTranslation()); |
| EXPECT_EQ(FramePreTranslation(), |
| clipper->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(FrameContentClip(), |
| clipper->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| |
| auto contents_properties = clipper->FirstFragment().ContentsProperties(); |
| EXPECT_EQ(LayoutPoint(30, 20), clipper->FirstFragment().PaintOffset()); |
| EXPECT_EQ(FramePreTranslation(), contents_properties.Transform()); |
| EXPECT_EQ(clip_properties->OverflowClip(), contents_properties.Clip()); |
| |
| EXPECT_EQ(FramePreTranslation(), |
| child->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(clip_properties->OverflowClip(), |
| child->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| |
| EXPECT_NE(nullptr, |
| child->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 500, 600), child, clipper); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ContainsPaintContentsTreeState) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 20px 30px; }</style> |
| <div id='clipper' |
| style='contain: paint; width: 300px; height: 200px;'> |
| <div id='child' |
| style='position: relative; width: 400px; height: 500px;'></div> |
| </div> |
| )HTML"); |
| |
| LayoutBoxModelObject* clipper = |
| ToLayoutBoxModelObject(GetLayoutObjectByElementId("clipper")); |
| const ObjectPaintProperties* clip_properties = |
| clipper->FirstFragment().PaintProperties(); |
| LayoutObject* child = GetLayoutObjectByElementId("child"); |
| |
| // No scroll translation because the document does not scroll (not enough |
| // content). |
| EXPECT_TRUE(!FrameScrollTranslation()); |
| EXPECT_EQ(FramePreTranslation(), |
| clipper->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(FrameContentClip(), |
| clipper->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| |
| auto contents_properties = clipper->FirstFragment().ContentsProperties(); |
| EXPECT_EQ(LayoutPoint(30, 20), clipper->FirstFragment().PaintOffset()); |
| EXPECT_EQ(FramePreTranslation(), contents_properties.Transform()); |
| EXPECT_EQ(clip_properties->OverflowClip(), contents_properties.Clip()); |
| |
| EXPECT_EQ(FramePreTranslation(), |
| child->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(clip_properties->OverflowClip(), |
| child->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| |
| EXPECT_NE(nullptr, |
| child->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 400, 500), child, clipper); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollContentsTreeState) { |
| // This test verifies the tree builder correctly computes and records the |
| // property tree context for a (pseudo) stacking context that is scrolled by a |
| // containing block that is not one of the painting ancestors. |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 20px 30px; }</style> |
| <div id='clipper' style='overflow:scroll; width:400px; height:300px;'> |
| <div id='child' |
| style='position:relative; width:500px; height: 600px;'></div> |
| <div style='width: 200px; height: 10000px'></div> |
| </div> |
| <div id='forceScroll' style='height: 4000px;'></div> |
| )HTML"); |
| |
| Element* clipper_element = GetDocument().getElementById("clipper"); |
| clipper_element->scrollTo(1, 2); |
| |
| LayoutBoxModelObject* clipper = |
| ToLayoutBoxModelObject(clipper_element->GetLayoutObject()); |
| const ObjectPaintProperties* clip_properties = |
| clipper->FirstFragment().PaintProperties(); |
| LayoutObject* child = GetLayoutObjectByElementId("child"); |
| |
| EXPECT_EQ(FrameScrollTranslation(), clipper->FirstFragment() |
| .LocalBorderBoxProperties() |
| ->Transform() |
| ->Parent()); |
| EXPECT_EQ(clip_properties->PaintOffsetTranslation(), |
| clipper->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(FrameContentClip(), |
| clipper->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| |
| auto contents_properties = clipper->FirstFragment().ContentsProperties(); |
| EXPECT_EQ( |
| FloatSize(30, 20), |
| clip_properties->PaintOffsetTranslation()->Matrix().To2DTranslation()); |
| EXPECT_EQ(LayoutPoint(0, 0), clipper->FirstFragment().PaintOffset()); |
| EXPECT_EQ(clip_properties->ScrollTranslation(), |
| contents_properties.Transform()); |
| EXPECT_EQ(clip_properties->OverflowClip(), contents_properties.Clip()); |
| |
| EXPECT_EQ(clip_properties->ScrollTranslation(), |
| child->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| EXPECT_EQ(clip_properties->OverflowClip(), |
| child->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 500, 600), child, clipper); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, OverflowScrollWithRoundedRect) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { margin: 0; } |
| ::-webkit-scrollbar { |
| width: 13px; |
| height: 13px; |
| } |
| #roundedBox { |
| width: 200px; |
| height: 200px; |
| border-radius: 100px; |
| background-color: red; |
| border: 50px solid green; |
| overflow: scroll; |
| } |
| #roundedBoxChild { |
| width: 200px; |
| height: 200px; |
| background-color: orange; |
| } |
| </style> |
| <div id='roundedBox'> |
| <div id='roundedBoxChild'></div> |
| </div> |
| )HTML"); |
| |
| LayoutObject& rounded_box = *GetLayoutObjectByElementId("roundedBox"); |
| const ObjectPaintProperties* rounded_box_properties = |
| rounded_box.FirstFragment().PaintProperties(); |
| EXPECT_EQ( |
| FloatRoundedRect(FloatRect(50, 50, 200, 200), FloatSize(50, 50), |
| FloatSize(50, 50), FloatSize(50, 50), FloatSize(50, 50)), |
| rounded_box_properties->InnerBorderRadiusClip()->ClipRect()); |
| |
| // Unlike the inner border radius clip, the overflow clip is inset by the |
| // scrollbars (13px). |
| EXPECT_EQ(FloatRoundedRect(50, 50, 187, 187), |
| rounded_box_properties->OverflowClip()->ClipRect()); |
| EXPECT_EQ(FrameContentClip(), |
| rounded_box_properties->InnerBorderRadiusClip()->Parent()); |
| EXPECT_EQ(rounded_box_properties->InnerBorderRadiusClip(), |
| rounded_box_properties->OverflowClip()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, CssClipContentsTreeState) { |
| // This test verifies the tree builder correctly computes and records the |
| // property tree context for a (pseudo) stacking context that is scrolled by a |
| // containing block that is not one of the painting ancestors. |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 20px 30px; }</style> |
| <div id='clipper' style='position: absolute; |
| clip: rect(10px, 80px, 70px, 40px); width:300px; height:200px;'> |
| <div id='child' style='position:relative; width:400px; height: 500px;'> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutBoxModelObject* clipper = |
| ToLayoutBoxModelObject(GetLayoutObjectByElementId("clipper")); |
| const ObjectPaintProperties* clip_properties = |
| clipper->FirstFragment().PaintProperties(); |
| LayoutObject* child = GetLayoutObjectByElementId("child"); |
| |
| // No scroll translation because the document does not scroll (not enough |
| // content). |
| EXPECT_TRUE(!FrameScrollTranslation()); |
| EXPECT_EQ(FramePreTranslation(), |
| clipper->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| // CSS clip on an element causes it to clip itself, not just descendants. |
| EXPECT_EQ(clip_properties->CssClip(), |
| clipper->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| |
| auto contents_properties = clipper->FirstFragment().ContentsProperties(); |
| EXPECT_EQ(LayoutPoint(30, 20), clipper->FirstFragment().PaintOffset()); |
| EXPECT_EQ(FramePreTranslation(), contents_properties.Transform()); |
| EXPECT_EQ(clip_properties->CssClip(), contents_properties.Clip()); |
| |
| CHECK_EXACT_VISUAL_RECT(LayoutRect(0, 0, 400, 500), child, clipper); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| SvgLocalToBorderBoxTransformContentsTreeState) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 20px 30px; |
| } |
| svg { |
| position: absolute; |
| } |
| rect { |
| transform: translate(100px, 100px); |
| } |
| </style> |
| <svg id='svgWithViewBox' width='100px' height='100px' |
| viewBox='50 50 100 100'> |
| <rect id='rect' width='100px' height='100px' /> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg_with_view_box = |
| *GetLayoutObjectByElementId("svgWithViewBox"); |
| EXPECT_EQ(FramePreTranslation(), svg_with_view_box.FirstFragment() |
| .LocalBorderBoxProperties() |
| ->Transform() |
| ->Parent()); |
| EXPECT_EQ(FloatSize(30, 20), svg_with_view_box.FirstFragment() |
| .LocalBorderBoxProperties() |
| ->Transform() |
| ->Matrix() |
| .To2DTranslation()); |
| |
| EXPECT_EQ(LayoutPoint(0, 0), svg_with_view_box.FirstFragment().PaintOffset()); |
| auto contents_properties = |
| svg_with_view_box.FirstFragment().ContentsProperties(); |
| EXPECT_EQ(svg_with_view_box.FirstFragment() |
| .PaintProperties() |
| ->PaintOffsetTranslation(), |
| contents_properties.Transform()); |
| EXPECT_EQ(FramePreTranslation(), contents_properties.Transform()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, OverflowHiddenScrollProperties) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0px; |
| } |
| #overflowHidden { |
| overflow: hidden; |
| width: 5px; |
| height: 3px; |
| } |
| .forceScroll { |
| height: 79px; |
| } |
| </style> |
| <div id='overflowHidden'> |
| <div class='forceScroll'></div> |
| </div> |
| )HTML"); |
| |
| Element* overflow_hidden = GetDocument().getElementById("overflowHidden"); |
| overflow_hidden->setScrollTop(37); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| const ObjectPaintProperties* overflow_hidden_scroll_properties = |
| overflow_hidden->GetLayoutObject()->FirstFragment().PaintProperties(); |
| |
| // Because the overflow hidden does not scroll and only has a static scroll |
| // offset, there should be a scroll translation node but no scroll node. |
| auto* scroll_translation = |
| overflow_hidden_scroll_properties->ScrollTranslation(); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -37), |
| scroll_translation->Matrix()); |
| EXPECT_EQ(nullptr, scroll_translation->ScrollNode()); |
| EXPECT_EQ(nullptr, overflow_hidden_scroll_properties->Scroll()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FrameOverflowHiddenScrollProperties) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| html { |
| margin: 0px; |
| overflow: hidden; |
| width: 300px; |
| height: 300px; |
| } |
| .forceScroll { |
| height: 5000px; |
| } |
| </style> |
| <div class='forceScroll'></div> |
| )HTML"); |
| |
| GetDocument().domWindow()->scrollTo(0, 37); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| // Because the overflow hidden does not scroll and only has a static scroll |
| // offset, there should be a scroll translation node but no scroll node. |
| EXPECT_EQ(TransformationMatrix().Translate(0, -37), |
| FrameScrollTranslation()->Matrix()); |
| EXPECT_EQ(nullptr, FrameScrollTranslation()->ScrollNode()); |
| EXPECT_EQ(nullptr, FrameScroll()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, NestedScrollProperties) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { |
| margin: 0px; |
| } |
| #overflowA { |
| overflow: scroll; |
| width: 5px; |
| height: 3px; |
| } |
| #overflowB { |
| overflow: scroll; |
| width: 9px; |
| height: 7px; |
| } |
| .forceScroll { |
| height: 100px; |
| } |
| </style> |
| <div id='overflowA'> |
| <div id='overflowB'> |
| <div class='forceScroll'></div> |
| </div> |
| <div class='forceScroll'></div> |
| </div> |
| )HTML"); |
| |
| Element* overflow_a = GetDocument().getElementById("overflowA"); |
| overflow_a->setScrollTop(37); |
| Element* overflow_b = GetDocument().getElementById("overflowB"); |
| overflow_b->setScrollTop(41); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| const ObjectPaintProperties* overflow_a_scroll_properties = |
| overflow_a->GetLayoutObject()->FirstFragment().PaintProperties(); |
| // Because the frameView is does not scroll, overflowA's scroll should be |
| // under the root. |
| auto* scroll_a_translation = |
| overflow_a_scroll_properties->ScrollTranslation(); |
| auto* overflow_a_scroll_node = scroll_a_translation->ScrollNode(); |
| EXPECT_TRUE(overflow_a_scroll_node->Parent()->IsRoot()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -37), |
| scroll_a_translation->Matrix()); |
| EXPECT_EQ(IntRect(0, 0, 5, 3), overflow_a_scroll_node->ContainerRect()); |
| // 107 is the forceScroll element plus the height of the overflow scroll child |
| // (overflowB). |
| EXPECT_EQ(IntRect(0, 0, 9, 107), overflow_a_scroll_node->ContentsRect()); |
| EXPECT_TRUE(overflow_a_scroll_node->UserScrollableHorizontal()); |
| EXPECT_TRUE(overflow_a_scroll_node->UserScrollableVertical()); |
| |
| const ObjectPaintProperties* overflow_b_scroll_properties = |
| overflow_b->GetLayoutObject()->FirstFragment().PaintProperties(); |
| // The overflow child's scroll node should be a child of the parent's |
| // (overflowA) scroll node. |
| auto* scroll_b_translation = |
| overflow_b_scroll_properties->ScrollTranslation(); |
| auto* overflow_b_scroll_node = scroll_b_translation->ScrollNode(); |
| EXPECT_EQ(overflow_a_scroll_node, overflow_b_scroll_node->Parent()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -41), |
| scroll_b_translation->Matrix()); |
| EXPECT_EQ(IntRect(0, 0, 9, 7), overflow_b_scroll_node->ContainerRect()); |
| EXPECT_EQ(IntRect(0, 0, 9, 100), overflow_b_scroll_node->ContentsRect()); |
| EXPECT_TRUE(overflow_b_scroll_node->UserScrollableHorizontal()); |
| EXPECT_TRUE(overflow_b_scroll_node->UserScrollableVertical()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, PositionedScrollerIsNotNested) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { |
| margin: 0px; |
| } |
| #overflow { |
| overflow: scroll; |
| width: 5px; |
| height: 3px; |
| } |
| #absposOverflow { |
| position: absolute; |
| top: 0; |
| left: 0; |
| overflow: scroll; |
| width: 9px; |
| height: 7px; |
| } |
| #fixedOverflow { |
| position: fixed; |
| top: 0; |
| left: 0; |
| overflow: scroll; |
| width: 13px; |
| height: 11px; |
| } |
| .forceScroll { |
| height: 4000px; |
| } |
| </style> |
| <div id='overflow'> |
| <div id='absposOverflow'> |
| <div class='forceScroll'></div> |
| </div> |
| <div id='fixedOverflow'> |
| <div class='forceScroll'></div> |
| </div> |
| <div class='forceScroll'></div> |
| </div> |
| <div class='forceScroll'></div> |
| )HTML"); |
| |
| Element* overflow = GetDocument().getElementById("overflow"); |
| overflow->setScrollTop(37); |
| Element* abspos_overflow = GetDocument().getElementById("absposOverflow"); |
| abspos_overflow->setScrollTop(41); |
| Element* fixed_overflow = GetDocument().getElementById("fixedOverflow"); |
| fixed_overflow->setScrollTop(43); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| // The frame should scroll due to the "forceScroll" element. |
| EXPECT_NE(nullptr, FrameScroll()); |
| |
| const ObjectPaintProperties* overflow_scroll_properties = |
| overflow->GetLayoutObject()->FirstFragment().PaintProperties(); |
| auto* scroll_translation = overflow_scroll_properties->ScrollTranslation(); |
| auto* overflow_scroll_node = scroll_translation->ScrollNode(); |
| EXPECT_EQ( |
| FrameScroll(), |
| overflow_scroll_properties->ScrollTranslation()->ScrollNode()->Parent()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -37), |
| scroll_translation->Matrix()); |
| EXPECT_EQ(IntRect(0, 0, 5, 3), overflow_scroll_node->ContainerRect()); |
| // The height should be 4000px because the (dom-order) overflow children are |
| // positioned and do not contribute to the height. Only the 4000px |
| // "forceScroll" height is present. |
| EXPECT_EQ(IntRect(0, 0, 5, 4000), overflow_scroll_node->ContentsRect()); |
| |
| const ObjectPaintProperties* abspos_overflow_scroll_properties = |
| abspos_overflow->GetLayoutObject()->FirstFragment().PaintProperties(); |
| auto* abspos_scroll_translation = |
| abspos_overflow_scroll_properties->ScrollTranslation(); |
| auto* abspos_overflow_scroll_node = abspos_scroll_translation->ScrollNode(); |
| // The absolute position overflow scroll node is parented under the frame, not |
| // the dom-order parent. |
| EXPECT_EQ(FrameScroll(), abspos_overflow_scroll_node->Parent()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -41), |
| abspos_scroll_translation->Matrix()); |
| EXPECT_EQ(IntRect(0, 0, 9, 7), abspos_overflow_scroll_node->ContainerRect()); |
| EXPECT_EQ(IntRect(0, 0, 9, 4000), |
| abspos_overflow_scroll_node->ContentsRect()); |
| |
| const ObjectPaintProperties* fixed_overflow_scroll_properties = |
| fixed_overflow->GetLayoutObject()->FirstFragment().PaintProperties(); |
| auto* fixed_scroll_translation = |
| fixed_overflow_scroll_properties->ScrollTranslation(); |
| auto* fixed_overflow_scroll_node = fixed_scroll_translation->ScrollNode(); |
| // The fixed position overflow scroll node is parented under the root, not the |
| // dom-order parent or frame's scroll. |
| EXPECT_TRUE(fixed_overflow_scroll_node->Parent()->IsRoot()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -43), |
| fixed_scroll_translation->Matrix()); |
| EXPECT_EQ(IntRect(0, 0, 13, 11), fixed_overflow_scroll_node->ContainerRect()); |
| EXPECT_EQ(IntRect(0, 0, 13, 4000), |
| fixed_overflow_scroll_node->ContentsRect()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, NestedPositionedScrollProperties) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { |
| margin: 0px; |
| } |
| #overflowA { |
| position: absolute; |
| top: 7px; |
| left: 11px; |
| overflow: scroll; |
| width: 20px; |
| height: 20px; |
| } |
| #overflowB { |
| position: absolute; |
| top: 1px; |
| left: 3px; |
| overflow: scroll; |
| width: 5px; |
| height: 3px; |
| } |
| .forceScroll { |
| height: 100px; |
| } |
| </style> |
| <div id='overflowA'> |
| <div id='overflowB'> |
| <div class='forceScroll'></div> |
| </div> |
| <div class='forceScroll'></div> |
| </div> |
| )HTML"); |
| |
| Element* overflow_a = GetDocument().getElementById("overflowA"); |
| overflow_a->setScrollTop(37); |
| Element* overflow_b = GetDocument().getElementById("overflowB"); |
| overflow_b->setScrollTop(41); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| const ObjectPaintProperties* overflow_a_scroll_properties = |
| overflow_a->GetLayoutObject()->FirstFragment().PaintProperties(); |
| // Because the frameView is does not scroll, overflowA's scroll should be |
| // under the root. |
| auto* scroll_a_translation = |
| overflow_a_scroll_properties->ScrollTranslation(); |
| auto* overflow_a_scroll_node = scroll_a_translation->ScrollNode(); |
| EXPECT_TRUE(overflow_a_scroll_node->Parent()->IsRoot()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -37), |
| scroll_a_translation->Matrix()); |
| EXPECT_EQ(IntRect(0, 0, 20, 20), overflow_a_scroll_node->ContainerRect()); |
| // 100 is the forceScroll element's height because the overflow child does not |
| // contribute to the height. |
| EXPECT_EQ(IntRect(0, 0, 20, 100), overflow_a_scroll_node->ContentsRect()); |
| EXPECT_TRUE(overflow_a_scroll_node->UserScrollableHorizontal()); |
| EXPECT_TRUE(overflow_a_scroll_node->UserScrollableVertical()); |
| |
| const ObjectPaintProperties* overflow_b_scroll_properties = |
| overflow_b->GetLayoutObject()->FirstFragment().PaintProperties(); |
| // The overflow child's scroll node should be a child of the parent's |
| // (overflowA) scroll node. |
| auto* scroll_b_translation = |
| overflow_b_scroll_properties->ScrollTranslation(); |
| auto* overflow_b_scroll_node = scroll_b_translation->ScrollNode(); |
| EXPECT_EQ(overflow_a_scroll_node, overflow_b_scroll_node->Parent()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -41), |
| scroll_b_translation->Matrix()); |
| EXPECT_EQ(IntRect(0, 0, 5, 3), overflow_b_scroll_node->ContainerRect()); |
| EXPECT_EQ(IntRect(0, 0, 5, 100), overflow_b_scroll_node->ContentsRect()); |
| EXPECT_TRUE(overflow_b_scroll_node->UserScrollableHorizontal()); |
| EXPECT_TRUE(overflow_b_scroll_node->UserScrollableVertical()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGRootClip) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg' width='100px' height='100px'> |
| <rect width='200' height='200' fill='red' /> |
| </svg> |
| )HTML"); |
| |
| const ClipPaintPropertyNode* clip = GetLayoutObjectByElementId("svg") |
| ->FirstFragment() |
| .PaintProperties() |
| ->OverflowClip(); |
| EXPECT_EQ(FrameContentClip(), clip->Parent()); |
| EXPECT_EQ(FloatSize(8, 8), GetLayoutObjectByElementId("svg") |
| ->FirstFragment() |
| .PaintProperties() |
| ->PaintOffsetTranslation() |
| ->Matrix() |
| .To2DTranslation()); |
| EXPECT_EQ(FloatRoundedRect(0, 0, 100, 100), clip->ClipRect()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGRootNoClip) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg' xmlns='http://www.w3.org/2000/svg' width='100px' |
| height='100px' style='overflow: visible'> |
| <rect width='200' height='200' fill='red' /> |
| </svg> |
| )HTML"); |
| |
| EXPECT_FALSE(GetLayoutObjectByElementId("svg") |
| ->FirstFragment() |
| .PaintProperties() |
| ->OverflowClip()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, MainThreadScrollReasonsWithoutScrolling) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #overflow { |
| overflow: scroll; |
| width: 100px; |
| height: 100px; |
| } |
| .backgroundAttachmentFixed { |
| background-image: url('foo'); |
| background-attachment: fixed; |
| width: 10px; |
| height: 10px; |
| } |
| .forceScroll { |
| height: 4000px; |
| } |
| </style> |
| <div id='overflow'> |
| <div class='backgroundAttachmentFixed'></div> |
| </div> |
| <div class='forceScroll'></div> |
| )HTML"); |
| Element* overflow = GetDocument().getElementById("overflow"); |
| EXPECT_TRUE(FrameScroll()->HasBackgroundAttachmentFixedDescendants()); |
| // No scroll node is needed. |
| EXPECT_EQ(overflow->GetLayoutObject() |
| ->FirstFragment() |
| .PaintProperties() |
| ->ScrollTranslation(), |
| nullptr); |
| } |
| |
| static unsigned NumFragments(LayoutObject* obj) { |
| unsigned count = 0; |
| auto* fragment = &obj->FirstFragment(); |
| while (fragment) { |
| count++; |
| fragment = fragment->NextFragment(); |
| } |
| return count; |
| } |
| |
| static const FragmentData& FragmentAt(LayoutObject* obj, unsigned count) { |
| auto* fragment = &obj->FirstFragment(); |
| while (count > 0) { |
| count--; |
| fragment = fragment->NextFragment(); |
| } |
| return *fragment; |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetsUnderMultiColumnScrolled) { |
| SetBodyInnerHTML(R"HTML( |
| <!doctype HTML> |
| <div style='columns: 1;'> |
| <div id=scroller style='height: 400px; width: 400px; overflow: auto;'> |
| <div style='width: 50px; height: 1000px; background: lightgray'> |
| </div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* scroller = GetLayoutObjectByElementId("scroller"); |
| ToLayoutBoxModelObject(scroller)->Layer()->GetScrollableArea()->ScrollBy( |
| ScrollOffset(0, 300), kUserScroll); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| EXPECT_EQ(FloatSize(8, 8), scroller->FirstFragment() |
| .PaintProperties() |
| ->PaintOffsetTranslation() |
| ->Matrix() |
| .To2DTranslation()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, PaintOffsetsUnderMultiColumnWithOutline) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='columns: 2; height: 100px'> |
| <div id=target1 style='outline: 2px solid black; width: 100px; |
| height: 100px'></div> |
| <div id=target2 style='outline: 2px solid black; width: 100px; |
| height: 100px'></div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* target1 = GetLayoutObjectByElementId("target1"); |
| |
| // Outline does not affect paint offset, since it is positioned to the |
| // top-left of the border box. |
| EXPECT_EQ(LayoutPoint(8, 8), target1->FirstFragment().PaintOffset()); |
| // |target1| is only in the first column. |
| EXPECT_FALSE(target1->FirstFragment().NextFragment()); |
| |
| LayoutObject* target2 = GetLayoutObjectByElementId("target2"); |
| EXPECT_EQ(LayoutPoint(LayoutUnit(400.5f), LayoutUnit(8.0f)), |
| target2->FirstFragment().PaintOffset()); |
| // |target2| is only in the second column. |
| EXPECT_FALSE(target2->FirstFragment().NextFragment()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| PaintOffsetUnderMulticolumnScrollFixedPos) { |
| SetBodyInnerHTML(R"HTML( |
| <div id=fixed style='position: fixed; columns: 2'> |
| <div style='width: 50px; height: 20px; background: lightblue'></div> |
| <div style='width: 50px; height: 20px; background: lightgray'></div> |
| </div> |
| <div style='height: 2000px'></div> |
| )HTML"); |
| LayoutObject* fixed = GetLayoutObjectByElementId("fixed"); |
| LayoutObject* multicol_container = fixed->SlowFirstChild(); |
| |
| ASSERT_TRUE(multicol_container->FirstFragment().NextFragment()); |
| ASSERT_FALSE( |
| multicol_container->FirstFragment().NextFragment()->NextFragment()); |
| EXPECT_EQ(LayoutPoint(8, 8), |
| multicol_container->FirstFragment().PaintOffset()); |
| EXPECT_EQ(LayoutPoint(59, -12), |
| multicol_container->FirstFragment().NextFragment()->PaintOffset()); |
| |
| GetDocument().View()->LayoutViewportScrollableArea()->ScrollBy( |
| ScrollOffset(0, 25), kUserScroll); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| ASSERT_TRUE(multicol_container->FirstFragment().NextFragment()); |
| ASSERT_FALSE( |
| multicol_container->FirstFragment().NextFragment()->NextFragment()); |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled() || |
| !RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| EXPECT_EQ(LayoutPoint(8, 8), |
| multicol_container->FirstFragment().PaintOffset()); |
| EXPECT_EQ( |
| LayoutPoint(59, -12), |
| multicol_container->FirstFragment().NextFragment()->PaintOffset()); |
| } else { |
| // TODO(crbug.com/793051): The paint offsets are incorrect. |
| EXPECT_EQ(LayoutPoint(8, 33), |
| multicol_container->FirstFragment().PaintOffset()); |
| EXPECT_EQ( |
| LayoutPoint(59, 13), |
| multicol_container->FirstFragment().NextFragment()->PaintOffset()); |
| } |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FragmentsUnderMultiColumn) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { margin: 0; } |
| .space { height: 30px; } |
| .abs { position: absolute; width: 20px; height: 20px; } |
| </style> |
| <div style='columns:2; width: 200px; column-gap: 0'> |
| <div id=relpos style='position: relative'> |
| <div id=space1 class=space></div> |
| <div id=space2 class=space></div> |
| <div id=spanner style='column-span: all'> |
| <div id=normal style='height: 50px'></div> |
| <div id=top-left class=abs style='top: 0; left: 0'></div> |
| <div id=bottom-right class=abs style='bottom: 0; right: 0'></div> |
| </div> |
| <div id=space3 class=space></div> |
| <div id=space4 class=space></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* relpos = GetLayoutObjectByElementId("relpos"); |
| EXPECT_EQ(4u, NumFragments(relpos)); |
| EXPECT_EQ(LayoutPoint(0, 0), FragmentAt(relpos, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 0), FragmentAt(relpos, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(relpos, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(FloatRect(-1000000, -1000000, 1000100, 1000030), |
| FragmentAt(relpos, 0) |
| .PaintProperties() |
| ->FragmentClip() |
| ->ClipRect() |
| .Rect()); |
| |
| EXPECT_EQ(LayoutPoint(100, -30), FragmentAt(relpos, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -30), FragmentAt(relpos, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(30), FragmentAt(relpos, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ(FloatRect(100, 0, 1000000, 30), FragmentAt(relpos, 1) |
| .PaintProperties() |
| ->FragmentClip() |
| ->ClipRect() |
| .Rect()); |
| |
| EXPECT_EQ(LayoutPoint(0, 20), FragmentAt(relpos, 2).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 20), FragmentAt(relpos, 2).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(60), FragmentAt(relpos, 2).LogicalTopInFlowThread()); |
| EXPECT_EQ(FloatRect(-1000000, 80, 1000100, 30), FragmentAt(relpos, 2) |
| .PaintProperties() |
| ->FragmentClip() |
| ->ClipRect() |
| .Rect()); |
| |
| EXPECT_EQ(LayoutPoint(100, -10), FragmentAt(relpos, 3).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -10), FragmentAt(relpos, 3).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(90), FragmentAt(relpos, 3).LogicalTopInFlowThread()); |
| EXPECT_EQ(FloatRect(100, 80, 1000000, 999910), FragmentAt(relpos, 3) |
| .PaintProperties() |
| ->FragmentClip() |
| ->ClipRect() |
| .Rect()); |
| |
| LayoutObject* flowthread = GetLayoutObjectByElementId("relpos")->Parent(); |
| EXPECT_EQ(4u, NumFragments(flowthread)); |
| EXPECT_EQ(LayoutPoint(0, 0), FragmentAt(flowthread, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 0), FragmentAt(flowthread, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(flowthread, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ( |
| FragmentAt(flowthread, 0).PaintProperties()->FragmentClip()->ClipRect(), |
| FragmentAt(relpos, 0).PaintProperties()->FragmentClip()->ClipRect()); |
| |
| EXPECT_EQ(LayoutPoint(100, -30), FragmentAt(flowthread, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -30), |
| FragmentAt(flowthread, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(30), FragmentAt(flowthread, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ( |
| FragmentAt(flowthread, 1).PaintProperties()->FragmentClip()->ClipRect(), |
| FragmentAt(relpos, 1).PaintProperties()->FragmentClip()->ClipRect()); |
| |
| EXPECT_EQ(LayoutPoint(0, 20), FragmentAt(flowthread, 2).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 20), FragmentAt(flowthread, 2).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(60), FragmentAt(flowthread, 2).LogicalTopInFlowThread()); |
| EXPECT_EQ( |
| FragmentAt(flowthread, 2).PaintProperties()->FragmentClip()->ClipRect(), |
| FragmentAt(relpos, 2).PaintProperties()->FragmentClip()->ClipRect()); |
| |
| EXPECT_EQ(LayoutPoint(100, -10), FragmentAt(flowthread, 3).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -10), |
| FragmentAt(flowthread, 3).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(90), FragmentAt(flowthread, 3).LogicalTopInFlowThread()); |
| EXPECT_EQ( |
| FragmentAt(flowthread, 3).PaintProperties()->FragmentClip()->ClipRect(), |
| FragmentAt(relpos, 3).PaintProperties()->FragmentClip()->ClipRect()); |
| |
| // Above the spanner. |
| // Column 1. |
| EXPECT_EQ( |
| LayoutPoint(), |
| GetLayoutObjectByElementId("space1")->FirstFragment().PaintOffset()); |
| EXPECT_EQ( |
| LayoutPoint(100, 0), |
| GetLayoutObjectByElementId("space2")->FirstFragment().PaintOffset()); |
| |
| // The spanner's normal flow. |
| EXPECT_EQ( |
| LayoutPoint(0, 30), |
| GetLayoutObjectByElementId("spanner")->FirstFragment().PaintOffset()); |
| EXPECT_EQ( |
| LayoutPoint(0, 30), |
| GetLayoutObjectByElementId("normal")->FirstFragment().PaintOffset()); |
| |
| // Below the spanner. |
| EXPECT_EQ( |
| LayoutPoint(0, 80), |
| GetLayoutObjectByElementId("space3")->FirstFragment().PaintOffset()); |
| EXPECT_EQ( |
| LayoutPoint(100, 80), |
| GetLayoutObjectByElementId("space4")->FirstFragment().PaintOffset()); |
| |
| // Out-of-flow positioned descendants of the spanner. They are laid out in |
| // the relative-position container. |
| |
| // "top-left" should be aligned to the top-left corner of space1. |
| EXPECT_EQ( |
| LayoutPoint(0, 0), |
| GetLayoutObjectByElementId("top-left")->FirstFragment().PaintOffset()); |
| |
| // "bottom-right" should be aligned to the bottom-right corner of space4. |
| EXPECT_EQ(LayoutPoint(180, 90), GetLayoutObjectByElementId("bottom-right") |
| ->FirstFragment() |
| .PaintOffset()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FragmentsUnderMultiColumnVerticalRLWithOverflow) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0; }</style> |
| <div id='multicol' style='columns:2; column-fill:auto; column-gap: 0; |
| width: 200px; height: 200px; writing-mode: vertical-rl'> |
| <div style='width: 100px'> |
| <div id='content' style='width: 400px'></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* thread = |
| GetLayoutObjectByElementId("multicol")->SlowFirstChild(); |
| EXPECT_TRUE(thread->IsLayoutFlowThread()); |
| EXPECT_EQ(2u, NumFragments(thread)); |
| EXPECT_EQ(LayoutPoint(100, 0), FragmentAt(thread, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 0), FragmentAt(thread, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(thread, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(300, 100), FragmentAt(thread, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(200, 100), FragmentAt(thread, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), FragmentAt(thread, 1).LogicalTopInFlowThread()); |
| |
| LayoutObject* content = GetLayoutObjectByElementId("content"); |
| EXPECT_EQ(2u, NumFragments(content)); |
| EXPECT_EQ(LayoutPoint(-200, 0), FragmentAt(content, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 0), FragmentAt(content, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(content, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(0, 100), FragmentAt(content, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(200, 100), FragmentAt(content, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), FragmentAt(content, 1).LogicalTopInFlowThread()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, CompositedUnderMultiColumn) { |
| // TODO(crbug.com/796768): Currently this test crashes for SPv2 when mapping |
| // layer clip rects from one fragment to another. May need to adjust fragment |
| // clip hierarchy to fix the crash. |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) |
| return; |
| |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0; }</style> |
| <div id='multicol' style='columns:3; column-fill:auto; column-gap: 0; |
| width: 300px; height: 200px'> |
| <div style='height: 300px'></div> |
| <div id='composited' style='will-change: transform; height: 300px'> |
| <div id='non-composited-child' style='height: 150px'></div> |
| <div id='composited-child' |
| style='will-change: transform; height: 150px'></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* thread = |
| GetLayoutObjectByElementId("multicol")->SlowFirstChild(); |
| EXPECT_TRUE(thread->IsLayoutFlowThread()); |
| EXPECT_EQ(3u, NumFragments(thread)); |
| EXPECT_EQ(LayoutPoint(0, 0), FragmentAt(thread, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 0), FragmentAt(thread, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(thread, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(100, -200), FragmentAt(thread, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -200), FragmentAt(thread, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), FragmentAt(thread, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(200, -400), FragmentAt(thread, 2).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(200, -400), FragmentAt(thread, 2).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(400), FragmentAt(thread, 2).LogicalTopInFlowThread()); |
| |
| LayoutObject* composited = GetLayoutObjectByElementId("composited"); |
| LayoutObject* non_composited_child = |
| GetLayoutObjectByElementId("non-composited-child"); |
| LayoutObject* composited_child = |
| GetLayoutObjectByElementId("composited-child"); |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| // Compositing doesn't affect SPv2 fragmentation. |
| EXPECT_EQ(2u, NumFragments(composited)); |
| EXPECT_EQ(LayoutPoint(100, 100), FragmentAt(composited, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -200), |
| FragmentAt(composited, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(composited, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(200, -100), FragmentAt(composited, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(200, -400), |
| FragmentAt(composited, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), |
| FragmentAt(composited, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ(2u, NumFragments(non_composited_child)); |
| EXPECT_EQ(LayoutPoint(100, 100), |
| FragmentAt(non_composited_child, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -200), |
| FragmentAt(non_composited_child, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), |
| FragmentAt(non_composited_child, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(200, -100), |
| FragmentAt(non_composited_child, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(200, -400), |
| FragmentAt(non_composited_child, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), |
| FragmentAt(non_composited_child, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ(1u, NumFragments(composited_child)); |
| EXPECT_EQ(LayoutPoint(200, 50), |
| FragmentAt(composited_child, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(200, -400), |
| FragmentAt(composited_child, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), |
| FragmentAt(composited_child, 0).LogicalTopInFlowThread()); |
| } else { |
| // SPv1* forces single fragment for composited layers. |
| EXPECT_EQ(1u, NumFragments(composited)); |
| EXPECT_EQ(LayoutPoint(100, 100), FragmentAt(composited, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -200), |
| FragmentAt(composited, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), |
| FragmentAt(composited, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(1u, NumFragments(non_composited_child)); |
| EXPECT_EQ(LayoutPoint(100, 100), |
| FragmentAt(non_composited_child, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -200), |
| FragmentAt(non_composited_child, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), |
| FragmentAt(non_composited_child, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(1u, NumFragments(composited_child)); |
| EXPECT_EQ(LayoutPoint(100, 250), |
| FragmentAt(composited_child, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -200), |
| FragmentAt(composited_child, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), |
| FragmentAt(composited_child, 0).LogicalTopInFlowThread()); |
| } |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FragmentsInPagedY) { |
| // TODO(crbug.com/796768): Currently this test crashes for SPv2 when mapping |
| // layer clip rects from one fragment to another. May need to adjust fragment |
| // clip hierarchy to fix the crash. |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) |
| return; |
| |
| SetBodyInnerHTML(R"HTML( |
| <div id='paged' style='overflow: -webkit-paged-y; column-gap: 0; |
| width: 100px; height: 100px'> |
| <div id='content' style='height: 250px'></div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* content = GetLayoutObjectByElementId("content"); |
| EXPECT_EQ(3u, NumFragments(content)); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(content, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(100), FragmentAt(content, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 2).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 2).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), FragmentAt(content, 2).LogicalTopInFlowThread()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FragmentsInPagedYWithGap) { |
| // TODO(crbug.com/796768): Currently this test crashes for SPv2 when mapping |
| // layer clip rects from one fragment to another. May need to adjust fragment |
| // clip hierarchy to fix the crash. |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) |
| return; |
| |
| SetBodyInnerHTML(R"HTML( |
| <div id='paged' style='overflow: -webkit-paged-y; column-gap: 10px; |
| width: 100px; height: 100px'> |
| <div id='content' style='height: 250px'></div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* content = GetLayoutObjectByElementId("content"); |
| EXPECT_EQ(3u, NumFragments(content)); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(content, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(0, 10), FragmentAt(content, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 10), FragmentAt(content, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(100), FragmentAt(content, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(0, 20), FragmentAt(content, 2).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(0, 20), FragmentAt(content, 2).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), FragmentAt(content, 2).LogicalTopInFlowThread()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FragmentsInPagedX) { |
| // TODO(crbug.com/796768): Currently this test crashes for SPv2 when mapping |
| // layer clip rects from one fragment to another. May need to adjust fragment |
| // clip hierarchy to fix the crash. |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) |
| return; |
| |
| SetBodyInnerHTML(R"HTML( |
| <div id='paged' style='overflow: -webkit-paged-x; column-gap: 0; |
| width: 100px; height: 100px'> |
| <div id='content' style='height: 250px'></div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* content = GetLayoutObjectByElementId("content"); |
| EXPECT_EQ(3u, NumFragments(content)); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(content, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(100, -100), FragmentAt(content, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, -100), FragmentAt(content, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(100), FragmentAt(content, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(200, -200), FragmentAt(content, 2).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(200, -200), FragmentAt(content, 2).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), FragmentAt(content, 2).LogicalTopInFlowThread()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FragmentsInPagedYVerticalRL) { |
| // TODO(crbug.com/796768): Currently this test crashes for SPv2 when mapping |
| // layer clip rects from one fragment to another. May need to adjust fragment |
| // clip hierarchy to fix the crash. |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) |
| return; |
| |
| SetBodyInnerHTML(R"HTML( |
| <div id='paged' style='overflow: -webkit-paged-y; column-gap: 0; |
| width: 100px; height: 100px; writing-mode: vertical-rl'> |
| <div id='content' style='width: 250px'></div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* content = GetLayoutObjectByElementId("content"); |
| EXPECT_EQ(3u, NumFragments(content)); |
| EXPECT_EQ(LayoutPoint(-150, 0), FragmentAt(content, 0).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(), FragmentAt(content, 0).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), FragmentAt(content, 0).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(-50, 100), FragmentAt(content, 1).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(100, 100), FragmentAt(content, 1).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(100), FragmentAt(content, 1).LogicalTopInFlowThread()); |
| EXPECT_EQ(LayoutPoint(50, 200), FragmentAt(content, 2).PaintOffset()); |
| EXPECT_EQ(LayoutPoint(200, 200), FragmentAt(content, 2).PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(200), FragmentAt(content, 2).LogicalTopInFlowThread()); |
| } |
| |
| // Ensures no crash with multi-column containing relative-position inline with |
| // spanner with absolute-position children. |
| TEST_P(PaintPropertyTreeBuilderTest, |
| MultiColumnInlineRelativeAndSpannerAndAbsPos) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='columns:2; width: 200px; column-gap: 0'> |
| <span style='position: relative'> |
| <span id=spanner style='column-span: all'> |
| <div id=absolute style='position: absolute'>absolute</div> |
| </span> |
| </span> |
| </div> |
| )HTML"); |
| // The "spanner" isn't a real spanner because it's an inline. |
| EXPECT_FALSE(GetLayoutObjectByElementId("spanner")->IsColumnSpanAll()); |
| |
| SetBodyInnerHTML(R"HTML( |
| <div style='columns:2; width: 200px; column-gap: 0'> |
| <span style='position: relative'> |
| <div id=spanner style='column-span: all'> |
| <div id=absolute style='position: absolute'>absolute</div> |
| </div> |
| </span> |
| </div> |
| )HTML"); |
| // There should be anonymous block created containing the inline "relative", |
| // serving as the container of "absolute". |
| EXPECT_TRUE( |
| GetLayoutObjectByElementId("absolute")->Container()->IsLayoutBlock()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FrameUnderMulticol) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='columns: 2; width: 200px; height: 100px; coloum-gap: 0'> |
| <iframe style='width: 50px; height: 150px'></iframe> |
| </div> |
| )HTML"); |
| SetChildFrameHTML(R"HTML( |
| <style> |
| body { margin: 0; } |
| div { height: 60px; } |
| </style> |
| <div id='div1' style='background: blue'></div> |
| <div id='div2' style='background: green'></div> |
| )HTML"); |
| |
| // This should not crash on duplicated subsequences in the iframe. |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| // TODO(crbug.com/797779): Add code to verify fragments under the iframe. |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, CompositedMulticolFrameUnderMulticol) { |
| // TODO(crbug.com/796768): Currently this test crashes for SPv2 when mapping |
| // layer clip rects from one fragment to another. May need to adjust fragment |
| // clip hierarchy to fix the crash. |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) |
| return; |
| |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0 }</style> |
| <div style='columns: 3; column-gap: 0; column-fill: auto; |
| width: 300px; height: 200px'> |
| <div style='height: 300px'></div> |
| <iframe id='iframe' style='will-change: transform; |
| width: 90px; height: 300px; border: none; background: green'></iframe> |
| </div> |
| )HTML"); |
| SetChildFrameHTML(R"HTML( |
| <style>body { margin: 0 }</style> |
| <div style='columns: 2; column-gap: 0; column-fill: auto; |
| width: 80px; height: 100px'> |
| <div id="multicolContent" style='height: 200px; background: blue'></div> |
| </div> |
| )HTML"); |
| |
| // This should not crash on duplicated subsequences in the iframe. |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| // TODO(crbug.com/797779): Add code to verify fragments under the iframe. |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| BecomingUnfragmentedClearsPaginationOffsetAndLogicalTopInFlowThread) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #target { |
| width: 30px; height: 20px; position: relative; |
| } |
| </style> |
| <div style='columns: 2; height: 20px width: 400px'> |
| <div style='height: 20px'></div> |
| <div id=target></div> |
| </div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* target = GetLayoutObjectByElementId("target"); |
| EXPECT_EQ(LayoutPoint(LayoutUnit(392.5f), LayoutUnit(-20)), |
| target->FirstFragment().PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(20), target->FirstFragment().LogicalTopInFlowThread()); |
| Element* target_element = GetDocument().getElementById("target"); |
| |
| target_element->setAttribute(HTMLNames::styleAttr, "position: absolute"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(LayoutPoint(0, 0), target->FirstFragment().PaginationOffset()); |
| EXPECT_EQ(LayoutUnit(), target->FirstFragment().LogicalTopInFlowThread()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, Reflection) { |
| SetBodyInnerHTML( |
| "<div id='filter' style='-webkit-box-reflect: below; height:1000px;'>" |
| "</div>"); |
| const ObjectPaintProperties* filter_properties = |
| GetLayoutObjectByElementId("filter")->FirstFragment().PaintProperties(); |
| EXPECT_TRUE(filter_properties->Filter()->Parent()->IsRoot()); |
| EXPECT_EQ(FrameScrollTranslation(), |
| filter_properties->Filter()->LocalTransformSpace()); |
| EXPECT_EQ(FrameContentClip(), filter_properties->Filter()->OutputClip()); |
| EXPECT_EQ(FloatPoint(8, 8), filter_properties->Filter()->PaintOffset()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SimpleFilter) { |
| SetBodyInnerHTML( |
| "<div id='filter' style='filter:opacity(0.5); height:1000px;'>" |
| "</div>"); |
| const ObjectPaintProperties* filter_properties = |
| GetLayoutObjectByElementId("filter")->FirstFragment().PaintProperties(); |
| EXPECT_TRUE(filter_properties->Filter()->Parent()->IsRoot()); |
| EXPECT_EQ(FrameScrollTranslation(), |
| filter_properties->Filter()->LocalTransformSpace()); |
| EXPECT_EQ(FrameContentClip(), filter_properties->Filter()->OutputClip()); |
| EXPECT_EQ(FloatPoint(8, 8), filter_properties->Filter()->PaintOffset()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FilterReparentClips) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='clip' style='overflow:hidden;'> |
| <div id='filter' style='filter:opacity(0.5); height:1000px;'> |
| <div id='child' style='position:fixed;'></div> |
| </div> |
| </div> |
| )HTML"); |
| const ObjectPaintProperties* clip_properties = |
| GetLayoutObjectByElementId("clip")->FirstFragment().PaintProperties(); |
| const ObjectPaintProperties* filter_properties = |
| GetLayoutObjectByElementId("filter")->FirstFragment().PaintProperties(); |
| EXPECT_TRUE(filter_properties->Filter()->Parent()->IsRoot()); |
| EXPECT_EQ(clip_properties->OverflowClip(), |
| filter_properties->Filter()->OutputClip()); |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| EXPECT_EQ(FrameScrollTranslation(), |
| filter_properties->Filter()->LocalTransformSpace()); |
| EXPECT_EQ(FloatPoint(8, 8), filter_properties->Filter()->PaintOffset()); |
| } else { |
| // For SPv1*, |filter| is composited so we created PaintOffsetTranslation. |
| EXPECT_EQ(filter_properties->PaintOffsetTranslation(), |
| filter_properties->Filter()->LocalTransformSpace()); |
| EXPECT_EQ(TransformationMatrix().Translate(8, 8), |
| filter_properties->PaintOffsetTranslation()->Matrix()); |
| EXPECT_EQ(FloatPoint(), filter_properties->Filter()->PaintOffset()); |
| } |
| |
| const PropertyTreeState& child_paint_state = |
| *GetLayoutObjectByElementId("child") |
| ->FirstFragment() |
| .LocalBorderBoxProperties(); |
| |
| // This will change once we added clip expansion node. |
| EXPECT_EQ(filter_properties->Filter()->OutputClip(), |
| child_paint_state.Clip()); |
| EXPECT_EQ(filter_properties->Filter(), child_paint_state.Effect()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TransformOriginWithAndWithoutTransform) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { margin: 0 } |
| div { |
| width: 400px; |
| height: 100px; |
| } |
| #transform { |
| transform: translate(100px, 200px); |
| transform-origin: 75% 75% 0; |
| } |
| #willChange { |
| will-change: opacity; |
| transform-origin: 75% 75% 0; |
| } |
| </style> |
| <div id='transform'></div> |
| <div id='willChange'></div> |
| )HTML"); |
| |
| auto* transform = GetLayoutObjectByElementId("transform"); |
| EXPECT_EQ( |
| TransformationMatrix().Translate3d(100, 200, 0), |
| transform->FirstFragment().PaintProperties()->Transform()->Matrix()); |
| EXPECT_EQ( |
| FloatPoint3D(300, 75, 0), |
| transform->FirstFragment().PaintProperties()->Transform()->Origin()); |
| |
| auto* will_change = GetLayoutObjectByElementId("willChange"); |
| EXPECT_EQ( |
| TransformationMatrix().Translate3d(0, 0, 0), |
| will_change->FirstFragment().PaintProperties()->Transform()->Matrix()); |
| EXPECT_EQ( |
| FloatPoint3D(0, 0, 0), |
| will_change->FirstFragment().PaintProperties()->Transform()->Origin()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, TransformOriginWithAndWithoutMotionPath) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { margin: 0 } |
| div { |
| width: 100px; |
| height: 100px; |
| } |
| #motionPath { |
| position: absolute; |
| offset-path: path('M0 0 L 200 400'); |
| offset-distance: 50%; |
| offset-rotate: 0deg; |
| transform-origin: 50% 50% 0; |
| } |
| #willChange { |
| will-change: opacity; |
| transform-origin: 50% 50% 0; |
| } |
| </style> |
| <div id='motionPath'></div> |
| <div id='willChange'></div> |
| )HTML"); |
| |
| auto* motion_path = GetLayoutObjectByElementId("motionPath"); |
| EXPECT_EQ( |
| TransformationMatrix().Translate3d(50, 150, 0), |
| motion_path->FirstFragment().PaintProperties()->Transform()->Matrix()); |
| EXPECT_EQ( |
| FloatPoint3D(50, 50, 0), |
| motion_path->FirstFragment().PaintProperties()->Transform()->Origin()); |
| |
| auto* will_change = GetLayoutObjectByElementId("willChange"); |
| EXPECT_EQ( |
| TransformationMatrix().Translate3d(0, 0, 0), |
| will_change->FirstFragment().PaintProperties()->Transform()->Matrix()); |
| EXPECT_EQ( |
| FloatPoint3D(0, 0, 0), |
| will_change->FirstFragment().PaintProperties()->Transform()->Origin()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ChangePositionUpdateDescendantProperties) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { margin: 0; } |
| #ancestor { position: absolute; overflow: hidden } |
| #descendant { position: absolute } |
| </style> |
| <div id='ancestor'> |
| <div id='descendant'></div> |
| </div> |
| )HTML"); |
| |
| LayoutObject* ancestor = GetLayoutObjectByElementId("ancestor"); |
| LayoutObject* descendant = GetLayoutObjectByElementId("descendant"); |
| EXPECT_EQ(ancestor->FirstFragment().PaintProperties()->OverflowClip(), |
| descendant->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| |
| ToElement(ancestor->GetNode()) |
| ->setAttribute(HTMLNames::styleAttr, "position: static"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_NE(ancestor->FirstFragment().PaintProperties()->OverflowClip(), |
| descendant->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| TransformNodeNotAnimatedStillHasCompositorElementId) { |
| SetBodyInnerHTML("<div id='target' style='transform: translateX(2em)'></div"); |
| const ObjectPaintProperties* properties = PaintPropertiesForElement("target"); |
| EXPECT_TRUE(properties->Transform()); |
| EXPECT_NE(CompositorElementId(), |
| properties->Transform()->GetCompositorElementId()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| EffectNodeNotAnimatedStillHasCompositorElementId) { |
| SetBodyInnerHTML("<div id='target' style='opacity: 0.5'></div"); |
| const ObjectPaintProperties* properties = PaintPropertiesForElement("target"); |
| EXPECT_TRUE(properties->Effect()); |
| EXPECT_NE(CompositorElementId(), |
| properties->Effect()->GetCompositorElementId()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| TransformNodeAnimatedHasCompositorElementId) { |
| LoadTestData("transform-animation.html"); |
| const ObjectPaintProperties* properties = PaintPropertiesForElement("target"); |
| EXPECT_TRUE(properties->Transform()); |
| EXPECT_NE(CompositorElementId(), |
| properties->Transform()->GetCompositorElementId()); |
| EXPECT_TRUE(properties->Transform()->RequiresCompositingForAnimation()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, EffectNodeAnimatedHasCompositorElementId) { |
| LoadTestData("opacity-animation.html"); |
| const ObjectPaintProperties* properties = PaintPropertiesForElement("target"); |
| EXPECT_TRUE(properties->Effect()); |
| EXPECT_NE(CompositorElementId(), |
| properties->Effect()->GetCompositorElementId()); |
| EXPECT_TRUE(properties->Effect()->RequiresCompositingForAnimation()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FloatUnderInline) { |
| SetBodyInnerHTML(R"HTML( |
| <div style='position: absolute; top: 55px; left: 66px'> |
| <span id='span' |
| style='position: relative; top: 100px; left: 200px; opacity: 0.5'> |
| <div id='target' |
| style='overflow: hidden; float: left; width: 3px; height: 4px'> |
| </div> |
| </span> |
| </div> |
| )HTML"); |
| |
| LayoutObject* span = GetLayoutObjectByElementId("span"); |
| const auto* effect = span->FirstFragment().PaintProperties()->Effect(); |
| ASSERT_TRUE(effect); |
| EXPECT_EQ(0.5f, effect->Opacity()); |
| |
| LayoutObject* target = GetLayoutObjectByElementId("target"); |
| ASSERT_TRUE(target->FirstFragment().LocalBorderBoxProperties()); |
| EXPECT_EQ(LayoutPoint(66, 55), target->FirstFragment().PaintOffset()); |
| EXPECT_EQ(effect, |
| target->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ScrollNodeHasCompositorElementId) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' style='overflow: auto; width: 100px; height: 100px'> |
| <div style='width: 200px; height: 200px'></div> |
| </div> |
| )HTML"); |
| |
| const ObjectPaintProperties* properties = PaintPropertiesForElement("target"); |
| // The scroll translation node should not have the element id as it should be |
| // stored directly on the ScrollNode. |
| EXPECT_EQ(CompositorElementId(), |
| properties->ScrollTranslation()->GetCompositorElementId()); |
| EXPECT_NE(CompositorElementId(), |
| properties->Scroll()->GetCompositorElementId()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, OverflowClipSubpixelPosition) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 20px 30px; }</style> |
| <div id='clipper' |
| style='position: relative; overflow: hidden; |
| width: 400px; height: 300px; left: 1.5px'> |
| </div> |
| )HTML"); |
| |
| LayoutBoxModelObject* clipper = |
| ToLayoutBoxModelObject(GetLayoutObjectByElementId("clipper")); |
| const ObjectPaintProperties* clip_properties = |
| clipper->FirstFragment().PaintProperties(); |
| |
| EXPECT_EQ(LayoutPoint(FloatPoint(31.5, 20)), |
| clipper->FirstFragment().PaintOffset()); |
| EXPECT_EQ(FloatRect(31.5, 20, 400, 300), |
| clip_properties->OverflowClip()->ClipRect().Rect()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, MaskSimple) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' style='width:300px; height:200px; |
| -webkit-mask:linear-gradient(red,red)'> |
| Lorem ipsum |
| </div> |
| )HTML"); |
| |
| const ObjectPaintProperties* properties = PaintPropertiesForElement("target"); |
| const ClipPaintPropertyNode* output_clip = properties->MaskClip(); |
| |
| const auto* target = GetLayoutObjectByElementId("target"); |
| EXPECT_EQ(output_clip, |
| target->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(FrameContentClip(), output_clip->Parent()); |
| EXPECT_EQ(FloatRoundedRect(8, 8, 300, 200), output_clip->ClipRect()); |
| |
| EXPECT_EQ(properties->Effect(), |
| target->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| EXPECT_TRUE(properties->Effect()->Parent()->IsRoot()); |
| EXPECT_EQ(SkBlendMode::kSrcOver, properties->Effect()->BlendMode()); |
| EXPECT_EQ(output_clip, properties->Effect()->OutputClip()); |
| |
| EXPECT_EQ(properties->Effect(), properties->Mask()->Parent()); |
| EXPECT_EQ(SkBlendMode::kDstIn, properties->Mask()->BlendMode()); |
| EXPECT_EQ(output_clip, properties->Mask()->OutputClip()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, MaskWithOutset) { |
| SetBodyInnerHTML(R"HTML( |
| <div id='target' style='width:300px; height:200px; |
| -webkit-mask-box-image-source:linear-gradient(red,red); |
| -webkit-mask-box-image-outset:10px 20px;'> |
| Lorem ipsum |
| </div> |
| )HTML"); |
| |
| const ObjectPaintProperties* properties = PaintPropertiesForElement("target"); |
| const ClipPaintPropertyNode* output_clip = properties->MaskClip(); |
| |
| const auto* target = GetLayoutObjectByElementId("target"); |
| EXPECT_EQ(output_clip, |
| target->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(FrameContentClip(), output_clip->Parent()); |
| EXPECT_EQ(FloatRoundedRect(-12, -2, 340, 220), output_clip->ClipRect()); |
| |
| EXPECT_EQ(properties->Effect(), |
| target->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| EXPECT_TRUE(properties->Effect()->Parent()->IsRoot()); |
| EXPECT_EQ(SkBlendMode::kSrcOver, properties->Effect()->BlendMode()); |
| EXPECT_EQ(output_clip, properties->Effect()->OutputClip()); |
| |
| EXPECT_EQ(properties->Effect(), properties->Mask()->Parent()); |
| EXPECT_EQ(SkBlendMode::kDstIn, properties->Mask()->BlendMode()); |
| EXPECT_EQ(output_clip, properties->Mask()->OutputClip()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, MaskEscapeClip) { |
| // This test verifies an abs-pos element still escape the scroll of a |
| // static-pos ancestor, but gets clipped due to the presence of a mask. |
| SetBodyInnerHTML(R"HTML( |
| <div id='scroll' style='width:300px; height:200px; overflow:scroll;'> |
| <div id='target' style='width:200px; height:300px; |
| -webkit-mask:linear-gradient(red,red); border:10px dashed black; |
| overflow:hidden;'> |
| <div id='absolute' style='position:absolute; left:0; top:0;'> |
| Lorem ipsum |
| </div> |
| </div> |
| </div> |
| )HTML"); |
| |
| const ObjectPaintProperties* target_properties = |
| PaintPropertiesForElement("target"); |
| const ClipPaintPropertyNode* overflow_clip1 = |
| target_properties->MaskClip()->Parent(); |
| const ClipPaintPropertyNode* mask_clip = target_properties->MaskClip(); |
| const ClipPaintPropertyNode* overflow_clip2 = |
| target_properties->OverflowClip(); |
| const auto* target = GetLayoutObjectByElementId("target"); |
| const TransformPaintPropertyNode* scroll_translation = |
| target->FirstFragment().LocalBorderBoxProperties()->Transform(); |
| |
| const ObjectPaintProperties* scroll_properties = |
| PaintPropertiesForElement("scroll"); |
| |
| EXPECT_EQ(FrameContentClip(), overflow_clip1->Parent()); |
| EXPECT_EQ(FloatRoundedRect(0, 0, 300, 200), overflow_clip1->ClipRect()); |
| EXPECT_EQ(scroll_properties->PaintOffsetTranslation(), |
| overflow_clip1->LocalTransformSpace()); |
| |
| EXPECT_EQ(mask_clip, |
| target->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(overflow_clip1, mask_clip->Parent()); |
| EXPECT_EQ(FloatRoundedRect(0, 0, 220, 320), mask_clip->ClipRect()); |
| EXPECT_EQ(scroll_translation, mask_clip->LocalTransformSpace()); |
| |
| EXPECT_EQ(mask_clip, overflow_clip2->Parent()); |
| EXPECT_EQ(FloatRoundedRect(10, 10, 200, 300), overflow_clip2->ClipRect()); |
| EXPECT_EQ(scroll_translation, overflow_clip2->LocalTransformSpace()); |
| |
| EXPECT_EQ(target_properties->Effect(), |
| target->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| EXPECT_TRUE(target_properties->Effect()->Parent()->IsRoot()); |
| EXPECT_EQ(SkBlendMode::kSrcOver, target_properties->Effect()->BlendMode()); |
| EXPECT_EQ(mask_clip, target_properties->Effect()->OutputClip()); |
| |
| EXPECT_EQ(target_properties->Effect(), target_properties->Mask()->Parent()); |
| EXPECT_EQ(SkBlendMode::kDstIn, target_properties->Mask()->BlendMode()); |
| EXPECT_EQ(mask_clip, target_properties->Mask()->OutputClip()); |
| |
| const auto* absolute = GetLayoutObjectByElementId("absolute"); |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| EXPECT_EQ( |
| FramePreTranslation(), |
| absolute->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| } else { |
| // For SPv1*, |absolute| is composited so we created PaintOffsetTranslation. |
| EXPECT_EQ( |
| absolute->FirstFragment().PaintProperties()->PaintOffsetTranslation(), |
| absolute->FirstFragment().LocalBorderBoxProperties()->Transform()); |
| } |
| EXPECT_EQ(mask_clip, |
| absolute->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, MaskInline) { |
| LoadAhem(); |
| // This test verifies CSS mask applied on an inline element is clipped to |
| // the line box of the said element. In this test the masked element has |
| // only one box, and one of the child element overflows the box. |
| SetBodyInnerHTML(R"HTML( |
| <style>* { font-family:Ahem; font-size:16px; }</style> |
| Lorem |
| <span id='target' style='-webkit-mask:linear-gradient(red,red);'> |
| ipsum |
| <span id='overflowing' style='position:relative; font-size:32px;'> |
| dolor |
| </span> |
| sit amet, |
| </span> |
| )HTML"); |
| |
| const ObjectPaintProperties* properties = PaintPropertiesForElement("target"); |
| const ClipPaintPropertyNode* output_clip = properties->MaskClip(); |
| const auto* target = GetLayoutObjectByElementId("target"); |
| |
| EXPECT_EQ(output_clip, |
| target->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(FrameContentClip(), output_clip->Parent()); |
| EXPECT_EQ(FloatRoundedRect(104, 21, 432, 16), output_clip->ClipRect()); |
| |
| EXPECT_EQ(properties->Effect(), |
| target->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| EXPECT_TRUE(properties->Effect()->Parent()->IsRoot()); |
| EXPECT_EQ(SkBlendMode::kSrcOver, properties->Effect()->BlendMode()); |
| EXPECT_EQ(output_clip, properties->Effect()->OutputClip()); |
| |
| EXPECT_EQ(properties->Effect(), properties->Mask()->Parent()); |
| EXPECT_EQ(SkBlendMode::kDstIn, properties->Mask()->BlendMode()); |
| EXPECT_EQ(output_clip, properties->Mask()->OutputClip()); |
| |
| const auto* overflowing = GetLayoutObjectByElementId("overflowing"); |
| EXPECT_EQ(output_clip, |
| overflowing->FirstFragment().LocalBorderBoxProperties()->Clip()); |
| EXPECT_EQ(properties->Effect(), |
| overflowing->FirstFragment().LocalBorderBoxProperties()->Effect()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGResource) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg' xmlns='http://www.w3.org/2000/svg' > |
| <g transform='scale(1000)'> |
| <marker id='markerMiddle' markerWidth='2' markerHeight='2' refX='5' |
| refY='5' markerUnits='strokeWidth'> |
| <g id='transformInsideMarker' transform='scale(4)'> |
| <circle cx='5' cy='5' r='7' fill='green'/> |
| </g> |
| </marker> |
| </g> |
| <g id='transformOutsidePath' transform='scale(2)'> |
| <path d='M 130 135 L 180 135 L 180 185' |
| marker-mid='url(#markerMiddle)' fill='none' stroke-width='8px' |
| stroke='black'/> |
| </g> |
| </svg> |
| )HTML"); |
| |
| const ObjectPaintProperties* transform_inside_marker_properties = |
| PaintPropertiesForElement("transformInsideMarker"); |
| const ObjectPaintProperties* transform_outside_path_properties = |
| PaintPropertiesForElement("transformOutsidePath"); |
| const ObjectPaintProperties* svg_properties = |
| PaintPropertiesForElement("svg"); |
| |
| // The <marker> object resets to a new paint property tree, so the |
| // transform within it should have the root as parent. |
| EXPECT_EQ(TransformPaintPropertyNode::Root(), |
| transform_inside_marker_properties->Transform()->Parent()); |
| |
| // Whereas this is not true of the transform above the path. |
| EXPECT_EQ(svg_properties->PaintOffsetTranslation(), |
| transform_outside_path_properties->Transform()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGHiddenResource) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg' xmlns='http://www.w3.org/2000/svg' > |
| <g transform='scale(1000)'> |
| <symbol id='symbol'> |
| <g id='transformInsideSymbol' transform='scale(4)'> |
| <circle cx='5' cy='5' r='7' fill='green'/> |
| </g> |
| </symbol> |
| </g> |
| <g id='transformOutsideUse' transform='scale(2)'> |
| <use x='25' y='25' width='400' height='400' xlink:href='#symbol'/> |
| </g> |
| </svg> |
| )HTML"); |
| |
| const ObjectPaintProperties* transform_inside_symbol_properties = |
| PaintPropertiesForElement("transformInsideSymbol"); |
| const ObjectPaintProperties* transform_outside_use_properties = |
| PaintPropertiesForElement("transformOutsideUse"); |
| const ObjectPaintProperties* svg_properties = |
| PaintPropertiesForElement("svg"); |
| |
| // The <marker> object resets to a new paint property tree, so the |
| // transform within it should have the root as parent. |
| EXPECT_EQ(TransformPaintPropertyNode::Root(), |
| transform_inside_symbol_properties->Transform()->Parent()); |
| |
| // Whereas this is not true of the transform above the path. |
| EXPECT_EQ(svg_properties->PaintOffsetTranslation(), |
| transform_outside_use_properties->Transform()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, SVGRootBlending) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svgroot' 'width=100' height='100' |
| style='position: relative; z-index: 0'> |
| <rect width='100' height='100' fill='#00FF00' |
| style='mix-blend-mode: difference'/> |
| </svg> |
| )HTML"); |
| |
| LayoutObject& svg_root = *GetLayoutObjectByElementId("svgroot"); |
| const ObjectPaintProperties* svg_root_properties = |
| svg_root.FirstFragment().PaintProperties(); |
| EXPECT_TRUE(svg_root_properties->Effect()); |
| EXPECT_EQ(EffectPaintPropertyNode::Root(), |
| svg_root_properties->Effect()->Parent()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ScrollBoundsOffset) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| body { |
| margin: 0px; |
| } |
| #scroller { |
| overflow-y: scroll; |
| width: 100px; |
| height: 100px; |
| margin-left: 7px; |
| margin-top: 11px; |
| } |
| .forceScroll { |
| height: 200px; |
| } |
| </style> |
| <div id='scroller'> |
| <div class='forceScroll'></div> |
| </div> |
| )HTML"); |
| |
| Element* scroller = GetDocument().getElementById("scroller"); |
| scroller->setScrollTop(42); |
| |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| const ObjectPaintProperties* scroll_properties = |
| scroller->GetLayoutObject()->FirstFragment().PaintProperties(); |
| // Because the frameView is does not scroll, overflowHidden's scroll should be |
| // under the root. |
| auto* scroll_translation = scroll_properties->ScrollTranslation(); |
| auto* paint_offset_translation = scroll_properties->PaintOffsetTranslation(); |
| auto* scroll_node = scroll_translation->ScrollNode(); |
| EXPECT_TRUE(scroll_node->Parent()->IsRoot()); |
| EXPECT_EQ(TransformationMatrix().Translate(0, -42), |
| scroll_translation->Matrix()); |
| // The paint offset node should be offset by the margin. |
| EXPECT_EQ(FloatSize(7, 11), |
| paint_offset_translation->Matrix().To2DTranslation()); |
| // And the scroll node should not. |
| EXPECT_EQ(IntRect(0, 0, 100, 100), scroll_node->ContainerRect()); |
| |
| scroller->setAttribute(HTMLNames::styleAttr, "border: 20px solid black;"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| // The paint offset node should be offset by the margin. |
| EXPECT_EQ(FloatSize(7, 11), |
| paint_offset_translation->Matrix().To2DTranslation()); |
| // The scroll node should be offset by the border. |
| EXPECT_EQ(IntRect(20, 20, 100, 100), scroll_node->ContainerRect()); |
| |
| scroller->setAttribute(HTMLNames::styleAttr, |
| "border: 20px solid black;" |
| "transform: translate(20px, 30px);"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| // The scroll node's offset should not include margin if it has already been |
| // included in a paint offset node. |
| EXPECT_EQ(IntRect(20, 20, 100, 100), scroll_node->ContainerRect()); |
| EXPECT_EQ(TransformationMatrix().Translate(7, 11), |
| scroll_properties->PaintOffsetTranslation()->Matrix()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FilterContainingBlockUseCounterRelPos) { |
| SetBodyInnerHTML( |
| "<div style='filter:blur(2px)'>" |
| " <div id=target style='position:relative'></div>" |
| "</div>"); |
| |
| EXPECT_FALSE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FilterContainingBlockUseCounterAbsPos) { |
| SetBodyInnerHTML( |
| "<div style='filter:blur(2px)'>" |
| " <div id=target style='position:absolute'></div>" |
| "</div>"); |
| |
| EXPECT_TRUE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FilterContainingBlockUseCounterStickyPos) { |
| SetBodyInnerHTML( |
| "<div style='filter:blur(2px)'>" |
| " <div id=target style='position:sticky'></div>" |
| "</div>"); |
| |
| EXPECT_FALSE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FilterContainingBlockUseCounterFixedPos) { |
| SetBodyInnerHTML( |
| "<div style='filter:blur(2px)'>" |
| " <div id=target style='position:fixed'></div>" |
| "</div>"); |
| |
| EXPECT_TRUE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FilterContainingBlockUseCounterAbsPosAbsPos) { |
| SetBodyInnerHTML( |
| "<div style='filter:blur(2px); position:absolute'>" |
| " <div id=target style='position:absolute'></div>" |
| "</div>"); |
| |
| EXPECT_FALSE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FilterContainingBlockUseCounterAbsPosFixedPos) { |
| SetBodyInnerHTML( |
| "<div style='filter:blur(2px); position:absolute'>" |
| " <div id=target style='position:fixed'></div>" |
| "</div>" |
| "<div style='width: 10px; height: 1000px'></div>"); |
| |
| EXPECT_TRUE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FilterContainingBlockUseCounterFixedPosFixedPos) { |
| SetBodyInnerHTML( |
| "<div style='filter:blur(2px); position:fixed'>" |
| " <div id=target style='position:fixed'></div>" |
| "</div>"); |
| |
| EXPECT_TRUE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FilterContainingBlockUseCounterFixedPosTransform) { |
| SetBodyInnerHTML( |
| "<div style='filter:blur(2px); transform: translateX(0)'>" |
| " <div id=target style='position:fixed'></div>" |
| "</div>"); |
| |
| EXPECT_FALSE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| // The body and html elements are special and won't affect change of |
| // containing block for filter. |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FilterContainingBlockUseCounterFixedPosUnderFilteredBody) { |
| SetBodyInnerHTML( |
| "<body style='filter:blur(2px);'>" |
| " <div id=target style='position:fixed'></div>" |
| "</body>"); |
| |
| EXPECT_FALSE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, |
| FilterContainingBlockUseCounterFixedPosUnderFilteredHTML) { |
| SetBodyInnerHTML( |
| "<html style='filter:blur(2px);'>" |
| " <div id=target style='position:fixed'></div>" |
| "</html>"); |
| |
| EXPECT_FALSE(UseCounter::IsCounted( |
| GetDocument(), WebFeature::kFilterAsContainingBlockMayChangeOutput)); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, CompositedLayerPaintOffsetTranslation) { |
| SetBodyInnerHTML(R"HTML( |
| <style>#target { position: absolute; top: 50px; left: 60px }</style> |
| <div id='target' style='backface-visibility: hidden'></div> |
| )HTML"); |
| |
| const auto* target = GetLayoutObjectByElementId("target"); |
| if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| EXPECT_EQ(nullptr, target->FirstFragment().PaintProperties()); |
| EXPECT_EQ(LayoutPoint(60, 50), target->FirstFragment().PaintOffset()); |
| } else { |
| // For SPv1*, |target| is composited so we created PaintOffsetTranslation. |
| const auto* paint_offset_translation = |
| target->FirstFragment().PaintProperties()->PaintOffsetTranslation(); |
| ASSERT_NE(nullptr, paint_offset_translation); |
| EXPECT_EQ(TransformationMatrix().Translate(60, 50), |
| paint_offset_translation->Matrix()); |
| } |
| |
| ToElement(target->GetNode())->setAttribute(HTMLNames::styleAttr, ""); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| EXPECT_EQ(LayoutPoint(60, 50), target->FirstFragment().PaintOffset()); |
| EXPECT_EQ(nullptr, target->FirstFragment().PaintProperties()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FrameBorderRadius) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| #iframe { |
| width: 200px; |
| height: 200px; |
| border: 10px solid blue; |
| border-radius: 50px; |
| padding: 10px; |
| } |
| </style> |
| <iframe id='iframe'></iframe> |
| )HTML"); |
| |
| const auto* properties = PaintPropertiesForElement("iframe"); |
| const auto* border_radius_clip = properties->InnerBorderRadiusClip(); |
| ASSERT_NE(nullptr, border_radius_clip); |
| FloatSize radius(30, 30); |
| EXPECT_EQ(FloatRoundedRect(FloatRect(28, 28, 200, 200), radius, radius, |
| radius, radius), |
| border_radius_clip->ClipRect()); |
| EXPECT_EQ(FrameContentClip(), border_radius_clip->Parent()); |
| EXPECT_EQ(FramePreTranslation(), border_radius_clip->LocalTransformSpace()); |
| EXPECT_EQ(nullptr, properties->OverflowClip()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, NoPropertyForSVGTextWithReflection) { |
| SetBodyInnerHTML(R"HTML( |
| <svg> |
| <text id='target' style='-webkit-box-reflect: below 2px'>X</text> |
| </svg> |
| )HTML"); |
| EXPECT_FALSE(PaintPropertiesForElement("target")); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, ImageBorderRadius) { |
| SetBodyInnerHTML(R"HTML( |
| <img id='img' |
| style='width: 50px; height: 50px; border-radius: 30px; padding: 10px'> |
| )HTML"); |
| |
| const auto* properties = PaintPropertiesForElement("img"); |
| const auto* border_radius_clip = properties->InnerBorderRadiusClip(); |
| ASSERT_NE(nullptr, border_radius_clip); |
| FloatSize radius(20, 20); |
| EXPECT_EQ(FloatRoundedRect(FloatRect(18, 18, 50, 50), radius, radius, radius, |
| radius), |
| border_radius_clip->ClipRect()); |
| EXPECT_EQ(FrameContentClip(), border_radius_clip->Parent()); |
| EXPECT_EQ(FramePreTranslation(), border_radius_clip->LocalTransformSpace()); |
| EXPECT_EQ(nullptr, properties->OverflowClip()); |
| } |
| |
| TEST_P(PaintPropertyTreeBuilderTest, FrameClipWhenPrinting) { |
| SetBodyInnerHTML("<iframe></iframe>"); |
| SetChildFrameHTML(""); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| // When not printing, both main and child frame views have content clip. |
| const auto& main_frame_view = GetDocument().View(); |
| const auto& child_frame_view = ChildDocument().View(); |
| ASSERT_NE(nullptr, FrameContentClip(main_frame_view)); |
| EXPECT_NE(FloatRect(LayoutRect::InfiniteIntRect()), |
| FrameContentClip(main_frame_view)->ClipRect().Rect()); |
| ASSERT_NE(nullptr, FrameContentClip(child_frame_view)); |
| EXPECT_NE(FloatRect(LayoutRect::InfiniteIntRect()), |
| FrameContentClip(child_frame_view)->ClipRect().Rect()); |
| |
| // When the main frame is printing, it should not have content clip. |
| FloatSize page_size(100, 100); |
| GetFrame().SetPrinting(true, page_size, page_size, 1); |
| GetDocument().View()->UpdateLifecyclePhasesForPrinting(); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| EXPECT_EQ(nullptr, FrameContentClip(main_frame_view)); |
| } else { |
| EXPECT_EQ(FloatRect(LayoutRect::InfiniteIntRect()), |
| FrameContentClip(main_frame_view)->ClipRect().Rect()); |
| } |
| ASSERT_NE(nullptr, FrameContentClip(child_frame_view)); |
| EXPECT_NE(FloatRect(LayoutRect::InfiniteIntRect()), |
| FrameContentClip(child_frame_view)->ClipRect().Rect()); |
| |
| GetFrame().SetPrinting(false, page_size, page_size, 1); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| // When only the child frame is printing, it should not have content clip but |
| // the main frame still have (which doesn't matter though). |
| ChildFrame().SetPrinting(true, page_size, page_size, 1); |
| GetDocument().View()->UpdateLifecyclePhasesForPrinting(); |
| ASSERT_NE(nullptr, FrameContentClip(main_frame_view)); |
| EXPECT_NE(FloatRect(LayoutRect::InfiniteIntRect()), |
| FrameContentClip(main_frame_view)->ClipRect().Rect()); |
| if (RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| EXPECT_EQ(nullptr, FrameContentClip(child_frame_view)); |
| } else { |
| EXPECT_EQ(FloatRect(LayoutRect::InfiniteIntRect()), |
| FrameContentClip(child_frame_view)->ClipRect().Rect()); |
| } |
| } |
| |
| } // namespace blink |