Title: [245062] trunk
Revision
245062
Author
[email protected]
Date
2019-05-08 12:36:33 -0700 (Wed, 08 May 2019)

Log Message

[iOS] Add a quirk to synthesize mouse events when modifying the selection
https://bugs.webkit.org/show_bug.cgi?id=197683
<rdar://problem/48003980>

Reviewed by Tim Horton.

Source/WebCore:

See WebKit ChangeLog for more details.

Test: editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk.html

* page/EventHandler.cpp:
(WebCore::EventHandler::handleMousePressEvent):
(WebCore::EventHandler::supportsSelectionUpdatesOnMouseDrag const):

Add some platform hooks to prevent mousemove events from updating the selection on iOS.

(WebCore::EventHandler::shouldAllowMouseDownToStartDrag const):

Add some platform hooks to prevent drag and drop from kicking in when sending synthetic mousemove events to the
page on iOS (drag and drop is instead triggered by EventHandler::tryToBeginDragAtPoint).

(WebCore::EventHandler::updateSelectionForMouseDrag):
* page/EventHandler.h:
* page/Quirks.cpp:
(WebCore::Quirks::shouldDispatchSyntheticMouseEventsWhenModifyingSelection const):
* page/Quirks.h:

Add the new site-specific quirk.

* page/Settings.yaml:
* page/ios/EventHandlerIOS.mm:
(WebCore::EventHandler::tryToBeginDragAtPoint):
(WebCore::EventHandler::supportsSelectionUpdatesOnMouseDrag const):
(WebCore::EventHandler::shouldAllowMouseDownToStartDrag const):
* testing/InternalSettings.cpp:
(WebCore::InternalSettings::Backup::Backup):
(WebCore::InternalSettings::Backup::restoreTo):
(WebCore::InternalSettings::setShouldDispatchSyntheticMouseEventsWhenModifyingSelection):
* testing/InternalSettings.h:
* testing/InternalSettings.idl:

Add an internal settings hook to opt into this quirk, for use in layout tests.

Source/WebKit:

Introduces support for dispatching synthetic mouse events when modifying the selection on some websites. See
below for more details.

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::selectAll):
* UIProcess/WebPageProxy.h:

Instead of executing a "SelectAll" editing command using the generic WebPage::executeEditCommand method,
introduce a separate method for selectAll that executes the "SelectAll" edit command and then does some
platform-specific work. See platformDidSelectAll.

* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView selectAllForWebView:]):
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::selectAll):
(WebKit::WebPage::shouldDispatchSyntheticMouseEventsWhenModifyingSelection const):

Add a helper method to determine whether the quirk should be enabled.

(WebKit::WebPage::platformDidSelectAll):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::elementRectInRootViewCoordinates):

Move this function closer to the top of the file so that it can be used in
dispatchSyntheticMouseEventsForSelectionGesture.

(WebKit::WebPage::clearSelection):
(WebKit::WebPage::dispatchSyntheticMouseEventsForSelectionGesture):

Add a helper method to dispatch a synthetic mouse event for a given selection gesture type. Used in several
places in WebPageIOS to synthesize and dispatch mouse events during selection.

(WebKit::WebPage::updateSelectionWithTouches):

When changing the selection with selection handles, fake mousedown when the user first touches down on the
selection handle; mousemove as the user is moving the handle around; and finally, mouseup when the user lets go.

(WebKit::WebPage::extendSelection):
(WebKit::WebPage::platformDidSelectAll):

When tapping "Select All" and/or "Select" in the callout menu, fake a mousedown at the selection start, then a
mousemove at selection end, and finally, a mouseup at selection end.

(WebKit::WebPage::getFocusedElementInformation):

LayoutTests:

Adds a new layout test to enable the site-specific quirk and verify that mouse events are dispatched when
changing selection, both via the callout menu and by moving the selection grabber using gestures.

* editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk-expected.txt: Added.
* editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk.html: Added.
* resources/ui-helper.js:
(window.UIHelper.waitForMenuToHide.return.new.Promise):
(window.UIHelper.waitForMenuToHide):

Introduce a new helper method to wait for the menu to hide (on iOS, this refers to the callout menu).

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (245061 => 245062)


--- trunk/LayoutTests/ChangeLog	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/LayoutTests/ChangeLog	2019-05-08 19:36:33 UTC (rev 245062)
@@ -1,3 +1,22 @@
+2019-05-08  Wenson Hsieh  <[email protected]>
+
+        [iOS] Add a quirk to synthesize mouse events when modifying the selection
+        https://bugs.webkit.org/show_bug.cgi?id=197683
+        <rdar://problem/48003980>
+
+        Reviewed by Tim Horton.
+
+        Adds a new layout test to enable the site-specific quirk and verify that mouse events are dispatched when
+        changing selection, both via the callout menu and by moving the selection grabber using gestures.
+
+        * editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk-expected.txt: Added.
+        * editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk.html: Added.
+        * resources/ui-helper.js:
+        (window.UIHelper.waitForMenuToHide.return.new.Promise):
+        (window.UIHelper.waitForMenuToHide):
+
+        Introduce a new helper method to wait for the menu to hide (on iOS, this refers to the callout menu).
+
 2019-05-07  Ryan Haddad  <[email protected]>
 
         Unreviewed test gardening for Mojave.

