Title: [242401] trunk
Revision
242401
Author
wenson_hs...@apple.com
Date
2019-03-04 16:31:31 -0800 (Mon, 04 Mar 2019)

Log Message

Native text selection UI is incorrectly suppressed in Microsoft Visio
https://bugs.webkit.org/show_bug.cgi?id=195178
<rdar://problem/48519394>

Reviewed by Darin Adler.

Source/WebCore:

Currently, our heuristics for detecting hidden editable areas attempt to search for empty parent renderers with
"overflow: hidden". It does this by ascending the layer tree in search of renderers that have an empty content
size, and whose renderers' styles indicate that they have overflow: hidden in the X or Y directions. This fails
in the case where a child renderer is positioned out of flow, relative to one of its parent layers, since the
child will be visible, but we'll incorrectly believe that it is hidden. This leads to selection UI unexpectedly
disappearing in the online version of Microsoft Visio.

To fix this, we check whether the enclosing layer around the editable element has an empty clip rect; if the
element is inside of a subframe, we additionally walk up to each enclosing frame's layer and check if that
frame's layer has an empty clip rect.

Test: editing/selection/ios/do-not-hide-selection-in-visible-container.html

* rendering/RenderObject.cpp:
(WebCore::RenderObject::isTransparentOrFullyClippedRespectingParentFrames const):

LayoutTests:

Add a new layout test that focuses several different text fields and checks whether or not editing UI is shown:

1. A text field inside an overflow: hidden container, all within an absolutely positioned iframe, such that the
text field is not visible. The caret should be hidden.

2. A text field inside an absolutely positioned iframe, inside an overflow: hidden container, such that the
text field is visible. The caret should be visible.

3. A text field inside a relatively positioned iframe in an overflow: hidden container, such that the text field
is not visible. The caret should be hidden.

4. A text field that is position: fixed inside an overflow: hidden container, such that the text field is
visible. The caret should be visible.

* editing/selection/ios/do-not-hide-selection-in-visible-container-expected.txt: Added.
* editing/selection/ios/do-not-hide-selection-in-visible-container.html: Added.
* editing/selection/ios/hide-selection-in-empty-overflow-hidden-container.html:
* resources/ui-helper.js:
(window.UIHelper.activateElementAndWaitForInputSession):

Add a convenience function in UIHelper that taps a given element and waits for the keyboard to show.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (242400 => 242401)


--- trunk/LayoutTests/ChangeLog	2019-03-05 00:09:15 UTC (rev 242400)
+++ trunk/LayoutTests/ChangeLog	2019-03-05 00:31:31 UTC (rev 242401)
@@ -1,3 +1,33 @@
+2019-03-04  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Native text selection UI is incorrectly suppressed in Microsoft Visio
+        https://bugs.webkit.org/show_bug.cgi?id=195178
+        <rdar://problem/48519394>
+
+        Reviewed by Darin Adler.
+
+        Add a new layout test that focuses several different text fields and checks whether or not editing UI is shown:
+
+        1. A text field inside an overflow: hidden container, all within an absolutely positioned iframe, such that the
+        text field is not visible. The caret should be hidden.
+
+        2. A text field inside an absolutely positioned iframe, inside an overflow: hidden container, such that the
+        text field is visible. The caret should be visible.
+
+        3. A text field inside a relatively positioned iframe in an overflow: hidden container, such that the text field
+        is not visible. The caret should be hidden.
+
+        4. A text field that is position: fixed inside an overflow: hidden container, such that the text field is
+        visible. The caret should be visible.
+
+        * editing/selection/ios/do-not-hide-selection-in-visible-container-expected.txt: Added.
+        * editing/selection/ios/do-not-hide-selection-in-visible-container.html: Added.
+        * editing/selection/ios/hide-selection-in-empty-overflow-hidden-container.html:
+        * resources/ui-helper.js:
+        (window.UIHelper.activateElementAndWaitForInputSession):
+
+        Add a convenience function in UIHelper that taps a given element and waits for the keyboard to show.
+
 2019-03-04  Daniel Bates  <daba...@apple.com>
 
         [iOS] Caret x-position in empty text area does not match text field

