Title: [249339] trunk
Revision
249339
Author
wenson_hs...@apple.com
Date
2019-08-30 12:28:57 -0700 (Fri, 30 Aug 2019)

Log Message

Caret does not appear in text field inside a transformed, overflow: hidden container
https://bugs.webkit.org/show_bug.cgi?id=201317
<rdar://problem/54859264>

Reviewed by Simon Fraser.

Source/WebCore:

This patch refactors the heuristic for determining whether to suppress selection gestures and UI in a way that
fixes the corner case encountered in this bug. To understand why this test case fails with our existing
heuristic, consider the below test case.

Let's say we have an input field inside an "overflow: hidden;" container, which is positioned in such a way that
it is completely clipped by its enclosing container which is also "overflow: hidden". Our existing logic would
appropriately identify this as a hidden editable element.

However, let's now apply a transform to the input field's closest "overflow: hidden" ancestor, such that the
field is now visible. Since RenderLayer::offsetFromAncestor doesn't take transforms into account when we try to
find the offset of the "overflow: hidden" layer relative to the root view, we end up passing an offsetFromRoot
of (0, 100vw) to RenderLayer::calculateClipRects, which computes a background clip rect of (0, 0, 100vw, 100vh).

This means that at the end of RenderLayer::calculateClipRects, we end up intersecting the background clip rect
(0, 0, 100vw, 100vh) against (100vw, 0, 100vw, 100vh), which results in the empty rect, and subsequently makes
us believe we're editing a hidden editable element.

Instead of tacking on more logic to isTransparentOrFullyClippedRespectingParentFrames, we can fix this by using
RenderObject::computeVisibleRectInContainer instead, performing a similar walk up the render tree to compute the
visible rect of each focused element or subframe relative to its root. This is capable of taking transforms into
account. See comments below for more details.

Test: editing/selection/ios/show-selection-in-transformed-container-2.html

* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::isTransparentRespectingParentFrames const):

Split out isTransparentOrFullyClippedRespectingParentFrames into two methods: RenderLayer's
isTransparentRespectingParentFrames, and RenderObject's hasNonEmptyVisibleRectRespectingParentFrames. The
transparency check starts at the enclosing layer and walks up the layer tree, while the non-empty visible rect
check looks for renderers that are completely empty relative to their root views.

* rendering/RenderLayer.h:
* rendering/RenderObject.cpp:
(WebCore::RenderObject::hasNonEmptyVisibleRectRespectingParentFrames const):

Rewrite logic for detecting completely clipped editable areas (that formerly lived in
isTransparentOrFullyClippedRespectingParentFrames) to use computeVisibleRectInContainer instead.

* rendering/RenderObject.h:

Source/WebKit:

Adjust isTransparentOrFullyClipped to use the new methods in RenderLayer and RenderObject. See WebCore ChangeLog
for more details.

* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::isTransparentOrFullyClipped const):

LayoutTests:

Add a new layout test that covers this scenario. See WebCore ChangeLog for additional detail.

* editing/selection/ios/show-selection-in-transformed-container-2-expected.txt: Added.
* editing/selection/ios/show-selection-in-transformed-container-2.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (249338 => 249339)


--- trunk/LayoutTests/ChangeLog	2019-08-30 19:03:35 UTC (rev 249338)
+++ trunk/LayoutTests/ChangeLog	2019-08-30 19:28:57 UTC (rev 249339)
@@ -1,3 +1,16 @@
+2019-08-30  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Caret does not appear in text field inside a transformed, overflow: hidden container
+        https://bugs.webkit.org/show_bug.cgi?id=201317
+        <rdar://problem/54859264>
+
+        Reviewed by Simon Fraser.
+
+        Add a new layout test that covers this scenario. See WebCore ChangeLog for additional detail.
+
+        * editing/selection/ios/show-selection-in-transformed-container-2-expected.txt: Added.
+        * editing/selection/ios/show-selection-in-transformed-container-2.html: Added.
+
 2019-08-30  Chris Dumez  <cdu...@apple.com>
 
         Add support for postMessage buffering between the service worker and window

Added: trunk/LayoutTests/editing/selection/ios/show-selection-in-transformed-container-2-expected.txt (0 => 249339)


