Changeset 216212 in webkit


Ignore:
Timestamp:
May 4, 2017 3:28:22 PM (7 years ago)
Author:
Wenson Hsieh
Message:

[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:]):

Location:
trunk
Files:
1 added
26 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebCore/ChangeLog

    r216209 r216212  
     12017-05-04  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [WK2] Add support for keeping the selection in a focused editable element when dragging begins
     4        https://bugs.webkit.org/show_bug.cgi?id=171585
     5        <rdar://problem/31544320>
     6
     7        Reviewed by Beth Dakin and Zalan Bujtas.
     8
     9        Covered by 4 API tests.
     10
     11        * dom/DocumentMarker.h:
     12
     13        Introduces the DraggedContent DocumentMarker type, which applies to the Range in the DOM that is being used as
     14        a drag source. Also adds DraggedContentData, which contains nodes found by the TextIterator in the process of
     15        finding Ranges to mark.
     16
     17        (WebCore::DocumentMarker::AllMarkers::AllMarkers):
     18        * dom/DocumentMarkerController.cpp:
     19        (WebCore::DocumentMarkerController::addDraggedContentMarker):
     20        (WebCore::shouldInsertAsSeparateMarker):
     21        (WebCore::DocumentMarkerController::addMarker):
     22
     23        When adding DocumentMarkers of type DraggedContent, keep adjacent RenderReplaced elements separate, rather than
     24        merging them into existing RenderedDocumentMarkers. This is because the data for each of these (i.e. the target
     25        node) needs to be preserved.
     26
     27        (WebCore::DocumentMarkerController::markersFor):
     28
     29        Bail and return an empty list if the map of document markers cannot possibly contain a dragged content marker.
     30
     31        * dom/DocumentMarkerController.h:
     32        * page/DragController.h:
     33        * page/DragState.h:
     34
     35        Add draggedContentRange to DragState. This tracks the Range that is being dragged; it is created when the drag
     36        session has begun, and ends when drag session finishes (either via WebPage::dragEnded or WebPage::dragCancelled).
     37
     38        * page/EventHandler.cpp:
     39        (WebCore::repaintContentsOfRange):
     40        (WebCore::EventHandler::dragCancelled):
     41
     42        Called when a drag is cancelled in the UI process without a session ever getting a chance to begin. We use this
     43        as a hook to remove all DraggedContent document markers from the document of the dragged content range.
     44
     45        (WebCore::EventHandler::didStartDrag):
     46
     47        Called when a drag session has begun in the UI process. We use this as a hook to set up document markers for the
     48        Range of content being dragged.
     49
     50        (WebCore::EventHandler::dragSourceEndedAt):
     51
     52        Called when a drag session ends. We use this as a hook to remove all DraggedContent document markers from the
     53        document of the dragged content range.
     54
     55        (WebCore::EventHandler::draggedElement):
     56        * page/EventHandler.h:
     57        * page/FocusController.cpp:
     58        (WebCore::shouldClearSelectionWhenChangingFocusedElement):
     59
     60        Prevent the selection from clearing when the previously focused element is editable and also contains the drag
     61        source element. Ideally, we should experiment with clearing out the selection whenever the element is blurred
     62        (and not have additional restrictions on editability and containing the drag source), but this change is much
     63        riskier.
     64
     65        (WebCore::FocusController::setFocusedElement):
     66        * rendering/InlineTextBox.cpp:
     67        (WebCore::InlineTextBox::paint):
     68
     69        Use RenderText::draggedContentStartEnd to find the range of text (if any) that is dragged content, and paint
     70        these ranges of text at a lower alpha using TextPainter::paintTextInRange.
     71
     72        * rendering/RenderReplaced.cpp:
     73        (WebCore::draggedContentContainsReplacedElement):
     74
     75        Determines whether or not the element being rendered is contained within a dragged content range. Assuming that
     76        the DraggedContent type flag is set in DocumentMarkerController, we first look to see whether or not the
     77        container node is in the document marker map. If so, instead of consulting node offset ranges (since this is, in
     78        the worst-case, linear in the number of sibling nodes per RenderReplaced) we simply check the DraggedContentData
     79        to see if the current element being rendered matches one of the target nodes.
     80
     81        (WebCore::RenderReplaced::paint):
     82
     83        If the element rendered by this RenderReplaced is dragged content, then render it at a low alpha.
     84
     85        * rendering/RenderText.cpp:
     86        (WebCore::RenderText::draggedContentRangesBetweenOffsets):
     87
     88        Determines what range of text, if any, contains dragged content by consulting the Document's DocumentMarkers.
     89
     90        * rendering/RenderText.h:
     91        * rendering/TextPainter.cpp:
     92        (WebCore::TextPainter::paintTextInRange):
     93
     94        Teach TextPainter to only paint a given range in a TextRun.
     95
     96        * rendering/TextPainter.h:
     97
     98        Add TextPainter support for specifying special text offset ranges when rendering a TextRun, such that each
     99        special range in text is rendered after applying some modification to the GraphicsContext.
     100
    11012017-05-04  Jeremy Jones  <jeremyj@apple.com>
    2102
  • trunk/Source/WebCore/dom/DocumentMarker.h

    r210216 r216212  
    2020
    2121#pragma once
     22
     23#include "Node.h"
    2224
    2325#include <wtf/Forward.h>
     
    7678        // This marker indicates that the user has selected a text candidate.
    7779        AcceptedCandidate = 1 << 13,
     80        // This marker indicates that the user has initiated a drag with this content.
     81        DraggedContent = 1 << 14
    7882    };
    7983
     
    116120                | DictationResult
    117121#endif
     122                | DraggedContent
    118123            )
    119124        {
     
    133138#endif
    134139    };
    135     using Data = Variant<IsActiveMatchData, DescriptionData, DictationData, DictationAlternativesData>;
     140    struct DraggedContentData {
     141        RefPtr<Node> targetNode;
     142    };
     143    using Data = Variant<IsActiveMatchData, DescriptionData, DictationData, DictationAlternativesData, DraggedContentData>;
    136144
    137145    DocumentMarker(unsigned startOffset, unsigned endOffset, bool isActiveMatch);
  • trunk/Source/WebCore/dom/DocumentMarkerController.cpp

    r216019 r216212  
    136136#endif
    137137
     138void DocumentMarkerController::addDraggedContentMarker(RefPtr<Range> range)
     139{
     140    for (TextIterator markedText(range.get()); !markedText.atEnd(); markedText.advance()) {
     141        RefPtr<Range> textPiece = markedText.range();
     142        DocumentMarker::DraggedContentData draggedContentData { markedText.node() };
     143        addMarker(&textPiece->startContainer(), { DocumentMarker::DraggedContent, textPiece->startOffset(), textPiece->endOffset(), WTFMove(draggedContentData) });
     144    }
     145}
     146
    138147void DocumentMarkerController::removeMarkers(Range* range, DocumentMarker::MarkerTypes markerTypes, RemovePartiallyOverlappingMarkerOrNot shouldRemovePartiallyOverlappingMarker)
    139148{
     
    294303}
    295304
     305static bool shouldInsertAsSeparateMarker(const DocumentMarker& newMarker)
     306{
     307#if PLATFORM(IOS)
     308    if (newMarker.type() == DocumentMarker::DictationPhraseWithAlternatives || newMarker.type() == DocumentMarker::DictationResult)
     309        return true;
     310#endif
     311    if (newMarker.type() == DocumentMarker::DraggedContent) {
     312        if (auto targetNode = WTF::get<DocumentMarker::DraggedContentData>(newMarker.data()).targetNode)
     313            return targetNode->renderer() && targetNode->renderer()->isRenderReplaced();
     314    }
     315
     316    return false;
     317}
     318
    296319// Markers are stored in order sorted by their start offset.
    297320// Markers of the same type do not overlap each other.
     
    318341        list = std::make_unique<MarkerList>();
    319342        list->append(RenderedDocumentMarker(newMarker));
    320 #if PLATFORM(IOS)
    321     } else if (newMarker.type() == DocumentMarker::DictationPhraseWithAlternatives || newMarker.type() == DocumentMarker::DictationResult) {
     343    } else if (shouldInsertAsSeparateMarker(newMarker)) {
    322344        // We don't merge dictation markers.
    323345        size_t i;
     
    329351        }
    330352        list->insert(i, RenderedDocumentMarker(newMarker));
    331 #endif
    332353    } else {
    333354        RenderedDocumentMarker toInsert(newMarker);
     
    505526Vector<RenderedDocumentMarker*> DocumentMarkerController::markersFor(Node* node, DocumentMarker::MarkerTypes markerTypes)
    506527{
     528    if (!possiblyHasMarkers(markerTypes))
     529        return { };
     530
    507531    Vector<RenderedDocumentMarker*> result;
    508532    MarkerList* list = m_markers.get(node);
  • trunk/Source/WebCore/dom/DocumentMarkerController.h

    r216019 r216212  
    6060    void addDictationResultMarker(Range*, const RetainPtr<id>& metadata);
    6161#endif
     62    void addDraggedContentMarker(RefPtr<Range>);
    6263
    6364    void copyMarkers(Node* srcNode, unsigned startOffset, int length, Node* dstNode, int delta);
  • trunk/Source/WebCore/page/DragController.h

    r215917 r216212  
    6363        WEBCORE_EXPORT DragOperation dragUpdated(const DragData&);
    6464        WEBCORE_EXPORT bool performDragOperation(const DragData&);
     65        WEBCORE_EXPORT void dragCancelled();
    6566
    6667        bool mouseIsOverFileInput() const { return m_fileInputElementUnderMouse; }
  • trunk/Source/WebCore/page/DragState.h

    r208179 r216212  
    3535struct DragState {
    3636    RefPtr<Element> source; // Element that may be a drag source, for the current mouse gesture.
     37    RefPtr<Range> draggedContentRange;
    3738    bool shouldDispatchEvents;
    3839    DragSourceAction type;
  • trunk/Source/WebCore/page/EventHandler.cpp

    r215872 r216212  
    3535#include "ChromeClient.h"
    3636#include "CursorList.h"
     37#include "DocumentMarkerController.h"
    3738#include "DragController.h"
    3839#include "DragState.h"
     
    34523453}
    34533454
     3455static void repaintContentsOfRange(RefPtr<Range> range)
     3456{
     3457    if (!range)
     3458        return;
     3459
     3460    auto* container = range->commonAncestorContainer();
     3461    if (!container)
     3462        return;
     3463
     3464    // This ensures that all nodes enclosed in this Range are repainted.
     3465    if (auto rendererToRepaint = container->renderer()) {
     3466        if (auto* containingRenderer = rendererToRepaint->container())
     3467            rendererToRepaint = containingRenderer;
     3468        rendererToRepaint->repaint();
     3469    }
     3470}
     3471
     3472void EventHandler::dragCancelled()
     3473{
     3474#if ENABLE(DATA_INTERACTION)
     3475    if (auto range = dragState().draggedContentRange) {
     3476        range->ownerDocument().markers().removeMarkers(DocumentMarker::DraggedContent);
     3477        repaintContentsOfRange(range);
     3478    }
     3479    dragState().draggedContentRange = nullptr;
     3480#endif
     3481}
     3482
     3483void EventHandler::didStartDrag()
     3484{
     3485#if ENABLE(DATA_INTERACTION)
     3486    auto dragSource = dragState().source;
     3487    if (!dragSource)
     3488        return;
     3489
     3490    auto* renderer = dragSource->renderer();
     3491    if (!renderer)
     3492        return;
     3493
     3494    if (dragState().type & DragSourceActionSelection)
     3495        dragState().draggedContentRange = m_frame.selection().selection().toNormalizedRange();
     3496    else {
     3497        Position startPosition(dragSource.get(), Position::PositionIsBeforeAnchor);
     3498        Position endPosition(dragSource.get(), Position::PositionIsAfterAnchor);
     3499        dragState().draggedContentRange = Range::create(dragSource->document(), startPosition, endPosition);
     3500    }
     3501
     3502    if (auto range = dragState().draggedContentRange) {
     3503        range->ownerDocument().markers().addDraggedContentMarker(range.get());
     3504        repaintContentsOfRange(range);
     3505    }
     3506#endif
     3507}
     3508
    34543509void EventHandler::dragSourceEndedAt(const PlatformMouseEvent& event, DragOperation operation)
    34553510{
     
    34643519    }
    34653520    invalidateDataTransfer();
     3521
     3522    if (auto range = dragState().draggedContentRange) {
     3523        range->ownerDocument().markers().removeMarkers(DocumentMarker::DraggedContent);
     3524        repaintContentsOfRange(range);
     3525    }
     3526
    34663527    dragState().source = nullptr;
    34673528    // In case the drag was ended due to an escape key press we need to ensure
     
    34863547{
    34873548    return n && !(n & (n - 1));
     3549}
     3550
     3551RefPtr<Element> EventHandler::draggedElement() const
     3552{
     3553    return dragState().source;
    34883554}
    34893555
  • trunk/Source/WebCore/page/EventHandler.h

    r215872 r216212  
    160160    bool performDragAndDrop(const PlatformMouseEvent&, DataTransfer&);
    161161    void updateDragStateAfterEditDragIfNeeded(Element& rootEditableElement);
     162    RefPtr<Element> draggedElement() const;
    162163#endif
    163164
     
    254255    WEBCORE_EXPORT bool eventMayStartDrag(const PlatformMouseEvent&) const;
    255256   
     257    WEBCORE_EXPORT void didStartDrag();
     258    WEBCORE_EXPORT void dragCancelled();
    256259    WEBCORE_EXPORT void dragSourceEndedAt(const PlatformMouseEvent&, DragOperation);
    257260#endif
  • trunk/Source/WebCore/page/FocusController.cpp

    r215173 r216212  
    766766}
    767767
     768static bool shouldClearSelectionWhenChangingFocusedElement(const Page& page, RefPtr<Element> oldFocusedElement, RefPtr<Element> newFocusedElement)
     769{
     770#if ENABLE(DATA_INTERACTION)
     771    if (newFocusedElement || !oldFocusedElement)
     772        return true;
     773
     774    // FIXME: These additional checks should not be necessary. We should consider generally keeping the selection whenever the
     775    // focused element is blurred, with no new element taking focus.
     776    if (!oldFocusedElement->isRootEditableElement() && !is<HTMLInputElement>(oldFocusedElement.get()) && !is<HTMLTextAreaElement>(oldFocusedElement.get()))
     777        return true;
     778
     779    for (auto ancestor = page.mainFrame().eventHandler().draggedElement(); ancestor; ancestor = ancestor->parentOrShadowHostElement()) {
     780        if (ancestor == oldFocusedElement)
     781            return false;
     782    }
     783#else
     784    UNUSED_PARAM(page);
     785    UNUSED_PARAM(oldFocusedElement);
     786    UNUSED_PARAM(newFocusedElement);
     787#endif
     788    return true;
     789}
     790
    768791bool FocusController::setFocusedElement(Element* element, Frame& newFocusedFrame, FocusDirection direction)
    769792{
     
    782805    m_page.editorClient().willSetInputMethodState();
    783806
    784     clearSelectionIfNeeded(oldFocusedFrame.get(), &newFocusedFrame, element);
     807    if (shouldClearSelectionWhenChangingFocusedElement(m_page, oldFocusedElement, element))
     808        clearSelectionIfNeeded(oldFocusedFrame.get(), &newFocusedFrame, element);
    785809
    786810    if (!element) {
  • trunk/Source/WebCore/rendering/InlineTextBox.cpp

    r213614 r216212  
    553553    textPainter.addEmphasis(emphasisMark, emphasisMarkOffset, combinedText);
    554554
    555     textPainter.paintText(textRun, length, boxRect, textOrigin, selectionStart, selectionEnd, paintSelectedTextOnly, paintSelectedTextSeparately, paintNonSelectedTextOnly);
     555    auto draggedContentRanges = renderer().draggedContentRangesBetweenOffsets(m_start, m_start + m_len);
     556    if (!draggedContentRanges.isEmpty() && !paintSelectedTextOnly && !paintNonSelectedTextOnly) {
     557        // FIXME: Painting with text effects ranges currently only works if we're not also painting the selection.
     558        // In the future, we may want to support this capability, but in the meantime, this isn't required by anything.
     559        unsigned currentEnd = 0;
     560        for (size_t index = 0; index < draggedContentRanges.size(); ++index) {
     561            unsigned previousEnd = index ? std::min(draggedContentRanges[index - 1].second, length) : 0;
     562            unsigned currentStart = draggedContentRanges[index].first - m_start;
     563            currentEnd = std::min(draggedContentRanges[index].second - m_start, length);
     564
     565            if (previousEnd < currentStart)
     566                textPainter.paintTextInRange(textRun, boxRect, textOrigin, previousEnd, currentStart);
     567
     568            if (currentStart < currentEnd) {
     569                context.save();
     570                context.setAlpha(0.25);
     571                textPainter.paintTextInRange(textRun, boxRect, textOrigin, currentStart, currentEnd);
     572                context.restore();
     573            }
     574        }
     575        if (currentEnd < length)
     576            textPainter.paintTextInRange(textRun, boxRect, textOrigin, currentEnd, length);
     577    } else
     578        textPainter.paintText(textRun, length, boxRect, textOrigin, selectionStart, selectionEnd, paintSelectedTextOnly, paintSelectedTextSeparately, paintNonSelectedTextOnly);
    556579
    557580    // Paint decorations
  • trunk/Source/WebCore/rendering/RenderReplaced.cpp

    r214082 r216212  
    2525#include "RenderReplaced.h"
    2626
     27#include "DocumentMarkerController.h"
    2728#include "FloatRoundedRect.h"
    2829#include "Frame.h"
     
    3839#include "RenderTheme.h"
    3940#include "RenderView.h"
     41#include "RenderedDocumentMarker.h"
    4042#include "VisiblePosition.h"
    4143#include <wtf/StackStats.h>
     
    135137}
    136138
     139inline static bool draggedContentContainsReplacedElement(const Vector<RenderedDocumentMarker*>& markers, const Element& element)
     140{
     141    if (markers.isEmpty())
     142        return false;
     143
     144    for (auto* marker : markers) {
     145        auto& draggedContentData = WTF::get<DocumentMarker::DraggedContentData>(marker->data());
     146        if (draggedContentData.targetNode == &element)
     147            return true;
     148    }
     149
     150    return false;
     151}
     152
    137153void RenderReplaced::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    138154{
     
    143159    SetLayoutNeededForbiddenScope scope(this);
    144160#endif
     161
     162    GraphicsContextStateSaver savedGraphicsContext(paintInfo.context());
     163    if (element() && element()->parentOrShadowHostElement()) {
     164        auto* parentContainer = element()->parentOrShadowHostElement();
     165        if (draggedContentContainsReplacedElement(document().markers().markersFor(parentContainer, DocumentMarker::DraggedContent), *element()))
     166            paintInfo.context().setAlpha(0.25);
     167    }
     168
    145169    LayoutPoint adjustedPaintOffset = paintOffset + location();
    146170   
  • trunk/Source/WebCore/rendering/RenderText.cpp

    r216096 r216212  
    3030#include "BreakingContext.h"
    3131#include "CharacterProperties.h"
     32#include "DocumentMarker.h"
     33#include "DocumentMarkerController.h"
    3234#include "EllipsisBox.h"
    3335#include "FloatQuad.h"
     
    4244#include "RenderLayer.h"
    4345#include "RenderView.h"
     46#include "RenderedDocumentMarker.h"
    4447#include "Settings.h"
    4548#include "SimpleLineLayoutFunctions.h"
     
    10641067}
    10651068
     1069Vector<std::pair<unsigned, unsigned>> RenderText::draggedContentRangesBetweenOffsets(unsigned startOffset, unsigned endOffset) const
     1070{
     1071    auto markers = document().markers().markersFor(textNode(), DocumentMarker::DraggedContent);
     1072    if (markers.isEmpty())
     1073        return { };
     1074
     1075    Vector<std::pair<unsigned, unsigned>> draggedContentRanges;
     1076    for (auto* marker : markers) {
     1077        unsigned markerStart = std::max(marker->startOffset(), startOffset);
     1078        unsigned markerEnd = std::min(marker->endOffset(), endOffset);
     1079        if (markerStart >= markerEnd || markerStart > endOffset || markerEnd < startOffset)
     1080            continue;
     1081
     1082        std::pair<unsigned, unsigned> draggedContentRange;
     1083        draggedContentRange.first = markerStart;
     1084        draggedContentRange.second = markerEnd;
     1085        draggedContentRanges.append(draggedContentRange);
     1086    }
     1087    return draggedContentRanges;
     1088}
     1089
    10661090IntPoint RenderText::firstRunLocation() const
    10671091{
  • trunk/Source/WebCore/rendering/RenderText.h

    r213020 r216212  
    177177    bool canUseSimplifiedTextMeasuring() const { return m_canUseSimplifiedTextMeasuring; }
    178178
     179    Vector<std::pair<unsigned, unsigned>> draggedContentRangesBetweenOffsets(unsigned startOffset, unsigned endOffset) const;
     180
    179181protected:
    180182    virtual void computePreferredLogicalWidths(float leadWidth);
  • trunk/Source/WebCore/rendering/TextPainter.cpp

    r213614 r216212  
    169169        m_context.concatCTM(rotation(boxRect, Counterclockwise));
    170170}
     171
     172void TextPainter::paintTextInRange(const TextRun& textRun, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned start, unsigned end)
     173{
     174    ASSERT(m_font);
     175    ASSERT(start < end);
     176
     177    GraphicsContextStateSaver stateSaver(m_context, m_textPaintStyle.strokeWidth > 0);
     178    updateGraphicsContext(m_context, m_textPaintStyle);
     179    paintTextAndEmphasisMarksIfNeeded(textRun, boxRect, textOrigin, start, end, m_textPaintStyle, m_textShadow);
     180}
    171181   
    172182void TextPainter::paintText(const TextRun& textRun, unsigned length, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned selectionStart, unsigned selectionEnd,
  • trunk/Source/WebCore/rendering/TextPainter.h

    r213614 r216212  
    5757    void addTextShadow(const ShadowData* textShadow, const ShadowData* selectionShadow);
    5858
     59    void paintTextInRange(const TextRun&, const FloatRect& boxRect, const FloatPoint& textOrigin, unsigned start, unsigned end);
    5960    void paintText(const TextRun&, unsigned length, const FloatRect& boxRect, const FloatPoint& textOrigin,
    6061        unsigned selectionStart = 0, unsigned selectionEnd = 0, bool paintSelectedTextOnly = false, bool paintSelectedTextSeparately = false, bool paintNonSelectedTextOnly = false);
  • trunk/Source/WebKit2/ChangeLog

    r216206 r216212  
     12017-05-04  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [WK2] Add support for keeping the selection in a focused editable element when dragging begins
     4        https://bugs.webkit.org/show_bug.cgi?id=171585
     5        <rdar://problem/31544320>
     6
     7        Reviewed by Beth Dakin and Zalan Bujtas.
     8
     9        Minor adjustments and refactoring in WebKit2. See WebCore ChangeLog for more details.
     10
     11        * UIProcess/WebPageProxy.cpp:
     12        (WebKit::WebPageProxy::startDrag):
     13        (WebKit::WebPageProxy::didStartDrag):
     14
     15        Factor out code in WebPageProxy that sends a WebPage::DidStartDrag message to the web process into a separate
     16        helper, and tweak the places where we directly send this IPC message to the web process to instead call this
     17        helper.
     18
     19        * UIProcess/WebPageProxy.h:
     20        * UIProcess/mac/WebPageProxyMac.mm:
     21        (WebKit::WebPageProxy::setDragImage):
     22        * WebProcess/WebPage/WebPage.cpp:
     23        (WebKit::WebPage::didStartDrag):
     24        (WebKit::WebPage::dragCancelled):
     25
     26        Clear out state in the web process and call out to the EventHandler to handle drag cancellation and the drag
     27        start response from the UI process.
     28
     29        * WebProcess/WebPage/WebPage.h:
     30        (WebKit::WebPage::didStartDrag): Deleted.
     31        (WebKit::WebPage::dragCancelled): Deleted.
     32
    1332017-05-04  Sam Weinig  <sam@webkit.org>
    234
  • trunk/Source/WebKit2/UIProcess/WebPageProxy.cpp

    r216197 r216212  
    18201820    m_pageClient.startDrag(WTFMove(selection.selectionData), static_cast<WebCore::DragOperation>(dragOperation), WTFMove(dragImage));
    18211821
    1822     m_process->send(Messages::WebPage::DidStartDrag(), m_pageID);
     1822    didStartDrag();
    18231823}
    18241824#endif
     
    18301830    m_process->send(Messages::WebPage::DragEnded(clientPosition, globalPosition, operation), m_pageID);
    18311831    setDragCaretRect({ });
     1832}
     1833
     1834void WebPageProxy::didStartDrag()
     1835{
     1836    if (isValid())
     1837        m_process->send(Messages::WebPage::DidStartDrag(), m_pageID);
    18321838}
    18331839   
  • trunk/Source/WebKit2/UIProcess/WebPageProxy.h

    r216197 r216212  
    842842    void didPerformDragControllerAction(uint64_t dragOperation, bool mouseIsOverFileInput, unsigned numberOfItemsToBeAccepted, const WebCore::IntRect& insertionRect, bool isHandlingNonDefaultDrag);
    843843    void dragEnded(const WebCore::IntPoint& clientPosition, const WebCore::IntPoint& globalPosition, uint64_t operation);
     844    void didStartDrag();
    844845    void dragCancelled();
    845846    void setDragCaretRect(const WebCore::IntRect&);
  • trunk/Source/WebKit2/UIProcess/mac/WebPageProxyMac.mm

    r213902 r216212  
    275275        m_pageClient.setDragImage(clientPosition, WTFMove(dragImage), static_cast<DragSourceAction>(action));
    276276
    277     process().send(Messages::WebPage::DidStartDrag(), m_pageID);
     277    didStartDrag();
    278278}
    279279
  • trunk/Source/WebKit2/WebProcess/WebPage/WebPage.cpp

    r216206 r216212  
    36563656    m_pendingDropExtensionsForFileUpload.clear();
    36573657}
     3658
     3659void WebPage::didStartDrag()
     3660{
     3661    m_isStartingDrag = false;
     3662    m_page->mainFrame().eventHandler().didStartDrag();
     3663}
     3664
     3665void WebPage::dragCancelled()
     3666{
     3667    m_isStartingDrag = false;
     3668    m_page->mainFrame().eventHandler().dragCancelled();
     3669}
    36583670   
    36593671#endif // ENABLE(DRAG_SUPPORT)
  • trunk/Source/WebKit2/WebProcess/WebPage/WebPage.h

    r216197 r216212  
    740740
    741741    void willStartDrag() { ASSERT(!m_isStartingDrag); m_isStartingDrag = true; }
    742     void didStartDrag() { ASSERT(m_isStartingDrag); m_isStartingDrag = false; }
    743     void dragCancelled() { m_isStartingDrag = false; }
     742    void didStartDrag();
     743    void dragCancelled();
    744744#endif // ENABLE(DRAG_SUPPORT)
    745745
  • trunk/Tools/ChangeLog

    r216211 r216212  
     12017-05-04  Wenson Hsieh  <wenson_hsieh@apple.com>
     2
     3        [WK2] Add support for keeping the selection in a focused editable element when dragging begins
     4        https://bugs.webkit.org/show_bug.cgi?id=171585
     5        <rdar://problem/31544320>
     6
     7        Reviewed by Beth Dakin and Zalan Bujtas.
     8
     9        Adds 1 new unit test and tweaks existing tests to check that when first responder status is lost after beginning
     10        a drag while editing, content is still moved (and not copied) when performing data interaction on a different
     11        element. ContentEditableMoveParagraphs checks that content can be shifted within a single element via a move
     12        operation rather than a copy.
     13
     14        See WebCore ChangeLog for more details.
     15
     16        Tests:  DataInteractionSimulator.ContentEditableToContentEditable
     17                DataInteractionSimulator.ContentEditableToTextarea
     18                DataInteractionSimulator.ContentEditableMoveParagraphs
     19                DataInteractionSimulator.TextAreaToInput
     20
     21        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
     22        * TestWebKitAPI/Tests/WebKit2Cocoa/two-paragraph-contenteditable.html: Added.
     23        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
     24        (TestWebKitAPI::TEST):
     25        * TestWebKitAPI/ios/DataInteractionSimulator.h:
     26        * TestWebKitAPI/ios/DataInteractionSimulator.mm:
     27        (-[DataInteractionSimulator initWithWebView:]):
     28        (-[DataInteractionSimulator dealloc]):
     29        (-[DataInteractionSimulator _advanceProgress]):
     30        (-[DataInteractionSimulator waitForInputSession]):
     31        (-[DataInteractionSimulator _webView:focusShouldStartInputSession:]):
     32        (-[DataInteractionSimulator _webView:didStartInputSession:]):
     33
    1342017-05-04  Said Abou-Hallawa  <sabouhallawa@apple.com>
    235
  • trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj

    r216197 r216212  
    605605                F415086D1DA040C50044BE9B /* play-audio-on-click.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F415086C1DA040C10044BE9B /* play-audio-on-click.html */; };
    606606                F42DA5161D8CEFE400336F40 /* large-input-field-focus-onload.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F42DA5151D8CEFDB00336F40 /* large-input-field-focus-onload.html */; };
     607                F4451C761EB8FD890020C5DA /* two-paragraph-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */; };
    607608                F4538EF71E8473E600B5C953 /* large-red-square.png in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4538EF01E846B4100B5C953 /* large-red-square.png */; };
    608609                F47728991E4AE3C1007ABF6A /* full-page-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */; };
     
    691692                                5797FE331EB15AB100B2F4A0 /* navigation-client-default-crypto.html in Copy Resources */,
    692693                                0799C34B1EBA3301003B7532 /* disableGetUserMedia.html in Copy Resources */,
     694                                F4451C761EB8FD890020C5DA /* two-paragraph-contenteditable.html in Copy Resources */,
    693695                                074994421EA5034B000DA44E /* getUserMedia.html in Copy Resources */,
    694696                                C9BF06EF1E9C132500595E3E /* autoplay-muted-with-controls.html in Copy Resources */,
     
    15071509                F415086C1DA040C10044BE9B /* play-audio-on-click.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "play-audio-on-click.html"; sourceTree = "<group>"; };
    15081510                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; };
     1511                F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "two-paragraph-contenteditable.html"; sourceTree = "<group>"; };
    15091512                F4538EF01E846B4100B5C953 /* large-red-square.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "large-red-square.png"; sourceTree = "<group>"; };
    15101513                F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "full-page-contenteditable.html"; sourceTree = "<group>"; };
     
    19671970                                515BE16E1D4288FF00DD7C68 /* StoreBlobToBeDeleted.html */,
    19681971                                2E9896141D8F092B00739892 /* text-and-password-inputs.html */,
     1972                                F4451C751EB8FD7C0020C5DA /* two-paragraph-contenteditable.html */,
    19691973                                51714EB21CF8C761004723C4 /* WebProcessKillIDBCleanup-1.html */,
    19701974                                51714EB31CF8C761004723C4 /* WebProcessKillIDBCleanup-2.html */,
  • trunk/Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm

    r216203 r216212  
    163163{
    164164    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
    165     [webView synchronouslyLoadTestPageNamed:@"autofocus-contenteditable"];
    166 
    167     RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     165    RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     166
     167    [webView loadTestPageNamed:@"autofocus-contenteditable"];
     168    [dataInteractionSimulator waitForInputSession];
    168169    [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(100, 300)];
    169170
     
    182183{
    183184    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
    184     [webView synchronouslyLoadTestPageNamed:@"contenteditable-and-textarea"];
    185 
    186     RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     185    RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     186
     187    [webView loadTestPageNamed:@"contenteditable-and-textarea"];
     188    [dataInteractionSimulator waitForInputSession];
    187189    [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(100, 300)];
    188190
     
    198200}
    199201
     202TEST(DataInteractionTests, ContentEditableMoveParagraphs)
     203{
     204    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
     205    RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     206
     207    [webView loadTestPageNamed:@"two-paragraph-contenteditable"];
     208    [dataInteractionSimulator waitForInputSession];
     209    [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(250, 450)];
     210
     211    NSString *finalTextContent = [webView stringByEvaluatingJavaScript:@"editor.textContent"];
     212    NSUInteger firstParagraphOffset = [finalTextContent rangeOfString:@"This is the first paragraph"].location;
     213    NSUInteger secondParagraphOffset = [finalTextContent rangeOfString:@"This is the second paragraph"].location;
     214
     215    EXPECT_FALSE(firstParagraphOffset == NSNotFound);
     216    EXPECT_FALSE(secondParagraphOffset == NSNotFound);
     217    EXPECT_GT(firstParagraphOffset, secondParagraphOffset);
     218    checkSelectionRectsWithLogging(@[ makeCGRectValue(190, 100, 130, 20), makeCGRectValue(0, 120, 320, 100), makeCGRectValue(0, 220, 252, 20) ], [dataInteractionSimulator finalSelectionRects]);
     219}
     220
    200221TEST(DataInteractionTests, TextAreaToInput)
    201222{
    202223    RetainPtr<TestWKWebView> webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
    203     [webView synchronouslyLoadTestPageNamed:@"textarea-to-input"];
    204 
    205     RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     224    RetainPtr<DataInteractionSimulator> dataInteractionSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
     225
     226    [webView loadTestPageNamed:@"textarea-to-input"];
     227    [dataInteractionSimulator waitForInputSession];
    206228    [dataInteractionSimulator runFrom:CGPointMake(100, 50) to:CGPointMake(100, 300)];
    207229
  • trunk/Tools/TestWebKitAPI/ios/DataInteractionSimulator.h

    r215962 r216212  
    3030#import <UIKit/UIKit.h>
    3131#import <WebKit/WKUIDelegatePrivate.h>
     32#import <WebKit/_WKInputDelegate.h>
    3233#import <wtf/BlockPtr.h>
    3334
     
    4950};
    5051
    51 @interface DataInteractionSimulator : NSObject<WKUIDelegatePrivate> {
     52@interface DataInteractionSimulator : NSObject<WKUIDelegatePrivate, _WKInputDelegate> {
     53@private
    5254    RetainPtr<TestWKWebView> _webView;
    5355    RetainPtr<MockDataInteractionSession> _dataInteractionSession;
     
    6062    CGPoint _endLocation;
    6163
     64    bool _isDoneWaitingForInputSession;
    6265    BOOL _shouldPerformOperation;
    6366    double _currentProgress;
     
    6871- (instancetype)initWithWebView:(TestWKWebView *)webView;
    6972- (void)runFrom:(CGPoint)startLocation to:(CGPoint)endLocation;
     73- (void)waitForInputSession;
    7074
     75@property (nonatomic) BOOL allowsFocusToStartInputSession;
    7176@property (nonatomic) BOOL shouldEnsureUIApplication;
    7277@property (nonatomic) BlockPtr<BOOL(_WKActivatedElementInfo *)> showCustomActionSheetBlock;
  • trunk/Tools/TestWebKitAPI/ios/DataInteractionSimulator.mm

    r215962 r216212  
    3434#import <WebCore/SoftLinking.h>
    3535#import <WebKit/WKWebViewPrivate.h>
     36#import <WebKit/_WKFocusedElementInfo.h>
     37#import <WebKit/_WKFormInputSession.h>
    3638#import <wtf/RetainPtr.h>
    3739
     
    7678        _webView = webView;
    7779        _shouldEnsureUIApplication = NO;
     80        _isDoneWaitingForInputSession = true;
    7881        [_webView setUIDelegate:self];
     82        [_webView _setInputDelegate:self];
    7983    }
    8084    return self;
     
    8589    if ([_webView UIDelegate] == self)
    8690        [_webView setUIDelegate:nil];
     91
     92    if ([_webView _inputDelegate] == self)
     93        [_webView _setInputDelegate:nil];
    8794
    8895    [super dealloc];
     
    218225
    219226        [_webView _simulateWillBeginDataInteractionWithSession:_dataInteractionSession.get()];
     227
     228        RetainPtr<WKWebView> retainedWebView = _webView;
     229        dispatch_async(dispatch_get_main_queue(), ^() {
     230            [retainedWebView resignFirstResponder];
     231        });
     232
    220233        _phase = DataInteractionBegan;
    221234        break;
     
    268281}
    269282
     283- (void)waitForInputSession
     284{
     285    _isDoneWaitingForInputSession = false;
     286
     287    // Waiting for an input session implies that we should allow input sessions to begin.
     288    self.allowsFocusToStartInputSession = YES;
     289
     290    Util::run(&_isDoneWaitingForInputSession);
     291}
     292
    270293#pragma mark - WKUIDelegatePrivate
    271294
     
    304327}
    305328
     329#pragma mark - _WKInputDelegate
     330
     331- (BOOL)_webView:(WKWebView *)webView focusShouldStartInputSession:(id <_WKFocusedElementInfo>)info
     332{
     333    return _allowsFocusToStartInputSession;
     334}
     335
     336- (void)_webView:(WKWebView *)webView didStartInputSession:(id <_WKFormInputSession>)inputSession
     337{
     338    _isDoneWaitingForInputSession = true;
     339}
     340
    306341@end
    307342
Note: See TracChangeset for help on using the changeset viewer.