[WK2] Add support for keeping the selection in a focused editable element when draggi...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 4 May 2017 22:28:22 +0000 (22:28 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 4 May 2017 22:28:22 +0000 (22:28 +0000)
https://bugs.webkit.org/show_bug.cgi?id=171585
<rdar://problem/31544320>

Reviewed by Beth Dakin and Zalan Bujtas.

Source/WebCore:

Covered by 4 API tests.

* dom/DocumentMarker.h:

Introduces the DraggedContent DocumentMarker type, which applies to the Range in the DOM that is being used as
a drag source. Also adds DraggedContentData, which contains nodes found by the TextIterator in the process of
finding Ranges to mark.

(WebCore::DocumentMarker::AllMarkers::AllMarkers):
* dom/DocumentMarkerController.cpp:
(WebCore::DocumentMarkerController::addDraggedContentMarker):
(WebCore::shouldInsertAsSeparateMarker):
(WebCore::DocumentMarkerController::addMarker):

When adding DocumentMarkers of type DraggedContent, keep adjacent RenderReplaced elements separate, rather than
merging them into existing RenderedDocumentMarkers. This is because the data for each of these (i.e. the target
node) needs to be preserved.

(WebCore::DocumentMarkerController::markersFor):

Bail and return an empty list if the map of document markers cannot possibly contain a dragged content marker.

* dom/DocumentMarkerController.h:
* page/DragController.h:
* page/DragState.h:

Add draggedContentRange to DragState. This tracks the Range that is being dragged; it is created when the drag
session has begun, and ends when drag session finishes (either via WebPage::dragEnded or WebPage::dragCancelled).

* page/EventHandler.cpp:
(WebCore::repaintContentsOfRange):
(WebCore::EventHandler::dragCancelled):

Called when a drag is cancelled in the UI process without a session ever getting a chance to begin. We use this
as a hook to remove all DraggedContent document markers from the document of the dragged content range.

(WebCore::EventHandler::didStartDrag):

Called when a drag session has begun in the UI process. We use this as a hook to set up document markers for the
Range of content being dragged.

(WebCore::EventHandler::dragSourceEndedAt):

Called when a drag session ends. We use this as a hook to remove all DraggedContent document markers from the
document of the dragged content range.

(WebCore::EventHandler::draggedElement):
* page/EventHandler.h:
* page/FocusController.cpp:
(WebCore::shouldClearSelectionWhenChangingFocusedElement):

Prevent the selection from clearing when the previously focused element is editable and also contains the drag
source element. Ideally, we should experiment with clearing out the selection whenever the element is blurred
(and not have additional restrictions on editability and containing the drag source), but this change is much
riskier.

(WebCore::FocusController::setFocusedElement):
* rendering/InlineTextBox.cpp:
(WebCore::InlineTextBox::paint):

Use RenderText::draggedContentStartEnd to find the range of text (if any) that is dragged content, and paint
these ranges of text at a lower alpha using TextPainter::paintTextInRange.

* rendering/RenderReplaced.cpp:
(WebCore::draggedContentContainsReplacedElement):

Determines whether or not the element being rendered is contained within a dragged content range. Assuming that
the DraggedContent type flag is set in DocumentMarkerController, we first look to see whether or not the
container node is in the document marker map. If so, instead of consulting node offset ranges (since this is, in
the worst-case, linear in the number of sibling nodes per RenderReplaced) we simply check the DraggedContentData
to see if the current element being rendered matches one of the target nodes.

(WebCore::RenderReplaced::paint):

If the element rendered by this RenderReplaced is dragged content, then render it at a low alpha.

* rendering/RenderText.cpp:
(WebCore::RenderText::draggedContentRangesBetweenOffsets):

Determines what range of text, if any, contains dragged content by consulting the Document's DocumentMarkers.

* rendering/RenderText.h:
* rendering/TextPainter.cpp:
(WebCore::TextPainter::paintTextInRange):

Teach TextPainter to only paint a given range in a TextRun.

* rendering/TextPainter.h:

Add TextPainter support for specifying special text offset ranges when rendering a TextRun, such that each
special range in text is rendered after applying some modification to the GraphicsContext.

Source/WebKit2:

Minor adjustments and refactoring in WebKit2. See WebCore ChangeLog for more details.

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::startDrag):
(WebKit::WebPageProxy::didStartDrag):

Factor out code in WebPageProxy that sends a WebPage::DidStartDrag message to the web process into a separate
helper, and tweak the places where we directly send this IPC message to the web process to instead call this
helper.

* UIProcess/WebPageProxy.h:
* UIProcess/mac/WebPageProxyMac.mm:
(WebKit::WebPageProxy::setDragImage):
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::didStartDrag):
(WebKit::WebPage::dragCancelled):

Clear out state in the web process and call out to the EventHandler to handle drag cancellation and the drag
start response from the UI process.

* WebProcess/WebPage/WebPage.h:
(WebKit::WebPage::didStartDrag): Deleted.
(WebKit::WebPage::dragCancelled): Deleted.

Tools:

Adds 1 new unit test and tweaks existing tests to check that when first responder status is lost after beginning
a drag while editing, content is still moved (and not copied) when performing data interaction on a different
element. ContentEditableMoveParagraphs checks that content can be shifted within a single element via a move
operation rather than a copy.

See WebCore ChangeLog for more details.

Tests:  DataInteractionSimulator.ContentEditableToContentEditable
        DataInteractionSimulator.ContentEditableToTextarea
        DataInteractionSimulator.ContentEditableMoveParagraphs
        DataInteractionSimulator.TextAreaToInput

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKit2Cocoa/two-paragraph-contenteditable.html: Added.
* TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
(TestWebKitAPI::TEST):
* TestWebKitAPI/ios/DataInteractionSimulator.h:
* TestWebKitAPI/ios/DataInteractionSimulator.mm:
(-[DataInteractionSimulator initWithWebView:]):
(-[DataInteractionSimulator dealloc]):
(-[DataInteractionSimulator _advanceProgress]):
(-[DataInteractionSimulator waitForInputSession]):
(-[DataInteractionSimulator _webView:focusShouldStartInputSession:]):
(-[DataInteractionSimulator _webView:didStartInputSession:]):

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

