[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.
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: http://svn.webkit.org/repository/webkit/trunk@216212 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index c41c413..463845c 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -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.
diff --git a/Source/WebCore/dom/DocumentMarker.h b/Source/WebCore/dom/DocumentMarker.h
index 904bf8c..01ea049 100644
--- a/Source/WebCore/dom/DocumentMarker.h
+++ b/Source/WebCore/dom/DocumentMarker.h
@@ -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 @@
#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 @@
| DictationPhraseWithAlternatives
| DictationResult
#endif
+ | DraggedContent
)
{
}
@@ -132,7 +137,10 @@
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());
diff --git a/Source/WebCore/dom/DocumentMarkerController.cpp b/Source/WebCore/dom/DocumentMarkerController.cpp
index e9d4aa3..165a1d2 100644
--- a/Source/WebCore/dom/DocumentMarkerController.cpp
+++ b/Source/WebCore/dom/DocumentMarkerController.cpp
@@ -135,6 +135,15 @@
#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 @@
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 @@
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 @@
break;
}
list->insert(i, RenderedDocumentMarker(newMarker));
-#endif
} else {
RenderedDocumentMarker toInsert(newMarker);
size_t numMarkers = list->size();
@@ -504,6 +525,9 @@
Vector<RenderedDocumentMarker*> DocumentMarkerController::markersFor(Node* node, DocumentMarker::MarkerTypes markerTypes)
{
+ if (!possiblyHasMarkers(markerTypes))
+ return { };
+
Vector<RenderedDocumentMarker*> result;
MarkerList* list = m_markers.get(node);
if (!list)
diff --git a/Source/WebCore/dom/DocumentMarkerController.h b/Source/WebCore/dom/DocumentMarkerController.h
index 0f5c236..a977578 100644
--- a/Source/WebCore/dom/DocumentMarkerController.h
+++ b/Source/WebCore/dom/DocumentMarkerController.h
@@ -59,6 +59,7 @@
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
diff --git a/Source/WebCore/page/DragController.h b/Source/WebCore/page/DragController.h
index 8d94020..0779c0c 100644
--- a/Source/WebCore/page/DragController.h
+++ b/Source/WebCore/page/DragController.h
@@ -62,6 +62,7 @@
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; }
diff --git a/Source/WebCore/page/DragState.h b/Source/WebCore/page/DragState.h
index 6d16259..6258abd 100644
--- a/Source/WebCore/page/DragState.h
+++ b/Source/WebCore/page/DragState.h
@@ -34,6 +34,7 @@
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.
diff --git a/Source/WebCore/page/EventHandler.cpp b/Source/WebCore/page/EventHandler.cpp
index 7952cab..b89c365 100644
--- a/Source/WebCore/page/EventHandler.cpp
+++ b/Source/WebCore/page/EventHandler.cpp
@@ -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 @@
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 @@
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 @@
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) {
diff --git a/Source/WebCore/page/EventHandler.h b/Source/WebCore/page/EventHandler.h
index f337a6d..2251f9a 100644
--- a/Source/WebCore/page/EventHandler.h
+++ b/Source/WebCore/page/EventHandler.h
@@ -159,6 +159,7 @@
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 @@
#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
diff --git a/Source/WebCore/page/FocusController.cpp b/Source/WebCore/page/FocusController.cpp
index 8f0a277..ee45caf 100644
--- a/Source/WebCore/page/FocusController.cpp
+++ b/Source/WebCore/page/FocusController.cpp
@@ -765,6 +765,29 @@
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 @@
m_page.editorClient().willSetInputMethodState();
- clearSelectionIfNeeded(oldFocusedFrame.get(), &newFocusedFrame, element);
+ if (shouldClearSelectionWhenChangingFocusedElement(m_page, oldFocusedElement, element))
+ clearSelectionIfNeeded(oldFocusedFrame.get(), &newFocusedFrame, element);
if (!element) {
if (oldDocument)
diff --git a/Source/WebCore/rendering/InlineTextBox.cpp b/Source/WebCore/rendering/InlineTextBox.cpp
index 1ecb7df..a37702a 100644
--- a/Source/WebCore/rendering/InlineTextBox.cpp
+++ b/Source/WebCore/rendering/InlineTextBox.cpp
@@ -552,7 +552,30 @@
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();
diff --git a/Source/WebCore/rendering/RenderReplaced.cpp b/Source/WebCore/rendering/RenderReplaced.cpp
index 02ada7c..339dbcb 100644
--- a/Source/WebCore/rendering/RenderReplaced.cpp
+++ b/Source/WebCore/rendering/RenderReplaced.cpp
@@ -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 @@
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 @@
#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)
diff --git a/Source/WebCore/rendering/RenderText.cpp b/Source/WebCore/rendering/RenderText.cpp
index 72b1c02..be82e72 100644
--- a/Source/WebCore/rendering/RenderText.cpp
+++ b/Source/WebCore/rendering/RenderText.cpp
@@ -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 @@
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())
diff --git a/Source/WebCore/rendering/RenderText.h b/Source/WebCore/rendering/RenderText.h
index 810ac35..1411088 100644
--- a/Source/WebCore/rendering/RenderText.h
+++ b/Source/WebCore/rendering/RenderText.h
@@ -176,6 +176,8 @@
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;
diff --git a/Source/WebCore/rendering/TextPainter.cpp b/Source/WebCore/rendering/TextPainter.cpp
index affffa4..6e79a08 100644
--- a/Source/WebCore/rendering/TextPainter.cpp
+++ b/Source/WebCore/rendering/TextPainter.cpp
@@ -168,6 +168,16 @@
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)
diff --git a/Source/WebCore/rendering/TextPainter.h b/Source/WebCore/rendering/TextPainter.h
index d9536c7..abd8b47 100644
--- a/Source/WebCore/rendering/TextPainter.h
+++ b/Source/WebCore/rendering/TextPainter.h
@@ -56,6 +56,7 @@
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);
diff --git a/Source/WebKit2/ChangeLog b/Source/WebKit2/ChangeLog
index dbb30d5..ac9e7fe 100644
--- a/Source/WebKit2/ChangeLog
+++ b/Source/WebKit2/ChangeLog
@@ -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
diff --git a/Source/WebKit2/UIProcess/WebPageProxy.cpp b/Source/WebKit2/UIProcess/WebPageProxy.cpp
index 08c066f..77762f7 100644
--- a/Source/WebKit2/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit2/UIProcess/WebPageProxy.cpp
@@ -1819,7 +1819,7 @@
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 @@
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()
{
diff --git a/Source/WebKit2/UIProcess/WebPageProxy.h b/Source/WebKit2/UIProcess/WebPageProxy.h
index 18e33b6..4c8ee58 100644
--- a/Source/WebKit2/UIProcess/WebPageProxy.h
+++ b/Source/WebKit2/UIProcess/WebPageProxy.h
@@ -841,6 +841,7 @@
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)
diff --git a/Source/WebKit2/UIProcess/mac/WebPageProxyMac.mm b/Source/WebKit2/UIProcess/mac/WebPageProxyMac.mm
index 9f54f7a..340b9f0 100644
--- a/Source/WebKit2/UIProcess/mac/WebPageProxyMac.mm
+++ b/Source/WebKit2/UIProcess/mac/WebPageProxyMac.mm
@@ -274,7 +274,7 @@
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,
diff --git a/Source/WebKit2/WebProcess/WebPage/WebPage.cpp b/Source/WebKit2/WebProcess/WebPage/WebPage.cpp
index 63fb475..bb95d70 100644
--- a/Source/WebKit2/WebProcess/WebPage/WebPage.cpp
+++ b/Source/WebKit2/WebProcess/WebPage/WebPage.cpp
@@ -3655,6 +3655,18 @@
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)
diff --git a/Source/WebKit2/WebProcess/WebPage/WebPage.h b/Source/WebKit2/WebProcess/WebPage/WebPage.h
index b011166..7b4f001 100644
--- a/Source/WebKit2/WebProcess/WebPage/WebPage.h
+++ b/Source/WebKit2/WebProcess/WebPage/WebPage.h
@@ -739,8 +739,8 @@
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&);
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index bd2961f..44c4d8a 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -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()
diff --git a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
index 19864cd..40cb4d0 100644
--- a/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
+++ b/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
@@ -604,6 +604,7 @@
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 */; };
@@ -690,6 +691,7 @@
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 */,
@@ -1506,6 +1508,7 @@
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>"; };
@@ -1966,6 +1969,7 @@
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
index 0000000..ea088d1
--- /dev/null
+++ b/Tools/TestWebKitAPI/Tests/WebKit2Cocoa/two-paragraph-contenteditable.html
@@ -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>
diff --git a/Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm b/Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm
index eddc73c..1c7110f 100644
--- a/Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm
+++ b/Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm
@@ -162,9 +162,10 @@
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, 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 @@
checkTypeIdentifierPrecedesOtherTypeIdentifier(dataInteractionSimulator.get(), (NSString *)kUTTypeRTFD, (NSString *)kUTTypeUTF8PlainText);
}
+TEST(DataInteractionTests, ContentEditableMoveParagraphs)
+{
+ RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+ 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)]);
- [webView synchronouslyLoadTestPageNamed:@"textarea-to-input"];
-
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);
diff --git a/Tools/TestWebKitAPI/ios/DataInteractionSimulator.h b/Tools/TestWebKitAPI/ios/DataInteractionSimulator.h
index 3436ec4..c114903 100644
--- a/Tools/TestWebKitAPI/ios/DataInteractionSimulator.h
+++ b/Tools/TestWebKitAPI/ios/DataInteractionSimulator.h
@@ -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 @@
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 @@
CGPoint _startLocation;
CGPoint _endLocation;
+ bool _isDoneWaitingForInputSession;
BOOL _shouldPerformOperation;
double _currentProgress;
bool _isDoneWithCurrentRun;
@@ -67,7 +70,9 @@
- (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;
diff --git a/Tools/TestWebKitAPI/ios/DataInteractionSimulator.mm b/Tools/TestWebKitAPI/ios/DataInteractionSimulator.mm
index 16c6537..200d704 100644
--- a/Tools/TestWebKitAPI/ios/DataInteractionSimulator.mm
+++ b/Tools/TestWebKitAPI/ios/DataInteractionSimulator.mm
@@ -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 @@
if (self = [super init]) {
_webView = webView;
_shouldEnsureUIApplication = NO;
+ _isDoneWaitingForInputSession = true;
[_webView setUIDelegate:self];
+ [_webView _setInputDelegate:self];
}
return self;
}
@@ -85,6 +89,9 @@
if ([_webView UIDelegate] == self)
[_webView setUIDelegate:nil];
+ if ([_webView _inputDelegate] == self)
+ [_webView _setInputDelegate:nil];
+
[super dealloc];
}
@@ -217,6 +224,12 @@
}
[_webView _simulateWillBeginDataInteractionWithSession:_dataInteractionSession.get()];
+
+ RetainPtr<WKWebView> retainedWebView = _webView;
+ dispatch_async(dispatch_get_main_queue(), ^() {
+ [retainedWebView resignFirstResponder];
+ });
+
_phase = DataInteractionBegan;
break;
}
@@ -267,6 +280,16 @@
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 @@
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)