Added: trunk/LayoutTests/editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk-expected.txt (0 => 245062)


--- trunk/LayoutTests/editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk-expected.txt	2019-05-08 19:36:33 UTC (rev 245062)
@@ -0,0 +1,30 @@
+This test verifies that when the 'mouse event synthesis on selection' quirk is enabled, text selection dispatches mouse events that mimic the user selecting text. To run the test manually, use the callout menu to select text or selection handles, and verify that mousedown, mousemove and mouseup are dispatched and logged in the output area below.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Attempting to show the callout bar.
+Observed event type: mousedown
+Observed event type: mouseup
+Observed event type: mousedown
+Observed event type: mouseup
+PASS Displayed the callout bar.
+
+Attempting to select the last word.
+Observed event type: mousedown
+Observed event type: mousemove
+Observed event type: mouseup
+PASS Selected the last word.
+
+Attempting to dismiss the callout bar by executing 'SelectAll'.
+PASS Dismissed the callout bar.
+
+Attempting to move the selection grabber.
+Observed event type: mousedown
+Observed event type: mousemove
+Observed event type: mouseup
+PASS Moved the selection grabber.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk.html (0 => 245062)


--- trunk/LayoutTests/editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk.html	                        (rev 0)
+++ trunk/LayoutTests/editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk.html	2019-05-08 19:36:33 UTC (rev 245062)
@@ -0,0 +1,98 @@
+<!DOCTYPE html><!-- webkit-test-runner [ useFlexibleViewport=true ] -->
+<html>
+<head>
+<script src=""
+<script src=""
+<script src=""
+<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+<style>
+body, html {
+    margin: 0;
+}
+.editor {
+    border: 2px solid tomato;
+    width: 300px;
+    height: 300px;
+    box-sizing: border-box;
+    font-size: 32px;
+    padding: 10px;
+}
+
+#console, #description {
+    width: 300px;
+    height: 100px;
+    overflow: scroll;
+}
+</style>
+<script>
+if (window.internals)
+    internals.settings.setShouldDispatchSyntheticMouseEventsWhenModifyingSelection(true);
+
+lastEvent = null;
+jsTestIsAsync = true;
+
+function recordEvent() {
+    if (!lastEvent || event.type !== lastEvent.type)
+        debug(`Observed event type: ${event.type}`);
+    lastEvent = event;
+}
+
+addEventListener("mousedown", recordEvent);
+addEventListener("mousemove", recordEvent);
+addEventListener("mouseup", recordEvent);
+
+async function waitForSelectionToAppear()
+{
+    while (true) {
+        const rects = await UIHelper.getUISelectionViewRects();
+        if (rects.length)
+            return rects;
+    }
+}
+
+function midPointOfRect(rect) {
+    return [rect.left + (rect.width / 2), rect.top + (rect.height / 2)];
+}
+
+addEventListener("load", async () => {
+    if (!window.testRunner)
+        return;
+
+    description("This test verifies that when the 'mouse event synthesis on selection' quirk is enabled, text selection dispatches mouse events that mimic the user selecting text. To run the test manually, use the callout menu to select text or selection handles, and verify that mousedown, mousemove and mouseup are dispatched and logged in the output area below.");
+
+    debug("\nAttempting to show the callout bar.");
+    const editor = document.querySelector(".editor");
+    await UIHelper.activateElementAndWaitForInputSession(editor);
+    await UIHelper.activateElement(editor);
+    await UIHelper.waitForMenuToShow();
+    testPassed("Displayed the callout bar.");
+
+    debug("\nAttempting to select the last word.");
+    await UIHelper.chooseMenuAction("Select");
+    const rects = await waitForSelectionToAppear();
+    testPassed("Selected the last word.");
+
+    debug("\nAttempting to dismiss the callout bar by executing 'SelectAll'.");
+    document.execCommand("SelectAll");
+    await UIHelper.waitForMenuToHide();
+    testPassed("Dismissed the callout bar.");
+
+    debug("\nAttempting to move the selection grabber.");
+    const [grabberX, grabberY] = midPointOfRect(await UIHelper.getSelectionEndGrabberViewRect());
+    const touchDestinationX = grabberX - rects[0].width;
+
+    await touchAndDragFromPointToPoint(grabberX, grabberY, touchDestinationX, grabberY);
+    await liftUpAtPoint(touchDestinationX, grabberY);
+    testPassed("Moved the selection grabber.");
+
+    document.querySelector(".editor").remove();
+    finishJSTest();
+});
+</script>
+</head>
+<body>
+<div class="editor" contenteditable>The quick brown fox jumped over the lazy dog.</div>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>

