dataTransfer.types is empty when handling the "dragstart" event
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 3 Jun 2020 19:41:50 +0000 (19:41 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 3 Jun 2020 19:41:50 +0000 (19:41 +0000)
https://bugs.webkit.org/show_bug.cgi?id=212685
<rdar://problem/61368402>

Reviewed by Andy Estes.

Source/WebCore:

Implements several currently stubbed methods on StaticPasteboard, so that the DataTransfer provided to the page
on the "dragstart" event contains the DOM-exposed data types that will be written to the system pasteboard. This
includes "text/html", "text/plain", and "text/uri-list".

Tests:  DragAndDropTests.DataTransferTypesOnDragStartForTextSelection
        DragAndDropTests.DataTransferTypesOnDragStartForImage
        DragAndDropTests.DataTransferTypesOnDragStartForLink

...as well as several existing tests in DragAndDropTestsIOS.mm that attempt to set pasteboard data during the
dragstart event:

        DragAndDropTests.DataTransferSanitizeHTML
        DragAndDropTests.DataTransferSetDataCannotWritePlatformTypes
        DragAndDropTests.DataTransferSetDataInvalidURL
        DragAndDropTests.DataTransferSetDataUnescapedURL
        DragAndDropTests.DataTransferSetDataValidURL

* dom/DataTransfer.cpp:
(WebCore::DataTransfer::commitToPasteboard):

Only commit data to the native pasteboard if the page actually tried to write or modify the data. This allows us
to preserve existing behavior by allowing DragController to write dragged data to the pasteboard normally in the
case where the page didn't specify any custom data. In the case where the page does specify custom data, we will
write this custom data *in addition* to any default data that was written to the static pasteboard. While this
is a departure from our current behavior (which is to treat the pasteboard as a blank slate that contains only
whatever custom data was provided by the page), it matches behavior in both Chrome and Firefox, and is likely
more compatible with webpages that don't have UA-specific logic targeting WebKit.

* editing/cocoa/EditorCocoa.mm:
(WebCore::Editor::writeSelectionToPasteboard):

Avoid calling into the injected bundle (as well as writing a few particular non-web-exposed types, such as web
archive data) in the case where we're writing to a static pasteboard (there's no point in doing this for the
static pasteboard, and in the worst case, it could confuse some internal clients).

* editing/ios/EditorIOS.mm:
(WebCore::Editor::writeImageToPasteboard): Ditto.
* editing/mac/EditorMac.mm:
(WebCore::Editor::writeImageToPasteboard):

Ditto. But additionally, introduce a markup string to PasteboardImage, so that we will expose the "text/html"
type when starting a drag on an image element.

* page/DragController.cpp:
(WebCore::DragController::startDrag):

Only attempt to call into `Pasteboard::writeTrustworthyWebURLsPboardType` in the case where the pasteboard
supports this type (i.e. on macOS). This fixes an existing assertion that was hit by my new API test, which
attempts to override the contents of the pasteboard with custom data while starting a drag on a link.

* page/EventHandler.cpp:
(WebCore::EventHandler::handleDrag):

Since the StaticPasteboard contains data before the page has written anything, don't use `Pasteboard::hasData()`
to determine whether there's custom data; instead, use the new `hasNonDefaultData()` method on
`StaticPasteboard` (see below).

* platform/Pasteboard.cpp:
(WebCore::Pasteboard::canWriteTrustworthyWebURLsPboardType):

On non-macOS ports, return false.

* platform/Pasteboard.h:
* platform/StaticPasteboard.cpp:
(WebCore::StaticPasteboard::hasNonDefaultData const):

Keep track of whether the page attempted to stage any custom data during "dragstart" by maintaining the set of
types written by the page, via calls to `writeString()` and similar. I'm using a set of types here instead of a
simple `bool` flag to ensure correctness in the case where the page adds a type, and then later removes that
same custom type, such that there is no longer non-default data.

(WebCore::StaticPasteboard::writeString):
(WebCore::StaticPasteboard::writeData):
(WebCore::StaticPasteboard::writeStringInCustomData):
(WebCore::StaticPasteboard::clear):

See above.

(WebCore::StaticPasteboard::writeMarkup):
(WebCore::StaticPasteboard::writePlainText):
(WebCore::StaticPasteboard::write):

Implement these methods by writing to the `PasteboardCustomData`. These methods are invoked by our own code
rather than the bindings, and should only be used to stage default data types when starting a drag.

* platform/StaticPasteboard.h:
* platform/mac/PasteboardMac.mm:
(WebCore::Pasteboard::write):
(WebCore::Pasteboard::canWriteTrustworthyWebURLsPboardType):

Tools:

Adds new API tests and test infrastructure to verify that DataTransfer types and data are accessible during
the "dragstart" event. See below for more details.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/DragAndDropTests.mm:
(-[TestWKWebView selectElementWithID:]):
(-[DragAndDropSimulator dragFromElementWithID:to:]):

Add a few (very specialized) helper methods to assist with simulating drags over the various elements in the new
drag and drop test harness page below (dragstart-data.html).

(runDragStartDataTestCase):

Test the following scenarios (split between three API tests) by dumping the resulting DataTransfer types and
their data strings, and observing the results:

- Dragging a normal text selection.
- Dragging a normal text selection, and then adding a URL string.
- Dragging a normal text selection, and then adding a custom pasteboard type.
- Dragging a normal text selection, but then replacing the data with just a URL string.
- Dragging a normal text selection, but then replacing the data with just a custom data type.
- Dragging an image element.
- Dragging an image element, and then overriding the plain text data.
- Dragging a link (anchor element).
- Dragging a link, and then adding a custom type.

* TestWebKitAPI/Tests/WebKitCocoa/dragstart-data.html: Added.

Add a new test harness to help test DataTransfer types when starting a drag. This test page can also be used as
a manual test harness, by simply opening the test page, starting drags on the various elements and observing the
output in the textarea.

* TestWebKitAPI/Tests/WebKitCocoa/dump-datatransfer-types.html:

Tweak this test page to replace the DataTransfer with custom data (rather than simply append it) by calling
`DataTransfer.clearData()` prior to writing the custom types.

* TestWebKitAPI/Tests/ios/DragAndDropTestsIOS.mm:
* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[TestWKWebViewHostWindow initWithWebView:contentRect:styleMask:backing:defer:]):

Add a `__weak` reference on TestWKWebViewHostWindow back to the TestWKWebView, so that we can consult
`-eventTimestamp` when synthesizing mouse events on macOS during API tests.

(-[TestWKWebViewHostWindow _mouseDownAtPoint:simulatePressure:clickCount:]):
(-[TestWKWebViewHostWindow _mouseUpAtPoint:clickCount:]):
(-[TestWKWebViewHostWindow initWithWebView:frame:]):
(-[TestWKWebView _setUpTestWindow:]):
(-[TestWKWebView setEventTimestampOffset:]):
(-[TestWKWebView eventTimestamp]):

Add a mechanism to offset synthetic event timestamps by a given time interval (i.e. the event timestamp offset).

(-[TestWKWebView mouseMoveToPoint:withFlags:]):
(-[TestWKWebView _mouseEventWithType:atLocation:]):
(-[TestWKWebView typeCharacter:]):
* TestWebKitAPI/mac/DragAndDropSimulatorMac.mm:
(-[DragAndDropSimulator runFrom:to:]):