27 files changed:
Source/WebCore/ChangeLog
Source/WebCore/dom/DocumentMarker.h
Source/WebCore/dom/DocumentMarkerController.cpp
Source/WebCore/dom/DocumentMarkerController.h
Source/WebCore/page/DragController.h
Source/WebCore/page/DragState.h
Source/WebCore/page/EventHandler.cpp
Source/WebCore/page/EventHandler.h
Source/WebCore/page/FocusController.cpp
Source/WebCore/rendering/InlineTextBox.cpp
Source/WebCore/rendering/RenderReplaced.cpp
Source/WebCore/rendering/RenderText.cpp
Source/WebCore/rendering/RenderText.h
Source/WebCore/rendering/TextPainter.cpp
Source/WebCore/rendering/TextPainter.h
Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/WebPageProxy.cpp
Source/WebKit2/UIProcess/WebPageProxy.h
Source/WebKit2/UIProcess/mac/WebPageProxyMac.mm
Source/WebKit2/WebProcess/WebPage/WebPage.cpp
Source/WebKit2/WebProcess/WebPage/WebPage.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKit2Cocoa/two-paragraph-contenteditable.html [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm
Tools/TestWebKitAPI/ios/DataInteractionSimulator.h
Tools/TestWebKitAPI/ios/DataInteractionSimulator.mm

index c41c413092c039d2dc005b7232f026ac9f13b41c..463845c2e07c88f4211894739d206ec0cc961f4c 100644 (file)
@@ -1,3 +1,103 @@
+2017-05-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] Add support for keeping the selection in a focused editable element when dragging begins
+        https://bugs.webkit.org/show_bug.cgi?id=171585
+        <rdar://problem/31544320>
+
+        Reviewed by Beth Dakin and Zalan Bujtas.
+
+        Covered by 4 API tests.
+
+        * dom/DocumentMarker.h:
+
+        Introduces the DraggedContent DocumentMarker type, which applies to the Range in the DOM that is being used as
+        a drag source. Also adds DraggedContentData, which contains nodes found by the TextIterator in the process of
+        finding Ranges to mark.
+
+        (WebCore::DocumentMarker::AllMarkers::AllMarkers):
+        * dom/DocumentMarkerController.cpp:
+        (WebCore::DocumentMarkerController::addDraggedContentMarker):
+        (WebCore::shouldInsertAsSeparateMarker):
+        (WebCore::DocumentMarkerController::addMarker):
+
+        When adding DocumentMarkers of type DraggedContent, keep adjacent RenderReplaced elements separate, rather than
+        merging them into existing RenderedDocumentMarkers. This is because the data for each of these (i.e. the target
+        node) needs to be preserved.
+
+        (WebCore::DocumentMarkerController::markersFor):
+
+        Bail and return an empty list if the map of document markers cannot possibly contain a dragged content marker.
+
+        * dom/DocumentMarkerController.h:
+        * page/DragController.h:
+        * page/DragState.h:
+
+        Add draggedContentRange to DragState. This tracks the Range that is being dragged; it is created when the drag
+        session has begun, and ends when drag session finishes (either via WebPage::dragEnded or WebPage::dragCancelled).
+
+        * page/EventHandler.cpp:
+        (WebCore::repaintContentsOfRange):
+        (WebCore::EventHandler::dragCancelled):
+
+        Called when a drag is cancelled in the UI process without a session ever getting a chance to begin. We use this
+        as a hook to remove all DraggedContent document markers from the document of the dragged content range.
+
+        (WebCore::EventHandler::didStartDrag):
+
+        Called when a drag session has begun in the UI process. We use this as a hook to set up document markers for the
+        Range of content being dragged.
+
+        (WebCore::EventHandler::dragSourceEndedAt):
+
+        Called when a drag session ends. We use this as a hook to remove all DraggedContent document markers from the
+        document of the dragged content range.
+
+        (WebCore::EventHandler::draggedElement):
+        * page/EventHandler.h:
+        * page/FocusController.cpp:
+        (WebCore::shouldClearSelectionWhenChangingFocusedElement):
+
+        Prevent the selection from clearing when the previously focused element is editable and also contains the drag
+        source element. Ideally, we should experiment with clearing out the selection whenever the element is blurred
+        (and not have additional restrictions on editability and containing the drag source), but this change is much
+        riskier.
+
+        (WebCore::FocusController::setFocusedElement):
+        * rendering/InlineTextBox.cpp:
+        (WebCore::InlineTextBox::paint):
+
+        Use RenderText::draggedContentStartEnd to find the range of text (if any) that is dragged content, and paint
+        these ranges of text at a lower alpha using TextPainter::paintTextInRange.
+
+        * rendering/RenderReplaced.cpp:
+        (WebCore::draggedContentContainsReplacedElement):
+
+        Determines whether or not the element being rendered is contained within a dragged content range. Assuming that
+        the DraggedContent type flag is set in DocumentMarkerController, we first look to see whether or not the
+        container node is in the document marker map. If so, instead of consulting node offset ranges (since this is, in
+        the worst-case, linear in the number of sibling nodes per RenderReplaced) we simply check the DraggedContentData
+        to see if the current element being rendered matches one of the target nodes.
+
+        (WebCore::RenderReplaced::paint):
+
+        If the element rendered by this RenderReplaced is dragged content, then render it at a low alpha.
+
+        * rendering/RenderText.cpp:
+        (WebCore::RenderText::draggedContentRangesBetweenOffsets):
+
+        Determines what range of text, if any, contains dragged content by consulting the Document's DocumentMarkers.
+
+        * rendering/RenderText.h:
+        * rendering/TextPainter.cpp:
+        (WebCore::TextPainter::paintTextInRange):
+
+        Teach TextPainter to only paint a given range in a TextRun.
+
+        * rendering/TextPainter.h:
+
+        Add TextPainter support for specifying special text offset ranges when rendering a TextRun, such that each
+        special range in text is rendered after applying some modification to the GraphicsContext.
+
 2017-05-04  Jeremy Jones  <jeremyj@apple.com>
 
         Crash when pointer lock element is removed before pointer lock allowed arrives.
index 904bf8c3f70d24b8bd76f97963b917f8721e53d6..01ea049f9ecb601c7a165133e5cb5a231eb5b9f6 100644 (file)
@@ -20,6 +20,8 @@
 
 #pragma once
 
+#include "Node.h"
+
 #include <wtf/Forward.h>
 #include <wtf/Variant.h>
 #include <wtf/text/WTFString.h>
@@ -75,6 +77,8 @@ public:
 #endif
         // This marker indicates that the user has selected a text candidate.
         AcceptedCandidate = 1 << 13,
+        // This marker indicates that the user has initiated a drag with this content.
+        DraggedContent = 1 << 14
     };
 
     class MarkerTypes {
@@ -115,6 +119,7 @@ public:
                 | DictationPhraseWithAlternatives
                 | DictationResult
 #endif
+                | DraggedContent
             )
         {
         }
@@ -132,7 +137,10 @@ public:
         RetainPtr<id> metadata;
 #endif
     };
-    using Data = Variant<IsActiveMatchData, DescriptionData, DictationData, DictationAlternativesData>;
+    struct DraggedContentData {
+        RefPtr<Node> targetNode;
+    };
+    using Data = Variant<IsActiveMatchData, DescriptionData, DictationData, DictationAlternativesData, DraggedContentData>;
 
     DocumentMarker(unsigned startOffset, unsigned endOffset, bool isActiveMatch);
     DocumentMarker(MarkerType, unsigned startOffset, unsigned endOffset, const String& description = String());