Added: trunk/LayoutTests/editing/selection/ios/do-not-hide-selection-in-visible-container-expected.txt (0 => 242401)


--- trunk/LayoutTests/editing/selection/ios/do-not-hide-selection-in-visible-container-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/do-not-hide-selection-in-visible-container-expected.txt	2019-03-05 00:31:31 UTC (rev 242401)
@@ -0,0 +1,18 @@
+First field  Second field  Third field Fourth field 
+
+Verifies that native selection UI is not suppressed when focusing an input that is inside an empty container with `overflow: hidden`, but is positioned absolutely such that it is still visible. To manually verify, click on each of the four buttons to move focus into editable areas. After tapping the first and third buttons, you should not see a caret view; after tapping the second and fourth buttons, you should see a caret view.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Waiting for caret to hide.
+PASS Caret was hidden.
+Waiting for caret to show.
+PASS Caret was shown.
+Waiting for caret to hide.
+PASS Caret was hidden.
+Waiting for caret to show.
+PASS Caret was shown.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/selection/ios/do-not-hide-selection-in-visible-container.html (0 => 242401)


--- trunk/LayoutTests/editing/selection/ios/do-not-hide-selection-in-visible-container.html	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/do-not-hide-selection-in-visible-container.html	2019-03-05 00:31:31 UTC (rev 242401)
@@ -0,0 +1,105 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+<script src=""
+<script src=""
+<style>
+.hidden {
+    height: 0;
+    overflow: hidden;
+}
+
+iframe {
+    width: 100px;
+    height: 44px;
+    position: absolute;
+    border: none;
+}
+
+#frame2 {
+    top: 100px;
+}
+
+#frame3 {
+    position: relative;
+}
+
+input {
+    position: fixed;
+}
+</style>
+<script>
+jsTestIsAsync = true;
+
+async function ensureCaretIsVisible(isVisible)
+{
+    if (isVisible)
+        debug("Waiting for caret to show.");
+    else
+        debug("Waiting for caret to hide.");
+
+    let rect = null;
+    while (!rect || (!isVisible && rect.width && rect.height) || (isVisible && (!rect.width || !rect.height)))
+        rect = await UIHelper.getUICaretViewRect();
+
+    if (isVisible)
+        testPassed("Caret was shown.");
+    else
+        testPassed("Caret was hidden.");
+}
+
+addEventListener("load", async () => {
+    description("Verifies that native selection UI is not suppressed when focusing an input that is inside an empty container with `overflow: hidden`, but is positioned absolutely such that it is still visible. To manually verify, click on each of the four buttons to move focus into editable areas. After tapping the first and third buttons, you should not see a caret view; after tapping the second and fourth buttons, you should see a caret view.");
+
+    if (!window.testRunner)
+        return;
+
+    for (const testCase of [[first, false], [second, true], [third, false], [fourth, true]]) {
+        const [buttonToTap, expectedVisibility] = testCase;
+        await UIHelper.activateElementAndWaitForInputSession(buttonToTap);
+        await ensureCaretIsVisible(expectedVisibility);
+        document.activeElement.blur();
+    }
+
+    await UIHelper.waitForKeyboardToHide();
+    finishJSTest();
+});
+</script>
+</head>
+<body class="hidden">
+    <button id="first">First field</button>
+    <button id="second">Second field</button>
+    <button id="third">Third field</button>
+    <button id="fourth">Fourth field</button>
+    <iframe id="frame1" srcdoc="
+        <div style='overflow: hidden; height: 0; box-sizing: border-box; border: none; padding: 0;'>
+            <input></input>
+        </div>"></iframe>
+    <div class="hidden">
+        <iframe id="frame2" srcdoc="<input></input>"></iframe>
+        <iframe id="frame3" srcdoc="<input></input>"></iframe>
+        <input></input>
+    </div>
+    <div id="description"></div>
+    <div id="console"></div>
+</body>
+<script>
+function createFrameFocusHandler(identifier) {
+    return (event) => {
+        const frame = document.getElementById(identifier);
+        frame.focus();
+        frame.contentDocument.querySelector("input").focus();
+        event.preventDefault();
+    };
+}
+
+first.addEventListener("click", createFrameFocusHandler("frame1"));
+second.addEventListener("click", createFrameFocusHandler("frame2"));
+third.addEventListener("click", createFrameFocusHandler("frame3"));
+fourth.addEventListener("click", event => {
+    document.querySelector("input").focus();
+    event.preventDefault();
+});
+</script>
+</html>