While simulating drag and drop on macOS, use `-setEventTimestampOffset:` to "leap forward" in time, so that the
150 millisecond delay when dragging a text selection doesn't prevent drags from beginning.

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

21 files changed:
Source/WebCore/ChangeLog
Source/WebCore/dom/DataTransfer.cpp
Source/WebCore/editing/cocoa/EditorCocoa.mm
Source/WebCore/editing/ios/EditorIOS.mm
Source/WebCore/editing/mac/EditorMac.mm
Source/WebCore/page/DragController.cpp
Source/WebCore/page/EventHandler.cpp
Source/WebCore/platform/Pasteboard.cpp
Source/WebCore/platform/Pasteboard.h
Source/WebCore/platform/StaticPasteboard.cpp
Source/WebCore/platform/StaticPasteboard.h
Source/WebCore/platform/mac/PasteboardMac.mm
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/DragAndDropTests.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/dragstart-data.html [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitCocoa/dump-datatransfer-types.html
Tools/TestWebKitAPI/Tests/ios/DragAndDropTestsIOS.mm
Tools/TestWebKitAPI/cocoa/TestWKWebView.h
Tools/TestWebKitAPI/cocoa/TestWKWebView.mm
Tools/TestWebKitAPI/mac/DragAndDropSimulatorMac.mm

index 826da45..6dc66ad 100644 (file)
@@ -1,3 +1,101 @@
+2020-06-03  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        dataTransfer.types is empty when handling the "dragstart" event
+        https://bugs.webkit.org/show_bug.cgi?id=212685
+        <rdar://problem/61368402>
+
+        Reviewed by Andy Estes.
+
+        Implements several currently stubbed methods on StaticPasteboard, so that the DataTransfer provided to the page
+        on the "dragstart" event contains the DOM-exposed data types that will be written to the system pasteboard. This
+        includes "text/html", "text/plain", and "text/uri-list".
+
+        Tests:  DragAndDropTests.DataTransferTypesOnDragStartForTextSelection
+                DragAndDropTests.DataTransferTypesOnDragStartForImage
+                DragAndDropTests.DataTransferTypesOnDragStartForLink
+
+        ...as well as several existing tests in DragAndDropTestsIOS.mm that attempt to set pasteboard data during the
+        dragstart event:
+
+                DragAndDropTests.DataTransferSanitizeHTML
+                DragAndDropTests.DataTransferSetDataCannotWritePlatformTypes
+                DragAndDropTests.DataTransferSetDataInvalidURL
+                DragAndDropTests.DataTransferSetDataUnescapedURL
+                DragAndDropTests.DataTransferSetDataValidURL
+
+        * dom/DataTransfer.cpp:
+        (WebCore::DataTransfer::commitToPasteboard):
+
+        Only commit data to the native pasteboard if the page actually tried to write or modify the data. This allows us
+        to preserve existing behavior by allowing DragController to write dragged data to the pasteboard normally in the
+        case where the page didn't specify any custom data. In the case where the page does specify custom data, we will
+        write this custom data *in addition* to any default data that was written to the static pasteboard. While this
+        is a departure from our current behavior (which is to treat the pasteboard as a blank slate that contains only
+        whatever custom data was provided by the page), it matches behavior in both Chrome and Firefox, and is likely
+        more compatible with webpages that don't have UA-specific logic targeting WebKit.
+
+        * editing/cocoa/EditorCocoa.mm:
+        (WebCore::Editor::writeSelectionToPasteboard):
+
+        Avoid calling into the injected bundle (as well as writing a few particular non-web-exposed types, such as web
+        archive data) in the case where we're writing to a static pasteboard (there's no point in doing this for the
+        static pasteboard, and in the worst case, it could confuse some internal clients).
+
+        * editing/ios/EditorIOS.mm:
+        (WebCore::Editor::writeImageToPasteboard): Ditto.
+        * editing/mac/EditorMac.mm:
+        (WebCore::Editor::writeImageToPasteboard):
+
+        Ditto. But additionally, introduce a markup string to PasteboardImage, so that we will expose the "text/html"
+        type when starting a drag on an image element.
+
+        * page/DragController.cpp:
+        (WebCore::DragController::startDrag):
+
+        Only attempt to call into `Pasteboard::writeTrustworthyWebURLsPboardType` in the case where the pasteboard
+        supports this type (i.e. on macOS). This fixes an existing assertion that was hit by my new API test, which
+        attempts to override the contents of the pasteboard with custom data while starting a drag on a link.
+
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::handleDrag):
+
+        Since the StaticPasteboard contains data before the page has written anything, don't use `Pasteboard::hasData()`
+        to determine whether there's custom data; instead, use the new `hasNonDefaultData()` method on
+        `StaticPasteboard` (see below).
+
+        * platform/Pasteboard.cpp:
+        (WebCore::Pasteboard::canWriteTrustworthyWebURLsPboardType):
+
+        On non-macOS ports, return false.
+
+        * platform/Pasteboard.h:
+        * platform/StaticPasteboard.cpp:
+        (WebCore::StaticPasteboard::hasNonDefaultData const):
+
+        Keep track of whether the page attempted to stage any custom data during "dragstart" by maintaining the set of
+        types written by the page, via calls to `writeString()` and similar. I'm using a set of types here instead of a
+        simple `bool` flag to ensure correctness in the case where the page adds a type, and then later removes that
+        same custom type, such that there is no longer non-default data.
+
+        (WebCore::StaticPasteboard::writeString):
+        (WebCore::StaticPasteboard::writeData):
+        (WebCore::StaticPasteboard::writeStringInCustomData):
+        (WebCore::StaticPasteboard::clear):
+
+        See above.
+
+        (WebCore::StaticPasteboard::writeMarkup):
+        (WebCore::StaticPasteboard::writePlainText):
+        (WebCore::StaticPasteboard::write):
+
+        Implement these methods by writing to the `PasteboardCustomData`. These methods are invoked by our own code
+        rather than the bindings, and should only be used to stage default data types when starting a drag.
+
+        * platform/StaticPasteboard.h:
+        * platform/mac/PasteboardMac.mm:
+        (WebCore::Pasteboard::write):
+        (WebCore::Pasteboard::canWriteTrustworthyWebURLsPboardType):
+
 2020-06-03  Jer Noble  <jer.noble@apple.com>
 
         Crash with uncaught exception: *** -[AVSampleBufferAudioRenderer enqueueSampleBuffer:] Sample buffer has media type 'vide' instead of 'soun'
