Title: [278343] trunk
Revision
278343
Author
[email protected]
Date
2021-06-02 01:28:32 -0700 (Wed, 02 Jun 2021)

Log Message

Hit-testing does not account for clip-path on <iframe>
https://bugs.webkit.org/show_bug.cgi?id=226380
<rdar://problem/78621486>

Reviewed by Antti Koivisto.

Source/WebCore:

The logic to account for the clip-path property during hit-testing was only found in RenderBlock::nodeAtPoint()
although other types of RenderBox objects may need this, such as RenderIFrame. So we move some of the logic
from RenderBlock::nodeAtPoint() to dedicated methods on RenderBox such that RenderBox::nodeAtPoint() may call them
but also allow for RenderBlock::nodeAtPoint() to call them.

Test: css3/masking/clip-path-hit-test-iframe.html
      css3/masking/clip-path-hit-test-img.html

* rendering/RenderBlock.cpp:
(WebCore::RenderBlock::nodeAtPoint):
* rendering/RenderBox.cpp:
(WebCore::RenderBox::hitTestVisualOverflow const):
(WebCore::RenderBox::hitTestClipPath const):
(WebCore::RenderBox::hitTestBorderRadius const):
(WebCore::RenderBox::nodeAtPoint):
* rendering/RenderBox.h:

LayoutTests:

Add a new test which checks that we hit-test correctly in part of an
<iframe> or <img> clipped by the clip-path property.

* css3/masking/clip-path-hit-test-iframe-expected.txt: Added.
* css3/masking/clip-path-hit-test-iframe.html: Added.
* css3/masking/clip-path-hit-test-img-expected.txt: Added.
* css3/masking/clip-path-hit-test-img.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (278342 => 278343)


--- trunk/LayoutTests/ChangeLog	2021-06-02 08:11:14 UTC (rev 278342)
+++ trunk/LayoutTests/ChangeLog	2021-06-02 08:28:32 UTC (rev 278343)
@@ -1,3 +1,19 @@
+2021-05-28  Antoine Quint  <[email protected]>
+
+        Hit-testing does not account for clip-path on <iframe>
+        https://bugs.webkit.org/show_bug.cgi?id=226380
+        <rdar://problem/78621486>
+
+        Reviewed by Antti Koivisto.
+
+        Add a new test which checks that we hit-test correctly in part of an
+        <iframe> or <img> clipped by the clip-path property. 
+
+        * css3/masking/clip-path-hit-test-iframe-expected.txt: Added.
+        * css3/masking/clip-path-hit-test-iframe.html: Added.
+        * css3/masking/clip-path-hit-test-img-expected.txt: Added.
+        * css3/masking/clip-path-hit-test-img.html: Added.
+
 2021-06-01  Lauro Moura  <[email protected]>
 
         [WPE] Garden imported/w3c/web-platform-tests/html/semantics/embedded-content/the-img-element/source-media-outside-doc.html flakiness

Added: trunk/LayoutTests/css3/masking/clip-path-hit-test-iframe-expected.txt (0 => 278343)


--- trunk/LayoutTests/css3/masking/clip-path-hit-test-iframe-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/css3/masking/clip-path-hit-test-iframe-expected.txt	2021-06-02 08:28:32 UTC (rev 278343)
@@ -0,0 +1,10 @@
+Test clip-path is accounted for during hit-testing with iframe.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.elementFromPoint(50, 50).localName is "body"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/css3/masking/clip-path-hit-test-iframe.html (0 => 278343)


--- trunk/LayoutTests/css3/masking/clip-path-hit-test-iframe.html	                        (rev 0)
+++ trunk/LayoutTests/css3/masking/clip-path-hit-test-iframe.html	2021-06-02 08:28:32 UTC (rev 278343)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Hit testing of iframe with clip-path</title>
+<script src=""
+<script type="text/_javascript_">
+
+function runTest() {
+    description('Test clip-path is accounted for during hit-testing with iframe.');
+    shouldBeEqualToString("document.elementFromPoint(50, 50).localName", "body");
+}
+
+</script>
+<style>
+
+p {
+    pointer-events: none;
+}
+
+iframe {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 400px;
+    height: 300px;
+    border: 0;
+    clip-path: inset(100px 0 0 0);
+}
+
+</style>
+</head>
+<body>
+    <iframe srcdoc="<body style='margin: 0; background: white'></body>" _onload_="runTest()"></iframe>
+</body>
+</html>