Modified: trunk/LayoutTests/resources/ui-helper.js (245061 => 245062)


--- trunk/LayoutTests/resources/ui-helper.js	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/LayoutTests/resources/ui-helper.js	2019-05-08 19:36:33 UTC (rev 245062)
@@ -827,6 +827,19 @@
         });
     }
 
+    static waitForMenuToHide()
+    {
+        return new Promise(resolve => {
+            testRunner.runUIScript(`
+                (function() {
+                    if (uiController.isShowingMenu)
+                        uiController.didHideMenuCallback = () => uiController.uiScriptComplete();
+                    else
+                        uiController.uiScriptComplete();
+                })()`, resolve);
+        });
+    }
+
     static isShowingMenu()
     {
         return new Promise(resolve => {

Modified: trunk/Source/WebCore/ChangeLog (245061 => 245062)


--- trunk/Source/WebCore/ChangeLog	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/ChangeLog	2019-05-08 19:36:33 UTC (rev 245062)
@@ -1,3 +1,48 @@
+2019-05-08  Wenson Hsieh  <[email protected]>
+
+        [iOS] Add a quirk to synthesize mouse events when modifying the selection
+        https://bugs.webkit.org/show_bug.cgi?id=197683
+        <rdar://problem/48003980>
+
+        Reviewed by Tim Horton.
+
+        See WebKit ChangeLog for more details.
+
+        Test: editing/selection/ios/dispatch-mouse-events-when-modifying-selection-quirk.html
+
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::handleMousePressEvent):
+        (WebCore::EventHandler::supportsSelectionUpdatesOnMouseDrag const):
+
+        Add some platform hooks to prevent mousemove events from updating the selection on iOS.
+
+        (WebCore::EventHandler::shouldAllowMouseDownToStartDrag const):
+
+        Add some platform hooks to prevent drag and drop from kicking in when sending synthetic mousemove events to the
+        page on iOS (drag and drop is instead triggered by EventHandler::tryToBeginDragAtPoint).
+
+        (WebCore::EventHandler::updateSelectionForMouseDrag):
+        * page/EventHandler.h:
+        * page/Quirks.cpp:
+        (WebCore::Quirks::shouldDispatchSyntheticMouseEventsWhenModifyingSelection const):
+        * page/Quirks.h:
+
+        Add the new site-specific quirk.
+
+        * page/Settings.yaml:
+        * page/ios/EventHandlerIOS.mm:
+        (WebCore::EventHandler::tryToBeginDragAtPoint):
+        (WebCore::EventHandler::supportsSelectionUpdatesOnMouseDrag const):
+        (WebCore::EventHandler::shouldAllowMouseDownToStartDrag const):
+        * testing/InternalSettings.cpp:
+        (WebCore::InternalSettings::Backup::Backup):
+        (WebCore::InternalSettings::Backup::restoreTo):
+        (WebCore::InternalSettings::setShouldDispatchSyntheticMouseEventsWhenModifyingSelection):
+        * testing/InternalSettings.h:
+        * testing/InternalSettings.idl:
+
+        Add an internal settings hook to opt into this quirk, for use in layout tests.
+
 2019-05-08  Simon Fraser  <[email protected]>
 
         Implement backing-sharing in compositing layers, allowing overlap layers to paint into the backing store of another layer

Modified: trunk/Source/WebCore/page/EventHandler.cpp (245061 => 245062)


--- trunk/Source/WebCore/page/EventHandler.cpp	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/page/EventHandler.cpp	2019-05-08 19:36:33 UTC (rev 245062)
@@ -776,7 +776,7 @@
 
     // Single mouse down on links or images can always trigger drag-n-drop.
     bool isMouseDownOnLinkOrImage = event.isOverLink() || event.hitTestResult().image();
-    m_mouseDownMayStartDrag = singleClick && (!event.event().shiftKey() || isMouseDownOnLinkOrImage);
+    m_mouseDownMayStartDrag = singleClick && (!event.event().shiftKey() || isMouseDownOnLinkOrImage) && shouldAllowMouseDownToStartDrag();
 #endif
 
     m_mouseDownWasSingleClickInSelection = false;
@@ -847,6 +847,21 @@
 }
 
 #if ENABLE(DRAG_SUPPORT)
+
+#if !PLATFORM(IOS_FAMILY)
+
+bool EventHandler::supportsSelectionUpdatesOnMouseDrag() const
+{
+    return true;
+}
+
+bool EventHandler::shouldAllowMouseDownToStartDrag() const
+{
+    return true;
+}
+
+#endif
+
 bool EventHandler::handleMouseDraggedEvent(const MouseEventWithHitTestResults& event, CheckDragHysteresis checkDragHysteresis)
 {
     if (!m_mousePressed)
@@ -926,6 +941,9 @@
 
 void EventHandler::updateSelectionForMouseDrag()
 {
+    if (!supportsSelectionUpdatesOnMouseDrag())
+        return;
+
     FrameView* view = m_frame.view();
     if (!view)
         return;
@@ -941,6 +959,9 @@
 
 void EventHandler::updateSelectionForMouseDrag(const HitTestResult& hitTestResult)
 {
+    if (!supportsSelectionUpdatesOnMouseDrag())
+        return;
+
     if (!m_mouseDownMayStartSelect)
         return;
 

Modified: trunk/Source/WebCore/page/EventHandler.h (245061 => 245062)


--- trunk/Source/WebCore/page/EventHandler.h	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/page/EventHandler.h	2019-05-08 19:36:33 UTC (rev 245062)
@@ -365,6 +365,7 @@
 
 #if ENABLE(DRAG_SUPPORT)
     bool handleMouseDraggedEvent(const MouseEventWithHitTestResults&, CheckDragHysteresis = ShouldCheckDragHysteresis);
+    bool shouldAllowMouseDownToStartDrag() const;
 #endif
 
     WEBCORE_EXPORT bool handleMouseReleaseEvent(const MouseEventWithHitTestResults&);
@@ -457,6 +458,7 @@
 
 #if ENABLE(DRAG_SUPPORT)
     DragSourceAction updateDragSourceActionsAllowed() const;
+    bool supportsSelectionUpdatesOnMouseDrag() const;
 #endif
 
     // The following are called at the beginning of handleMouseUp and handleDrag.  
@@ -616,6 +618,10 @@
     bool m_didStartDrag { false };
     bool m_isHandlingWheelEvent { false };
 
+#if PLATFORM(IOS_FAMILY)
+    bool m_shouldAllowMouseDownToStartDrag { false };
+#endif
+
 #if ENABLE(CURSOR_VISIBILITY)
     Timer m_autoHideCursorTimer;
 #endif

Modified: trunk/Source/WebCore/page/Quirks.cpp (245061 => 245062)


--- trunk/Source/WebCore/page/Quirks.cpp	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/page/Quirks.cpp	2019-05-08 19:36:33 UTC (rev 245062)
@@ -237,6 +237,24 @@
 
 #endif
 
+bool Quirks::shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const
+{
+    if (m_document->settings().shouldDispatchSyntheticMouseEventsWhenModifyingSelection())
+        return true;
+
+    if (!needsQuirks())
+        return false;
+
+    auto host = m_document->topDocument().url().host();
+    if (equalLettersIgnoringASCIICase(host, "medium.com") || host.endsWithIgnoringASCIICase(".medium.com"))
+        return true;
+
+    if (equalLettersIgnoringASCIICase(host, "weebly.com") || host.endsWithIgnoringASCIICase(".weebly.com"))
+        return true;
+
+    return false;
+}
+
 bool Quirks::shouldSuppressAutocorrectionAndAutocaptializationInHiddenEditableAreas() const
 {
     if (!needsQuirks())

Modified: trunk/Source/WebCore/page/Quirks.h (245061 => 245062)


--- trunk/Source/WebCore/page/Quirks.h	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/page/Quirks.h	2019-05-08 19:36:33 UTC (rev 245062)
@@ -52,6 +52,7 @@
     bool shouldDisablePointerEventsQuirk() const;
     bool needsInputModeNoneImplicitly(const HTMLElement&) const;
 
+    WEBCORE_EXPORT bool shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const;
     WEBCORE_EXPORT bool shouldSuppressAutocorrectionAndAutocaptializationInHiddenEditableAreas() const;
     WEBCORE_EXPORT bool isTouchBarUpdateSupressedForHiddenContentEditable() const;
     WEBCORE_EXPORT bool isNeverRichlyEditableForTouchBar() const;

Modified: trunk/Source/WebCore/page/Settings.yaml (245061 => 245062)


--- trunk/Source/WebCore/page/Settings.yaml	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/page/Settings.yaml	2019-05-08 19:36:33 UTC (rev 245062)
@@ -834,6 +834,9 @@
 shouldDecidePolicyBeforeLoadingQuickLookPreview:
   initial: false
 
+shouldDispatchSyntheticMouseEventsWhenModifyingSelection:
+  initial: false
+
 # Deprecated
 
 iceCandidateFilteringEnabled:

Modified: trunk/Source/WebCore/page/ios/EventHandlerIOS.mm (245061 => 245062)


--- trunk/Source/WebCore/page/ios/EventHandlerIOS.mm	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/page/ios/EventHandlerIOS.mm	2019-05-08 19:36:33 UTC (rev 245062)
@@ -671,6 +671,8 @@
     if (!document)
         return false;
 
+    SetForScope<bool> shouldAllowMouseDownToStartDrag { m_shouldAllowMouseDownToStartDrag, true };
+
     document->updateLayoutIgnorePendingStylesheets();
 
     FloatPoint adjustedClientPositionAsFloatPoint(clientPosition);
@@ -699,8 +701,18 @@
     return handledDrag;
 }
 
-#endif
+bool EventHandler::supportsSelectionUpdatesOnMouseDrag() const
+{
+    return false;
+}
 
+bool EventHandler::shouldAllowMouseDownToStartDrag() const
+{
+    return m_shouldAllowMouseDownToStartDrag;
 }
 
+#endif // ENABLE(DRAG_SUPPORT)
+
+}
+
 #endif // PLATFORM(IOS_FAMILY)

Modified: trunk/Source/WebCore/testing/InternalSettings.cpp (245061 => 245062)


--- trunk/Source/WebCore/testing/InternalSettings.cpp	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/testing/InternalSettings.cpp	2019-05-08 19:36:33 UTC (rev 245062)
@@ -100,6 +100,7 @@
     , m_deferredCSSParserEnabled(settings.deferredCSSParserEnabled())
     , m_inputEventsEnabled(settings.inputEventsEnabled())
     , m_incompleteImageBorderEnabled(settings.incompleteImageBorderEnabled())
+    , m_shouldDispatchSyntheticMouseEventsWhenModifyingSelection(settings.shouldDispatchSyntheticMouseEventsWhenModifyingSelection())
     , m_shouldDeactivateAudioSession(PlatformMediaSessionManager::shouldDeactivateAudioSession())
     , m_userInterfaceDirectionPolicy(settings.userInterfaceDirectionPolicy())
     , m_systemLayoutDirection(settings.systemLayoutDirection())
@@ -207,6 +208,7 @@
     FontCache::singleton().setShouldMockBoldSystemFontForAccessibility(m_shouldMockBoldSystemFontForAccessibility);
     settings.setFrameFlattening(m_frameFlattening);
     settings.setIncompleteImageBorderEnabled(m_incompleteImageBorderEnabled);
+    settings.setShouldDispatchSyntheticMouseEventsWhenModifyingSelection(m_shouldDispatchSyntheticMouseEventsWhenModifyingSelection);
     PlatformMediaSessionManager::setShouldDeactivateAudioSession(m_shouldDeactivateAudioSession);
 
 #if ENABLE(INDEXED_DATABASE_IN_WORKERS)
@@ -926,6 +928,14 @@
     return { };
 }
 
+ExceptionOr<void> InternalSettings::setShouldDispatchSyntheticMouseEventsWhenModifyingSelection(bool shouldDispatch)
+{
+    if (!m_page)
+        return Exception { InvalidAccessError };
+    settings().setShouldDispatchSyntheticMouseEventsWhenModifyingSelection(shouldDispatch);
+    return { };
+}
+
 static InternalSettings::ForcedAccessibilityValue settingsToInternalSettingsValue(Settings::ForcedAccessibilityValue value)
 {
     switch (value) {

Modified: trunk/Source/WebCore/testing/InternalSettings.h (245061 => 245062)


--- trunk/Source/WebCore/testing/InternalSettings.h	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/testing/InternalSettings.h	2019-05-08 19:36:33 UTC (rev 245062)
@@ -103,6 +103,7 @@
     ExceptionOr<void> setShouldManageAudioSessionCategory(bool);
     ExceptionOr<void> setCustomPasteboardDataEnabled(bool);
     ExceptionOr<void> setIncompleteImageBorderEnabled(bool);
+    ExceptionOr<void> setShouldDispatchSyntheticMouseEventsWhenModifyingSelection(bool);
 
     using FrameFlatteningValue = FrameFlattening;
     ExceptionOr<void> setFrameFlattening(FrameFlatteningValue);
@@ -198,6 +199,7 @@
         bool m_deferredCSSParserEnabled;
         bool m_inputEventsEnabled;
         bool m_incompleteImageBorderEnabled;
+        bool m_shouldDispatchSyntheticMouseEventsWhenModifyingSelection;
         bool m_shouldDeactivateAudioSession;
         UserInterfaceDirectionPolicy m_userInterfaceDirectionPolicy;
         TextDirection m_systemLayoutDirection;

Modified: trunk/Source/WebCore/testing/InternalSettings.idl (245061 => 245062)


--- trunk/Source/WebCore/testing/InternalSettings.idl	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebCore/testing/InternalSettings.idl	2019-05-08 19:36:33 UTC (rev 245062)
@@ -88,6 +88,7 @@
     [MayThrowException] void setInlineMediaPlaybackRequiresPlaysInlineAttribute(boolean requires);
     [MayThrowException] void setFrameFlattening(FrameFlatteningValue frameFlattening);
     [MayThrowException] void setIncompleteImageBorderEnabled(boolean enabled);
+    [MayThrowException] void setShouldDispatchSyntheticMouseEventsWhenModifyingSelection(boolean shouldDispatch);
 
     // RuntimeEnabledFeatures.
     void setIndexedDBWorkersEnabled(boolean enabled);

Modified: trunk/Source/WebKit/ChangeLog (245061 => 245062)


--- trunk/Source/WebKit/ChangeLog	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebKit/ChangeLog	2019-05-08 19:36:33 UTC (rev 245062)
@@ -1,3 +1,58 @@
+2019-05-08  Wenson Hsieh  <[email protected]>
+
+        [iOS] Add a quirk to synthesize mouse events when modifying the selection
+        https://bugs.webkit.org/show_bug.cgi?id=197683
+        <rdar://problem/48003980>
+
+        Reviewed by Tim Horton.
+
+        Introduces support for dispatching synthetic mouse events when modifying the selection on some websites. See
+        below for more details.
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::selectAll):
+        * UIProcess/WebPageProxy.h:
+
+        Instead of executing a "SelectAll" editing command using the generic WebPage::executeEditCommand method,
+        introduce a separate method for selectAll that executes the "SelectAll" edit command and then does some
+        platform-specific work. See platformDidSelectAll.
+
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView selectAllForWebView:]):
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::selectAll):
+        (WebKit::WebPage::shouldDispatchSyntheticMouseEventsWhenModifyingSelection const):
+
+        Add a helper method to determine whether the quirk should be enabled.
+
+        (WebKit::WebPage::platformDidSelectAll):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::elementRectInRootViewCoordinates):
+
+        Move this function closer to the top of the file so that it can be used in
+        dispatchSyntheticMouseEventsForSelectionGesture.
+
+        (WebKit::WebPage::clearSelection):
+        (WebKit::WebPage::dispatchSyntheticMouseEventsForSelectionGesture):
+
+        Add a helper method to dispatch a synthetic mouse event for a given selection gesture type. Used in several
+        places in WebPageIOS to synthesize and dispatch mouse events during selection.
+
+        (WebKit::WebPage::updateSelectionWithTouches):
+
+        When changing the selection with selection handles, fake mousedown when the user first touches down on the
+        selection handle; mousemove as the user is moving the handle around; and finally, mouseup when the user lets go.
+
+        (WebKit::WebPage::extendSelection):
+        (WebKit::WebPage::platformDidSelectAll):
+
+        When tapping "Select All" and/or "Select" in the callout menu, fake a mousedown at the selection start, then a
+        mousemove at selection end, and finally, a mouseup at selection end.
+
+        (WebKit::WebPage::getFocusedElementInformation):
+
 2019-05-08  Alexander Mikhaylenko  <[email protected]>
 
         [GTK] Pinch Zooming has no maximum

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.cpp (245061 => 245062)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.cpp	2019-05-08 19:36:33 UTC (rev 245062)
@@ -2014,6 +2014,14 @@
     m_process->send(Messages::WebPage::ScheduleFullEditorStateUpdate(), m_pageID);
 }
 