index 8463a48..904bdd9 100644 (file)
@@ -424,8 +424,8 @@ Ref<DataTransfer> DataTransfer::createForInputEvent(const String& plainText, con
 void DataTransfer::commitToPasteboard(Pasteboard& nativePasteboard)
 {
     ASSERT(is<StaticPasteboard>(*m_pasteboard) && !is<StaticPasteboard>(nativePasteboard));
-    PasteboardCustomData customData = downcast<StaticPasteboard>(*m_pasteboard).takeCustomData();
-    if (!customData.hasData()) {
+    auto& staticPasteboard = downcast<StaticPasteboard>(*m_pasteboard);
+    if (!staticPasteboard.hasNonDefaultData()) {
         // We clear the platform pasteboard here to ensure that the pasteboard doesn't contain any data
         // that may have been written before starting the drag or copying, and after ending the last
         // drag session or paste. After pushing the static pasteboard's contents to the platform, the
@@ -434,6 +434,7 @@ void DataTransfer::commitToPasteboard(Pasteboard& nativePasteboard)
         return;
     }
 
+    auto customData = staticPasteboard.takeCustomData();
     if (RuntimeEnabledFeatures::sharedFeatures().customPasteboardDataEnabled()) {
         customData.setOrigin(m_originIdentifier);
         nativePasteboard.writeCustomData({ customData });
index eb5fadb..dce641e 100644 (file)
@@ -119,13 +119,15 @@ void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
     PasteboardWebContent content;
     content.contentOrigin = m_document.originIdentifierForPasteboard();
     content.canSmartCopyOrDelete = canSmartCopyOrDelete();
-    content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
-    content.dataInRTFDFormat = [string containsAttachments] ? dataInRTFDFormat(string.get()) : nullptr;
-    content.dataInRTFFormat = dataInRTFFormat(string.get());
-    content.dataInAttributedStringFormat = archivedDataForAttributedString(string.get());
+    if (!pasteboard.isStatic()) {
+        content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
+        content.dataInRTFDFormat = [string containsAttachments] ? dataInRTFDFormat(string.get()) : nullptr;
+        content.dataInRTFFormat = dataInRTFFormat(string.get());
+        content.dataInAttributedStringFormat = archivedDataForAttributedString(string.get());
+        client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
+    }
     content.dataInHTMLFormat = selectionInHTMLFormat();
     content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
-    client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
 
     pasteboard.write(content);
 }
index 6724db8..312325f 100644 (file)
@@ -206,10 +206,12 @@ void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElemen
     pasteboardImage.resourceMIMEType = pasteboard.resourceMIMEType(cachedImage->response().mimeType());
     pasteboardImage.resourceData = cachedImage->resourceBuffer();
 
-    Position beforeImagePosition(&imageElement, Position::PositionIsBeforeAnchor);
-    Position afterImagePosition(&imageElement, Position::PositionIsAfterAnchor);
-    auto imageRange = Range::create(imageElement.document(), beforeImagePosition, afterImagePosition);
-    client()->getClientPasteboardDataForRange(imageRange.ptr(), pasteboardImage.clientTypes, pasteboardImage.clientData);
+    if (!pasteboard.isStatic()) {
+        Position beforeImagePosition(&imageElement, Position::PositionIsBeforeAnchor);
+        Position afterImagePosition(&imageElement, Position::PositionIsAfterAnchor);
+        auto imageRange = Range::create(imageElement.document(), beforeImagePosition, afterImagePosition);
+        client()->getClientPasteboardDataForRange(imageRange.ptr(), pasteboardImage.clientTypes, pasteboardImage.clientData);
+    }
 
     pasteboard.write(pasteboardImage);
 }
index a741e51..1cd8c39 100644 (file)
@@ -250,7 +250,14 @@ void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElemen
         return;
     ASSERT(cachedImage);
 
-    pasteboardImage.dataInWebArchiveFormat = imageInWebArchiveFormat(imageElement);
+    if (!pasteboard.isStatic())
+        pasteboardImage.dataInWebArchiveFormat = imageInWebArchiveFormat(imageElement);
+
+    if (auto imageRange = makeRangeSelectingNode(imageElement)) {
+        auto serializeComposedTree = m_document.settings().selectionAcrossShadowBoundariesEnabled() ? SerializeComposedTree::Yes : SerializeComposedTree::No;
+        pasteboardImage.dataInHTMLFormat = serializePreservingVisualAppearance(VisibleSelection { *imageRange }, ResolveURLs::YesExcludingLocalFileURLsForPrivacy, serializeComposedTree);
+    }
+
     pasteboardImage.url.url = url;
     pasteboardImage.url.title = title;
     pasteboardImage.url.userVisibleForm = WTF::userVisibleString(pasteboardImage.url.url);
index 4ee2e2f..4193d4d 100644 (file)
@@ -1123,7 +1123,7 @@ bool DragController::startDrag(Frame& src, const DragState& state, DragOperation
                 src.editor().copyURL(linkURL, textContentWithSimplifiedWhiteSpace, dataTransfer.pasteboard());
             else
                 pasteboardWriterData.setURLData(src.editor().pasteboardWriterURL(linkURL, textContentWithSimplifiedWhiteSpace));
-        } else {
+        } else if (dataTransfer.pasteboard().canWriteTrustworthyWebURLsPboardType()) {
             // Make sure the pasteboard also contains trustworthy link data
             // but don't overwrite more general pasteboard types.
             PasteboardURL pasteboardURL;
index d5577ee..37d4b41 100644 (file)
@@ -3872,13 +3872,14 @@ bool EventHandler::handleDrag(const MouseEventWithHitTestResults& event, CheckDr
     invalidateDataTransfer();
 
     dragState().dataTransfer = DataTransfer::createForDrag();
-    HasNonDefaultPasteboardData hasNonDefaultPasteboardData = HasNonDefaultPasteboardData::No;
+    auto hasNonDefaultPasteboardData = HasNonDefaultPasteboardData::No;
     
     if (dragState().shouldDispatchEvents) {
         ASSERT(dragState().source);
         auto dragStartDataTransfer = DataTransfer::createForDragStartEvent(dragState().source->document());
         m_mouseDownMayStartDrag = dispatchDragStartEventOnSourceElement(dragStartDataTransfer);
-        hasNonDefaultPasteboardData = dragStartDataTransfer->pasteboard().hasData() ? HasNonDefaultPasteboardData::Yes : HasNonDefaultPasteboardData::No;
+        if (downcast<StaticPasteboard>(dragStartDataTransfer->pasteboard()).hasNonDefaultData())
+            hasNonDefaultPasteboardData = HasNonDefaultPasteboardData::Yes;
         dragState().dataTransfer->moveDragState(WTFMove(dragStartDataTransfer));
 
         if (dragState().source && dragState().type == DragSourceActionDHTML && !dragState().dataTransfer->hasDragImage()) {
index 51064ff..9f16d88 100644 (file)
@@ -103,4 +103,13 @@ URL Pasteboard::readURL(size_t index, String& title)
     return { };
 }
 
+#if !PLATFORM(MAC)
+
+bool Pasteboard::canWriteTrustworthyWebURLsPboardType()
+{
+    return false;
+}
+
+#endif
+
 };
index dcc0a39..e84ed1e 100644 (file)
@@ -118,6 +118,7 @@ struct PasteboardImage {
     RefPtr<Image> image;
 #if PLATFORM(MAC)
     RefPtr<SharedBuffer> dataInWebArchiveFormat;
+    String dataInHTMLFormat;
 #endif
 #if !PLATFORM(WIN)
     PasteboardURL url;
@@ -214,6 +215,8 @@ public:
     virtual WEBCORE_EXPORT void read(PasteboardWebContentReader&, WebContentReadingPolicy = WebContentReadingPolicy::AnyType, Optional<size_t> itemIndex = WTF::nullopt);
     virtual WEBCORE_EXPORT void read(PasteboardFileReader&, Optional<size_t> itemIndex = WTF::nullopt);
 
+    static bool canWriteTrustworthyWebURLsPboardType();
+
     virtual WEBCORE_EXPORT void write(const Color&);
     virtual WEBCORE_EXPORT void write(const PasteboardURL&);
     virtual WEBCORE_EXPORT void writeTrustworthyWebURLsPboardType(const PasteboardURL&);
@@ -222,7 +225,7 @@ public:
 
     virtual WEBCORE_EXPORT void writeCustomData(const Vector<PasteboardCustomData>&);
 
-    enum class FileContentState { NoFileOrImageData, InMemoryImage, MayContainFilePaths };
+    enum class FileContentState : uint8_t { NoFileOrImageData, InMemoryImage, MayContainFilePaths };
     virtual WEBCORE_EXPORT FileContentState fileContentState();
     virtual WEBCORE_EXPORT bool canSmartReplace();
 
index e480666..6f321fa 100644 (file)
@@ -58,28 +58,39 @@ String StaticPasteboard::readStringInCustomData(const String& type)
     return m_customData.readStringInCustomData(type);
 }
 
+bool StaticPasteboard::hasNonDefaultData() const
+{
+    return !m_nonDefaultDataTypes.isEmpty();
+}
+
 void StaticPasteboard::writeString(const String& type, const String& value)
 {
+    m_nonDefaultDataTypes.add(type);
     m_customData.writeString(type, value);
 }
 
 void StaticPasteboard::writeData(const String& type, Ref<SharedBuffer>&& data)
 {
+    m_nonDefaultDataTypes.add(type);
     m_customData.writeData(type, WTFMove(data));
 }
 
 void StaticPasteboard::writeStringInCustomData(const String& type, const String& value)
 {
+    m_nonDefaultDataTypes.add(type);
     m_customData.writeStringInCustomData(type, value);
 }
 
 void StaticPasteboard::clear()
 {
+    m_nonDefaultDataTypes.clear();
+    m_fileContentState = Pasteboard::FileContentState::NoFileOrImageData;
     m_customData.clear();
 }
 
 void StaticPasteboard::clear(const String& type)
 {
+    m_nonDefaultDataTypes.remove(type);
     m_customData.clear(type);
 }
 
@@ -88,4 +99,60 @@ PasteboardCustomData StaticPasteboard::takeCustomData()
     return std::exchange(m_customData, { });
 }
 
+void StaticPasteboard::writeMarkup(const String& markup)
+{
+    m_customData.writeString("text/html"_s, markup);
+}
+
+void StaticPasteboard::writePlainText(const String& text, SmartReplaceOption)
+{
+    m_customData.writeString("text/plain"_s, text);
+}
+
+void StaticPasteboard::write(const PasteboardURL& url)
+{
+    m_customData.writeString("text/uri-list"_s, url.url.string());
+}
+
+void StaticPasteboard::write(const PasteboardImage& image)
+{
+    // FIXME: This should ideally remember the image data, so that when this StaticPasteboard
+    // is committed to the native pasteboard, we'll preserve the image as well. For now, stick
+    // with our existing behavior, which prevents image data from being copied in the case where
+    // any non-default data was written by the page.
+    m_fileContentState = Pasteboard::FileContentState::InMemoryImage;
+
+#if PLATFORM(MAC)
+    if (!image.dataInHTMLFormat.isEmpty())
+        writeMarkup(image.dataInHTMLFormat);
+#else
+    UNUSED_PARAM(image);
+#endif
+
+#if !PLATFORM(WIN)
+    if (Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(image.url.url.string()))
+        write(image.url);
+#endif
+}
+
+void StaticPasteboard::write(const PasteboardWebContent& content)
+{
+    String markup;
+    String text;
+
+#if PLATFORM(COCOA)
+    markup = content.dataInHTMLFormat;
+    text = content.dataInStringFormat;
+#elif PLATFORM(GTK) || USE(LIBWPE)
+    markup = content.markup;
+    text = content.text;
+#endif
+
+    if (!markup.isEmpty())
+        writeMarkup(markup);
+
+    if (!text.isEmpty())
+        writePlainText(text, SmartReplaceOption::CannotSmartReplace);
+}
+
 }
index a6d50d8..69f07f4 100644 (file)
@@ -41,6 +41,7 @@ public:
 
     PasteboardCustomData takeCustomData();
 
+    bool hasNonDefaultData() const;
     bool isStatic() const final { return true; }
 
     bool hasData() final;
@@ -59,24 +60,25 @@ public:
     void read(PasteboardPlainText&, PlainTextURLReadingPolicy = PlainTextURLReadingPolicy::AllowURL, Optional<size_t> = WTF::nullopt) final { }
     void read(PasteboardWebContentReader&, WebContentReadingPolicy, Optional<size_t> = WTF::nullopt) final { }
 
-    void write(const PasteboardURL&) final { }
-    void write(const PasteboardImage&) final { }
-    void write(const PasteboardWebContent&) final { }
+    void write(const PasteboardURL&) final;
+    void write(const PasteboardImage&) final;
+    void write(const PasteboardWebContent&) final;
+    void writeMarkup(const String&) final;
+    void writePlainText(const String&, SmartReplaceOption) final;
 
     void writeCustomData(const Vector<PasteboardCustomData>&) final { }
 
-    Pasteboard::FileContentState fileContentState() final { return FileContentState::NoFileOrImageData; }
+    Pasteboard::FileContentState fileContentState() final { return m_fileContentState; }
     bool canSmartReplace() final { return false; }
 
-    void writeMarkup(const String&) final { }
-    void writePlainText(const String&, SmartReplaceOption) final { }
-
 #if ENABLE(DRAG_SUPPORT)
     void setDragImage(DragImage, const IntPoint&) final { }
 #endif
 
 private:
     PasteboardCustomData m_customData;
+    HashSet<String> m_nonDefaultDataTypes;
+    Pasteboard::FileContentState m_fileContentState { Pasteboard::FileContentState::NoFileOrImageData };
 };
 
 }