Added: trunk/LayoutTests/css3/masking/clip-path-hit-test-img-expected.txt (0 => 278343)


--- trunk/LayoutTests/css3/masking/clip-path-hit-test-img-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/css3/masking/clip-path-hit-test-img-expected.txt	2021-06-02 08:28:32 UTC (rev 278343)
@@ -0,0 +1,10 @@
+Test clip-path is accounted for during hit-testing with replaced elements.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS document.elementFromPoint(50, 50).localName is "body"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/css3/masking/clip-path-hit-test-img.html (0 => 278343)


--- trunk/LayoutTests/css3/masking/clip-path-hit-test-img.html	                        (rev 0)
+++ trunk/LayoutTests/css3/masking/clip-path-hit-test-img.html	2021-06-02 08:28:32 UTC (rev 278343)
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Hit testing of iframe with clip-path</title>
+<script src=""
+<style>
+
+p {
+    pointer-events: none;
+}
+
+img {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 400px;
+    height: 300px;
+    clip-path: inset(100px 0 0 0);
+}
+
+</style>
+</head>
+<body>
+<img>
+<script type="text/_javascript_">
+
+description('Test clip-path is accounted for during hit-testing with replaced elements.');
+shouldBeEqualToString("document.elementFromPoint(50, 50).localName", "body");
+
+</script>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (278342 => 278343)


--- trunk/Source/WebCore/ChangeLog	2021-06-02 08:11:14 UTC (rev 278342)
+++ trunk/Source/WebCore/ChangeLog	2021-06-02 08:28:32 UTC (rev 278343)
@@ -1,3 +1,28 @@
+2021-05-28  Antoine Quint  <[email protected]>
+
+        Hit-testing does not account for clip-path on <iframe>
+        https://bugs.webkit.org/show_bug.cgi?id=226380
+        <rdar://problem/78621486>
+
+        Reviewed by Antti Koivisto.
+
+        The logic to account for the clip-path property during hit-testing was only found in RenderBlock::nodeAtPoint()
+        although other types of RenderBox objects may need this, such as RenderIFrame. So we move some of the logic
+        from RenderBlock::nodeAtPoint() to dedicated methods on RenderBox such that RenderBox::nodeAtPoint() may call them
+        but also allow for RenderBlock::nodeAtPoint() to call them.
+
+        Test: css3/masking/clip-path-hit-test-iframe.html
+              css3/masking/clip-path-hit-test-img.html
+
+        * rendering/RenderBlock.cpp:
+        (WebCore::RenderBlock::nodeAtPoint):
+        * rendering/RenderBox.cpp:
+        (WebCore::RenderBox::hitTestVisualOverflow const):
+        (WebCore::RenderBox::hitTestClipPath const):
+        (WebCore::RenderBox::hitTestBorderRadius const):
+        (WebCore::RenderBox::nodeAtPoint):
+        * rendering/RenderBox.h:
+
 2021-06-02  Youenn Fablet  <[email protected]>
 
         Improve AudioMediaStreamTrackRendererUnit logging to know which source is being played

Modified: trunk/Source/WebCore/rendering/RenderBlock.cpp (278342 => 278343)


--- trunk/Source/WebCore/rendering/RenderBlock.cpp	2021-06-02 08:11:14 UTC (rev 278342)
+++ trunk/Source/WebCore/rendering/RenderBlock.cpp	2021-06-02 08:28:32 UTC (rev 278343)
@@ -63,7 +63,6 @@
 #include "RenderLayoutState.h"
 #include "RenderListMarker.h"
 #include "RenderMenuList.h"
-#include "RenderSVGResourceClipper.h"
 #include "RenderSVGRoot.h"
 #include "RenderTableCell.h"
 #include "RenderTextControl.h"
@@ -2062,14 +2061,9 @@
     const LayoutPoint adjustedLocation(accumulatedOffset + location());
     const LayoutSize localOffset = toLayoutSize(adjustedLocation);
 
