[iOS] caret appears in the middle of a search field when field is focused on agoda.com
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 May 2020 17:57:39 +0000 (17:57 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 8 May 2020 17:57:39 +0000 (17:57 +0000)
https://bugs.webkit.org/show_bug.cgi?id=211591
<rdar://problem/60605873>

Reviewed by Antoine Quint.

Source/WebCore:

See WebKit/ChangeLog for more details.

Test: editing/selection/ios/caret-rect-after-animating-focused-text-field.html

* animation/WebAnimation.cpp:
(WebCore::WebAnimation::finishNotificationSteps):

Add plumbing to call out to the WebKit client layer after an animation finishes running.

* dom/Node.h:
* editing/VisibleSelection.h:

WEBCORE_EXPORT a couple of methods.

* page/ChromeClient.h:
(WebCore::ChromeClient::animationDidFinishForElement):

Source/WebKit:

The main search field on the mobile version of this website begins offscreen, with a CSS transform that moves it
to the far right; tapping the button element that (visually) has a search-field-like appearance on the front
page programmatically focuses the real offscreen search field, and animates it onscreen by changing the CSS
transform attribute to remove the x-axis translation.

On iOS, the caret rect is computed and sent to the UI process via editor state updates; however, the editor
state is computed immediately after focusing the input field. As such, the caret rect at this moment is computed
in the middle of the animation, leaving it stuck in an unpredictable location.

To fix this, add plumbing to call into the WebKit client layer when an animation has ended. On iOS, if the
selection is visible (i.e. a ranged selection, or editable caret), then check to see whether the element that
has finished animating contains either endpoint of the selection. If so, then schedule a followup editor state
update to push updated selection information to the UI process.

* WebProcess/WebCoreSupport/WebChromeClient.cpp:
(WebKit::WebChromeClient::animationDidFinishForElement):
* WebProcess/WebCoreSupport/WebChromeClient.h:

Add a new client hook for when animations end.

* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::animationDidFinishForElement):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/ios/WebPageIOS.mm:

Add logic to schedule a new editor state update if needed, after an animation ends that might affect either
the start or end of the selection.

(WebKit::WebPage::animationDidFinishForElement):

LayoutTests:

Add a new layout test to verify that the caret view eventually becomes visible when after a focused text field
containing the caret is animated.

* editing/selection/ios/caret-rect-after-animating-focused-text-field-expected.txt: Added.
* editing/selection/ios/caret-rect-after-animating-focused-text-field.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@261398 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field-expected.txt [new file with mode: 0644]
LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/animation/WebAnimation.cpp
Source/WebCore/dom/Node.h
Source/WebCore/editing/VisibleSelection.h
Source/WebCore/page/ChromeClient.h
Source/WebKit/ChangeLog
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm

index 032cb18..a84c5c4 100644 (file)
@@ -1,5 +1,19 @@
 2020-05-08  Wenson Hsieh  <wenson_hsieh@apple.com>
 
+        [iOS] caret appears in the middle of a search field when field is focused on agoda.com
+        https://bugs.webkit.org/show_bug.cgi?id=211591
+        <rdar://problem/60605873>
+
+        Reviewed by Antoine Quint.
+
+        Add a new layout test to verify that the caret view eventually becomes visible when after a focused text field
+        containing the caret is animated.
+
+        * editing/selection/ios/caret-rect-after-animating-focused-text-field-expected.txt: Added.
+        * editing/selection/ios/caret-rect-after-animating-focused-text-field.html: Added.
+
+2020-05-08  Wenson Hsieh  <wenson_hsieh@apple.com>
+
         Preserve character set information when writing to the pasteboard when copying rich text
         https://bugs.webkit.org/show_bug.cgi?id=211524
 
diff --git a/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field-expected.txt b/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field-expected.txt
new file mode 100644 (file)
index 0000000..cbaabf7
--- /dev/null
@@ -0,0 +1,10 @@
+This test verifies that the caret view is updated following a CSS animation. To manually run the test, tap the button to focus the offscreen input field; after the input field is animated onto the screen, the caret should become visible.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS The caret became visible
+PASS successfullyParsed is true
+
+TEST COMPLETE
diff --git a/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field.html b/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field.html
new file mode 100644 (file)
index 0000000..603c3c3
--- /dev/null
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<script src="../../../resources/js-test.js"></script>
+<script src="../../../resources/ui-helper.js"></script>
+<style>
+#container {
+    position: fixed;
+    width: 200px;
+    height: 200px;
+    transform: translate3d(-400px, 100px, 0);
+    transition: all 0.5s ease-out;
+}
+
+input {
+    font-size: 20px;
+    width: 100%;
+}
+</style>
+<script>
+jsTestIsAsync = true;
+
+addEventListener("load", async () => {
+    description("This test verifies that the caret view is updated following a CSS animation. To manually run the test, tap the button to focus the offscreen input field; after the input field is animated onto the screen, the caret should become visible.");
+
+    const container = document.getElementById("container");
+    const input = document.querySelector("input");
+    const button = document.querySelector("button");
+    button.addEventListener("click", () => {
+        container.style.transform = "translate3d(1em, 100px, 0)";
+        button.remove();
+        input.focus();
+    });
+
+    await UIHelper.activateElementAndWaitForInputSession(button);
+
+    let caretViewRect = {};
+    while (!caretViewRect.left || caretViewRect.left < 0 || !caretViewRect.top || caretViewRect.top < 0) {
+        await UIHelper.delayFor(100);
+        caretViewRect = await UIHelper.getUICaretViewRect();
+    }
+
+    testPassed(`The caret became visible`);
+
+    input.blur();
+    await UIHelper.waitForKeyboardToHide();
+    finishJSTest();
+});
+</script>
+</head>
+<body>
+<button>Tap to focus</button>
+<div id="container">
+    <input>
+</div>
+</body>
+</html>
\ No newline at end of file
index fcb4a11..f1d7625 100644 (file)
@@ -1,5 +1,30 @@
 2020-05-08  Wenson Hsieh  <wenson_hsieh@apple.com>
 