--- trunk/LayoutTests/editing/selection/ios/show-selection-in-transformed-container-2-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/show-selection-in-transformed-container-2-expected.txt	2019-08-30 19:28:57 UTC (rev 249339)
@@ -0,0 +1,14 @@
+
+This test verifies that after focusing a visible input field in a display: flex container that has been translated horizontally out of view, the caret is still visible. To run the test manually, tap the input field and check that the caret shows up.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS caretRect.left is 12
+PASS caretRect.top is 6
+PASS caretRect.width is 2
+PASS caretRect.height is 23
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/selection/ios/show-selection-in-transformed-container-2.html (0 => 249339)


--- trunk/LayoutTests/editing/selection/ios/show-selection-in-transformed-container-2.html	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/show-selection-in-transformed-container-2.html	2019-08-30 19:28:57 UTC (rev 249339)
@@ -0,0 +1,75 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+<script src=""
+<script src=""
+<style>
+html, body {
+    margin: 0;
+    padding: 0;
+}
+
+input {
+    font-size: 18px;
+}
+
+.scroller {
+    width: 100vw;
+    height: 100vh;
+    overflow: hidden;
+}
+
+.content-view {
+    transform: translateX(-100vw);
+    display: flex;
+    flex-wrap: nowrap;
+}
+
+.page {
+    flex-grow: 1;
+    flex-shrink: 0;
+    flex-flow: column nowrap;
+    overflow: hidden;
+    width: 100vw;
+    height: 100vh;
+}
+</style>
+<script>
+jsTestIsAsync = true;
+
+addEventListener("load", async () => {
+    description("This test verifies that after focusing a visible input field in a <code>display: flex</code> container that has been translated horizontally out of view, the caret is still visible. To run the test manually, tap the input field and check that the caret shows up.");
+
+    if (!window.testRunner)
+        return;
+
+    await UIHelper.activateAndWaitForInputSessionAt(160, 20);
+
+    caretRect = null;
+    while (!caretRect || !caretRect.width || !caretRect.height)
+        caretRect = await UIHelper.getUICaretViewRect();
+
+    shouldBe("caretRect.left", "12");
+    shouldBe("caretRect.top", "6");
+    shouldBe("caretRect.width", "2");
+    shouldBe("caretRect.height", "23");
+
+    document.activeElement.blur();
+    await UIHelper.waitForKeyboardToHide();
+    finishJSTest();
+});
+</script>
+</head>
+<body>
+    <div class="scroller">
+        <div class="content-view">
+            <div class="page"></div>
+            <div class="page"><input></div>
+        </div>
+    </div>
+    <p id="description"></p>
+    <p id="console"></p>
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (249338 => 249339)


--- trunk/Source/WebCore/ChangeLog	2019-08-30 19:03:35 UTC (rev 249338)
+++ trunk/Source/WebCore/ChangeLog	2019-08-30 19:28:57 UTC (rev 249339)
@@ -1,3 +1,52 @@
+2019-08-30  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Caret does not appear in text field inside a transformed, overflow: hidden container
+        https://bugs.webkit.org/show_bug.cgi?id=201317
+        <rdar://problem/54859264>
+
+        Reviewed by Simon Fraser.
+
+        This patch refactors the heuristic for determining whether to suppress selection gestures and UI in a way that
+        fixes the corner case encountered in this bug. To understand why this test case fails with our existing
+        heuristic, consider the below test case.
+
+        Let's say we have an input field inside an "overflow: hidden;" container, which is positioned in such a way that
+        it is completely clipped by its enclosing container which is also "overflow: hidden". Our existing logic would
+        appropriately identify this as a hidden editable element.
+
+        However, let's now apply a transform to the input field's closest "overflow: hidden" ancestor, such that the
+        field is now visible. Since RenderLayer::offsetFromAncestor doesn't take transforms into account when we try to
+        find the offset of the "overflow: hidden" layer relative to the root view, we end up passing an offsetFromRoot
+        of (0, 100vw) to RenderLayer::calculateClipRects, which computes a background clip rect of (0, 0, 100vw, 100vh).
+
+        This means that at the end of RenderLayer::calculateClipRects, we end up intersecting the background clip rect
+        (0, 0, 100vw, 100vh) against (100vw, 0, 100vw, 100vh), which results in the empty rect, and subsequently makes
+        us believe we're editing a hidden editable element.
+
+        Instead of tacking on more logic to isTransparentOrFullyClippedRespectingParentFrames, we can fix this by using
+        RenderObject::computeVisibleRectInContainer instead, performing a similar walk up the render tree to compute the
+        visible rect of each focused element or subframe relative to its root. This is capable of taking transforms into
+        account. See comments below for more details.
+
+        Test: editing/selection/ios/show-selection-in-transformed-container-2.html
+
+        * rendering/RenderLayer.cpp:
+        (WebCore::RenderLayer::isTransparentRespectingParentFrames const):
+
+        Split out isTransparentOrFullyClippedRespectingParentFrames into two methods: RenderLayer's
+        isTransparentRespectingParentFrames, and RenderObject's hasNonEmptyVisibleRectRespectingParentFrames. The
+        transparency check starts at the enclosing layer and walks up the layer tree, while the non-empty visible rect
+        check looks for renderers that are completely empty relative to their root views.
+
+        * rendering/RenderLayer.h:
+        * rendering/RenderObject.cpp:
+        (WebCore::RenderObject::hasNonEmptyVisibleRectRespectingParentFrames const):
+
+        Rewrite logic for detecting completely clipped editable areas (that formerly lived in
+        isTransparentOrFullyClippedRespectingParentFrames) to use computeVisibleRectInContainer instead.
+
+        * rendering/RenderObject.h:
+
 2019-08-30  Chris Dumez  <cdu...@apple.com>
 
         Add support for postMessage buffering between the service worker and window