-    if (!isRenderView()) {
-        // Check if we need to do anything at all.
-        LayoutRect overflowBox = visualOverflowRect();
-        flipForWritingMode(overflowBox);
-        overflowBox.moveBy(adjustedLocation);
-        if (!locationInContainer.intersects(overflowBox))
-            return false;
-    }
+    // Check if we need to do anything at all.
+    if (!hitTestVisualOverflow(locationInContainer, accumulatedOffset))
+        return false;
 
     if ((hitTestAction == HitTestBlockBackground || hitTestAction == HitTestChildBlockBackground) && isPointInOverflowControl(result, locationInContainer.point(), adjustedLocation)) {
         updateHitTestResult(result, locationInContainer.point() - localOffset);
@@ -2078,31 +2072,8 @@
            return true;
     }
 
-    if (style().clipPath()) {
-        switch (style().clipPath()->type()) {
-        case ClipPathOperation::Shape: {
-            auto& clipPath = downcast<ShapeClipPathOperation>(*style().clipPath());
-            auto referenceBoxRect = referenceBox(clipPath.referenceBox());
-            if (!clipPath.pathForReferenceRect(referenceBoxRect).contains(locationInContainer.point() - localOffset, clipPath.windRule()))
-                return false;
-            break;
-        }
-        case ClipPathOperation::Reference: {
-            const auto& referenceClipPathOperation = downcast<ReferenceClipPathOperation>(*style().clipPath());
-            auto* element = document().getElementById(referenceClipPathOperation.fragment());
-            if (!element || !element->renderer())
-                break;
-            if (!is<SVGClipPathElement>(*element))
-                break;
-            auto& clipper = downcast<RenderSVGResourceClipper>(*element->renderer());
-            if (!clipper.hitTestClipContent(FloatRect(borderBoxRect()), FloatPoint(locationInContainer.point() - localOffset)))
-                return false;
-            break;
-        }
-        case ClipPathOperation::Box:
-            break;
-        }
-    }
+    if (!hitTestClipPath(locationInContainer, accumulatedOffset))
+        return false;
 
     // If we have clipping, then we can't have any spillout.
     bool useClip = (hasControlClip() || hasOverflowClip());
@@ -2113,14 +2084,8 @@
     if (!checkChildren && hitTestExcludedChildrenInBorder(request, result, locationInContainer, adjustedLocation, hitTestAction))
         return true;
 
-    // Check if the point is outside radii.
-    if (!isRenderView() && style().hasBorderRadius()) {
-        LayoutRect borderRect = borderBoxRect();
-        borderRect.moveBy(adjustedLocation);
-        RoundedRect border = style().getRoundedBorderFor(borderRect);
-        if (!locationInContainer.intersects(border))
-            return false;
-    }
+    if (!hitTestBorderRadius(locationInContainer, accumulatedOffset))
+        return false;
 
     // Now hit test our background
     if (hitTestAction == HitTestBlockBackground || hitTestAction == HitTestChildBlockBackground) {

Modified: trunk/Source/WebCore/rendering/RenderBox.cpp (278342 => 278343)


--- trunk/Source/WebCore/rendering/RenderBox.cpp	2021-06-02 08:11:14 UTC (rev 278342)
+++ trunk/Source/WebCore/rendering/RenderBox.cpp	2021-06-02 08:28:32 UTC (rev 278343)
@@ -26,6 +26,7 @@
 #include "RenderBox.h"
 
 #include "CSSFontSelector.h"
+#include "ClipPathOperation.h"
 #include "ControlStates.h"
 #include "Document.h"
 #include "Editing.h"
@@ -66,10 +67,12 @@
 #include "RenderLayerScrollableArea.h"
 #include "RenderLayoutState.h"
 #include "RenderMultiColumnFlow.h"
+#include "RenderSVGResourceClipper.h"
 #include "RenderTableCell.h"
 #include "RenderTheme.h"
 #include "RenderView.h"
 #include "RuntimeApplicationChecks.h"
+#include "SVGClipPathElement.h"
 #include "ScrollAnimator.h"
 #include "ScrollbarTheme.h"
 #include "Settings.h"
@@ -1339,6 +1342,65 @@
 }
 
 // Hit Testing