+void WebPageProxy::selectAll()
+{
+    if (!hasRunningProcess())
+        return;
+
+    m_process->send(Messages::WebPage::SelectAll(), m_pageID);
+}
+
 void WebPageProxy::executeEditCommand(const String& commandName, const String& argument, WTF::Function<void(CallbackBase::Error)>&& callbackFunction)
 {
     if (!hasRunningProcess()) {

Modified: trunk/Source/WebKit/UIProcess/WebPageProxy.h (245061 => 245062)


--- trunk/Source/WebKit/UIProcess/WebPageProxy.h	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebKit/UIProcess/WebPageProxy.h	2019-05-08 19:36:33 UTC (rev 245062)
@@ -595,6 +595,7 @@
 
     void addMIMETypeWithCustomContentProvider(const String& mimeType);
 
+    void selectAll();
     void executeEditCommand(const String& commandName, const String& argument = String());
     void validateCommand(const String& commandName, WTF::Function<void (const String&, bool, int32_t, CallbackBase::Error)>&&);
 

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (245061 => 245062)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2019-05-08 19:36:33 UTC (rev 245062)
@@ -3079,7 +3079,7 @@
 - (void)selectAllForWebView:(id)sender
 {
     [_textSelectionAssistant selectAll:sender];
-    _page->executeEditCommand("selectAll"_s);
+    _page->selectAll();
 }
 
 - (void)toggleBoldfaceForWebView:(id)sender

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp (245061 => 245062)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2019-05-08 19:36:33 UTC (rev 245062)
@@ -1101,6 +1101,26 @@
     send(Messages::WebPageProxy::VoidCallback(callbackID));
 }
 
+void WebPage::selectAll()
+{
+    executeEditingCommand("SelectAll"_s, { });
+    platformDidSelectAll();
+}
+
+bool WebPage::shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const
+{
+    auto* document = m_page->mainFrame().document();
+    return document && document->quirks().shouldDispatchSyntheticMouseEventsWhenModifyingSelection();
+}
+
+#if !PLATFORM(IOS_FAMILY)
+
+void WebPage::platformDidSelectAll()
+{
+}
+
+#endif // !PLATFORM(IOS_FAMILY)
+
 void WebPage::updateEditorStateAfterLayoutIfEditabilityChanged()
 {
     // FIXME: We should update EditorStateIsContentEditable to track whether the state is richly

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.h (245061 => 245062)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.h	2019-05-08 19:36:33 UTC (rev 245062)
@@ -599,6 +599,7 @@
     void disabledAdaptationsDidChange(const OptionSet<WebCore::DisabledAdaptations>&);
     void viewportPropertiesDidChange(const WebCore::ViewportArguments&);
     void executeEditCommandWithCallback(const String&, const String& argument, CallbackID);
+    void selectAll();
 
     void textInputContextsInRect(WebCore::FloatRect, CompletionHandler<void(const Vector<WebKit::TextInputContext>&)>&&);
     void focusTextInputContext(const TextInputContext&, CompletionHandler<void(bool)>&&);
@@ -1228,6 +1229,7 @@
     void resetTextAutosizing();
     WebCore::VisiblePosition visiblePositionInFocusedNodeForPoint(const WebCore::Frame&, const WebCore::IntPoint&, bool isInteractingWithFocusedElement);
     RefPtr<WebCore::Range> rangeForGranularityAtPoint(WebCore::Frame&, const WebCore::IntPoint&, uint32_t granularity, bool isInteractingWithFocusedElement);
+    void dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch, const WebCore::IntPoint&);
 
     void sendPositionInformation(InteractionInformationAtPosition&&);
     InteractionInformationAtPosition positionInformation(const InteractionInformationRequest&);
@@ -1466,6 +1468,9 @@
     void capitalizeWord();
 #endif
 
+    bool shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const;
+    void platformDidSelectAll();
+
 #if ENABLE(CONTEXT_MENUS)
     void didSelectItemFromActiveContextMenu(const WebContextMenuItemData&);
 #endif

Modified: trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in (245061 => 245062)


--- trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebKit/WebProcess/WebPage/WebPage.messages.in	2019-05-08 19:36:33 UTC (rev 245062)
@@ -202,6 +202,7 @@
     RunJavaScriptInFrame(uint64_t frameID, String script, bool forceUserGesture, WebKit::CallbackID callbackID)
     ForceRepaint(WebKit::CallbackID callbackID)
 
+    SelectAll()
     ScheduleFullEditorStateUpdate()
 
 #if PLATFORM(COCOA)

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


--- trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-05-08 18:18:42 UTC (rev 245061)
+++ trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-05-08 19:36:33 UTC (rev 245062)
@@ -1479,12 +1479,61 @@
     return (base < extent) ? Range::create(*frame->document(), base, extent) : Range::create(*frame->document(), extent, base);
 }
 