Modified: trunk/Source/WebCore/rendering/RenderLayer.cpp (249338 => 249339)


--- trunk/Source/WebCore/rendering/RenderLayer.cpp	2019-08-30 19:03:35 UTC (rev 249338)
+++ trunk/Source/WebCore/rendering/RenderLayer.cpp	2019-08-30 19:28:57 UTC (rev 249339)
@@ -6835,7 +6835,7 @@
     renderer().repaint();
 }
 
-bool RenderLayer::isTransparentOrFullyClippedRespectingParentFrames() const
+bool RenderLayer::isTransparentRespectingParentFrames() const
 {
     static const double minimumVisibleOpacity = 0.01;
 
@@ -6846,34 +6846,6 @@
             return true;
     }
 
-    auto hasEmptyClipRect = [] (const RenderLayer& layer) -> bool {
-        auto* frameView = layer.renderer().document().view();
-        if (!frameView)
-            return false;
-
-        auto* renderView = frameView->renderView();
-        if (!renderView)
-            return false;
-
-        auto* renderViewLayer = renderView->layer();
-        if (!renderViewLayer)
-            return false;
-
-        if (is<HTMLFrameOwnerElement>(layer.renderer().element()) && layer.visibleSize().isEmpty())
-            return true;
-
-        LayoutRect layerBounds;
-        ClipRect backgroundRect;
-        ClipRect foregroundRect;
-        layer.calculateRects({ renderViewLayer, TemporaryClipRects }, LayoutRect::infiniteRect(), layerBounds, backgroundRect, foregroundRect, layer.offsetFromAncestor(renderViewLayer));
-        return backgroundRect.isEmpty();
-    };
-
-    for (auto* layer = this; layer; layer = enclosingFrameRenderLayer(*layer)) {
-        if (hasEmptyClipRect(*layer))
-            return true;
-    }
-
     return false;
 }
 

Modified: trunk/Source/WebCore/rendering/RenderLayer.h (249338 => 249339)


--- trunk/Source/WebCore/rendering/RenderLayer.h	2019-08-30 19:03:35 UTC (rev 249338)
+++ trunk/Source/WebCore/rendering/RenderLayer.h	2019-08-30 19:28:57 UTC (rev 249339)
@@ -897,7 +897,7 @@
     void simulateFrequentPaint() { SinglePaintFrequencyTracking { m_paintFrequencyTracker }; }
     bool paintingFrequently() const { return m_paintFrequencyTracker.paintingFrequently(); }
 
-    WEBCORE_EXPORT bool isTransparentOrFullyClippedRespectingParentFrames() const;
+    WEBCORE_EXPORT bool isTransparentRespectingParentFrames() const;
 
     void invalidateEventRegion();
 