Modified: trunk/LayoutTests/editing/selection/ios/hide-selection-in-empty-overflow-hidden-container.html (242400 => 242401)


--- trunk/LayoutTests/editing/selection/ios/hide-selection-in-empty-overflow-hidden-container.html	2019-03-05 00:09:15 UTC (rev 242400)
+++ trunk/LayoutTests/editing/selection/ios/hide-selection-in-empty-overflow-hidden-container.html	2019-03-05 00:31:31 UTC (rev 242401)
@@ -26,10 +26,9 @@
 }
 </style>
 <script>
-addEventListener("load", runTest);
 jsTestIsAsync = true;
 
-async function runTest() {
+addEventListener("load", async () => {
     description("Verifies that native selection UI is suppressed when focusing a textarea that is completely hidden "
         + "underneath an empty container with <code>overflow: hidden;</code>. To manually test, tap the top button, "
         + "and then tap on the bottom button. In both cases, there should be no platform selection shown.");
@@ -64,9 +63,9 @@
 
     frame.contentWindow.document.activeElement.blur();
     await UIHelper.waitForKeyboardToHide();
-    document.querySelectorAll("button, #container").forEach(element => element.remove())
+    document.querySelectorAll("button, #container").forEach(element => element.remove());
     finishJSTest();
-}
+});
 </script>
 </head>
 <body>
@@ -76,7 +75,7 @@
 </button>
 <div id="container">
     <textarea id="editor"></textarea>
-    <iframe id="frame" srcdoc="<textarea id='editor'></textarea>" _onload_="runTest()"></iframe>
+    <iframe id="frame" srcdoc="<textarea id='editor'></textarea>"></iframe>
 </div>
 <div id="description"></div>
 <div id="console"></div>

Modified: trunk/LayoutTests/resources/ui-helper.js (242400 => 242401)


--- trunk/LayoutTests/resources/ui-helper.js	2019-03-05 00:09:15 UTC (rev 242400)
+++ trunk/LayoutTests/resources/ui-helper.js	2019-03-05 00:31:31 UTC (rev 242401)
@@ -224,6 +224,13 @@
         });
     }
 