+        [iOS] caret appears in the middle of a search field when field is focused on agoda.com
+        https://bugs.webkit.org/show_bug.cgi?id=211591
+        <rdar://problem/60605873>
+
+        Reviewed by Antoine Quint.
+
+        See WebKit/ChangeLog for more details.
+
+        Test: editing/selection/ios/caret-rect-after-animating-focused-text-field.html
+
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::finishNotificationSteps):
+
+        Add plumbing to call out to the WebKit client layer after an animation finishes running.
+
+        * dom/Node.h:
+        * editing/VisibleSelection.h:
+
+        WEBCORE_EXPORT a couple of methods.
+
+        * page/ChromeClient.h:
+        (WebCore::ChromeClient::animationDidFinishForElement):
+
+2020-05-08  Wenson Hsieh  <wenson_hsieh@apple.com>
+
         Preserve character set information when writing to the pasteboard when copying rich text
         https://bugs.webkit.org/show_bug.cgi?id=211524
 
index 005163f..3b7e966 100644 (file)
 #include "AnimationPlaybackEvent.h"
 #include "AnimationTimeline.h"
 #include "CSSComputedStyleDeclaration.h"
+#include "Chrome.h"
+#include "ChromeClient.h"
 #include "DOMPromiseProxy.h"
 #include "DeclarativeAnimation.h"
 #include "Document.h"
 #include "DocumentTimeline.h"
+#include "Element.h"
 #include "EventLoop.h"
 #include "EventNames.h"
 #include "InspectorInstrumentation.h"
@@ -904,6 +907,13 @@ void WebAnimation::finishNotificationSteps()
     //    effect end to an origin-relative time.
     //    Otherwise, queue a task to dispatch finishEvent at animation. The task source for this task is the DOM manipulation task source.
     enqueueAnimationPlaybackEvent(eventNames().finishEvent, currentTime(), m_timeline ? m_timeline->currentTime() : WTF::nullopt);
+
+    if (is<KeyframeEffect>(m_effect)) {
+        if (auto target = makeRefPtr(downcast<KeyframeEffect>(*m_effect).target())) {
+            if (auto* page = target->document().page())
+                page->chrome().client().animationDidFinishForElement(*target);
+        }
+    }
 }
 
 ExceptionOr<void> WebAnimation::play()
index eaaa358..3b0b702 100644 (file)
@@ -375,7 +375,7 @@ public:
 
     bool isDescendantOrShadowDescendantOf(const Node*) const;
     WEBCORE_EXPORT bool contains(const Node*) const;
-    bool containsIncludingShadowDOM(const Node*) const;
+    WEBCORE_EXPORT bool containsIncludingShadowDOM(const Node*) const;
 
     // Whether or not a selection can be started in this object
     virtual bool canStartSelection() const;
index 4d8bd75..8f4fc98 100644 (file)
@@ -97,7 +97,7 @@ public:
 
     WEBCORE_EXPORT Element* rootEditableElement() const;
     WEBCORE_EXPORT bool isContentEditable() const;
-    bool hasEditableStyle() const;
+    WEBCORE_EXPORT bool hasEditableStyle() const;
     WEBCORE_EXPORT bool isContentRichlyEditable() const;
     // Returns a shadow tree node for legacy shadow trees, a child of the
     // ShadowRoot node for new shadow trees, or 0 for non-shadow trees.
index 1f694d1..4b1aede 100644 (file)
@@ -528,6 +528,8 @@ public:
     virtual void setMockWebAuthenticationConfiguration(const MockWebAuthenticationConfiguration&) { }
 #endif
 
+    virtual void animationDidFinishForElement(const Element&) { }
+
 protected:
     virtual ~ChromeClient() = default;
 };