+bool RenderBox::hitTestVisualOverflow(const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) const
+{
+    if (isRenderView())
+        return true;
+
+    LayoutPoint adjustedLocation = accumulatedOffset + location();
+    LayoutRect overflowBox = visualOverflowRect();
+    flipForWritingMode(overflowBox);
+    overflowBox.moveBy(adjustedLocation);
+    return locationInContainer.intersects(overflowBox);
+}
+
+bool RenderBox::hitTestClipPath(const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) const
+{
+    if (!style().clipPath())
+        return true;
+
+    LayoutPoint adjustedLocation = accumulatedOffset + location();
+    const LayoutSize localOffset = toLayoutSize(adjustedLocation);
+
+    switch (style().clipPath()->type()) {
+    case ClipPathOperation::Shape: {
+        auto& clipPath = downcast<ShapeClipPathOperation>(*style().clipPath());
+        auto referenceBoxRect = referenceBox(clipPath.referenceBox());
+        if (!clipPath.pathForReferenceRect(referenceBoxRect).contains(locationInContainer.point() - localOffset, clipPath.windRule()))
+            return false;
+        break;
+    }
+    case ClipPathOperation::Reference: {
+        const auto& referenceClipPathOperation = downcast<ReferenceClipPathOperation>(*style().clipPath());
+        auto* element = document().getElementById(referenceClipPathOperation.fragment());
+        if (!element || !element->renderer())
+            break;
+        if (!is<SVGClipPathElement>(*element))
+            break;
+        auto& clipper = downcast<RenderSVGResourceClipper>(*element->renderer());
+        if (!clipper.hitTestClipContent(FloatRect(borderBoxRect()), FloatPoint(locationInContainer.point() - localOffset)))
+            return false;
+        break;
+    }
+    case ClipPathOperation::Box:
+        break;
+    }
+
+    return true;
+}
+
+bool RenderBox::hitTestBorderRadius(const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) const
+{
+    if (isRenderView() || !style().hasBorderRadius())
+        return true;
+
+    LayoutPoint adjustedLocation = accumulatedOffset + location();
+    LayoutRect borderRect = borderBoxRect();
+    borderRect.moveBy(adjustedLocation);
+    RoundedRect border = style().getRoundedBorderFor(borderRect);
+    return locationInContainer.intersects(border);
+}
+
 bool RenderBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action)
 {
     LayoutPoint adjustedLocation = accumulatedOffset + location();
@@ -1356,12 +1418,21 @@
     LayoutRect boundsRect = borderBoxRectInFragment(nullptr);
     boundsRect.moveBy(adjustedLocation);
     if (visibleToHitTesting(request) && action == HitTestForeground && locationInContainer.intersects(boundsRect)) {
+        if (!hitTestVisualOverflow(locationInContainer, accumulatedOffset))
+            return false;
+
+        if (!hitTestClipPath(locationInContainer, accumulatedOffset))
+            return false;
+
+        if (!hitTestBorderRadius(locationInContainer, accumulatedOffset))
+            return false;
+
         updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation));
         if (result.addNodeToListBasedTestResult(nodeForHitTest(), request, locationInContainer, boundsRect) == HitTestProgress::Stop)
             return true;
     }
 
-    return false;
+    return RenderBoxModelObject::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, action);
 }
 
 // --------------------- painting stuff -------------------------------

Modified: trunk/Source/WebCore/rendering/RenderBox.h (278342 => 278343)


--- trunk/Source/WebCore/rendering/RenderBox.h	2021-06-02 08:11:14 UTC (rev 278342)
+++ trunk/Source/WebCore/rendering/RenderBox.h	2021-06-02 08:28:32 UTC (rev 278343)
@@ -306,6 +306,9 @@
 
     void layout() override;
     bool nodeAtPoint(const HitTestRequest&, HitTestResult&, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction) override;
+    bool hitTestVisualOverflow(const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) const;
+    bool hitTestClipPath(const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) const;
+    bool hitTestBorderRadius(const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) const;
 
     LayoutUnit minPreferredLogicalWidth() const override;
     LayoutUnit maxPreferredLogicalWidth() const override;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to