index e9d4aa3a5d8618b6f350655d5721e8a4898c1e0b..165a1d235a8be719043f08104e09263e7a2372a1 100644 (file)
@@ -135,6 +135,15 @@ void DocumentMarkerController::addDictationResultMarker(Range* range, const Reta
 
 #endif
 
+void DocumentMarkerController::addDraggedContentMarker(RefPtr<Range> range)
+{
+    for (TextIterator markedText(range.get()); !markedText.atEnd(); markedText.advance()) {
+        RefPtr<Range> textPiece = markedText.range();
+        DocumentMarker::DraggedContentData draggedContentData { markedText.node() };
+        addMarker(&textPiece->startContainer(), { DocumentMarker::DraggedContent, textPiece->startOffset(), textPiece->endOffset(), WTFMove(draggedContentData) });
+    }
+}
+
 void DocumentMarkerController::removeMarkers(Range* range, DocumentMarker::MarkerTypes markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
 {
     for (TextIterator markedText(range); !markedText.atEnd(); markedText.advance()) {
@@ -293,6 +302,20 @@ Vector<FloatRect> DocumentMarkerController::renderedRectsForMarkers(DocumentMark
     return result;
 }
 
+static bool shouldInsertAsSeparateMarker(const DocumentMarker& newMarker)
+{
+#if PLATFORM(IOS)
+    if (newMarker.type() == DocumentMarker::DictationPhraseWithAlternatives || newMarker.type() == DocumentMarker::DictationResult)
+        return true;
+#endif
+    if (newMarker.type() == DocumentMarker::DraggedContent) {
+        if (auto targetNode = WTF::get<DocumentMarker::DraggedContentData>(newMarker.data()).targetNode)
+            return targetNode->renderer() && targetNode->renderer()->isRenderReplaced();
+    }
+
+    return false;
+}
+
 // Markers are stored in order sorted by their start offset.
 // Markers of the same type do not overlap each other.
 
@@ -317,8 +340,7 @@ void DocumentMarkerController::addMarker(Node* node, const DocumentMarker& newMa
     if (!list) {
         list = std::make_unique<MarkerList>();
         list->append(RenderedDocumentMarker(newMarker));
-#if PLATFORM(IOS)
-    } else if (newMarker.type() == DocumentMarker::DictationPhraseWithAlternatives || newMarker.type() == DocumentMarker::DictationResult) {
+    } else if (shouldInsertAsSeparateMarker(newMarker)) {
         // We don't merge dictation markers.
         size_t i;
         size_t numberOfMarkers = list->size();
@@ -328,7 +350,6 @@ void DocumentMarkerController::addMarker(Node* node, const DocumentMarker& newMa
                 break;
         }
         list->insert(i, RenderedDocumentMarker(newMarker));
-#endif
     } else {
         RenderedDocumentMarker toInsert(newMarker);
         size_t numMarkers = list->size();
@@ -504,6 +525,9 @@ DocumentMarker* DocumentMarkerController::markerContainingPoint(const LayoutPoin
 
 Vector<RenderedDocumentMarker*> DocumentMarkerController::markersFor(Node* node, DocumentMarker::MarkerTypes markerTypes)
 {
+    if (!possiblyHasMarkers(markerTypes))
+        return { };
+
     Vector<RenderedDocumentMarker*> result;
     MarkerList* list = m_markers.get(node);
     if (!list)
index 0f5c23646417a24933f3aaa02b3c42bb98c1aa6c..a9775782a83ec252b5bf629151f63a0388461070 100644 (file)
@@ -59,6 +59,7 @@ public:
     void addDictationPhraseWithAlternativesMarker(Range*, const Vector<String>& interpretations);
     void addDictationResultMarker(Range*, const RetainPtr<id>& metadata);
 #endif
+    void addDraggedContentMarker(RefPtr<Range>);
 
     void copyMarkers(Node* srcNode, unsigned startOffset, int length, Node* dstNode, int delta);
     bool hasMarkers() const
index 8d94020ea26cf557840b58c2f28ea0e334a5859c..0779c0c6aa34a979739aba79837081edcbb8bff0 100644 (file)
@@ -62,6 +62,7 @@ struct DragState;
         WEBCORE_EXPORT void dragExited(const DragData&);
         WEBCORE_EXPORT DragOperation dragUpdated(const DragData&);
         WEBCORE_EXPORT bool performDragOperation(const DragData&);
+        WEBCORE_EXPORT void dragCancelled();
 
         bool mouseIsOverFileInput() const { return m_fileInputElementUnderMouse; }
         unsigned numberOfItemsToBeAccepted() const { return m_numberOfItemsToBeAccepted; }
index 6d1625900e345f33efa6508a05e5336214ac8bbd..6258abd5ccd544f0f7952c445013a58f8f21c157 100644 (file)
@@ -34,6 +34,7 @@ namespace WebCore {
 
 struct DragState {
     RefPtr<Element> source; // Element that may be a drag source, for the current mouse gesture.
+    RefPtr<Range> draggedContentRange;
     bool shouldDispatchEvents;
     DragSourceAction type;
     RefPtr<DataTransfer> dataTransfer; // Used on only the source side of dragging.
index 7952cabf825ee213dbfcf934cf7e1765e8951088..b89c365ff0d094cca4075aa8ba8f9d674965d8b8 100644 (file)
@@ -34,6 +34,7 @@
 #include "Chrome.h"
 #include "ChromeClient.h"
 #include "CursorList.h"
+#include "DocumentMarkerController.h"
 #include "DragController.h"
 #include "DragState.h"
 #include "Editing.h"
@@ -3451,6 +3452,60 @@ void EventHandler::invalidateDataTransfer()
     dragState().dataTransfer = nullptr;
 }
 
+static void repaintContentsOfRange(RefPtr<Range> range)
+{
+    if (!range)
+        return;
+
+    auto* container = range->commonAncestorContainer();
+    if (!container)
+        return;
+
+    // This ensures that all nodes enclosed in this Range are repainted.
+    if (auto rendererToRepaint = container->renderer()) {
+        if (auto* containingRenderer = rendererToRepaint->container())
+            rendererToRepaint = containingRenderer;
+        rendererToRepaint->repaint();
+    }
+}
+
+void EventHandler::dragCancelled()
+{
+#if ENABLE(DATA_INTERACTION)
+    if (auto range = dragState().draggedContentRange) {
+        range->ownerDocument().markers().removeMarkers(DocumentMarker::DraggedContent);
+        repaintContentsOfRange(range);
+    }
+    dragState().draggedContentRange = nullptr;
+#endif
+}
+
+void EventHandler::didStartDrag()
+{
+#if ENABLE(DATA_INTERACTION)
+    auto dragSource = dragState().source;
+    if (!dragSource)
+        return;
+
+    auto* renderer = dragSource->renderer();
+    if (!renderer)
+        return;
+
+    if (dragState().type & DragSourceActionSelection)
+        dragState().draggedContentRange = m_frame.selection().selection().toNormalizedRange();
+    else {
+        Position startPosition(dragSource.get(), Position::PositionIsBeforeAnchor);
+        Position endPosition(dragSource.get(), Position::PositionIsAfterAnchor);
+        dragState().draggedContentRange = Range::create(dragSource->document(), startPosition, endPosition);
+    }
+
+    if (auto range = dragState().draggedContentRange) {
+        range->ownerDocument().markers().addDraggedContentMarker(range.get());
+        repaintContentsOfRange(range);
+    }
+#endif
+}
+
 void EventHandler::dragSourceEndedAt(const PlatformMouseEvent& event, DragOperation operation)
 {
     // Send a hit test request so that RenderLayer gets a chance to update the :hover and :active pseudoclasses.
@@ -3463,6 +3518,12 @@ void EventHandler::dragSourceEndedAt(const PlatformMouseEvent& event, DragOperat
         dispatchDragSrcEvent(eventNames().dragendEvent, event);
     }
     invalidateDataTransfer();
+
+    if (auto range = dragState().draggedContentRange) {
+        range->ownerDocument().markers().removeMarkers(DocumentMarker::DraggedContent);
+        repaintContentsOfRange(range);
+    }
+
     dragState().source = nullptr;
     // In case the drag was ended due to an escape key press we need to ensure
     // that consecutive mousemove events don't reinitiate the drag and drop.
@@ -3487,6 +3548,11 @@ static bool ExactlyOneBitSet(DragSourceAction n)
     return n && !(n & (n - 1));
 }
 
+RefPtr<Element> EventHandler::draggedElement() const
+{
+    return dragState().source;
+}
+
 bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDragHysteresis checkDragHysteresis)
 {
     if (event.event().button() != LeftButton || event.event().type() != PlatformEvent::MouseMoved) {
index f337a6dd24c6b769b210858fd333517cb29c9bca..2251f9aed540bde4a7930b07c2cd13df62c24606 100644 (file)
@@ -159,6 +159,7 @@ public:
     void cancelDragAndDrop(const PlatformMouseEvent&, DataTransfer&);
     bool performDragAndDrop(const PlatformMouseEvent&, DataTransfer&);
     void updateDragStateAfterEditDragIfNeeded(Element& rootEditableElement);
+    RefPtr<Element> draggedElement() const;
 #endif
 
     void scheduleHoverStateUpdate();
@@ -253,6 +254,8 @@ public:
 #if ENABLE(DRAG_SUPPORT)
     WEBCORE_EXPORT bool eventMayStartDrag(const PlatformMouseEvent&) const;
     
+    WEBCORE_EXPORT void didStartDrag();
+    WEBCORE_EXPORT void dragCancelled();
     WEBCORE_EXPORT void dragSourceEndedAt(const PlatformMouseEvent&, DragOperation);
 #endif
 
index 8f0a277b3356bd1720147d3731660f267bc5dc20..ee45caf942bb62f18a9afab9cc2c6f4915642727 100644 (file)
@@ -765,6 +765,29 @@ static void clearSelectionIfNeeded(Frame* oldFocusedFrame, Frame* newFocusedFram
     oldFocusedFrame->selection().clear();
 }
 
+static bool shouldClearSelectionWhenChangingFocusedElement(const Page& page, RefPtr<Element> oldFocusedElement, RefPtr<Element> newFocusedElement)
+{
+#if ENABLE(DATA_INTERACTION)
+    if (newFocusedElement || !oldFocusedElement)
+        return true;
+
+    // FIXME: These additional checks should not be necessary. We should consider generally keeping the selection whenever the
+    // focused element is blurred, with no new element taking focus.
+    if (!oldFocusedElement->isRootEditableElement() && !is<HTMLInputElement>(oldFocusedElement.get()) && !is<HTMLTextAreaElement>(oldFocusedElement.get()))
+        return true;
+
+    for (auto ancestor = page.mainFrame().eventHandler().draggedElement(); ancestor; ancestor = ancestor->parentOrShadowHostElement()) {
+        if (ancestor == oldFocusedElement)
+            return false;
+    }
+#else
+    UNUSED_PARAM(page);
+    UNUSED_PARAM(oldFocusedElement);
+    UNUSED_PARAM(newFocusedElement);
+#endif
+    return true;
+}
+
 bool FocusController::setFocusedElement(Element* element, Frame& newFocusedFrame, FocusDirection direction)
 {
     Ref<Frame> protectedNewFocusedFrame = newFocusedFrame;
@@ -781,7 +804,8 @@ bool FocusController::setFocusedElement(Element* element, Frame& newFocusedFrame
 
     m_page.editorClient().willSetInputMethodState();
 
-    clearSelectionIfNeeded(oldFocusedFrame.get(), &newFocusedFrame, element);
+    if (shouldClearSelectionWhenChangingFocusedElement(m_page, oldFocusedElement, element))
+        clearSelectionIfNeeded(oldFocusedFrame.get(), &newFocusedFrame, element);
 
     if (!element) {
         if (oldDocument)
index 1ecb7dfe3d58e22da0df785f66b3097e0e3c6e1a..a37702aa90c2e99776bdb2dd6b4f706f7555a2ac 100644 (file)
@@ -552,7 +552,30 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
     textPainter.addTextShadow(textShadow, selectionShadow);
     textPainter.addEmphasis(emphasisMark, emphasisMarkOffset, combinedText);
 
-    textPainter.paintText(textRun, length, boxRect, textOrigin, selectionStart, selectionEnd, paintSelectedTextOnly, paintSelectedTextSeparately, paintNonSelectedTextOnly);
+    auto draggedContentRanges = renderer().draggedContentRangesBetweenOffsets(m_start, m_start + m_len);
+    if (!draggedContentRanges.isEmpty() && !paintSelectedTextOnly && !paintNonSelectedTextOnly) {
+        // FIXME: Painting with text effects ranges currently only works if we're not also painting the selection.
+        // In the future, we may want to support this capability, but in the meantime, this isn't required by anything.
+        unsigned currentEnd = 0;
+        for (size_t index = 0; index < draggedContentRanges.size(); ++index) {
+            unsigned previousEnd = index ? std::min(draggedContentRanges[index - 1].second, length) : 0;
+            unsigned currentStart = draggedContentRanges[index].first - m_start;
+            currentEnd = std::min(draggedContentRanges[index].second - m_start, length);
+
+            if (previousEnd < currentStart)
+                textPainter.paintTextInRange(textRun, boxRect, textOrigin, previousEnd, currentStart);
+
+            if (currentStart < currentEnd) {
+                context.save();
+                context.setAlpha(0.25);
+                textPainter.paintTextInRange(textRun, boxRect, textOrigin, currentStart, currentEnd);
+                context.restore();
+            }
+        }
+        if (currentEnd < length)
+            textPainter.paintTextInRange(textRun, boxRect, textOrigin, currentEnd, length);
+    } else
+        textPainter.paintText(textRun, length, boxRect, textOrigin, selectionStart, selectionEnd, paintSelectedTextOnly, paintSelectedTextSeparately, paintNonSelectedTextOnly);
 
     // Paint decorations
     TextDecoration textDecorations = lineStyle.textDecorationsInEffect();
index 02ada7c64eb5c1c92837a9771025f3cf20f99340..339dbcb2983702141c65762dd20b21cd350d7652 100644 (file)
@@ -24,6 +24,7 @@
 #include "config.h"
 #include "RenderReplaced.h"
 
+#include "DocumentMarkerController.h"
 #include "FloatRoundedRect.h"
 #include "Frame.h"
 #include "GraphicsContext.h"
@@ -37,6 +38,7 @@
 #include "RenderNamedFlowFragment.h"
 #include "RenderTheme.h"
 #include "RenderView.h"
+#include "RenderedDocumentMarker.h"
 #include "VisiblePosition.h"
 #include <wtf/StackStats.h>
 
@@ -134,6 +136,20 @@ bool RenderReplaced::shouldDrawSelectionTint() const
     return selectionState() != SelectionNone && !document().printing();
 }
 
+inline static bool draggedContentContainsReplacedElement(const Vector<RenderedDocumentMarker*>& markers, const Element& element)
+{
+    if (markers.isEmpty())
+        return false;
+
+    for (auto* marker : markers) {
+        auto& draggedContentData = WTF::get<DocumentMarker::DraggedContentData>(marker->data());
+        if (draggedContentData.targetNode == &element)
+            return true;
+    }
+
+    return false;
+}
+
 void RenderReplaced::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
 {
     if (!shouldPaint(paintInfo, paintOffset))
@@ -142,6 +158,14 @@ void RenderReplaced::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
 #ifndef NDEBUG
     SetLayoutNeededForbiddenScope scope(this);
 #endif
+
+    GraphicsContextStateSaver savedGraphicsContext(paintInfo.context());
+    if (element() && element()->parentOrShadowHostElement()) {
+        auto* parentContainer = element()->parentOrShadowHostElement();
+        if (draggedContentContainsReplacedElement(document().markers().markersFor(parentContainer, DocumentMarker::DraggedContent), *element()))
+            paintInfo.context().setAlpha(0.25);
+    }
+
     LayoutPoint adjustedPaintOffset = paintOffset + location();
     
     if (hasVisibleBoxDecorations() && paintInfo.phase == PaintPhaseForeground)
index 72b1c0248b907fd66d6bb5e0c55613a61eb584c9..be82e72d2b04d794a0afdb8bee9ba1c3c0a535d9 100644 (file)
@@ -29,6 +29,8 @@
 #include "BreakLines.h"
 #include "BreakingContext.h"
 #include "CharacterProperties.h"
+#include "DocumentMarker.h"
+#include "DocumentMarkerController.h"
 #include "EllipsisBox.h"
 #include "FloatQuad.h"
 #include "Frame.h"
@@ -41,6 +43,7 @@
 #include "RenderInline.h"
 #include "RenderLayer.h"
 #include "RenderView.h"
+#include "RenderedDocumentMarker.h"
 #include "Settings.h"
 #include "SimpleLineLayoutFunctions.h"
 #include "Text.h"
@@ -1063,6 +1066,27 @@ bool RenderText::containsOnlyWhitespace(unsigned from, unsigned len) const
     return currPos >= (from + len);
 }
 
+Vector<std::pair<unsigned, unsigned>> RenderText::draggedContentRangesBetweenOffsets(unsigned startOffset, unsigned endOffset) const
+{
+    auto markers = document().markers().markersFor(textNode(), DocumentMarker::DraggedContent);
+    if (markers.isEmpty())
+        return { };
+
+    Vector<std::pair<unsigned, unsigned>> draggedContentRanges;
+    for (auto* marker : markers) {
+        unsigned markerStart = std::max(marker->startOffset(), startOffset);
+        unsigned markerEnd = std::min(marker->endOffset(), endOffset);
+        if (markerStart >= markerEnd || markerStart > endOffset || markerEnd < startOffset)
+            continue;
+
+        std::pair<unsigned, unsigned> draggedContentRange;
+        draggedContentRange.first = markerStart;
+        draggedContentRange.second = markerEnd;
+        draggedContentRanges.append(draggedContentRange);
+    }
+    return draggedContentRanges;
+}
+
 IntPoint RenderText::firstRunLocation() const
 {
     if (auto* layout = simpleLineLayout())
index 810ac35a901605678b29180815c2d485ee84cd7e..14110885b515a9a1fef3df13cb28cfbcdd4638c9 100644 (file)
@@ -176,6 +176,8 @@ public:
     
     bool canUseSimplifiedTextMeasuring() const { return m_canUseSimplifiedTextMeasuring; }
 
+    Vector<std::pair<unsigned, unsigned>> draggedContentRangesBetweenOffsets(unsigned startOffset, unsigned endOffset) const;
+
 protected:
     virtual void computePreferredLogicalWidths(float leadWidth);
     void willBeDestroyed() override;
index affffa4f6e968baad8135e60041a01033965e1ed..6e79a0811c98c8fd24983c075c894d7771f63e9c 100644 (file)
@@ -168,6 +168,16 @@ void TextPainter::paintTextAndEmphasisMarksIfNeeded(const TextRun& textRun, cons
     if (m_combinedText)
         m_context.concatCTM(rotation(boxRect, Counterclockwise));
 }
+
+void TextPainter::paintTextInRange(const TextRun& textRun, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned start, unsigned end)
+{
+    ASSERT(m_font);
+    ASSERT(start < end);
+
+    GraphicsContextStateSaver stateSaver(m_context, m_textPaintStyle.strokeWidth > 0);
+    updateGraphicsContext(m_context, m_textPaintStyle);
+    paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, start, end, m_textPaintStyle, m_textShadow);
+}
     
 void TextPainter::paintText(const TextRun& textRun, unsigned length, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned selectionStart, unsigned selectionEnd,
     bool paintSelectedTextOnly, bool paintSelectedTextSeparately, bool paintNonSelectedTextOnly)
index d9536c700e8302b4d3293589d69d35a1e716274e..abd8b476ec05e5ea22f66c5ddf72f608cad13487 100644 (file)
@@ -56,6 +56,7 @@ public:
     void addEmphasis(const AtomicString& emphasisMark, float emphasisMarkOffset, RenderCombineText*);
     void addTextShadow(const ShadowData* textShadow, const ShadowData* selectionShadow);
 
+    void paintTextInRange(const TextRun&, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned start, unsigned end);
     void paintText(const TextRun&, unsigned length, const FloatRect& boxRect, const FloatPoint& textOrigin,
         unsigned selectionStart = 0, unsigned selectionEnd = 0, bool paintSelectedTextOnly = false, bool paintSelectedTextSeparately = false, bool paintNonSelectedTextOnly = false);
 
index dbb30d5dd28f69c566410d49e4115264966d0318..ac9e7fe401ff782cfb487a627531347178cff849 100644 (file)
@@ -1,3 +1,35 @@
+2017-05-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] Add support for keeping the selection in a focused editable element when dragging begins
+        https://bugs.webkit.org/show_bug.cgi?id=171585
+        <rdar://problem/31544320>
+
+        Reviewed by Beth Dakin and Zalan Bujtas.
+
+        Minor adjustments and refactoring in WebKit2. See WebCore ChangeLog for more details.
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::startDrag):
+        (WebKit::WebPageProxy::didStartDrag):
+
+        Factor out code in WebPageProxy that sends a WebPage::DidStartDrag message to the web process into a separate
+        helper, and tweak the places where we directly send this IPC message to the web process to instead call this
+        helper.
+
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/mac/WebPageProxyMac.mm:
+        (WebKit::WebPageProxy::setDragImage):
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::didStartDrag):
+        (WebKit::WebPage::dragCancelled):
+
+        Clear out state in the web process and call out to the EventHandler to handle drag cancellation and the drag
+        start response from the UI process.
+
+        * WebProcess/WebPage/WebPage.h:
+        (WebKit::WebPage::didStartDrag): Deleted.
+        (WebKit::WebPage::dragCancelled): Deleted.
+
 2017-05-04  Sam Weinig  <sam@webkit.org>
 
         Remove support for legacy Notifications
index 08c066f9ae65394f6cbe5587c5e6f65ef8926c3f..77762f725764917971d05155cde8d50551935868 100644 (file)
@@ -1819,7 +1819,7 @@ void WebPageProxy::startDrag(WebSelectionData&& selection, uint64_t dragOperatio
     RefPtr<ShareableBitmap> dragImage = !dragImageHandle.isNull() ? ShareableBitmap::create(dragImageHandle) : nullptr;
     m_pageClient.startDrag(WTFMove(selection.selectionData), static_cast<WebCore::DragOperation>(dragOperation), WTFMove(dragImage));
 
-    m_process->send(Messages::WebPage::DidStartDrag(), m_pageID);
+    didStartDrag();
 }
 #endif
 
@@ -1830,6 +1830,12 @@ void WebPageProxy::dragEnded(const IntPoint& clientPosition, const IntPoint& glo
     m_process->send(Messages::WebPage::DragEnded(clientPosition, globalPosition, operation), m_pageID);
     setDragCaretRect({ });
 }
+
+void WebPageProxy::didStartDrag()
+{
+    if (isValid())
+        m_process->send(Messages::WebPage::DidStartDrag(), m_pageID);
+}
     
 void WebPageProxy::dragCancelled()
 {
index 18e33b68c27744cca2a88ea86f06f54adc2659f9..4c8ee583278dd250a6a2e88a9b686357dd7f63c5 100644 (file)
@@ -841,6 +841,7 @@ public:
 
     void didPerformDragControllerAction(uint64_t dragOperation, bool mouseIsOverFileInput, unsigned numberOfItemsToBeAccepted, const WebCore::IntRect& insertionRect, bool isHandlingNonDefaultDrag);
     void dragEnded(const WebCore::IntPoint& clientPosition, const WebCore::IntPoint& globalPosition, uint64_t operation);
+    void didStartDrag();
     void dragCancelled();
     void setDragCaretRect(const WebCore::IntRect&);
 #if PLATFORM(COCOA)
index 9f54f7af2bff415ec29f8063cccb485e2704bdbb..340b9f0035510c8629faf261f3417cbd2f4e2f0e 100644 (file)
@@ -274,7 +274,7 @@ void WebPageProxy::setDragImage(const WebCore::IntPoint& clientPosition, const S
     if (auto dragImage = ShareableBitmap::create(dragImageHandle))
         m_pageClient.setDragImage(clientPosition, WTFMove(dragImage), static_cast<DragSourceAction>(action));
 
-    process().send(Messages::WebPage::DidStartDrag(), m_pageID);
+    didStartDrag();
 }
 
 void WebPageProxy::setPromisedDataForImage(const String& pasteboardName, const SharedMemory::Handle& imageHandle, uint64_t imageSize, const String& filename, const String& extension,
index 63fb47507086db49b96e338c978a1094a6605480..bb95d704eb8186df4c2ba6ddb2dcfa809e2bbc51 100644 (file)
@@ -3655,6 +3655,18 @@ void WebPage::mayPerformUploadDragDestinationAction()
         m_pendingDropExtensionsForFileUpload[i]->consumePermanently();
     m_pendingDropExtensionsForFileUpload.clear();
 }
+
+void WebPage::didStartDrag()
+{
+    m_isStartingDrag = false;
+    m_page->mainFrame().eventHandler().didStartDrag();
+}
+
+void WebPage::dragCancelled()
+{
+    m_isStartingDrag = false;
+    m_page->mainFrame().eventHandler().dragCancelled();
+}
     
 #endif // ENABLE(DRAG_SUPPORT)
 
index b01116637bb806a6493d10d1cd11a9fa0e379e95..7b4f001b47d972a8d687cdaeedc696d0d4645b33 100644 (file)
@@ -739,8 +739,8 @@ public:
     void mayPerformUploadDragDestinationAction();
 
     void willStartDrag() { ASSERT(!m_isStartingDrag); m_isStartingDrag = true; }
-    void didStartDrag() { ASSERT(m_isStartingDrag); m_isStartingDrag = false; }
-    void dragCancelled() { m_isStartingDrag = false; }
+    void didStartDrag();
+    void dragCancelled();
 #endif // ENABLE(DRAG_SUPPORT)
 
     void beginPrinting(uint64_t frameID, const PrintInfo&);
index bd2961fd941c68ca43ec687c9fc3267f38f55f97..44c4d8a5d93d1a2bdf4a14c2ae252d23ec207543 100644 (file)
@@ -1,3 +1,36 @@
+2017-05-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [WK2] Add support for keeping the selection in a focused editable element when dragging begins
+        https://bugs.webkit.org/show_bug.cgi?id=171585
+        <rdar://problem/31544320>
+
+        Reviewed by Beth Dakin and Zalan Bujtas.
+
+        Adds 1 new unit test and tweaks existing tests to check that when first responder status is lost after beginning
+        a drag while editing, content is still moved (and not copied) when performing data interaction on a different
+        element. ContentEditableMoveParagraphs checks that content can be shifted within a single element via a move
+        operation rather than a copy.
+
+        See WebCore ChangeLog for more details.
+
+        Tests:  DataInteractionSimulator.ContentEditableToContentEditable
+                DataInteractionSimulator.ContentEditableToTextarea
+                DataInteractionSimulator.ContentEditableMoveParagraphs
+                DataInteractionSimulator.TextAreaToInput
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKit2Cocoa/two-paragraph-contenteditable.html: Added.
+        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/ios/DataInteractionSimulator.h:
+        * TestWebKitAPI/ios/DataInteractionSimulator.mm:
+        (-[DataInteractionSimulator initWithWebView:]):
+        (-[DataInteractionSimulator dealloc]):
+        (-[DataInteractionSimulator _advanceProgress]):
+        (-[DataInteractionSimulator waitForInputSession]):
+        (-[DataInteractionSimulator _webView:focusShouldStartInputSession:]):
+        (-[DataInteractionSimulator _webView:didStartInputSession:]):
+
 2017-05-04  Said Abou-Hallawa  <sabouhallawa@apple.com>
 
         Rename TestRunner.display() to TestRunner::displayAndTrackRepaints()
index 19864cdead5e628f1d4367bfd5d6b6f0c34ae608..40cb4d00eabe039d4b73ffc63ad72318008c39ca 100644 (file)
                ECA680CE1E68CC0900731D20 /* StringUtilities.mm in Sources */ = {isa = PBXBuildFile; fileRef = ECA680CD1E68CC0900731D20 /* StringUtilities.mm */; };
                F415086D1DA040C50044BE9B /* play-audio-on-click.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F415086C1DA040C10044BE9B /* play-audio-on-click.html */; };
                F42DA5161D8CEFE400336F40 /* large-input-field-focus-onload.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F42DA5151D8CEFDB00336F40 /* large-input-field-focus-onload.html */; };
+               F4451C761EB8FD890020C5DA /* two-paragraph-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */; };
                F4538EF71E8473E600B5C953 /* large-red-square.png in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4538EF01E846B4100B5C953 /* large-red-square.png */; };
                F47728991E4AE3C1007ABF6A /* full-page-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */; };
                F4856CA31E649EA8009D7EE7 /* attachment-element.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4856CA21E6498A8009D7EE7 /* attachment-element.html */; };
                                55226A2F1EBA44B900C36AD0 /* large-red-square-image.html in Copy Resources */,
                                5797FE331EB15AB100B2F4A0 /* navigation-client-default-crypto.html in Copy Resources */,
                                0799C34B1EBA3301003B7532 /* disableGetUserMedia.html in Copy Resources */,
+                               F4451C761EB8FD890020C5DA /* two-paragraph-contenteditable.html in Copy Resources */,
                                074994421EA5034B000DA44E /* getUserMedia.html in Copy Resources */,
                                C9BF06EF1E9C132500595E3E /* autoplay-muted-with-controls.html in Copy Resources */,
                                F4DEF6ED1E9B4DB60048EF61 /* image-in-link-and-input.html in Copy Resources */,
                F3FC3EE213678B7300126A65 /* libgtest.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgtest.a; sourceTree = BUILT_PRODUCTS_DIR; };
                F415086C1DA040C10044BE9B /* play-audio-on-click.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "play-audio-on-click.html"; sourceTree = "<group>"; };
                F42DA5151D8CEFDB00336F40 /* large-input-field-focus-onload.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = "large-input-field-focus-onload.html"; path = "Tests/WebKit2Cocoa/large-input-field-focus-onload.html"; sourceTree = SOURCE_ROOT; };
+               F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "two-paragraph-contenteditable.html"; sourceTree = "<group>"; };
                F4538EF01E846B4100B5C953 /* large-red-square.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "large-red-square.png"; sourceTree = "<group>"; };
                F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "full-page-contenteditable.html"; sourceTree = "<group>"; };
                F4856CA21E6498A8009D7EE7 /* attachment-element.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "attachment-element.html"; sourceTree = "<group>"; };
                                F4F405BB1D4C0CF8007A9707 /* skinny-autoplaying-video-with-audio.html */,
                                515BE16E1D4288FF00DD7C68 /* StoreBlobToBeDeleted.html */,
                                2E9896141D8F092B00739892 /* text-and-password-inputs.html */,
+                               F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */,
                                51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */,
                                51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */,
                                5120C83B1E674E350025B250 /* WebsiteDataStoreCustomPaths.html */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/two-paragraph-contenteditable.html b/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/two-paragraph-contenteditable.html
new file mode 100644 (file)
index 0000000..ea088d1
--- /dev/null
@@ -0,0 +1,19 @@
+<meta name="viewport" content="width=device-width">
+<style>
+body, html {
+    padding: 0;
+    margin: 0;
+    width: 100%;
+    height: 100%;
+    font-family: -apple-system;
+    font-size: 1em;
+}
+</style>
+<body id="editor" contenteditable>
+<p id="first">This is the first paragraph. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
+<p id="second">This is the second paragraph. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
+</body>
+<script>
+let text = first.childNodes[0];
+getSelection().setBaseAndExtent(text, 0, text, text.data.length);
+</script>
index eddc73c3456808120906242e3f735da5aced3cd4..1c7110ff72802df3bfdf6cd6d6977b9b4637e989 100644 (file)
@@ -162,9 +162,10 @@ TEST(DataInteractionTests, ImageInLinkWithoutHREFToInput)
 TEST(DataInteractionTests, ContentEditableToContentEditable)
 {
     RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
-    [webView synchronouslyLoadTestPageNamed:@"autofocus-contenteditable"];
-
     RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+
+    [webView loadTestPageNamed:@"autofocus-contenteditable"];
+    [dataInteractionSimulator waitForInputSession];
     [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(100, 300)];
 
     EXPECT_EQ([webView stringByEvaluatingJavaScript:@"source.textContent"].length, 0UL);
@@ -181,9 +182,10 @@ TEST(DataInteractionTests, ContentEditableToContentEditable)
 TEST(DataInteractionTests, ContentEditableToTextarea)
 {
     RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
-    [webView synchronouslyLoadTestPageNamed:@"contenteditable-and-textarea"];
-
     RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+
+    [webView loadTestPageNamed:@"contenteditable-and-textarea"];
+    [dataInteractionSimulator waitForInputSession];
     [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(100, 300)];
 
     EXPECT_EQ([webView stringByEvaluatingJavaScript:@"source.textContent"].length, 0UL);
@@ -197,12 +199,32 @@ TEST(DataInteractionTests, ContentEditableToTextarea)
     checkTypeIdentifierPrecedesOtherTypeIdentifier(dataInteractionSimulator.get(), (NSString *)kUTTypeRTFD, (NSString *)kUTTypeUTF8PlainText);
 }
 
-TEST(DataInteractionTests, TextAreaToInput)
+TEST(DataInteractionTests, ContentEditableMoveParagraphs)
 {
     RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
-    [webView synchronouslyLoadTestPageNamed:@"textarea-to-input"];
+    RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+
+    [webView loadTestPageNamed:@"two-paragraph-contenteditable"];
+    [dataInteractionSimulator waitForInputSession];
+    [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(250, 450)];
 
+    NSString *finalTextContent = [webView stringByEvaluatingJavaScript:@"editor.textContent"];
+    NSUInteger firstParagraphOffset = [finalTextContent rangeOfString:@"This is the first paragraph"].location;
+    NSUInteger secondParagraphOffset = [finalTextContent rangeOfString:@"This is the second paragraph"].location;
+
+    EXPECT_FALSE(firstParagraphOffset == NSNotFound);
+    EXPECT_FALSE(secondParagraphOffset == NSNotFound);
+    EXPECT_GT(firstParagraphOffset, secondParagraphOffset);
+    checkSelectionRectsWithLogging(@[ makeCGRectValue(190, 100, 130, 20), makeCGRectValue(0, 120, 320, 100), makeCGRectValue(0, 220, 252, 20) ], [dataInteractionSimulator finalSelectionRects]);
+}
+
+TEST(DataInteractionTests, TextAreaToInput)
+{
+    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
     RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+
+    [webView loadTestPageNamed:@"textarea-to-input"];
+    [dataInteractionSimulator waitForInputSession];
     [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(100, 300)];
 
     EXPECT_EQ([webView stringByEvaluatingJavaScript:@"source.value"].length, 0UL);
index 3436ec48ea5222c9ed52824a52a5e90e448d061a..c11490368d630f2841092f661d4a1c2aa882c59f 100644 (file)
@@ -29,6 +29,7 @@
 #import <UIKit/UIItemProvider.h>
 #import <UIKit/UIKit.h>
 #import <WebKit/WKUIDelegatePrivate.h>
+#import <WebKit/_WKInputDelegate.h>
 #import <wtf/BlockPtr.h>
 
 @class MockDataOperationSession;
@@ -48,7 +49,8 @@ typedef NS_ENUM(NSInteger, DataInteractionPhase) {
     DataInteractionPerforming = 4
 };
 
-@interface DataInteractionSimulator : NSObject<WKUIDelegatePrivate> {
+@interface DataInteractionSimulator : NSObject<WKUIDelegatePrivate, _WKInputDelegate> {
+@private
     RetainPtr<TestWKWebView> _webView;
     RetainPtr<MockDataInteractionSession> _dataInteractionSession;
     RetainPtr<MockDataOperationSession> _dataOperationSession;
@@ -59,6 +61,7 @@ typedef NS_ENUM(NSInteger, DataInteractionPhase) {
     CGPoint _startLocation;
     CGPoint _endLocation;
 
+    bool _isDoneWaitingForInputSession;
     BOOL _shouldPerformOperation;
     double _currentProgress;
     bool _isDoneWithCurrentRun;
@@ -67,7 +70,9 @@ typedef NS_ENUM(NSInteger, DataInteractionPhase) {
 
 - (instancetype)initWithWebView:(TestWKWebView *)webView;
 - (void)runFrom:(CGPoint)startLocation to:(CGPoint)endLocation;
+- (void)waitForInputSession;
 
+@property (nonatomic) BOOL allowsFocusToStartInputSession;
 @property (nonatomic) BOOL shouldEnsureUIApplication;
 @property (nonatomic) BlockPtr<BOOL(_WKActivatedElementInfo *)> showCustomActionSheetBlock;
 @property (nonatomic) BlockPtr<NSArray *(NSArray *)> convertItemProvidersBlock;
index 16c6537c033e376d7d4e5bddc80d20963376bb9c..200d704f8cd30a27b36506a073eeb8726a7faa2f 100644 (file)
@@ -33,6 +33,8 @@
 #import <UIKit/UIItemProvider_Private.h>
 #import <WebCore/SoftLinking.h>
 #import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/_WKFocusedElementInfo.h>
+#import <WebKit/_WKFormInputSession.h>
 #import <wtf/RetainPtr.h>
 
 SOFT_LINK_FRAMEWORK(UIKit)
@@ -75,7 +77,9 @@ static NSArray *dataInteractionEventNames()
     if (self = [super init]) {
         _webView = webView;
         _shouldEnsureUIApplication = NO;
+        _isDoneWaitingForInputSession = true;
         [_webView setUIDelegate:self];
+        [_webView _setInputDelegate:self];
     }
     return self;
 }
@@ -85,6 +89,9 @@ static NSArray *dataInteractionEventNames()
     if ([_webView UIDelegate] == self)
         [_webView setUIDelegate:nil];
 
+    if ([_webView _inputDelegate] == self)
+        [_webView _setInputDelegate:nil];
+
     [super dealloc];
 }
 
@@ -217,6 +224,12 @@ static NSArray *dataInteractionEventNames()
         }
 
         [_webView _simulateWillBeginDataInteractionWithSession:_dataInteractionSession.get()];
+
+        RetainPtr<WKWebView> retainedWebView = _webView;
+        dispatch_async(dispatch_get_main_queue(), ^() {
+            [retainedWebView resignFirstResponder];
+        });
+
         _phase = DataInteractionBegan;
         break;
     }
@@ -267,6 +280,16 @@ static NSArray *dataInteractionEventNames()
     return _phase;
 }
 
+- (void)waitForInputSession
+{
+    _isDoneWaitingForInputSession = false;
+
+    // Waiting for an input session implies that we should allow input sessions to begin.
+    self.allowsFocusToStartInputSession = YES;
+
+    Util::run(&_isDoneWaitingForInputSession);
+}
+
 #pragma mark - WKUIDelegatePrivate
 
 - (void)_webView:(WKWebView *)webView dataInteractionOperationWasHandled:(BOOL)handled forSession:(id)session itemProviders:(NSArray<UIItemProvider *> *)itemProviders
@@ -303,6 +326,18 @@ static NSArray *dataInteractionEventNames()
     return self.showCustomActionSheetBlock(element);
 }
 
+#pragma mark - _WKInputDelegate
+
+- (BOOL)_webView:(WKWebView *)webView focusShouldStartInputSession:(id <_WKFocusedElementInfo>)info
+{
+    return _allowsFocusToStartInputSession;
+}
+
+- (void)_webView:(WKWebView *)webView didStartInputSession:(id <_WKFormInputSession>)inputSession
+{
+    _isDoneWaitingForInputSession = true;
+}
+
 @end
 
 #endif // ENABLE(DATA_INTERACTION)