+    static activateElementAndWaitForInputSession(element)
+    {
+        const x = element.offsetLeft + element.offsetWidth / 2;
+        const y = element.offsetTop + element.offsetHeight / 2;
+        return this.activateAndWaitForInputSessionAt(x, y);
+    }
+
     static activateFormControl(element)
     {
         if (!this.isWebKit2() || !this.isIOS())

Modified: trunk/Source/WebCore/ChangeLog (242400 => 242401)


--- trunk/Source/WebCore/ChangeLog	2019-03-05 00:09:15 UTC (rev 242400)
+++ trunk/Source/WebCore/ChangeLog	2019-03-05 00:31:31 UTC (rev 242401)
@@ -1,3 +1,27 @@
+2019-03-04  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Native text selection UI is incorrectly suppressed in Microsoft Visio
+        https://bugs.webkit.org/show_bug.cgi?id=195178
+        <rdar://problem/48519394>
+
+        Reviewed by Darin Adler.
+
+        Currently, our heuristics for detecting hidden editable areas attempt to search for empty parent renderers with
+        "overflow: hidden". It does this by ascending the layer tree in search of renderers that have an empty content
+        size, and whose renderers' styles indicate that they have overflow: hidden in the X or Y directions. This fails
+        in the case where a child renderer is positioned out of flow, relative to one of its parent layers, since the
+        child will be visible, but we'll incorrectly believe that it is hidden. This leads to selection UI unexpectedly
+        disappearing in the online version of Microsoft Visio.
+
+        To fix this, we check whether the enclosing layer around the editable element has an empty clip rect; if the
+        element is inside of a subframe, we additionally walk up to each enclosing frame's layer and check if that
+        frame's layer has an empty clip rect.
+
+        Test: editing/selection/ios/do-not-hide-selection-in-visible-container.html
+
+        * rendering/RenderObject.cpp:
+        (WebCore::RenderObject::isTransparentOrFullyClippedRespectingParentFrames const):
+
 2019-03-01  Ryosuke Niwa  <rn...@webkit.org>
 
         gPictureOwnerMap is unnecessary

Modified: trunk/Source/WebCore/rendering/RenderObject.cpp (242400 => 242401)


--- trunk/Source/WebCore/rendering/RenderObject.cpp	2019-03-05 00:09:15 UTC (rev 242400)
+++ trunk/Source/WebCore/rendering/RenderObject.cpp	2019-03-05 00:31:31 UTC (rev 242401)
@@ -1527,38 +1527,40 @@
     delete this;
 }
 
+static RenderLayer* enclosingFrameRenderLayer(RenderObject& renderObject)
+{
+    auto* owner = renderObject.frame().ownerElement();
+    if (!owner)
+        return nullptr;
+
+    auto* frameOwnerRenderer = owner->renderer();
+    return frameOwnerRenderer ? frameOwnerRenderer->enclosingLayer() : nullptr;
+}
+
+static RenderLayer* parentLayerCrossingFrameBoundaries(RenderLayer& layer)
+{
+    if (auto* parentLayer = layer.parent())
+        return parentLayer;
+
+    return enclosingFrameRenderLayer(layer.renderer());
+}
+
 bool RenderObject::isTransparentOrFullyClippedRespectingParentFrames() const
 {
     static const double minimumVisibleOpacity = 0.01;
 
     float currentOpacity = 1;
-    auto* layer = enclosingLayer();
-    while (layer) {
-        auto& layerRenderer = layer->renderer();
-        auto& style = layerRenderer.style();
-        if (auto* box = layer->renderBox()) {
-            bool isOverflowHidden = style.overflowX() == Overflow::Hidden || style.overflowY() == Overflow::Hidden;
-            if (isOverflowHidden && !box->isDocumentElementRenderer() && box->contentSize().isEmpty())
-                return true;
-        }
-        currentOpacity *= style.opacity();
+    for (auto* layer = enclosingLayer(); layer; layer = parentLayerCrossingFrameBoundaries(*layer)) {
+        currentOpacity *= layer->renderer().style().opacity();
         if (currentOpacity < minimumVisibleOpacity)
             return true;
+    }
 
-        auto* parentLayer = layer->parent();
-        if (!parentLayer) {
-            if (!is<RenderView>(layerRenderer))
-                return false;
+    for (auto* layer = enclosingLayer(); layer; layer = enclosingFrameRenderLayer(layer->renderer())) {
+        if (layer->selfClipRect().isEmpty())
+            return true;
+    }
 
-            auto& enclosingFrame = downcast<RenderView>(layerRenderer).view().frame();
-            if (enclosingFrame.isMainFrame())
-                return false;
-
-            if (auto *frameOwnerRenderer = enclosingFrame.ownerElement()->renderer())
-                parentLayer = frameOwnerRenderer->enclosingLayer();
-        }
-        layer = parentLayer;
-    }
     return false;
 }
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to