index 3008c6c..cfeb1be 100644 (file)
@@ -281,6 +281,8 @@ void Pasteboard::write(const PasteboardImage& pasteboardImage)
         m_changeCount = platformStrategies()->pasteboardStrategy()->setBufferForType(archiveData.get(), WebArchivePboardType, m_pasteboardName);
         m_changeCount = platformStrategies()->pasteboardStrategy()->setBufferForType(archiveData.get(), kUTTypeWebArchive, m_pasteboardName);
     }
+    if (!pasteboardImage.dataInHTMLFormat.isEmpty())
+        m_changeCount = platformStrategies()->pasteboardStrategy()->setStringForType(pasteboardImage.dataInHTMLFormat, legacyHTMLPasteboardType(), m_pasteboardName);
     writeFileWrapperAsRTFDAttachment(fileWrapper(pasteboardImage), m_pasteboardName, m_changeCount);
 }
 
@@ -789,6 +791,11 @@ void Pasteboard::setDragImage(DragImage image, const IntPoint& location)
 }
 #endif
 
+bool Pasteboard::canWriteTrustworthyWebURLsPboardType()
+{
+    return true;
+}
+
 }
 
 #endif // PLATFORM(MAC)
index ecda65e..22dec94 100644 (file)
@@ -1,3 +1,74 @@
+2020-06-03  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        dataTransfer.types is empty when handling the "dragstart" event
+        https://bugs.webkit.org/show_bug.cgi?id=212685
+        <rdar://problem/61368402>
+
+        Reviewed by Andy Estes.
+
+        Adds new API tests and test infrastructure to verify that DataTransfer types and data are accessible during
+        the "dragstart" event. See below for more details.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/DragAndDropTests.mm:
+        (-[TestWKWebView selectElementWithID:]):
+        (-[DragAndDropSimulator dragFromElementWithID:to:]):
+
+        Add a few (very specialized) helper methods to assist with simulating drags over the various elements in the new
+        drag and drop test harness page below (dragstart-data.html).
+
+        (runDragStartDataTestCase):
+
+        Test the following scenarios (split between three API tests) by dumping the resulting DataTransfer types and
+        their data strings, and observing the results:
+
+        - Dragging a normal text selection.
+        - Dragging a normal text selection, and then adding a URL string.
+        - Dragging a normal text selection, and then adding a custom pasteboard type.
+        - Dragging a normal text selection, but then replacing the data with just a URL string.
+        - Dragging a normal text selection, but then replacing the data with just a custom data type.
+        - Dragging an image element.
+        - Dragging an image element, and then overriding the plain text data.
+        - Dragging a link (anchor element).
+        - Dragging a link, and then adding a custom type.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/dragstart-data.html: Added.
+
+        Add a new test harness to help test DataTransfer types when starting a drag. This test page can also be used as
+        a manual test harness, by simply opening the test page, starting drags on the various elements and observing the
+        output in the textarea.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/dump-datatransfer-types.html:
+
+        Tweak this test page to replace the DataTransfer with custom data (rather than simply append it) by calling
+        `DataTransfer.clearData()` prior to writing the custom types.
+
+        * TestWebKitAPI/Tests/ios/DragAndDropTestsIOS.mm:
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[TestWKWebViewHostWindow initWithWebView:contentRect:styleMask:backing:defer:]):
+
+        Add a `__weak` reference on TestWKWebViewHostWindow back to the TestWKWebView, so that we can consult
+        `-eventTimestamp` when synthesizing mouse events on macOS during API tests.
+
+        (-[TestWKWebViewHostWindow _mouseDownAtPoint:simulatePressure:clickCount:]):
+        (-[TestWKWebViewHostWindow _mouseUpAtPoint:clickCount:]):
+        (-[TestWKWebViewHostWindow initWithWebView:frame:]):
+        (-[TestWKWebView _setUpTestWindow:]):
+        (-[TestWKWebView setEventTimestampOffset:]):
+        (-[TestWKWebView eventTimestamp]):
+
+        Add a mechanism to offset synthetic event timestamps by a given time interval (i.e. the event timestamp offset).
+
+        (-[TestWKWebView mouseMoveToPoint:withFlags:]):
+        (-[TestWKWebView _mouseEventWithType:atLocation:]):
+        (-[TestWKWebView typeCharacter:]):
+        * TestWebKitAPI/mac/DragAndDropSimulatorMac.mm:
+        (-[DragAndDropSimulator runFrom:to:]):
+
+        While simulating drag and drop on macOS, use `-setEventTimestampOffset:` to "leap forward" in time, so that the
+        150 millisecond delay when dragging a text selection doesn't prevent drags from beginning.
+
 2020-06-03  Sihui Liu  <sihui_liu@apple.com>
 
         Text manipulation sometimes fails to replace text in attributes
