Title: [259722] trunk
Revision
259722
Author
[email protected]
Date
2020-04-08 10:00:34 -0700 (Wed, 08 Apr 2020)

Log Message

Hit test with clipPath referencing parent element causes infinite recursion
https://bugs.webkit.org/show_bug.cgi?id=209773
<rdar://problem/60002347>

Reviewed by Geoffrey Garen.

Source/WebCore:

Upon further investigation, the original fix for the hit test in RenderSVGResourceClipper to prevent
infinite recursion was incomplete, as something such as a use element could easily cause another cycle which
would not be detected by the initial fix.  Instead, by maintaining a set of visited elements, we can prevent
visiting the same element twice, and thus breaking any cycles which might occur in the SVG document.  We
track these elements within the SVGHitTestCycleDetectionScope class, where the set of visited elements are
maintained statically, and instances of the class will manage the scope, as an RAII-style object.

This is covered by an existing test, but includes additional test cases which illustrate the more complex
document structure.

Tests: svg/hittest/svg-clip-path-child-element-with-use-root.html
       svg/hittest/svg-clip-path-child-element-with-use.html

* rendering/svg/RenderSVGContainer.cpp:
(WebCore::RenderSVGContainer::nodeAtFloatPoint):
* rendering/svg/RenderSVGImage.cpp:
(WebCore::RenderSVGImage::nodeAtFloatPoint):
* rendering/svg/RenderSVGResourceClipper.cpp:
(WebCore::RenderSVGResourceClipper::hitTestClipContent):
* rendering/svg/RenderSVGRoot.cpp:
(WebCore::RenderSVGRoot::nodeAtPoint):
* rendering/svg/RenderSVGShape.cpp:
(WebCore::RenderSVGShape::nodeAtFloatPoint):
* rendering/svg/RenderSVGText.cpp:
(WebCore::RenderSVGText::nodeAtFloatPoint):
* rendering/svg/SVGRenderSupport.cpp:
(WebCore::SVGRenderSupport::pointInClippingArea):
(WebCore::SVGHitTestCycleDetectionScope::SVGHitTestCycleDetectionScope):
(WebCore::SVGHitTestCycleDetectionScope::~SVGHitTestCycleDetectionScope):
(WebCore::SVGHitTestCycleDetectionScope::visitedElements):
(WebCore::SVGHitTestCycleDetectionScope::isEmpty):
(WebCore::SVGHitTestCycleDetectionScope::isVisiting):
* rendering/svg/SVGRenderSupport.h:

LayoutTests:

* svg/hittest/svg-clip-path-child-element-with-use-expected.txt: Added.
* svg/hittest/svg-clip-path-child-element-with-use-root-expected.txt: Added.
* svg/hittest/svg-clip-path-child-element-with-use-root.html: Added.
* svg/hittest/svg-clip-path-child-element-with-use.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (259721 => 259722)


--- trunk/LayoutTests/ChangeLog	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/LayoutTests/ChangeLog	2020-04-08 17:00:34 UTC (rev 259722)
@@ -1,3 +1,16 @@
+2020-04-08  Doug Kelly  <[email protected]>
+
+        Hit test with clipPath referencing parent element causes infinite recursion
+        https://bugs.webkit.org/show_bug.cgi?id=209773
+        <rdar://problem/60002347>
+
+        Reviewed by Geoffrey Garen.
+
+        * svg/hittest/svg-clip-path-child-element-with-use-expected.txt: Added.
+        * svg/hittest/svg-clip-path-child-element-with-use-root-expected.txt: Added.
+        * svg/hittest/svg-clip-path-child-element-with-use-root.html: Added.
+        * svg/hittest/svg-clip-path-child-element-with-use.html: Added.
+
 2020-04-08  Antoine Quint  <[email protected]>
 
         transition-property is not computed correctly when transition-duration is set to "inherit"

Added: trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-expected.txt (0 => 259722)


--- trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-expected.txt	2020-04-08 17:00:34 UTC (rev 259722)
@@ -0,0 +1 @@
+Tests SVG hit test with a recursive clipPath. Test passes if WebKit does not crash. PASS

Added: trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-root-expected.txt (0 => 259722)


--- trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-root-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-root-expected.txt	2020-04-08 17:00:34 UTC (rev 259722)
@@ -0,0 +1 @@
+Tests SVG hit test with a recursive clipPath. Test passes if WebKit does not crash. PASS

Added: trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-root.html (0 => 259722)


--- trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-root.html	                        (rev 0)
+++ trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use-root.html	2020-04-08 17:00:34 UTC (rev 259722)
@@ -0,0 +1,21 @@
+<style>
+.path { -webkit-clip-path: url(#clippath); }
+</style>
+<script>
+function loadevent() {
+    document.caretRangeFromPoint(37, 70);
+    if (window.testRunner) {
+        document.body.innerText = "Tests SVG hit test with a recursive clipPath.  Test passes if WebKit does not crash.  PASS";
+        testRunner.dumpAsText();
+    }
+}
+</script>
+<body _onload_=loadevent()>
+<svg contentScriptType="text/ecmascript" id="svg">
+<path d="M16 8 L32 56" class="path" />
+<g id="group">
+    <text clip-path="url(#clippath)" to="currentColor">Text</text>
+</g>
+<clipPath id="clippath" clipPathUnits="objectBoundingBox">
+    <use href=""
+</clipPath>

Added: trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use.html (0 => 259722)


--- trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use.html	                        (rev 0)
+++ trunk/LayoutTests/svg/hittest/svg-clip-path-child-element-with-use.html	2020-04-08 17:00:34 UTC (rev 259722)
@@ -0,0 +1,21 @@
+<style>
+.path { -webkit-clip-path: url(#clippath); }
+</style>
+<script>
+function loadevent() {
+    document.caretRangeFromPoint(37, 70);
+    if (window.testRunner) {
+        document.body.innerText = "Tests SVG hit test with a recursive clipPath.  Test passes if WebKit does not crash.  PASS";
+        testRunner.dumpAsText();
+    }
+}
+</script>
+<body _onload_=loadevent()>
+<svg contentScriptType="text/ecmascript">
+<path d="M16 8 L32 56" class="path" />
+<g id="group">
+    <text clip-path="url(#clippath)" to="currentColor">Text</text>
+</g>
+<clipPath id="clippath" clipPathUnits="objectBoundingBox">
+    <use href=""
+</clipPath>

Modified: trunk/Source/WebCore/ChangeLog (259721 => 259722)


--- trunk/Source/WebCore/ChangeLog	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/ChangeLog	2020-04-08 17:00:34 UTC (rev 259722)
@@ -1,3 +1,45 @@
+2020-04-08  Doug Kelly  <[email protected]>
+
+        Hit test with clipPath referencing parent element causes infinite recursion
+        https://bugs.webkit.org/show_bug.cgi?id=209773
+        <rdar://problem/60002347>
+
+        Reviewed by Geoffrey Garen.
+
+        Upon further investigation, the original fix for the hit test in RenderSVGResourceClipper to prevent
+        infinite recursion was incomplete, as something such as a use element could easily cause another cycle which
+        would not be detected by the initial fix.  Instead, by maintaining a set of visited elements, we can prevent
+        visiting the same element twice, and thus breaking any cycles which might occur in the SVG document.  We
+        track these elements within the SVGHitTestCycleDetectionScope class, where the set of visited elements are
+        maintained statically, and instances of the class will manage the scope, as an RAII-style object.
+
+        This is covered by an existing test, but includes additional test cases which illustrate the more complex
+        document structure.
+
+        Tests: svg/hittest/svg-clip-path-child-element-with-use-root.html
+               svg/hittest/svg-clip-path-child-element-with-use.html
+
+        * rendering/svg/RenderSVGContainer.cpp:
+        (WebCore::RenderSVGContainer::nodeAtFloatPoint):
+        * rendering/svg/RenderSVGImage.cpp:
+        (WebCore::RenderSVGImage::nodeAtFloatPoint):
+        * rendering/svg/RenderSVGResourceClipper.cpp:
+        (WebCore::RenderSVGResourceClipper::hitTestClipContent):
+        * rendering/svg/RenderSVGRoot.cpp:
+        (WebCore::RenderSVGRoot::nodeAtPoint):
+        * rendering/svg/RenderSVGShape.cpp:
+        (WebCore::RenderSVGShape::nodeAtFloatPoint):
+        * rendering/svg/RenderSVGText.cpp:
+        (WebCore::RenderSVGText::nodeAtFloatPoint):
+        * rendering/svg/SVGRenderSupport.cpp:
+        (WebCore::SVGRenderSupport::pointInClippingArea):
+        (WebCore::SVGHitTestCycleDetectionScope::SVGHitTestCycleDetectionScope):
+        (WebCore::SVGHitTestCycleDetectionScope::~SVGHitTestCycleDetectionScope):
+        (WebCore::SVGHitTestCycleDetectionScope::visitedElements):
+        (WebCore::SVGHitTestCycleDetectionScope::isEmpty):
+        (WebCore::SVGHitTestCycleDetectionScope::isVisiting):
+        * rendering/svg/SVGRenderSupport.h:
+
 2020-04-08  Antoine Quint  <[email protected]>
 
         transition-property is not computed correctly when transition-duration is set to "inherit"

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGContainer.cpp (259721 => 259722)


--- trunk/Source/WebCore/rendering/svg/RenderSVGContainer.cpp	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGContainer.cpp	2020-04-08 17:00:34 UTC (rev 259722)
@@ -165,7 +165,9 @@
 
     if (!SVGRenderSupport::pointInClippingArea(*this, localPoint))
         return false;
-                
+
+    SVGHitTestCycleDetectionScope hitTestScope(*this);
+
     for (RenderObject* child = lastChild(); child; child = child->previousSibling()) {
         if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
             updateHitTestResult(result, LayoutPoint(localPoint));

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGImage.cpp (259721 => 259722)


--- trunk/Source/WebCore/rendering/svg/RenderSVGImage.cpp	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGImage.cpp	2020-04-08 17:00:34 UTC (rev 259722)
@@ -226,6 +226,8 @@
         if (!SVGRenderSupport::pointInClippingArea(*this, localPoint))
             return false;
 
+        SVGHitTestCycleDetectionScope hitTestScope(*this);
+
         if (hitRules.canHitFill) {
             if (m_objectBoundingBox.contains(localPoint)) {
                 updateHitTestResult(result, LayoutPoint(localPoint));

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGResourceClipper.cpp (259721 => 259722)


--- trunk/Source/WebCore/rendering/svg/RenderSVGResourceClipper.cpp	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGResourceClipper.cpp	2020-04-08 17:00:34 UTC (rev 259722)
@@ -268,6 +268,8 @@
     if (!SVGRenderSupport::pointInClippingArea(*this, point))
         return false;
 
+    SVGHitTestCycleDetectionScope hitTestScope(*this);
+
     if (clipPathElement().clipPathUnits() == SVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
         AffineTransform transform;
         transform.translate(objectBoundingBox.location());
@@ -284,15 +286,6 @@
         if (!renderer->isSVGShape() && !renderer->isSVGText() && !childNode->hasTagName(SVGNames::useTag))
             continue;
 
-        const RenderStyle& style = renderer->style();
-        if (is<ReferenceClipPathOperation>(style.clipPath())) {
-            auto& clipPath = downcast<ReferenceClipPathOperation>(*style.clipPath());
-            AtomString id(clipPath.fragment());
-            RenderSVGResourceClipper* clipper = getRenderSVGResourceById<RenderSVGResourceClipper>(document(), id);
-            if (clipper == this)
-                continue;
-        }
-
         IntPoint hitPoint;
         HitTestResult result(hitPoint);
         constexpr OptionSet<HitTestRequest::RequestType> hitType { HitTestRequest::SVGClipContent, HitTestRequest::DisallowUserAgentShadowContent };

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGRoot.cpp (259721 => 259722)


--- trunk/Source/WebCore/rendering/svg/RenderSVGRoot.cpp	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGRoot.cpp	2020-04-08 17:00:34 UTC (rev 259722)
@@ -402,6 +402,8 @@
     LayoutPoint pointInParent = locationInContainer.point() - toLayoutSize(accumulatedOffset);
     LayoutPoint pointInBorderBox = pointInParent - toLayoutSize(location());
 
+    ASSERT(SVGHitTestCycleDetectionScope::isEmpty());
+
     // Test SVG content if the point is in our content box or it is inside the visualOverflowRect and the overflow is visible.
     // FIXME: This should be an intersection when rect-based hit tests are supported by nodeAtFloatPoint.
     if (contentBoxRect().contains(pointInBorderBox) || (!shouldApplyViewportClip() && visualOverflowRect().contains(pointInParent))) {
@@ -411,8 +413,10 @@
             // FIXME: nodeAtFloatPoint() doesn't handle rect-based hit tests yet.
             if (child->nodeAtFloatPoint(request, result, localPoint, hitTestAction)) {
                 updateHitTestResult(result, pointInBorderBox);
-                if (result.addNodeToListBasedTestResult(child->node(), request, locationInContainer) == HitTestProgress::Stop)
+                if (result.addNodeToListBasedTestResult(child->node(), request, locationInContainer) == HitTestProgress::Stop) {
+                    ASSERT(SVGHitTestCycleDetectionScope::isEmpty());
                     return true;
+                }
             }
         }
     }
@@ -431,6 +435,8 @@
         }
     }
 
+    ASSERT(SVGHitTestCycleDetectionScope::isEmpty());
+
     return false;
 }
 

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGShape.cpp (259721 => 259722)


--- trunk/Source/WebCore/rendering/svg/RenderSVGShape.cpp	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGShape.cpp	2020-04-08 17:00:34 UTC (rev 259722)
@@ -366,6 +366,8 @@
     if (!SVGRenderSupport::pointInClippingArea(*this, localPoint))
         return false;
 
+    SVGHitTestCycleDetectionScope hitTestScope(*this);
+
     PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_PATH_HITTESTING, request, style().pointerEvents());
     bool isVisible = (style().visibility() == Visibility::Visible);
     if (isVisible || !hitRules.requireVisible) {

Modified: trunk/Source/WebCore/rendering/svg/RenderSVGText.cpp (259721 => 259722)


--- trunk/Source/WebCore/rendering/svg/RenderSVGText.cpp	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/rendering/svg/RenderSVGText.cpp	2020-04-08 17:00:34 UTC (rev 259722)
@@ -440,6 +440,8 @@
             if (!SVGRenderSupport::pointInClippingArea(*this, localPoint))
                 return false;       
 
+            SVGHitTestCycleDetectionScope hitTestScope(*this);
+
             HitTestLocation hitTestLocation(LayoutPoint(flooredIntPoint(localPoint)));
             return RenderBlock::nodeAtPoint(request, result, hitTestLocation, LayoutPoint(), hitTestAction);
         }

Modified: trunk/Source/WebCore/rendering/svg/SVGRenderSupport.cpp (259721 => 259722)


--- trunk/Source/WebCore/rendering/svg/SVGRenderSupport.cpp	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/rendering/svg/SVGRenderSupport.cpp	2020-04-08 17:00:34 UTC (rev 259722)
@@ -407,6 +407,9 @@
 
 bool SVGRenderSupport::pointInClippingArea(const RenderElement& renderer, const FloatPoint& point)
 {
+    if (SVGHitTestCycleDetectionScope::isVisiting(renderer))
+        return false;
+
     ClipPathOperation* clipPathOperation = renderer.style().clipPath();
     if (is<ShapeClipPathOperation>(clipPathOperation) || is<BoxClipPathOperation>(clipPathOperation))
         return isPointInCSSClippingArea(renderer, point);
@@ -506,4 +509,33 @@
 
 #endif
 
+SVGHitTestCycleDetectionScope::SVGHitTestCycleDetectionScope(const RenderElement& element)
+{
+    m_element = makeWeakPtr(&element);
+    auto result = visitedElements().add(m_element.get());
+    ASSERT_UNUSED(result, result.isNewEntry);
 }
+
+SVGHitTestCycleDetectionScope::~SVGHitTestCycleDetectionScope()
+{
+    bool result = visitedElements().remove(*m_element.get());
+    ASSERT_UNUSED(result, result);
+}
+
+WeakHashSet<RenderElement>& SVGHitTestCycleDetectionScope::visitedElements()
+{
+    static NeverDestroyed<WeakHashSet<RenderElement>> s_visitedElements;
+    return s_visitedElements;
+}
+
+bool SVGHitTestCycleDetectionScope::isEmpty()
+{
+    return visitedElements().computesEmpty();
+}
+
+bool SVGHitTestCycleDetectionScope::isVisiting(const RenderElement& element)
+{
+    return visitedElements().contains(element);
+}
+
+}

Modified: trunk/Source/WebCore/rendering/svg/SVGRenderSupport.h (259721 => 259722)


--- trunk/Source/WebCore/rendering/svg/SVGRenderSupport.h	2020-04-08 17:00:19 UTC (rev 259721)
+++ trunk/Source/WebCore/rendering/svg/SVGRenderSupport.h	2020-04-08 17:00:34 UTC (rev 259722)
@@ -95,4 +95,17 @@
     ~SVGRenderSupport();
 };
 
+class SVGHitTestCycleDetectionScope {
+    WTF_MAKE_NONCOPYABLE(SVGHitTestCycleDetectionScope);
+public:
+    SVGHitTestCycleDetectionScope(const RenderElement&);
+    ~SVGHitTestCycleDetectionScope();
+    static bool isEmpty();
+    static bool isVisiting(const RenderElement&);
+
+private:
+    static WeakHashSet<RenderElement>& visitedElements();
+    WeakPtr<RenderElement> m_element;
+};
+
 } // namespace WebCore
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to