index 21bd36e..27d6e82 100644 (file)
@@ -1,3 +1,41 @@
+2020-05-08  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] caret appears in the middle of a search field when field is focused on agoda.com
+        https://bugs.webkit.org/show_bug.cgi?id=211591
+        <rdar://problem/60605873>
+
+        Reviewed by Antoine Quint.
+
+        The main search field on the mobile version of this website begins offscreen, with a CSS transform that moves it
+        to the far right; tapping the button element that (visually) has a search-field-like appearance on the front
+        page programmatically focuses the real offscreen search field, and animates it onscreen by changing the CSS
+        transform attribute to remove the x-axis translation.
+
+        On iOS, the caret rect is computed and sent to the UI process via editor state updates; however, the editor
+        state is computed immediately after focusing the input field. As such, the caret rect at this moment is computed
+        in the middle of the animation, leaving it stuck in an unpredictable location.
+
+        To fix this, add plumbing to call into the WebKit client layer when an animation has ended. On iOS, if the
+        selection is visible (i.e. a ranged selection, or editable caret), then check to see whether the element that
+        has finished animating contains either endpoint of the selection. If so, then schedule a followup editor state
+        update to push updated selection information to the UI process.
+
+        * WebProcess/WebCoreSupport/WebChromeClient.cpp:
+        (WebKit::WebChromeClient::animationDidFinishForElement):
+        * WebProcess/WebCoreSupport/WebChromeClient.h:
+
+        Add a new client hook for when animations end.
+
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::animationDidFinishForElement):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+
+        Add logic to schedule a new editor state update if needed, after an animation ends that might affect either
+        the start or end of the selection.
+
+        (WebKit::WebPage::animationDidFinishForElement):
+
 2020-05-08  David Kilzer  <ddkilzer@apple.com>
 
         REGRESSION (r260228): Linker warning about limitsNavigationsToAppBoundDomains property overriding instance methods from class
index 1324229..878e2b4 100644 (file)
@@ -1395,4 +1395,9 @@ void WebChromeClient::setMockWebAuthenticationConfiguration(const MockWebAuthent
 }
 #endif
 
+void WebChromeClient::animationDidFinishForElement(const Element& element)
+{
+    m_page.animationDidFinishForElement(element);
+}
+
 } // namespace WebKit
index 92cede6..a5f5fe4 100644 (file)
@@ -230,6 +230,8 @@ private:
     void AXFinishFrameLoad() final { }
 #endif
 
+    void animationDidFinishForElement(const WebCore::Element&) final;
+
     RefPtr<WebCore::DisplayRefreshMonitor> createDisplayRefreshMonitor(WebCore::PlatformDisplayID) const final;
 
 #if ENABLE(GPU_PROCESS)
index 0080b0d..17eac41 100644 (file)
@@ -7094,6 +7094,14 @@ void WebPage::removeMediaUsageManagerSession(MediaSessionIdentifier identifier)
 }
 #endif // ENABLE(MEDIA_USAGE)
 
+#if !PLATFORM(IOS_FAMILY)
+
+void WebPage::animationDidFinishForElement(const WebCore::Element&)
+{
+}
+
+#endif
+
 } // namespace WebKit
 
 #undef RELEASE_LOG_IF_ALLOWED
index c08c76b..49caf46 100644 (file)
@@ -404,6 +404,8 @@ public:
     void didInsertMenuItemElement(WebCore::HTMLMenuItemElement&);
     void didRemoveMenuItemElement(WebCore::HTMLMenuItemElement&);
 
+    void animationDidFinishForElement(const WebCore::Element&);
+
     const String& overrideContentSecurityPolicy() const { return m_overrideContentSecurityPolicy; }
 
     WebUndoStep* webUndoStep(WebUndoStepID);
index 27d8ef9..b7bed03 100644 (file)
@@ -4391,6 +4391,34 @@ void WebPage::requestPasswordForQuickLookDocumentInMainFrame(const String& fileN
 
 #endif
 
+void WebPage::animationDidFinishForElement(const WebCore::Element& animatedElement)
+{
+    auto frame = makeRef(m_page->focusController().focusedOrMainFrame());
+    auto& selection = frame->selection().selection();
+    if (selection.isNoneOrOrphaned())
+        return;
+
+    if (selection.isCaret() && !selection.hasEditableStyle())
+        return;
+
+    auto scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded = [&](const Node* container) {
+        if (!animatedElement.containsIncludingShadowDOM(container))
+            return false;
+
+        frame->selection().setCaretRectNeedsUpdate();
+        scheduleFullEditorStateUpdate();
+        return true;
+    };
+
+    auto startContainer = makeRefPtr(selection.start().containerNode());
+    if (scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded(startContainer.get()))
+        return;
+
+    auto endContainer = makeRefPtr(selection.end().containerNode());
+    if (startContainer != endContainer)
+        scheduleEditorStateUpdateForStartOrEndContainerNodeIfNeeded(endContainer.get());
+}
+
 } // namespace WebKit
 
 #undef RELEASE_LOG_IF_ALLOWED