index 5800565..429ea99 100644 (file)
                F469FB241F01804B00401539 /* contenteditable-and-target.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F469FB231F01803500401539 /* contenteditable-and-target.html */; };
                F46A095A1ED8A6E600D4AA55 /* apple.gif in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47D30EB1ED28619000482E1 /* apple.gif */; };
                F46A095B1ED8A6E600D4AA55 /* gif-and-file-input.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47D30ED1ED28A6C000482E1 /* gif-and-file-input.html */; };
+               F46BD56924870643008282D6 /* dragstart-data.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F46BD5682487062D008282D6 /* dragstart-data.html */; };
                F47728991E4AE3C1007ABF6A /* full-page-contenteditable.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */; };
                F47DFB2621A878DF00021FB6 /* data-detectors.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F47DFB2421A8704A00021FB6 /* data-detectors.html */; };
                F4811E5921940BDE00A5E0FD /* WKWebViewEditActions.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4811E5821940B4400A5E0FD /* WKWebViewEditActions.mm */; };
                                5714ECBB1CA8BFE400051AC8 /* DownloadRequestOriginalURLFrame.html in Copy Resources */,
                                F4A32EC41F05F3850047C544 /* dragstart-change-selection-offscreen.html in Copy Resources */,
                                F4D5E4E81F0C5D38008C1A49 /* dragstart-clear-selection.html in Copy Resources */,
+                               F46BD56924870643008282D6 /* dragstart-data.html in Copy Resources */,
                                F4194AD11F5A320100ADD83F /* drop-targets.html in Copy Resources */,
                                2EB29D5E1F762DB90023A5F1 /* dump-datatransfer-types.html in Copy Resources */,
                                A155022C1E050D0300A24C57 /* duplicate-completion-handler-calls.html in Copy Resources */,
                F46849BD1EEF58E400B937FE /* UIPasteboardTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = UIPasteboardTests.mm; sourceTree = "<group>"; };
                F46849BF1EEF5EDC00B937FE /* rich-and-plain-text.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "rich-and-plain-text.html"; sourceTree = "<group>"; };
                F469FB231F01803500401539 /* contenteditable-and-target.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "contenteditable-and-target.html"; sourceTree = "<group>"; };
+               F46BD5682487062D008282D6 /* dragstart-data.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "dragstart-data.html"; sourceTree = "<group>"; };
                F47728981E4AE3AD007ABF6A /* full-page-contenteditable.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "full-page-contenteditable.html"; sourceTree = "<group>"; };
                F47D30EB1ED28619000482E1 /* apple.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = apple.gif; sourceTree = "<group>"; };
                F47D30ED1ED28A6C000482E1 /* gif-and-file-input.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "gif-and-file-input.html"; sourceTree = "<group>"; };
                                5714ECBA1CA8BFD100051AC8 /* DownloadRequestOriginalURLFrame.html */,
                                F4A32EC31F05F3780047C544 /* dragstart-change-selection-offscreen.html */,
                                F4D5E4E71F0C5D27008C1A49 /* dragstart-clear-selection.html */,
+                               F46BD5682487062D008282D6 /* dragstart-data.html */,
                                F4194AD01F5A2EA500ADD83F /* drop-targets.html */,
                                2EB29D5D1F762DA50023A5F1 /* dump-datatransfer-types.html */,
                                A155022B1E050BC500A24C57 /* duplicate-completion-handler-calls.html */,
index 33df159..e77e5a5 100644 (file)
@@ -24,6 +24,7 @@
  */
 
 #import "config.h"
+#import "Test.h"
 
 #if ENABLE(DRAG_SUPPORT) && !PLATFORM(MACCATALYST)
 
 #import <MobileCoreServices/MobileCoreServices.h>
 #endif
 
