| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/layout/layout_geometry_map.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h" |
| |
| namespace blink { |
| |
| class LayoutSVGForeignObjectTest : public RenderingTest { |
| public: |
| LayoutSVGForeignObjectTest() |
| : RenderingTest(SingleChildLocalFrameClient::Create()) {} |
| }; |
| |
| TEST_F(LayoutSVGForeignObjectTest, DivInForeignObject) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0 }</style> |
| <svg id='svg' style='width: 500px; height: 400px'> |
| <foreignObject id='foreign' x='100' y='100' width='300' height='200'> |
| <div id='div' style='margin: 50px; width: 200px; height: 100px'> |
| </div> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| const auto& svg = *GetDocument().getElementById("svg"); |
| const auto& foreign = *GetDocument().getElementById("foreign"); |
| const auto& foreign_object = *GetLayoutObjectByElementId("foreign"); |
| const auto& div = *GetLayoutObjectByElementId("div"); |
| |
| EXPECT_EQ(FloatRect(100, 100, 300, 200), foreign_object.ObjectBoundingBox()); |
| EXPECT_EQ(AffineTransform(), foreign_object.LocalSVGTransform()); |
| EXPECT_EQ(AffineTransform(), foreign_object.LocalToSVGParentTransform()); |
| |
| // mapToVisualRectInAncestorSpace |
| LayoutRect div_rect(0, 0, 100, 50); |
| EXPECT_TRUE(div.MapToVisualRectInAncestorSpace(&GetLayoutView(), div_rect)); |
| EXPECT_EQ(LayoutRect(150, 150, 100, 50), div_rect); |
| |
| // mapLocalToAncestor |
| TransformState transform_state(TransformState::kApplyTransformDirection, |
| FloatPoint()); |
| div.MapLocalToAncestor(&GetLayoutView(), transform_state, |
| kTraverseDocumentBoundaries); |
| transform_state.Flatten(); |
| EXPECT_EQ(FloatPoint(150, 150), transform_state.LastPlanarPoint()); |
| |
| // mapAncestorToLocal |
| TransformState transform_state1( |
| TransformState::kUnapplyInverseTransformDirection, FloatPoint()); |
| div.MapAncestorToLocal(&GetLayoutView(), transform_state1, |
| kTraverseDocumentBoundaries); |
| transform_state1.Flatten(); |
| EXPECT_EQ(FloatPoint(-150, -150), transform_state1.LastPlanarPoint()); |
| |
| // pushMappingToContainer |
| LayoutGeometryMap rgm(kTraverseDocumentBoundaries); |
| rgm.PushMappingsToAncestor(&div, nullptr); |
| EXPECT_EQ(FloatQuad(FloatRect(150, 150, 1, 2)), |
| rgm.MapToAncestor(FloatRect(0, 0, 1, 2), nullptr)); |
| |
| // Hit testing |
| EXPECT_EQ(svg, HitTest(1, 1)); |
| EXPECT_EQ(foreign, HitTest(149, 149)); |
| EXPECT_EQ(div.GetNode(), HitTest(150, 150)); |
| EXPECT_EQ(div.GetNode(), HitTest(349, 249)); |
| EXPECT_EQ(foreign, HitTest(350, 250)); |
| EXPECT_EQ(svg, HitTest(450, 350)); |
| |
| // Rect based hit testing |
| auto results = RectBasedHitTest(LayoutRect(0, 0, 300, 300)); |
| int count = 0; |
| EXPECT_EQ(3u, results.size()); |
| for (auto result : results) { |
| Node* node = result.Get(); |
| if (node == svg || node == div.GetNode() || node == foreign) |
| count++; |
| } |
| EXPECT_EQ(3, count); |
| } |
| |
| TEST_F(LayoutSVGForeignObjectTest, IframeInForeignObject) { |
| SetBodyInnerHTML(R"HTML( |
| <style>body { margin: 0 }</style> |
| <svg id='svg' style='width: 500px; height: 450px'> |
| <foreignObject id='foreign' x='100' y='100' width='300' height='250'> |
| <iframe id=iframe style='border: none; margin: 30px; |
| width: 240px; height: 190px'></iframe> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| SetChildFrameHTML(R"HTML( |
| <style> |
| body { margin: 0 } |
| * { background: white; } |
| </style> |
| <div id='div' style='margin: 70px; width: 100px; height: 50px'></div> |
| )HTML"); |
| GetDocument().View()->UpdateAllLifecyclePhases(); |
| |
| const auto& svg = *GetDocument().getElementById("svg"); |
| const auto& foreign = *GetDocument().getElementById("foreign"); |
| const auto& foreign_object = *GetLayoutObjectByElementId("foreign"); |
| const auto& iframe = *GetDocument().getElementById("iframe"); |
| const auto& div = *ChildDocument().getElementById("div")->GetLayoutObject(); |
| |
| EXPECT_EQ(FloatRect(100, 100, 300, 250), foreign_object.ObjectBoundingBox()); |
| EXPECT_EQ(AffineTransform(), foreign_object.LocalSVGTransform()); |
| EXPECT_EQ(AffineTransform(), foreign_object.LocalToSVGParentTransform()); |
| |
| // mapToVisualRectInAncestorSpace |
| LayoutRect div_rect(0, 0, 100, 50); |
| EXPECT_TRUE(div.MapToVisualRectInAncestorSpace(&GetLayoutView(), div_rect)); |
| EXPECT_EQ(LayoutRect(200, 200, 100, 50), div_rect); |
| |
| // mapLocalToAncestor |
| TransformState transform_state(TransformState::kApplyTransformDirection, |
| FloatPoint()); |
| div.MapLocalToAncestor(&GetLayoutView(), transform_state, |
| kTraverseDocumentBoundaries); |
| transform_state.Flatten(); |
| EXPECT_EQ(FloatPoint(200, 200), transform_state.LastPlanarPoint()); |
| |
| // mapAncestorToLocal |
| TransformState transform_state1( |
| TransformState::kUnapplyInverseTransformDirection, FloatPoint()); |
| div.MapAncestorToLocal(&GetLayoutView(), transform_state1, |
| kTraverseDocumentBoundaries); |
| transform_state1.Flatten(); |
| EXPECT_EQ(FloatPoint(-200, -200), transform_state1.LastPlanarPoint()); |
| |
| // pushMappingToContainer |
| LayoutGeometryMap rgm(kTraverseDocumentBoundaries); |
| rgm.PushMappingsToAncestor(&div, nullptr); |
| EXPECT_EQ(FloatQuad(FloatRect(200, 200, 1, 2)), |
| rgm.MapToAncestor(FloatRect(0, 0, 1, 2), nullptr)); |
| |
| // Hit testing |
| EXPECT_EQ(svg, HitTest(90, 90)); |
| EXPECT_EQ(foreign, HitTest(129, 129)); |
| EXPECT_EQ(ChildDocument().documentElement(), HitTest(130, 130)); |
| EXPECT_EQ(ChildDocument().documentElement(), HitTest(199, 199)); |
| EXPECT_EQ(div.GetNode(), HitTest(200, 200)); |
| EXPECT_EQ(div.GetNode(), HitTest(299, 249)); |
| EXPECT_EQ(ChildDocument().documentElement(), HitTest(300, 250)); |
| EXPECT_EQ(ChildDocument().documentElement(), HitTest(369, 319)); |
| EXPECT_EQ(foreign, HitTest(370, 320)); |
| EXPECT_EQ(svg, HitTest(450, 400)); |
| |
| // Rect based hit testing |
| auto results = RectBasedHitTest(LayoutRect(0, 0, 300, 300)); |
| int count = 0; |
| EXPECT_EQ(7u, results.size()); |
| for (auto result : results) { |
| Node* node = result.Get(); |
| if (node == svg || node == div.GetNode() || node == foreign || |
| node == iframe) |
| count++; |
| } |
| EXPECT_EQ(4, count); |
| } |
| |
| TEST_F(LayoutSVGForeignObjectTest, HitTestZoomedForeignObject) { |
| SetBodyInnerHTML(R"HTML( |
| <style>* { margin: 0; zoom: 150% }</style> |
| <svg id='svg' style='width: 200px; height: 200px'> |
| <foreignObject id='foreign' x='10' y='10' width='100' height='150' style='overflow: visible'> |
| <div id='div' style='margin: 50px; width: 50px; height: 50px'> |
| </div> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| const auto& svg = *GetDocument().getElementById("svg"); |
| const auto& foreign = *GetDocument().getElementById("foreign"); |
| const auto& foreign_object = *GetLayoutObjectByElementId("foreign"); |
| const auto& div = *GetDocument().getElementById("div"); |
| |
| EXPECT_EQ(FloatRect(10, 10, 100, 150), foreign_object.ObjectBoundingBox()); |
| EXPECT_EQ(AffineTransform(), foreign_object.LocalSVGTransform()); |
| EXPECT_EQ(AffineTransform(), foreign_object.LocalToSVGParentTransform()); |
| |
| // mapToVisualRectInAncestorSpace |
| LayoutRect div_rect(0, 0, 100, 50); |
| EXPECT_TRUE(div.GetLayoutObject()->MapToVisualRectInAncestorSpace( |
| &GetLayoutView(), div_rect)); |
| EXPECT_EQ(LayoutRect(286, 286, 339, 170), div_rect); |
| |
| // mapLocalToAncestor |
| TransformState transform_state(TransformState::kApplyTransformDirection, |
| FloatPoint()); |
| div.GetLayoutObject()->MapLocalToAncestor(&GetLayoutView(), transform_state, |
| kTraverseDocumentBoundaries); |
| transform_state.Flatten(); |
| EXPECT_EQ(FloatPoint(286.875, 286.875), transform_state.LastPlanarPoint()); |
| |
| // mapAncestorToLocal |
| TransformState transform_state1( |
| TransformState::kUnapplyInverseTransformDirection, |
| FloatPoint(286.875, 286.875)); |
| div.GetLayoutObject()->MapAncestorToLocal(&GetLayoutView(), transform_state1, |
| kTraverseDocumentBoundaries); |
| transform_state1.Flatten(); |
| EXPECT_EQ(FloatPoint(), transform_state1.LastPlanarPoint()); |
| |
| EXPECT_EQ(svg, HitTest(20, 20)); |
| EXPECT_EQ(foreign, HitTest(280, 280)); |
| EXPECT_EQ(div, HitTest(290, 290)); |
| |
| // Rect based hit testing |
| auto results = RectBasedHitTest(LayoutRect(0, 0, 300, 300)); |
| int count = 0; |
| EXPECT_EQ(3u, results.size()); |
| for (auto result : results) { |
| Node* node = result.Get(); |
| if (node == svg || node == &div || node == foreign) |
| count++; |
| } |
| EXPECT_EQ(3, count); |
| } |
| |
| TEST_F(LayoutSVGForeignObjectTest, HitTestViewBoxForeignObject) { |
| SetBodyInnerHTML(R"HTML( |
| <svg id='svg' style='width: 200px; height: 200px' viewBox='0 0 100 100'> |
| <foreignObject id='foreign' x='10' y='10' width='100' height='150'> |
| <div id='div' style='margin: 50px; width: 50px; height: 50px'> |
| </div> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| const auto& svg = *GetDocument().getElementById("svg"); |
| const auto& foreign = *GetDocument().getElementById("foreign"); |
| const auto& div = *GetDocument().getElementById("div"); |
| |
| // mapLocalToAncestor |
| TransformState transform_state(TransformState::kApplyTransformDirection, |
| FloatPoint()); |
| div.GetLayoutObject()->MapLocalToAncestor(&GetLayoutView(), transform_state, |
| kTraverseDocumentBoundaries); |
| transform_state.Flatten(); |
| EXPECT_EQ(FloatPoint(128, 128), transform_state.LastPlanarPoint()); |
| |
| // mapAncestorToLocal |
| TransformState transform_state1( |
| TransformState::kUnapplyInverseTransformDirection, FloatPoint(128, 128)); |
| div.GetLayoutObject()->MapAncestorToLocal(&GetLayoutView(), transform_state1, |
| kTraverseDocumentBoundaries); |
| transform_state1.Flatten(); |
| EXPECT_EQ(FloatPoint(), transform_state1.LastPlanarPoint()); |
| |
| EXPECT_EQ(svg, HitTest(20, 20)); |
| EXPECT_EQ(foreign, HitTest(120, 110)); |
| EXPECT_EQ(div, HitTest(160, 160)); |
| } |
| |
| TEST_F(LayoutSVGForeignObjectTest, HitTestUnderClipPath) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { |
| margin: 0 |
| } |
| #target { |
| width: 500px; |
| height: 500px; |
| background-color: blue; |
| } |
| #target:hover { |
| background-color: green; |
| } |
| </style> |
| <svg id="svg" style="width: 500px; height: 500px"> |
| <clipPath id="c"> |
| <circle cx="250" cy="250" r="200"/> |
| </clipPath> |
| <g clip-path="url(#c)"> |
| <foreignObject id="foreignObject" width="100%" height="100%"> |
| </foreignObject> |
| </g> |
| </svg> |
| )HTML"); |
| |
| const auto& svg = *GetDocument().getElementById("svg"); |
| const auto& foreignObject = *GetDocument().getElementById("foreignObject"); |
| |
| // The fist and the third return |svg| because the circle clip-path |
| // clips out the foreignObject. |
| EXPECT_EQ(svg, GetDocument().ElementFromPoint(20, 20)); |
| EXPECT_EQ(foreignObject, GetDocument().ElementFromPoint(250, 250)); |
| EXPECT_EQ(svg, GetDocument().ElementFromPoint(400, 400)); |
| } |
| |
| TEST_F(LayoutSVGForeignObjectTest, |
| HitTestUnderClippedPositionedForeignObjectDescendant) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { |
| margin: 0 |
| } |
| </style> |
| <svg id="svg" style="width: 600px; height: 600px"> |
| <foreignObject id="foreignObject" x="200" y="200" width="100" |
| height="100"> |
| <div id="target" style="overflow: hidden; position: relative; |
| width: 100px; height: 50px; left: 5px"></div> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| const auto& svg = *GetDocument().getElementById("svg"); |
| const auto& target = *GetDocument().getElementById("target"); |
| const auto& foreignObject = *GetDocument().getElementById("foreignObject"); |
| |
| EXPECT_EQ(svg, GetDocument().ElementFromPoint(1, 1)); |
| EXPECT_EQ(foreignObject, GetDocument().ElementFromPoint(201, 201)); |
| EXPECT_EQ(target, GetDocument().ElementFromPoint(206, 206)); |
| EXPECT_EQ(foreignObject, GetDocument().ElementFromPoint(205, 255)); |
| |
| HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive); |
| HitTestLocation location((LayoutPoint(206, 206))); |
| HitTestResult result(request, location); |
| GetDocument().GetLayoutView()->HitTest(location, result); |
| EXPECT_EQ(target, result.InnerNode()); |
| EXPECT_EQ(LayoutPoint(206, 206), result.PointInInnerNodeFrame()); |
| } |
| |
| TEST_F(LayoutSVGForeignObjectTest, |
| HitTestUnderTransformedForeignObjectDescendant) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { |
| margin: 0 |
| } |
| </style> |
| <svg id="svg" style="width: 600px; height: 600px"> |
| <foreignObject id="foreignObject" x="200" y="200" width="100" |
| height="100" transform="translate(30)"> |
| <div id="target" style="overflow: hidden; position: relative; |
| width: 100px; height: 50px; left: 5px"></div> |
| </foreignObject> |
| </svg> |
| )HTML"); |
| |
| const auto& svg = *GetDocument().getElementById("svg"); |
| const auto& target = *GetDocument().getElementById("target"); |
| const auto& foreign_object = *GetDocument().getElementById("foreignObject"); |
| |
| EXPECT_EQ(svg, GetDocument().ElementFromPoint(1, 1)); |
| EXPECT_EQ(foreign_object, GetDocument().ElementFromPoint(231, 201)); |
| EXPECT_EQ(target, GetDocument().ElementFromPoint(236, 206)); |
| EXPECT_EQ(foreign_object, GetDocument().ElementFromPoint(235, 255)); |
| |
| HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive); |
| HitTestLocation location((LayoutPoint(236, 206))); |
| HitTestResult result(request, location); |
| GetDocument().GetLayoutView()->HitTest(location, result); |
| EXPECT_EQ(target, result.InnerNode()); |
| EXPECT_EQ(LayoutPoint(236, 206), result.PointInInnerNodeFrame()); |
| } |
| |
| TEST_F(LayoutSVGForeignObjectTest, HitTestUnderScrollingAncestor) { |
| SetBodyInnerHTML(R"HTML( |
| <style> |
| * { |
| margin: 0 |
| } |
| </style> |
| <div id=scroller style="width: 500px; height: 500px; overflow: auto"> |
| <svg width="3000" height="3000"> |
| <foreignObject width="3000" height="3000"> |
| <div id="target" style="width: 3000px; height: 3000px; background: red"> |
| </div> |
| </foreignObject> |
| </svg> |
| </div> |
| )HTML"); |
| |
| auto& scroller = *GetDocument().getElementById("scroller"); |
| const auto& target = *GetDocument().getElementById("target"); |
| |
| EXPECT_EQ(target, GetDocument().ElementFromPoint(450, 450)); |
| |
| HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive); |
| HitTestLocation location((LayoutPoint(450, 450))); |
| HitTestResult result(request, location); |
| GetDocument().GetLayoutView()->HitTest(location, result); |
| EXPECT_EQ(target, result.InnerNode()); |
| EXPECT_EQ(LayoutPoint(450, 450), result.PointInInnerNodeFrame()); |
| |
| scroller.setScrollTop(3000); |
| |
| EXPECT_EQ(target, GetDocument().ElementFromPoint(450, 450)); |
| |
| GetDocument().GetLayoutView()->HitTest(location, result); |
| EXPECT_EQ(target, result.InnerNode()); |
| EXPECT_EQ(LayoutPoint(450, 450), result.PointInInnerNodeFrame()); |
| } |
| |
| } // namespace blink |