Modified: trunk/Source/WebCore/rendering/RenderObject.cpp (249338 => 249339)


--- trunk/Source/WebCore/rendering/RenderObject.cpp	2019-08-30 19:03:35 UTC (rev 249338)
+++ trunk/Source/WebCore/rendering/RenderObject.cpp	2019-08-30 19:28:57 UTC (rev 249339)
@@ -1902,6 +1902,28 @@
     setHasRareData(false);
 }
 
+bool RenderObject::hasNonEmptyVisibleRectRespectingParentFrames() const
+{
+    auto enclosingFrameRenderer = [] (const RenderObject& renderer) {
+        auto* ownerElement = renderer.document().ownerElement();
+        return ownerElement ? ownerElement->renderer() : nullptr;
+    };
+
+    auto hasEmptyVisibleRect = [] (const RenderObject& renderer) {
+        VisibleRectContext context { false, false, { VisibleRectContextOption::UseEdgeInclusiveIntersection, VisibleRectContextOption::ApplyCompositedClips }};
+        auto& box = renderer.enclosingBoxModelObject();
+        auto clippedBounds = box.computeVisibleRectInContainer(box.borderBoundingBox(), &box.view(), context);
+        return !clippedBounds || clippedBounds->isEmpty();
+    };
+
+    for (auto* renderer = this; renderer; renderer = enclosingFrameRenderer(*renderer)) {
+        if (hasEmptyVisibleRect(*renderer))
+            return true;
+    }
+
+    return false;
+}
+
 #if ENABLE(TREE_DEBUGGING)
 
 void printRenderTreeForLiveDocuments()

Modified: trunk/Source/WebCore/rendering/RenderObject.h (249338 => 249339)


--- trunk/Source/WebCore/rendering/RenderObject.h	2019-08-30 19:03:35 UTC (rev 249338)
+++ trunk/Source/WebCore/rendering/RenderObject.h	2019-08-30 19:28:57 UTC (rev 249339)
@@ -684,6 +684,8 @@
     virtual Optional<LayoutRect> computeVisibleRectInContainer(const LayoutRect&, const RenderLayerModelObject* repaintContainer, VisibleRectContext) const;
     virtual Optional<FloatRect> computeFloatVisibleRectInContainer(const FloatRect&, const RenderLayerModelObject* repaintContainer, VisibleRectContext) const;
 
+    WEBCORE_EXPORT bool hasNonEmptyVisibleRectRespectingParentFrames() const;
+
     virtual unsigned int length() const { return 1; }
 
     bool isFloatingOrOutOfFlowPositioned() const { return (isFloating() || isOutOfFlowPositioned()); }

Modified: trunk/Source/WebKit/ChangeLog (249338 => 249339)


--- trunk/Source/WebKit/ChangeLog	2019-08-30 19:03:35 UTC (rev 249338)
+++ trunk/Source/WebKit/ChangeLog	2019-08-30 19:28:57 UTC (rev 249339)
@@ -1,3 +1,17 @@
+2019-08-30  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Caret does not appear in text field inside a transformed, overflow: hidden container
+        https://bugs.webkit.org/show_bug.cgi?id=201317
+        <rdar://problem/54859264>
+
+        Reviewed by Simon Fraser.
+
+        Adjust isTransparentOrFullyClipped to use the new methods in RenderLayer and RenderObject. See WebCore ChangeLog
+        for more details.
+
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::isTransparentOrFullyClipped const):
+
 2019-08-30  Simon Fraser  <simon.fra...@apple.com>
 
         Add system tracing points for compositing updates, and touch-event dispatching

Modified: trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (249338 => 249339)


--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-08-30 19:03:35 UTC (rev 249338)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-08-30 19:28:57 UTC (rev 249339)
@@ -197,7 +197,10 @@
         return false;
 
     auto* enclosingLayer = renderer->enclosingLayer();
-    return enclosingLayer && enclosingLayer->isTransparentOrFullyClippedRespectingParentFrames();
+    if (enclosingLayer && enclosingLayer->isTransparentRespectingParentFrames())
+        return true;
+
+    return renderer->hasNonEmptyVisibleRectRespectingParentFrames();
 }
 
 void WebPage::platformEditorState(Frame& frame, EditorState& result, IncludePostLayoutDataHint shouldIncludePostLayoutData) const
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to