+@implementation TestWKWebView (DragAndDropTests)
+
+- (void)selectElementWithID:(NSString *)elementID
+{
+    [self evaluateJavaScript:[NSString stringWithFormat:@"getSelection().selectAllChildren(document.getElementById('%@'))", elementID] completionHandler:nil];
+    [self waitForNextPresentationUpdate];
+}
+
+@end
+
+@implementation DragAndDropSimulator (DragAndDropTests)
+
+- (void)dragFromElementWithID:(NSString *)elementID to:(CGPoint)destination
+{
+    NSArray<NSNumber *> *rectValues = [self.webView objectByEvaluatingJavaScript:[NSString stringWithFormat:@"(() => {"
+        "    const element = document.getElementById('%@');"
+        "    const bounds = element.getBoundingClientRect();"
+        "    return [bounds.left, bounds.top, bounds.width, bounds.height];"
+        "})();", elementID]];
+
+    auto bounds = CGRectMake(rectValues[0].floatValue, rectValues[1].floatValue, rectValues[2].floatValue, rectValues[3].floatValue);
+    auto midPoint = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
+    [self runFrom:midPoint to:destination];
+}
+
+@end
+
 TEST(DragAndDropTests, ModernWebArchiveType)
 {
     NSData *markupData = [@"<strong><i>Hello world</i></strong>" dataUsingEncoding:NSUTF8StringEncoding];
@@ -160,6 +188,127 @@ TEST(DragAndDropTests, PreventingMouseDownShouldPreventDragStart)
     EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"observedMousedown"].boolValue);
 }
 
+struct DragStartData {
+    BOOL containsFile { NO };
+    NSString *text { nil };
+    NSString *url { nil };
+    NSString *html { nil };
+    NSDictionary<NSString *, NSString *> *customData { nil };
+};
+
+static DragStartData runDragStartDataTestCase(DragAndDropSimulator *simulator, NSString *elementID)
+{
+    auto webView = [simulator webView];
+    NSString *tagName = [webView stringByEvaluatingJavaScript:[NSString stringWithFormat:@"document.getElementById('%@').tagName", elementID]];
+    if (![tagName isEqualToString:@"IMG"] && ![tagName isEqualToString:@"A"])
+        [webView selectElementWithID:elementID];
+
+    [simulator dragFromElementWithID:elementID to:CGPointMake(400, 400)];
+    RetainPtr<NSMutableDictionary<NSString *, NSString *>> allData = adoptNS([[webView objectByEvaluatingJavaScript:@"allData"] mutableCopy]);
+    DragStartData result;
+    result.text = [allData objectForKey:@"text/plain"];
+    result.url = [allData objectForKey:@"text/uri-list"];
+    result.html = [allData objectForKey:@"text/html"];
+    result.containsFile = !![allData objectForKey:@"Files"];
+    [allData removeObjectForKey:@"text/plain"];
+    [allData removeObjectForKey:@"text/uri-list"];
+    [allData removeObjectForKey:@"text/html"];
+    [allData removeObjectForKey:@"Files"];
+    if ([allData count])
+        result.customData = allData.autorelease();
+    return result;
+}
+
+TEST(DragAndDropTests, DataTransferTypesOnDragStartForTextSelection)
+{
+    auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:CGRectMake(0, 0, 500, 500)]);
+    [[simulator webView] synchronouslyLoadTestPageNamed:@"dragstart-data"];
+
+    auto result = runDragStartDataTestCase(simulator.get(), @"regular");
+    EXPECT_WK_STREQ("Regular text", result.text);
+    EXPECT_NULL(result.url);
+    EXPECT_TRUE([result.html containsString:@"Regular text"]);
+    EXPECT_NULL(result.customData);
+    EXPECT_FALSE(result.containsFile);
+
+    result = runDragStartDataTestCase(simulator.get(), @"regular-url");
+    EXPECT_WK_STREQ("Regular text + URL data", result.text);
+    EXPECT_WK_STREQ("https://www.google.com", result.url);
+    EXPECT_TRUE([result.html containsString:@"Regular text + URL data"]);
+    EXPECT_NULL(result.customData);
+    EXPECT_FALSE(result.containsFile);
+
+    result = runDragStartDataTestCase(simulator.get(), @"regular-custom");
+    EXPECT_WK_STREQ("Regular text + custom data", result.text);
+    EXPECT_NULL(result.url);
+    EXPECT_TRUE([result.html containsString:@"Regular text + custom data"]);
+    EXPECT_WK_STREQ("Hello world", result.customData[@"text/foo"]);
+    EXPECT_FALSE(result.containsFile);
+
+    result = runDragStartDataTestCase(simulator.get(), @"url");
+    EXPECT_NULL(result.text);
+    EXPECT_WK_STREQ("https://www.google.com", result.url);
+    EXPECT_NULL([result.html containsString:@"Regular text + custom data"]);
+    EXPECT_NULL(result.customData);
+    EXPECT_FALSE(result.containsFile);
+
+    result = runDragStartDataTestCase(simulator.get(), @"custom");
+    EXPECT_NULL(result.text);
+    EXPECT_NULL(result.url);
+    EXPECT_NULL(result.html);
+    EXPECT_WK_STREQ("Hello world", result.customData[@"text/foo"]);
+    EXPECT_FALSE(result.containsFile);
+}
+
+TEST(DragAndDropTests, DataTransferTypesOnDragStartForImage)
+{
+    auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:CGRectMake(0, 0, 500, 500)]);
+    [[simulator webView] synchronouslyLoadTestPageNamed:@"dragstart-data"];
+
+    auto result = runDragStartDataTestCase(simulator.get(), @"image");
+    EXPECT_NULL(result.text);
+    // The URL should be nil here because the image source is a file URL, so we shoud avoid exposing it to the page.
+    EXPECT_NULL(result.url);
+#if PLATFORM(MAC)
+    EXPECT_TRUE([result.html containsString:@"<img"]);
+#else
+    EXPECT_NULL(result.html);
+#endif
+    EXPECT_NULL(result.customData);
+    EXPECT_TRUE(result.containsFile);
+
+    result = runDragStartDataTestCase(simulator.get(), @"image-text");
+    EXPECT_WK_STREQ("This is an image of Cupertino.", result.text);
+    EXPECT_NULL(result.url);
+#if PLATFORM(MAC)
+    EXPECT_TRUE([result.html containsString:@"<img"]);
+#else
+    EXPECT_NULL(result.html);
+#endif
+    EXPECT_NULL(result.customData);
+    EXPECT_TRUE(result.containsFile);
+}
+
+TEST(DragAndDropTests, DataTransferTypesOnDragStartForLink)
+{
+    auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:CGRectMake(0, 0, 500, 800)]);
+    [[simulator webView] synchronouslyLoadTestPageNamed:@"dragstart-data"];
+
+    auto result = runDragStartDataTestCase(simulator.get(), @"link");
+    EXPECT_NULL(result.text);
+    EXPECT_WK_STREQ("https://www.apple.com/", result.url);
+    EXPECT_NULL(result.html);
+    EXPECT_NULL(result.customData);
+    EXPECT_FALSE(result.containsFile);
+
+    result = runDragStartDataTestCase(simulator.get(), @"link-custom");
+    EXPECT_NULL(result.text);
+    EXPECT_WK_STREQ("https://www.apple.com/", result.url);
+    EXPECT_NULL(result.html);
+    EXPECT_WK_STREQ("bar", result.customData[@"text/foo"]);
+    EXPECT_FALSE(result.containsFile);
+}
+
 #if ENABLE(INPUT_TYPE_COLOR)
 
 TEST(DragAndDropTests, ColorInputToColorInput)
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/dragstart-data.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/dragstart-data.html
new file mode 100644 (file)
index 0000000..18b1db4
--- /dev/null
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width">
+<head>
+<style>
+body {
+    margin: 0;
+    padding: 0;
+}
+
+h1, a, p {
+    font-size: 20px;
+    font-family: system-ui;
+}
+
+h1 {
+    user-select: all;
+    -webkit-user-select: all;
+}
+
+a {
+    display: inline-block;
+    text-decoration: none;
+}
+
+a:active {
+    color: blue;
+}
+
+textarea {
+    margin: 1em;
+    width: calc(100% - 2em);
+    font-size: 16px;
+    font-family: monospace;
+}
+</style>
+</head>
+<body>
+    <h1 id="regular">Regular text</h1>
+    <h1 id="regular-url">Regular text + URL data</h1>
+    <h1 id="regular-custom">Regular text + custom data</h1>
+    <h1 id="url">URL data only</h1>
+    <h1 id="custom">Custom data only</h1>
+    <img id="image" src="sunset-in-cupertino-200px.png"></img>
+    <img id="image-text" src="sunset-in-cupertino-200px.png"></img>
+    <a id="link" href="https://www.apple.com/">Apple Homepage</a>
+    <a id="link-custom" href="https://www.apple.com/">Apple Homepage</a>
+    <textarea rows="20" readonly id="text-output"></textarea>
+</body>
+<script>
+window.allData = {};
+const textOutput = document.getElementById("text-output");
+
+function collectDataForEvent(event, element)
+{
+    allData = {};
+    for (let type of event.dataTransfer.types)
+        allData[type] = event.dataTransfer.getData(type);
+    textOutput.value = JSON.stringify(allData, null, 4);
+}
+
+const regular = document.getElementById("regular");
+regular.addEventListener("dragstart", event => collectDataForEvent(event, regular));
+
+const regularURL = document.getElementById("regular-url");
+regularURL.addEventListener("dragstart", event => {
+    event.dataTransfer.setData("text/uri-list", "https://www.google.com");
+    collectDataForEvent(event, regularURL);
+});
+
+const regularCustom = document.getElementById("regular-custom");
+regularCustom.addEventListener("dragstart", event => {
+    event.dataTransfer.setData("text/foo", "Hello world");
+    collectDataForEvent(event, regularCustom);
+});
+
+const url = document.getElementById("url");
+url.addEventListener("dragstart", event => {
+    event.dataTransfer.clearData();
+    event.dataTransfer.setData("text/uri-list", "https://www.google.com");
+    collectDataForEvent(event, url);
+});
+
+const custom = document.getElementById("custom");
+custom.addEventListener("dragstart", event => {
+    event.dataTransfer.clearData();
+    event.dataTransfer.setData("text/foo", "Hello world");
+    collectDataForEvent(event, custom);
+});
+
+const image = document.getElementById("image");
+image.addEventListener("dragstart", event => collectDataForEvent(event, image));
+
+const imageText = document.getElementById("image-text");
+imageText.addEventListener("dragstart", event => {
+    event.dataTransfer.setData("text/plain", "This is an image of Cupertino.");
+    collectDataForEvent(event, imageText);
+});
+
+const link = document.getElementById("link");
+link.addEventListener("dragstart", event => collectDataForEvent(event, link));
+
+const linkCustom = document.getElementById("link-custom");
+linkCustom.addEventListener("dragstart", event => {
+    event.dataTransfer.setData("text/foo", "bar");
+    collectDataForEvent(event, linkCustom);
+});
+</script>
+</html>
index d6e3542..eceb114 100644 (file)
@@ -100,6 +100,7 @@ function setCustomData(event) {
         return true;
 
     const pasteboard = event.dataTransfer || event.clipboardData;
+    pasteboard.clearData();
     for (const type in customData)
         pasteboard.setData(type, customData[type]);
 
index c844dd0..43b5888 100644 (file)
@@ -78,7 +78,7 @@ static NSData *testZIPArchive()
     return [NSData dataWithContentsOfURL:zipFileURL];
 }
 