-void WebPage::clearSelection(){
+static IntRect elementRectInRootViewCoordinates(const Element& element)
+{
+    auto* frame = element.document().frame();
+    if (!frame)
+        return { };
+
+    auto* view = frame->view();
+    if (!view)
+        return { };
+
+    auto* renderer = element.renderer();
+    if (!renderer)
+        return { };
+
+    return view->contentsToRootView(renderer->absoluteBoundingBoxRect());
+}
+
+void WebPage::clearSelection()
+{
     m_startingGestureRange = nullptr;
     m_currentBlockSelection = nullptr;
     m_page->focusController().focusedOrMainFrame().selection().clear();
 }
 
+void WebPage::dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch touch, const IntPoint& point)
+{
+    auto frame = makeRef(m_page->focusController().focusedOrMainFrame());
+    if (!frame->selection().selection().isContentEditable())
+        return;
+
+    IntRect focusedElementRect;
+    if (m_focusedElement)
+        focusedElementRect = elementRectInRootViewCoordinates(*m_focusedElement);
+
+    if (focusedElementRect.isEmpty())
+        return;
+
+    auto adjustedPoint = point.constrainedBetween(focusedElementRect.minXMinYCorner(), focusedElementRect.maxXMaxYCorner());
+    auto& eventHandler = m_page->mainFrame().eventHandler();
+    switch (touch) {
+    case SelectionTouch::Started:
+        eventHandler.handleMousePressEvent({ adjustedPoint, adjustedPoint, LeftButton, PlatformEvent::MousePressed, 1, false, false, false, false, WallTime::now(), WebCore::ForceAtClick, NoTap });
+        break;
+    case SelectionTouch::Moved:
+        eventHandler.dispatchSyntheticMouseMove({ adjustedPoint, adjustedPoint, LeftButton, PlatformEvent::MouseMoved, 0, false, false, false, false, WallTime::now(), WebCore::ForceAtClick, NoTap });
+        break;
+    case SelectionTouch::Ended:
+    case SelectionTouch::EndedMovingForward:
+    case SelectionTouch::EndedMovingBackward:
+    case SelectionTouch::EndedNotMoving:
+        eventHandler.handleMouseReleaseEvent({ adjustedPoint, adjustedPoint, LeftButton, PlatformEvent::MouseReleased, 1, false, false, false, false, WallTime::now(), WebCore::ForceAtClick, NoTap });
+        break;
+    }
+}
+
 void WebPage::updateSelectionWithTouches(const IntPoint& point, uint32_t touches, bool baseIsStart, CallbackID callbackID)
 {
     Frame& frame = m_page->focusController().focusedOrMainFrame();
@@ -1499,7 +1548,11 @@
     VisiblePosition result;
     SelectionFlags flags = None;
 
-    switch (static_cast<SelectionTouch>(touches)) {
+    auto selectionTouch = static_cast<SelectionTouch>(touches);
+    if (shouldDispatchSyntheticMouseEventsWhenModifyingSelection())
+        dispatchSyntheticMouseEventsForSelectionGesture(selectionTouch, point);
+
+    switch (selectionTouch) {
     case SelectionTouch::Started:
     case SelectionTouch::EndedNotMoving:
         break;
@@ -1557,9 +1610,38 @@
         return;
 
     VisiblePosition position = frame.selection().selection().start();
-    frame.selection().setSelectedRange(wordRangeFromPosition(position).get(), position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered);
+    auto wordRange = wordRangeFromPosition(position);
+    if (!wordRange)
+        return;
+
+    IntPoint endLocationForSyntheticMouseEvents;
+    bool shouldDispatchMouseEvents = shouldDispatchSyntheticMouseEventsWhenModifyingSelection();
+    if (shouldDispatchMouseEvents) {
+        auto startLocationForSyntheticMouseEvents = frame.view()->contentsToRootView(VisiblePosition(wordRange->startPosition()).absoluteCaretBounds()).center();
+        endLocationForSyntheticMouseEvents = frame.view()->contentsToRootView(VisiblePosition(wordRange->endPosition()).absoluteCaretBounds()).center();
+        dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Started, startLocationForSyntheticMouseEvents);
+        dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Moved, endLocationForSyntheticMouseEvents);
+    }
+
+    frame.selection().setSelectedRange(wordRange.get(), position.affinity(), WebCore::FrameSelection::ShouldCloseTyping::Yes, UserTriggered);
+
+    if (shouldDispatchMouseEvents)
+        dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Ended, endLocationForSyntheticMouseEvents);
 }
 
