Title: [261398] trunk
Revision
261398
Author
[email protected]
Date
2020-05-08 10:57:39 -0700 (Fri, 08 May 2020)

Log Message

[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.

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.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (261397 => 261398)


--- trunk/LayoutTests/ChangeLog	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/LayoutTests/ChangeLog	2020-05-08 17:57:39 UTC (rev 261398)
@@ -1,5 +1,19 @@
 2020-05-08  Wenson Hsieh  <[email protected]>
 
+        [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  <[email protected]>
+
         Preserve character set information when writing to the pasteboard when copying rich text
         https://bugs.webkit.org/show_bug.cgi?id=211524
 

Added: trunk/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field-expected.txt (0 => 261398)


--- trunk/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field-expected.txt	2020-05-08 17:57:39 UTC (rev 261398)
@@ -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
+ 

Added: trunk/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field.html (0 => 261398)


--- trunk/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field.html	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/caret-rect-after-animating-focused-text-field.html	2020-05-08 17:57:39 UTC (rev 261398)
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<script src=""
+<script src=""
+<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

Modified: trunk/Source/WebCore/ChangeLog (261397 => 261398)


--- trunk/Source/WebCore/ChangeLog	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebCore/ChangeLog	2020-05-08 17:57:39 UTC (rev 261398)
@@ -1,5 +1,30 @@
 2020-05-08  Wenson Hsieh  <[email protected]>
 
+        [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  <[email protected]>
+
         Preserve character set information when writing to the pasteboard when copying rich text
         https://bugs.webkit.org/show_bug.cgi?id=211524
 

Modified: trunk/Source/WebCore/animation/WebAnimation.cpp (261397 => 261398)


--- trunk/Source/WebCore/animation/WebAnimation.cpp	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebCore/animation/WebAnimation.cpp	2020-05-08 17:57:39 UTC (rev 261398)
@@ -30,10 +30,13 @@
 #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 @@
     //    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()

Modified: trunk/Source/WebCore/dom/Node.h (261397 => 261398)


--- trunk/Source/WebCore/dom/Node.h	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebCore/dom/Node.h	2020-05-08 17:57:39 UTC (rev 261398)
@@ -375,7 +375,7 @@
 
     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;

Modified: trunk/Source/WebCore/editing/VisibleSelection.h (261397 => 261398)


--- trunk/Source/WebCore/editing/VisibleSelection.h	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebCore/editing/VisibleSelection.h	2020-05-08 17:57:39 UTC (rev 261398)
@@ -97,7 +97,7 @@
 
     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.

Modified: trunk/Source/WebCore/page/ChromeClient.h (261397 => 261398)


--- trunk/Source/WebCore/page/ChromeClient.h	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebCore/page/ChromeClient.h	2020-05-08 17:57:39 UTC (rev 261398)
@@ -528,6 +528,8 @@
     virtual void setMockWebAuthenticationConfiguration(const MockWebAuthenticationConfiguration&) { }
 #endif
 
+    virtual void animationDidFinishForElement(const Element&) { }
+
 protected:
     virtual ~ChromeClient() = default;
 };

Modified: trunk/Source/WebKit/ChangeLog (261397 => 261398)


--- trunk/Source/WebKit/ChangeLog	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebKit/ChangeLog	2020-05-08 17:57:39 UTC (rev 261398)
@@ -1,3 +1,41 @@
+2020-05-08  Wenson Hsieh  <[email protected]>
+
+        [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  <[email protected]>
 
         REGRESSION (r260228): Linker warning about limitsNavigationsToAppBoundDomains property overriding instance methods from class

Modified: trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp (261397 => 261398)


--- trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.cpp	2020-05-08 17:57:39 UTC (rev 261398)
@@ -1395,4 +1395,9 @@
 }
 #endif
 
+void WebChromeClient::animationDidFinishForElement(const Element& element)
+{
+    m_page.animationDidFinishForElement(element);
+}
+
 } // namespace WebKit

Modified: trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h (261397 => 261398)


--- trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebKit/WebProcess/WebCoreSupport/WebChromeClient.h	2020-05-08 17:57:39 UTC (rev 261398)
@@ -230,6 +230,8 @@
     void AXFinishFrameLoad() final { }
 #endif
 
+    void animationDidFinishForElement(const WebCore::Element&) final;
+
     RefPtr<WebCore::DisplayRefreshMonitor> createDisplayRefreshMonitor(WebCore::PlatformDisplayID) const final;
 
 #if ENABLE(GPU_PROCESS)

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (261397 => 261398)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2020-05-08 17:57:39 UTC (rev 261398)
@@ -7094,6 +7094,14 @@
 }
 #endif // ENABLE(MEDIA_USAGE)
 
+#if !PLATFORM(IOS_FAMILY)
+
+void WebPage::animationDidFinishForElement(const WebCore::Element&)
+{
+}
+
+#endif
+
 } // namespace WebKit
 
 #undef RELEASE_LOG_IF_ALLOWED

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (261397 => 261398)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2020-05-08 17:57:39 UTC (rev 261398)
@@ -404,6 +404,8 @@
     void didInsertMenuItemElement(WebCore::HTMLMenuItemElement&);
     void didRemoveMenuItemElement(WebCore::HTMLMenuItemElement&);
 
+    void animationDidFinishForElement(const WebCore::Element&);
+
     const String& overrideContentSecurityPolicy() const { return m_overrideContentSecurityPolicy; }
 
     WebUndoStep* webUndoStep(WebUndoStepID);

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


--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2020-05-08 17:56:48 UTC (rev 261397)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2020-05-08 17:57:39 UTC (rev 261398)
@@ -4391,6 +4391,34 @@
 
 #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
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to