-@implementation TestWKWebView (DragAndDropTests)
+@implementation TestWKWebView (DragAndDropTestsIOS)
 
 - (BOOL)editorContainsImageElement
 {
index a612cc0..4587429 100644 (file)
 - (NSWindow *)hostWindow;
 - (void)typeCharacter:(char)character;
 - (void)waitForPendingMouseEvents;
+- (void)setEventTimestampOffset:(NSTimeInterval)offset;
+@property (nonatomic, readonly) NSTimeInterval eventTimestamp;
 @end
 #endif
 
index 8be2df0..e2357a9 100644 (file)
@@ -271,9 +271,11 @@ static NSString *overrideBundleIdentifier(id, SEL)
 
 @implementation TestWKWebViewHostWindow {
     BOOL _forceKeyWindow;
+    __weak TestWKWebView *_webView;
 }
 
 #if PLATFORM(MAC)
+
 static int gEventNumber = 1;
 
 NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
@@ -281,6 +283,13 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
     return NSEventMaskPressure | NSEventMaskLeftMouseDown | NSEventMaskLeftMouseUp | NSEventMaskLeftMouseDragged;
 }
 
+- (instancetype)initWithWebView:(TestWKWebView *)webView contentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style backing:(NSBackingStoreType)backingStoreType defer:(BOOL)flag
+{
+    if (self = [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:flag])
+        _webView = webView;
+    return self;
+}
+
 - (void)_mouseDownAtPoint:(NSPoint)point simulatePressure:(BOOL)simulatePressure clickCount:(NSUInteger)clickCount
 {
     NSEventType mouseEventType = NSEventTypeLeftMouseDown;
@@ -289,7 +298,7 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
     if (simulatePressure)
         modifierFlags |= NSEventMaskPressure;
 
-    NSEvent *event = [NSEvent mouseEventWithType:mouseEventType location:point modifierFlags:modifierFlags timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:simulatePressure];
+    NSEvent *event = [NSEvent mouseEventWithType:mouseEventType location:point modifierFlags:modifierFlags timestamp:_webView.eventTimestamp windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:simulatePressure];
     if (!simulatePressure) {
         [self sendEvent:event];
         return;
@@ -309,9 +318,10 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
 
 - (void)_mouseUpAtPoint:(NSPoint)point clickCount:(NSUInteger)clickCount
 {
-    [self sendEvent:[NSEvent mouseEventWithType:NSEventTypeLeftMouseUp location:point modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]];
+    [self sendEvent:[NSEvent mouseEventWithType:NSEventTypeLeftMouseUp location:point modifierFlags:0 timestamp:_webView.eventTimestamp windowNumber:self.windowNumber context:[NSGraphicsContext currentContext] eventNumber:++gEventNumber clickCount:clickCount pressure:0]];
 }
-#endif // PLATFORM(MAC)
+
+#endif
 
 - (BOOL)isKeyWindow
 {
@@ -319,6 +329,7 @@ NSEventMask __simulated_forceClickAssociatedEventsMask(id self, SEL _cmd)
 }
 
 #if PLATFORM(IOS_FAMILY)
+
 static NeverDestroyed<RetainPtr<UIWindow>> gOverriddenApplicationKeyWindow;
 static NeverDestroyed<std::unique_ptr<InstanceMethodSwizzler>> gApplicationKeyWindowSwizzler;
 static NeverDestroyed<std::unique_ptr<InstanceMethodSwizzler>> gSharedApplicationSwizzler;
@@ -345,6 +356,14 @@ static UIWindow *applicationKeyWindowOverride(id, SEL)
 {
     return gOverriddenApplicationKeyWindow.get().get();
 }
+
+- (instancetype)initWithWebView:(TestWKWebView *)webView frame:(CGRect)frame
+{
+    if (self = [super initWithFrame:frame])
+        _webView = webView;
+    return self;
+}
+
 #endif
 
 - (void)makeKeyWindow
@@ -388,6 +407,9 @@ static InputSessionChangeCount nextInputSessionChangeCount()
     std::unique_ptr<ClassMethodSwizzler> _sharedCalloutBarSwizzler;
     InputSessionChangeCount _inputSessionChangeCount;
 #endif
+#if PLATFORM(MAC)
+    NSTimeInterval _eventTimestampOffset;
+#endif
 }
 
 - (instancetype)initWithFrame:(CGRect)frame
@@ -437,14 +459,14 @@ static UICalloutBar *suppressUICalloutBar()
 - (void)_setUpTestWindow:(NSRect)frame
 {
 #if PLATFORM(MAC)
-    _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithContentRect:frame styleMask:(NSWindowStyleMaskBorderless | NSWindowStyleMaskMiniaturizable) backing:NSBackingStoreBuffered defer:NO]);
+    _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithWebView:self contentRect:frame styleMask:(NSWindowStyleMaskBorderless | NSWindowStyleMaskMiniaturizable) backing:NSBackingStoreBuffered defer:NO]);
     [_hostWindow setFrameOrigin:NSMakePoint(0, 0)];
     [_hostWindow setIsVisible:YES];
     [_hostWindow contentView].wantsLayer = YES;
     [[_hostWindow contentView] addSubview:self];
     [_hostWindow makeKeyAndOrderFront:self];
 #else