+void WebPage::platformDidSelectAll()
+{
+    if (!shouldDispatchSyntheticMouseEventsWhenModifyingSelection())
+        return;
+
+    auto frame = makeRef(m_page->focusController().focusedOrMainFrame());
+    auto startCaretRect = frame->view()->contentsToRootView(VisiblePosition(frame->selection().selection().start()).absoluteCaretBounds());
+    auto endCaretRect = frame->view()->contentsToRootView(VisiblePosition(frame->selection().selection().end()).absoluteCaretBounds());
+    dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Started, startCaretRect.center());
+    dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Moved, endCaretRect.center());
+    dispatchSyntheticMouseEventsForSelectionGesture(SelectionTouch::Ended, endCaretRect.center());
+}
+
 void WebPage::selectWordBackward()
 {
     Frame& frame = m_page->focusController().focusedOrMainFrame();
@@ -2584,19 +2666,6 @@
     send(Messages::WebPageProxy::VoidCallback(callbackID));
 }
 
-static IntRect elementRectInRootViewCoordinates(const Node& node, const Frame& frame)
-{
-    auto* view = frame.view();
-    if (!view)
-        return { };
-
-    auto* renderer = node.renderer();
-    if (!renderer)
-        return { };
-
-    return view->contentsToRootView(renderer->absoluteBoundingBoxRect());
-}
-
 void WebPage::getFocusedElementInformation(FocusedElementInformation& information)
 {
     layoutIfNeeded();
@@ -2604,8 +2673,7 @@
     information.lastInteractionLocation = m_lastInteractionLocation;
 
     if (auto* renderer = m_focusedElement->renderer()) {
-        auto& elementFrame = m_page->focusController().focusedOrMainFrame();
-        information.elementRect = elementRectInRootViewCoordinates(*m_focusedElement, elementFrame);
+        information.elementRect = elementRectInRootViewCoordinates(*m_focusedElement);
         information.nodeFontSize = renderer->style().fontDescription().computedSize();
 
         bool inFixed = false;
@@ -2621,13 +2689,11 @@
     information.allowsUserScaling = m_viewportConfiguration.allowsUserScaling();
     information.allowsUserScalingIgnoringAlwaysScalable = m_viewportConfiguration.allowsUserScalingIgnoringAlwaysScalable();
     if (auto* nextElement = nextAssistableElement(m_focusedElement.get(), *m_page, true)) {
-        if (auto* frame = nextElement->document().frame())
-            information.nextNodeRect = elementRectInRootViewCoordinates(*nextElement, *frame);
+        information.nextNodeRect = elementRectInRootViewCoordinates(*nextElement);
         information.hasNextNode = true;
     }
     if (auto* previousElement = nextAssistableElement(m_focusedElement.get(), *m_page, false)) {
-        if (auto* frame = previousElement->document().frame())
-            information.previousNodeRect = elementRectInRootViewCoordinates(*previousElement, *frame);
+        information.previousNodeRect = elementRectInRootViewCoordinates(*previousElement);
         information.hasPreviousNode = true;
     }
     information.focusedElementIdentifier = m_currentFocusedElementIdentifier;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to