-    _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithFrame:frame]);
+    _hostWindow = adoptNS([[TestWKWebViewHostWindow alloc] initWithWebView:self frame:frame]);
     [_hostWindow setHidden:NO];
     [_hostWindow addSubview:self];
 #endif
@@ -684,7 +706,19 @@ static WKContentView *recursiveFindWKContentView(UIView *view)
 #endif
 
 #if PLATFORM(MAC)
+
 @implementation TestWKWebView (MacOnly)
+
+- (void)setEventTimestampOffset:(NSTimeInterval)offset
+{
+    _eventTimestampOffset += offset;
+}
+
+- (NSTimeInterval)eventTimestamp
+{
+    return GetCurrentEventTime() + _eventTimestampOffset;
+}
+
 - (void)mouseDownAtPoint:(NSPoint)pointInWindow simulatePressure:(BOOL)simulatePressure
 {
     [_hostWindow _mouseDownAtPoint:pointInWindow simulatePressure:simulatePressure clickCount:1];
@@ -697,7 +731,7 @@ static WKContentView *recursiveFindWKContentView(UIView *view)
 
 - (void)mouseMoveToPoint:(NSPoint)pointInWindow withFlags:(NSEventModifierFlags)flags
 {
-    [self mouseMoved:[self _mouseEventWithType:NSEventTypeMouseMoved atLocation:pointInWindow flags:flags timestamp:GetCurrentEventTime() clickCount:0]];
+    [self mouseMoved:[self _mouseEventWithType:NSEventTypeMouseMoved atLocation:pointInWindow flags:flags timestamp:self.eventTimestamp clickCount:0]];
 }
 
 - (void)sendClicksAtPoint:(NSPoint)pointInWindow numberOfClicks:(NSUInteger)numberOfClicks
@@ -725,7 +759,7 @@ static WKContentView *recursiveFindWKContentView(UIView *view)
 
 - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)pointInWindow
 {
-    return [self _mouseEventWithType:type atLocation:pointInWindow flags:0 timestamp:GetCurrentEventTime() clickCount:0];
+    return [self _mouseEventWithType:type atLocation:pointInWindow flags:0 timestamp:self.eventTimestamp clickCount:0];
 }
 
 - (NSEvent *)_mouseEventWithType:(NSEventType)type atLocation:(NSPoint)locationInWindow flags:(NSEventModifierFlags)flags timestamp:(NSTimeInterval)timestamp clickCount:(NSUInteger)clickCount
@@ -749,8 +783,8 @@ static WKContentView *recursiveFindWKContentView(UIView *view)
     NSString *characterAsString = [NSString stringWithFormat:@"%c" , character];
     NSEventType keyDownEventType = NSEventTypeKeyDown;
     NSEventType keyUpEventType = NSEventTypeKeyUp;
-    [self keyDown:[NSEvent keyEventWithType:keyDownEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
-    [self keyUp:[NSEvent keyEventWithType:keyUpEventType location:NSZeroPoint modifierFlags:0 timestamp:GetCurrentEventTime() windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
+    [self keyDown:[NSEvent keyEventWithType:keyDownEventType location:NSZeroPoint modifierFlags:0 timestamp:self.eventTimestamp windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
+    [self keyUp:[NSEvent keyEventWithType:keyUpEventType location:NSZeroPoint modifierFlags:0 timestamp:self.eventTimestamp windowNumber:[_hostWindow windowNumber] context:nil characters:characterAsString charactersIgnoringModifiers:characterAsString isARepeat:NO keyCode:character]];
 }
 
 - (void)waitForPendingMouseEvents
@@ -763,6 +797,7 @@ static WKContentView *recursiveFindWKContentView(UIView *view)
 }
 
 @end
+
 #endif // PLATFORM(MAC)
 
 #if PLATFORM(IOS_FAMILY)
index 078ea11..7071f7b 100644 (file)
@@ -177,6 +177,8 @@ static NSImage *defaultExternalDragImage()
     [_webView mouseEnterAtPoint:_startLocationInWindow];
     [_webView mouseMoveToPoint:_startLocationInWindow withFlags:0];
     [_webView mouseDownAtPoint:_startLocationInWindow simulatePressure:NO];
+    // Make sure that we exceed the minimum 150ms delay between handling mousedown and drag when dragging a text selection.
+    [_webView setEventTimestampOffset:0.25];
     [_webView mouseDragToPoint:[self locationInViewForCurrentProgress]];
     [_webView waitForPendingMouseEvents];
 
@@ -186,6 +188,7 @@ static NSImage *defaultExternalDragImage()
     [_webView waitForPendingMouseEvents];
 
     TestWebKitAPI::Util::run(&_doneWaitingForDrop);
+    [_webView setEventTimestampOffset:0];
 }
 
 - (void)beginDraggingSessionInWebView:(DragAndDropTestWKWebView *)webView withItems:(NSArray<NSDraggingItem *> *)items source:(id<NSDraggingSource>)source