[Mac] Teach WebCore::Pasteboard about file promise drags
authoraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 6 Mar 2018 01:36:31 +0000 (01:36 +0000)
committeraestes@apple.com <aestes@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 6 Mar 2018 01:36:31 +0000 (01:36 +0000)
https://bugs.webkit.org/show_bug.cgi?id=183314
<rdar://problem/38105493>

Reviewed by Darin Adler.

Source/WebCore:

While WebKit does support receiving file promise drags (since r210360), WebCore::Pasteboard
has not been instructed on how to read their file paths. When the various pasteboard readers
ask for file paths after a file promise drop, they receive an empty vector. This impacts
various features, most notably the DataTransfer API.

Pasteboard actually cannot learn about promised file paths from the pasteboard itself, as
the pasteboard only contains the dragged files' UTIs. Promised file paths aren't known until
the WebKits call -[NSFilePromiseReceiver receivePromisedFilesAtDestination:...], at which
point the file paths are passed to WebCore as part of WebCore::DragData.

When we construct new Pasteboards for drag and drop, we need to store any promised file
paths from the DragData. Then, when the various pasteboard readers ask for file paths and
NSFilesPromisePboardType is on the pasteboard, we can return these promised file paths.

Tests: editing/pasteboard/data-transfer-items-drag-drop-file-promise.html
       editing/pasteboard/data-transfer-items-drop-file-promise.html
       editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html
       editing/pasteboard/datatransfer-types-dropping-text-file-promise.html
       editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html
       editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html
       editing/pasteboard/file-input-files-access-promise.html

* platform/FileSystem.h:
* platform/Pasteboard.h:
(WebCore::Pasteboard::Pasteboard):
* platform/mac/DragDataMac.mm:
(WebCore::DragData::containsPromise const):
* platform/mac/PasteboardMac.mm:
(WebCore::Pasteboard::Pasteboard):
(WebCore::Pasteboard::createForDragAndDrop):
(WebCore::toString):
(WebCore::Pasteboard::read):
(WebCore::Pasteboard::readFilePaths):
(WebCore::absoluteURLsFromPasteboardFilenames): Deleted.
* platform/mac/PlatformPasteboardMac.mm:
(WebCore::PlatformPasteboard::numberOfFiles const):

Source/WebKit:

Added a FIXME comment.

* UIProcess/Cocoa/WebViewImpl.mm:
(WebKit::WebViewImpl::performDragOperation):

Source/WebKitLegacy/mac:

Added a FIXME comment.

* WebView/WebView.mm:
(-[WebView performDragOperation:]):

Tools:

* DumpRenderTree/DumpRenderTreeFileDraggingSource.h:
* DumpRenderTree/DumpRenderTreeFileDraggingSource.m:
(-[DumpRenderTreeFileDraggingSource initWithPromisedFileURLs:]):
(-[DumpRenderTreeFileDraggingSource dealloc]):

Taught DumpRenderTreeFileDraggingSource to store the promised file URLs.

* DumpRenderTree/mac/DumpRenderTree.mm:
(runTest):

Called +[DumpRenderTreeDraggingInfo clearAllFilePromiseReceivers] after running a test.

* DumpRenderTree/mac/DumpRenderTreeDraggingInfo.h:
* DumpRenderTree/mac/DumpRenderTreeDraggingInfo.mm:
(-[DumpRenderTreeFilePromiseReceiver initWithPromisedUTIs:]):
(-[DumpRenderTreeFilePromiseReceiver fileTypes]):
(-[DumpRenderTreeFilePromiseReceiver fileNames]):
(-[DumpRenderTreeFilePromiseReceiver dealloc]):
(copyFile):
(-[DumpRenderTreeFilePromiseReceiver receivePromisedFilesAtDestination:options:operationQueue:reader:]):

We can't instantiate real NSFilePromiseReceivers in DumpRenderTree. They rely on the
pasteboard server to generate unique file URLs, which is incompatible with our swizzled
NSPasteboard.

Instead, create a subclass of NSFilePromiseReceiver that implements its own promise resolution.
-receivePromisedFilesAtDestination:... asks its DumpRenderTreeFileDraggingSource for the
array of file URLs, then copies each to the destination directory on the specified operation
queue. It emulates how NSPasteboard tries to find a unique destination name by appending
numbers to the file name.

All receivers are collected in a global array that is cleared when each test finishes.
DumpRenderTreeFilePromiseReceiver will delete the files it copied in -dealloc.

(+[DumpRenderTreeDraggingInfo clearAllFilePromiseReceivers]):
(-[DumpRenderTreeDraggingInfo enumerateDraggingItemsWithOptions:forView:classes:searchOptions:usingBlock:]):

If NSFilesPromisePboardType is on the pasteboard and classArray contains
NSFilePromiseReceiver, construct a DumpRenderTreeFilePromiseReceiver, add it to the array of
all file promise receivers, then wrap it in an NSDraggingItem and call block.

* DumpRenderTree/mac/EventSendingController.mm:
(+[EventSendingController isSelectorExcludedFromWebScript:]):
(+[EventSendingController webScriptNameForSelector:]):
(-[EventSendingController beginDragWithFilePromises:]):

Implement eventSender.beginDragWithFilePromises() by placing file UTIs on the pasteboard
with type NSFilesPromisePboardType, creating a DumpRenderTreeFileDraggingSource with the
file URLs, and creating a new DumpRenderTreeDraggingInfo and passing it to
-[WebView draggingEntered:].

LayoutTests:

Added versions of file dragging tests in editing/pasteboard/ that use
beginDragWithFilePromises() instead of beginDragWithFiles().

* TestExpectations: Skipped the new tests.
* editing/pasteboard/data-transfer-items-drag-drop-file-promise-expected.txt: Added.
* editing/pasteboard/data-transfer-items-drag-drop-file-promise.html: Added.
* editing/pasteboard/data-transfer-items-drop-file-promise-expected.txt: Added.
* editing/pasteboard/data-transfer-items-drop-file-promise.html: Added.
* editing/pasteboard/datatransfer-items-drop-plaintext-file-promise-expected.txt: Added.
* editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html: Added.
* editing/pasteboard/datatransfer-types-dropping-text-file-promise-expected.txt: Added.
* editing/pasteboard/datatransfer-types-dropping-text-file-promise.html: Added.
* editing/pasteboard/drag-file-promises-to-editable-element-as-URLs-expected.txt: Added.
* editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html: Added.
* editing/pasteboard/drag-file-promises-to-editable-element-as-attachment-expected.txt: Added.
* editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html: Added.
* editing/pasteboard/file-input-files-access-promise-expected.txt: Added.
* editing/pasteboard/file-input-files-access-promise.html: Added.
* platform/mac-wk1/TestExpectations: Un-skipped the new tests.
* platform/win/TestExpectations: Skipped the new tests.

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

34 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/editing/pasteboard/data-transfer-items-drag-drop-file-promise-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-items-drag-drop-file-promise.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-items-drop-file-promise-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/data-transfer-items-drop-file-promise.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/datatransfer-items-drop-plaintext-file-promise-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/datatransfer-types-dropping-text-file-promise-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/datatransfer-types-dropping-text-file-promise.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-URLs-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-attachment-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html [new file with mode: 0644]
LayoutTests/editing/pasteboard/file-input-files-access-promise-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/file-input-files-access-promise.html [new file with mode: 0644]
LayoutTests/platform/mac-wk1/TestExpectations
LayoutTests/platform/win/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/platform/Pasteboard.h
Source/WebCore/platform/mac/DragDataMac.mm
Source/WebCore/platform/mac/PasteboardMac.mm
Source/WebCore/platform/mac/PlatformPasteboardMac.mm
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/Cocoa/WebViewImpl.mm
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebView/WebView.mm
Tools/ChangeLog
Tools/DumpRenderTree/DumpRenderTreeFileDraggingSource.h
Tools/DumpRenderTree/DumpRenderTreeFileDraggingSource.m
Tools/DumpRenderTree/mac/DumpRenderTree.mm
Tools/DumpRenderTree/mac/DumpRenderTreeDraggingInfo.h
Tools/DumpRenderTree/mac/DumpRenderTreeDraggingInfo.mm
Tools/DumpRenderTree/mac/EventSendingController.mm

index 9f36420..39def34 100644 (file)
@@ -1,3 +1,32 @@
+2018-03-05  Andy Estes  <aestes@apple.com>
+
+        [Mac] Teach WebCore::Pasteboard about file promise drags
+        https://bugs.webkit.org/show_bug.cgi?id=183314
+        <rdar://problem/38105493>
+
+        Reviewed by Darin Adler.
+
+        Added versions of file dragging tests in editing/pasteboard/ that use
+        beginDragWithFilePromises() instead of beginDragWithFiles().
+
+        * TestExpectations: Skipped the new tests.
+        * editing/pasteboard/data-transfer-items-drag-drop-file-promise-expected.txt: Added.
+        * editing/pasteboard/data-transfer-items-drag-drop-file-promise.html: Added.
+        * editing/pasteboard/data-transfer-items-drop-file-promise-expected.txt: Added.
+        * editing/pasteboard/data-transfer-items-drop-file-promise.html: Added.
+        * editing/pasteboard/datatransfer-items-drop-plaintext-file-promise-expected.txt: Added.
+        * editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html: Added.
+        * editing/pasteboard/datatransfer-types-dropping-text-file-promise-expected.txt: Added.
+        * editing/pasteboard/datatransfer-types-dropping-text-file-promise.html: Added.
+        * editing/pasteboard/drag-file-promises-to-editable-element-as-URLs-expected.txt: Added.
+        * editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html: Added.
+        * editing/pasteboard/drag-file-promises-to-editable-element-as-attachment-expected.txt: Added.
+        * editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html: Added.
+        * editing/pasteboard/file-input-files-access-promise-expected.txt: Added.
+        * editing/pasteboard/file-input-files-access-promise.html: Added.
+        * platform/mac-wk1/TestExpectations: Un-skipped the new tests.
+        * platform/win/TestExpectations: Skipped the new tests.
+
 2018-03-05  Ryan Haddad  <ryanhaddad@apple.com>
 
         Unreviewed, add baseline for fast/text/combining-enclosing-keycap.html.
index 0f83795..e682ed9 100644 (file)
@@ -81,13 +81,19 @@ editing/pasteboard/data-transfer-get-data-on-drop-plain-text.html [ Skip ]
 editing/pasteboard/data-transfer-get-data-on-drop-rich-text.html [ Skip ]
 editing/pasteboard/data-transfer-get-data-on-drop-url.html [ Skip ]
 editing/pasteboard/data-transfer-is-unique-for-dragenter-and-dragleave.html [ Skip ]
+editing/pasteboard/data-transfer-item-list-add-file-on-drag.html [ Skip ]
+editing/pasteboard/data-transfer-items-drag-drop-file-promise.html [ Skip ]
+editing/pasteboard/data-transfer-items-drop-file.html [ Skip ]
+editing/pasteboard/data-transfer-items-drop-file-promise.html [ Skip ]
 editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin.html [ Skip ]
 editing/pasteboard/data-transfer-set-data-sanitize-url-when-dragging-in-null-origin.html [ Skip ]
-http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html [ Skip ]
-
+editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html [ Skip ]
+editing/pasteboard/datatransfer-types-dropping-text-file-promise.html [ Skip ]
 editing/pasteboard/drag-end-crash-accessing-item-list.html [ Skip ]
-editing/pasteboard/data-transfer-item-list-add-file-on-drag.html [ Skip ]
-editing/pasteboard/data-transfer-items-drop-file.html [ Skip ]
+editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html [ Skip ]
+editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html [ Skip ]
+editing/pasteboard/file-input-files-access-promise.html [ Skip ]
+http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html [ Skip ]
 
 # Only iOS supports QuickLook
 quicklook [ Skip ]
diff --git a/LayoutTests/editing/pasteboard/data-transfer-items-drag-drop-file-promise-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-items-drag-drop-file-promise-expected.txt
new file mode 100644 (file)
index 0000000..65643db
--- /dev/null
@@ -0,0 +1,23 @@
+This tests the basic functionality and properties of DataTransferItems for file promises with drag and drop. This test requires DRT.
+Drop files here if you test this manually
+Dragging file: resources/mozilla.gif
+Dragging file: resources/drop-file-svg.svg
+Dragging file: resources/copy-backslash-euc.html
+Verifying contents of DataTransferItems...
+PASS: "3" == "3"
+PASS: "file" == "file"
+PASS: "image/gif" == "image/gif"
+PASS: "image/gif" == "image/gif"
+PASS: "2593" == "2593"
+PASS: "mozilla.gif" == "mozilla.gif"
+PASS: "file" == "file"
+PASS: "image/svg+xml" == "image/svg+xml"
+PASS: "image/svg+xml" == "image/svg+xml"
+PASS: "109" == "109"
+PASS: "drop-file-svg.svg" == "drop-file-svg.svg"
+PASS: "file" == "file"
+PASS: "text/html" == "text/html"
+PASS: "text/html" == "text/html"
+PASS: "478" == "478"
+PASS: "copy-backslash-euc.html" == "copy-backslash-euc.html"
+
diff --git a/LayoutTests/editing/pasteboard/data-transfer-items-drag-drop-file-promise.html b/LayoutTests/editing/pasteboard/data-transfer-items-drag-drop-file-promise.html
new file mode 100644 (file)
index 0000000..2ef9db8
--- /dev/null
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div>This tests the basic functionality and properties of DataTransferItems for file promises with drag and drop. This test requires DRT.</div>
+
+<div id="destination" style="min-height:100px; border: solid 1px black">Drop files here if you test this manually</div>
+
+<div id="console"></div>
+
+<script>
+var testFiles = [
+  { path: 'resources/mozilla.gif',
+    type: 'image/gif',
+    size: 2593 },
+  { path: 'resources/drop-file-svg.svg',
+    type: 'image/svg+xml',
+    size: 109 },
+  { path: 'resources/copy-backslash-euc.html',
+    type: 'text/html',
+    size: 478 }
+];
+
+function log(text)
+{
+    var console = document.getElementById('console');
+    console.appendChild(document.createTextNode(text));
+    console.appendChild(document.createElement('br'));
+}
+
+function test(expect, actual)
+{
+    log((expect == actual ? 'PASS' : 'FAIL') + ': "' + expect + '" == "' + actual + '"');
+}
+
+function startTest()
+{
+    var destination = document.getElementById('destination');
+    destination.addEventListener('dragover', handleDragOver, false);
+    destination.addEventListener('drop', handleDrop, false);
+
+    if (!window.testRunner)
+        return;
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+
+    var files = [];
+    for (var i = 0; i < testFiles.length; ++i) {
+      log('Dragging file: ' + testFiles[i].path);
+      files.push(testFiles[i].path);
+    }
+
+    // Perform drag-and-drop with the testFiles.
+    eventSender.beginDragWithFilePromises(files);
+    eventSender.leapForward(500);
+    eventSender.mouseMoveTo(destination.offsetLeft + 10, destination.offsetTop + destination.offsetHeight / 2);
+    eventSender.mouseUp();
+}
+
+function handleDragOver(e)
+{
+    e.stopPropagation();
+    e.preventDefault();
+}
+
+function handleDrop(e)
+{
+    e.stopPropagation();
+    e.preventDefault();
+
+    log('Verifying contents of DataTransferItems...');
+    var items = e.dataTransfer.items;
+    var files = [];
+    test(testFiles.length, items.length);
+    for (var i = 0; i < items.length; ++i) {
+        // The items should be in the same order as we added.
+        var expected = testFiles[i];
+
+        var file = items[i].getAsFile();
+        files.push(file);
+
+        test('file', items[i].kind);
+        test(expected.type, items[i].type);
+        test(expected.type, file.type);
+        test(expected.size, file.size);
+
+        var components = expected.path.split('/');
+        var name = components[components.length - 1];
+        test(name, file.name);
+    }
+
+    testRunner.notifyDone();
+}
+
+startTest();
+
+</script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/data-transfer-items-drop-file-promise-expected.txt b/LayoutTests/editing/pasteboard/data-transfer-items-drop-file-promise-expected.txt
new file mode 100644 (file)
index 0000000..fde2e47
--- /dev/null
@@ -0,0 +1,9 @@
+
+*** Handling drop ***
+
+Types => data
+Files => ""
+Item list
+[0] => (name = 'apple.gif', size = 1476 bytes, type = 'image/gif')
+File list
+[0] => (name = 'apple.gif', size = 1476 bytes, type = 'image/gif')
diff --git a/LayoutTests/editing/pasteboard/data-transfer-items-drop-file-promise.html b/LayoutTests/editing/pasteboard/data-transfer-items-drop-file-promise.html
new file mode 100644 (file)
index 0000000..7f8c50d
--- /dev/null
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<style>
+html, body {
+    width: 100%;
+    height: 100%;
+}
+</style>
+<body contenteditable>
+    <pre id="output">
+        This test is best run under DumpRenderTree. To manually test, drop a file anywhere in the body.
+        This will dump the contents of the DataTransfer on drop as output text.
+        You should see an item of kind "file" in the resulting item list, as well as an entry in the item list.
+        There should <i>not</i> be an item of kind "string" and type "file".
+    </pre>
+</body>
+<script>
+
+function write(markup) {
+    output.insertAdjacentHTML("beforeend", markup);
+}
+
+function outputForFile(file) {
+    return file ? `(name = '${file.name}', size = ${file.size} bytes, type = '${file.type}')` : "(null)";
+}
+
+function writeOutputForEvent(event) {
+    output.textContent = "";
+
+    write(`<br><h3>*** Handling ${event.type} ***</h3>`);
+    const pasteboard = event.dataTransfer || event.clipboardData;
+    write(`<div><strong>Types => data</strong></div>`);
+    for (const type of pasteboard.types)
+        write(`<div>${type} => "${pasteboard.getData(type)}"</div>`);
+    let index = 0;
+    write(`<div><strong>Item list</strong></div>`);
+    for (const item of pasteboard.items) {
+        if (item.kind === "file")
+            write(`<div>[${index++}] => ${outputForFile(item.getAsFile())}</div>`);
+        else
+            write(`<div>[${index++}] => (kind = ${item.kind}, type = ${item.type})</div>`);
+    }
+    write(`<div><strong>File list</strong></div>`);
+    index = 0;
+    for (const file of pasteboard.files)
+        write(`<div>[${index++}] => ${outputForFile(file)}</div>`);
+}
+
+document.body.addEventListener("drop", event => {
+    event.preventDefault();
+    writeOutputForEvent(event);
+    testRunner.notifyDone();
+});
+
+if (window.testRunner && window.internals && window.eventSender) {
+    internals.settings.setCustomPasteboardDataEnabled(true);
+    testRunner.dumpAsText();
+    testRunner.waitUntilDone();
+
+    eventSender.beginDragWithFilePromises(["resources/apple.gif"]);
+    eventSender.mouseMoveTo(100, 100);
+    eventSender.mouseUp();
+}
+</script>
diff --git a/LayoutTests/editing/pasteboard/datatransfer-items-drop-plaintext-file-promise-expected.txt b/LayoutTests/editing/pasteboard/datatransfer-items-drop-plaintext-file-promise-expected.txt
new file mode 100644 (file)
index 0000000..bfd8d89
--- /dev/null
@@ -0,0 +1,17 @@
+This tests accessing DataTransferItemList when dropping a file promise. To manually test, drag and drop LayoutTests/editing/resources/text-pasteboard-data.txt from another app (e.g. Finder) to the box below.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS dataTransfer.items.length is 1
+PASS dataTransfer.items[0].kind is "file"
+PASS dataTransfer.items[0].type is "text/plain"
+PASS file = dataTransfer.items[0].getAsFile(); file is not null
+PASS file.name is "text-pasteboard-data.txt"
+PASS reader = new FileReader(); reader.onload = () => checkFileContent(reader.result); reader.readAsText(file); did not throw exception.
+PASS "hello, world." is "hello, world."
+PASS dataTransfer.items[0].getAsFile() is null
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html b/LayoutTests/editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html
new file mode 100644 (file)
index 0000000..eaab1a2
--- /dev/null
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+<body onload="runTest()">
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+
+description('This tests accessing DataTransferItemList when dropping a file promise.'
+    + ' To manually test, drag and drop <a href="../resources/text-pasteboard-data.txt">LayoutTests/editing/resources/text-pasteboard-data.txt</a> from another app (e.g. Finder) to the box below.');
+
+function runTest()
+{
+    if (!window.testRunner)
+        return;
+    if (!window.eventSender || !eventSender.beginDragWithFilePromises) {
+        testFailed('This test requires eventSender.beginDragWithFilePromises');
+        finishJSTest();
+        return;
+    }
+
+    eventSender.beginDragWithFilePromises(["../resources/text-pasteboard-data.txt"]);
+    const target = document.getElementById('target');
+    eventSender.mouseMoveTo(target.offsetLeft + 5, target.offsetTop + 5);
+    eventSender.mouseUp();
+}
+
+function drop(event)
+{
+    event.preventDefault();
+    dataTransfer = event.dataTransfer;
+    shouldBe('dataTransfer.items.length', '1');
+    shouldBeEqualToString('dataTransfer.items[0].kind', 'file');
+    shouldBeEqualToString('dataTransfer.items[0].type', 'text/plain');
+    shouldNotBe('file = dataTransfer.items[0].getAsFile(); file', 'null');
+    shouldBeEqualToString('file.name', 'text-pasteboard-data.txt');
+    shouldNotThrow('reader = new FileReader(); reader.onload = () => checkFileContent(reader.result); reader.readAsText(file);');
+}
+
+function checkFileContent(content)
+{
+    shouldBeEqualToString('"' + content + '"', 'hello, world.');
+    shouldBe('dataTransfer.items[0].getAsFile()', 'null');
+    finishJSTest();
+}
+
+jsTestIsAsync = true;
+successfullyParsed = true;
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+<div id="target" contenteditable="true" ondrop="drop(event)" style="width: 100px; height: 100px; border: solid 1px black;"></div>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/datatransfer-types-dropping-text-file-promise-expected.txt b/LayoutTests/editing/pasteboard/datatransfer-types-dropping-text-file-promise-expected.txt
new file mode 100644 (file)
index 0000000..3ef6832
--- /dev/null
@@ -0,0 +1,12 @@
+When dropping a file promise, dataTransfer.types must contain "Files" and not "text/uri-list". This test requires eventSender.beginDragWithFilePromises.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS dataTransfer.types.includes("Files") is true
+PASS dataTransfer.types.includes("text/uri-list") is false
+PASS dataTransfer.getData("url") is ""
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/datatransfer-types-dropping-text-file-promise.html b/LayoutTests/editing/pasteboard/datatransfer-types-dropping-text-file-promise.html
new file mode 100644 (file)
index 0000000..f643f1e
--- /dev/null
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div id="target" contentEditable="true" ondrop="check(event)"></div>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+description('When dropping a file promise, dataTransfer.types must contain "Files" and not "text/uri-list". This test requires eventSender.beginDragWithFilePromises.');
+
+function runTest() {
+    jsTestIsAsync = true;
+    const target = document.getElementById('target');
+    eventSender.beginDragWithFilePromises(['../resources/abe.png']);
+    eventSender.mouseMoveTo(target.offsetLeft + 5, target.offsetTop + 5);
+    eventSender.mouseUp();
+}
+
+function check(event) {
+    dataTransfer = event.dataTransfer;
+    shouldBeTrue('dataTransfer.types.includes("Files")');
+    shouldBeFalse('dataTransfer.types.includes("text/uri-list")');
+    shouldBeEqualToString('dataTransfer.getData("url")', '');
+    finishJSTest();
+}
+
+if (window.eventSender)
+    runTest();
+else
+    testFailed('This test requires eventSender.beginDragWithFilePromises');
+
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-URLs-expected.txt b/LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-URLs-expected.txt
new file mode 100644 (file)
index 0000000..6b9fe89
--- /dev/null
@@ -0,0 +1,13 @@
+If we drag file promises onto an editable area, then attachments should not be inserted into the editable area since attachment elements are disabled.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS window.HTMLAttachmentElement is undefined.
+PASS document.createElement("attachment") instanceof HTMLUnknownElement is true
+PASS editable.querySelector("attachment") is null
+PASS editable.textContent is ""
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html b/LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html
new file mode 100644 (file)
index 0000000..6cebd82
--- /dev/null
@@ -0,0 +1,43 @@
+<!DOCTYPE html><!-- webkit-test-runner [ enableAttachmentElement=false ] -->
+<html>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<div id="editable" contentEditable=true style="width:200px; height:200px"></div>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+description('If we drag file promises onto an editable area, then attachments should not be inserted into the editable area since attachment elements are disabled.');
+jsTestIsAsync = true;
+
+var editable = document.getElementById("editable");
+editable.addEventListener("drop", () => window.setTimeout(() => {
+    shouldBeUndefined('window.HTMLAttachmentElement');
+    shouldBeTrue('document.createElement("attachment") instanceof HTMLUnknownElement');
+    shouldBe('editable.querySelector("attachment")', 'null');
+    shouldBe('editable.textContent', '""');
+    editable.innerHTML = '';
+    finishJSTest();
+}, 0));
+
+if (window.eventSender)
+    dragFilesOntoEditable(['resources/apple.gif', 'resources/mozilla.gif', 'resources/drop-file-svg.svg']);
+
+function moveMouseToCenterOfElement(element)
+{
+    var centerX = element.offsetLeft + element.offsetWidth / 2;
+    var centerY = element.offsetTop + element.offsetHeight / 2;
+    eventSender.mouseMoveTo(centerX, centerY);
+}
+
+function dragFilesOntoEditable(files)
+{
+    eventSender.beginDragWithFilePromises(files);
+    moveMouseToCenterOfElement(editable);
+    eventSender.mouseUp();
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-attachment-expected.txt b/LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-attachment-expected.txt
new file mode 100644 (file)
index 0000000..32d2b76
--- /dev/null
@@ -0,0 +1,13 @@
+If we drag file promises onto an editable area, then attachments should be inserted into the editable area.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS attachment.nodeName is "ATTACHMENT"
+PASS attachment.nodeName is "ATTACHMENT"
+PASS attachment.nodeName is "ATTACHMENT"
+PASS fileNames is "apple.gif mozilla.gif drop-file-svg.svg "
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html b/LayoutTests/editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html
new file mode 100644 (file)
index 0000000..80de054
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html><!-- webkit-test-runner [ enableAttachmentElement=true ] -->
+<html>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<div id="editable" contentEditable=true style="width:200px; height:200px"></div>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+description('If we drag file promises onto an editable area, then attachments should be inserted into the editable area.');
+jsTestIsAsync = true;
+
+var editable = document.getElementById("editable");
+editable.addEventListener("drop", () => window.setTimeout(() => {
+    var resultChildren = editable.children;
+    fileNames = "";
+    for (var i = 0; i < resultChildren.length; i++) {
+        attachment = resultChildren[i];
+        shouldBeEqualToString('attachment.nodeName', 'ATTACHMENT');
+        fileNames += attachment.file.name + " ";
+    }
+    shouldBeEqualToString('fileNames', 'apple.gif mozilla.gif drop-file-svg.svg ');
+    editable.innerHTML = '';
+    finishJSTest();
+}, 0));
+
+if (window.eventSender)
+    dragFilesOntoEditable(['resources/apple.gif', 'resources/mozilla.gif', 'resources/drop-file-svg.svg']);
+
+function moveMouseToCenterOfElement(element)
+{
+    var centerX = element.offsetLeft + element.offsetWidth / 2;
+    var centerY = element.offsetTop + element.offsetHeight / 2;
+    eventSender.mouseMoveTo(centerX, centerY);
+}
+
+function dragFilesOntoEditable(files)
+{
+    eventSender.beginDragWithFilePromises(files);
+    moveMouseToCenterOfElement(editable);
+    eventSender.mouseUp();
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/editing/pasteboard/file-input-files-access-promise-expected.txt b/LayoutTests/editing/pasteboard/file-input-files-access-promise-expected.txt
new file mode 100644 (file)
index 0000000..24215ee
--- /dev/null
@@ -0,0 +1,27 @@
+Tests for multi-file promise drag onto file input elements
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Dragging a real file to a file input control:
+PASS fileInput.value is "C:\\fakepath\\apple.gif"
+PASS fileInput.files.length is 1
+PASS fileInput.files[0].name is "apple.gif"
+PASS fileInput.files[0].type is "image/gif"
+PASS fileInput.files[0].size is 1476
+Dragging two files to a single-file input control:
+PASS fileInput.value is ""
+PASS fileInput.files.length is 0
+Dragging three files to a multi-file input control:
+PASS fileInput.value is "C:\\fakepath\\apple 2.gif"
+PASS fileInput.files.length is 2
+PASS fileInput.files[0].name is "apple 2.gif"
+PASS fileInput.files[0].type is "image/gif"
+PASS fileInput.files[0].size is 1476
+PASS fileInput.files[1].name is "mozilla.gif"
+PASS fileInput.files[1].type is "image/gif"
+PASS fileInput.files[1].size is 2593
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/file-input-files-access-promise.html b/LayoutTests/editing/pasteboard/file-input-files-access-promise.html
new file mode 100644 (file)
index 0000000..d3bba52
--- /dev/null
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+description("Tests for multi-file promise drag onto file input elements");
+jsTestIsAsync = true;
+
+var fileInput = document.createElement("input");
+fileInput.type = 'file';
+fileInput.style.width = "100%"; // So that any manual testing will show full file names
+// Important that we put this at the top of the doc so that logging does not cause it to go out of view (where it can't be dragged to)
+document.body.insertBefore(fileInput, document.body.firstChild);
+
+function moveMouseToCenterOfElement(element)
+{
+    var centerX = element.offsetLeft + element.offsetWidth / 2;
+    var centerY = element.offsetTop + element.offsetHeight / 2;
+    eventSender.mouseMoveTo(centerX, centerY);
+}
+
+function dragFilesOntoInput(files)
+{
+    fileInput.value = ""; // Clear the <input>
+
+    eventSender.beginDragWithFilePromises(files);
+    moveMouseToCenterOfElement(fileInput);
+    eventSender.mouseUp();
+}
+
+function fileListShouldBe(fileListString, filesArray)
+{
+    shouldBe(fileListString + ".length", "" + filesArray.length);
+    for (var x = 0; x < filesArray.length; x++) {
+        var fileValueString = fileListString + "[" + x + "]";
+        shouldBeEqualToString(fileValueString + ".name", filesArray[x]['name']);
+        shouldBeEqualToString(fileValueString + ".type", filesArray[x]['type']);
+        shouldBe(fileValueString + ".size", "" + filesArray[x]['size']);
+    }
+}
+
+function filesShouldBe(filesArray)
+{
+    fileListShouldBe("fileInput.files", filesArray);
+}
+
+async function draggingPathsShouldResultInFiles(pathsArray, filesArray)
+{
+    const dropPromise = new Promise(resolve => fileInput.ondrop = () => window.setTimeout(resolve, 0));
+    const dragleavePromise = new Promise(resolve => fileInput.ondragleave = () => window.setTimeout(resolve, 0));
+    dragFilesOntoInput(pathsArray);
+    await Promise.race([dropPromise, dragleavePromise]);
+    shouldBeEqualToString("fileInput.value", filesArray[0] ? "C:\\fakepath\\" + filesArray[0]['name'] : '');
+    filesShouldBe(filesArray);
+}
+
+async function testDraggingFiles(filesArray)
+{
+    // We could make a way to parse the filename from the path, and then only need to pass
+    // the path in the filesArray.
+    var pathsOnly = filesArray.map(function(fileSpec) { return fileSpec['path']; });
+    await draggingPathsShouldResultInFiles(pathsOnly, filesArray);
+}
+
+async function runTest()
+{
+    debug("Dragging a real file to a file input control:");
+    await testDraggingFiles([
+        { 'path': 'resources/apple.gif', 'name' : 'apple.gif', 'size' : 1476, 'type' : 'image/gif' }
+    ]);
+
+    debug("Dragging two files to a single-file input control:")
+    await draggingPathsShouldResultInFiles(['resources/apple.gif', 'resources/mozilla.gif'], []);
+
+    fileInput.multiple = true;
+
+    debug("Dragging three files to a multi-file input control:");
+    await testDraggingFiles([
+        { 'path': 'resources/apple.gif', 'name' : 'apple 2.gif', 'size' : 1476, 'type' : 'image/gif' },
+        { 'path': 'resources/mozilla.gif', 'name' : 'mozilla.gif', 'size' : 2593, 'type' : 'image/gif' },
+    ]);
+
+    // Clean up after ourselves
+    fileInput.parentNode.removeChild(fileInput);
+
+    finishJSTest();
+}
+
+var successfullyParsed = true;
+
+if (window.eventSender) {
+    runTest();
+} else {
+    testFailed("This test is not interactive, please run using run-webkit-tests");
+}
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 925c18f..9fcc652 100644 (file)
@@ -6,17 +6,24 @@
 #//////////////////////////////////////////////////////////////////////////////////////////
 
 fast/forms/attributed-strings.html [ Pass ]
-editing/pasteboard/drag-drop-href-as-url.html [ Pass ]
 editing/pasteboard/data-transfer-get-data-on-drop-custom.html [ Pass ]
 editing/pasteboard/data-transfer-get-data-on-drop-plain-text.html [ Pass ]
 editing/pasteboard/data-transfer-get-data-on-drop-rich-text.html [ Pass ]
 editing/pasteboard/data-transfer-get-data-on-drop-url.html [ Pass ]
 editing/pasteboard/data-transfer-is-unique-for-dragenter-and-dragleave.html [ Pass ]
+editing/pasteboard/data-transfer-items-drag-drop-file-promise.html [ Pass ]
+editing/pasteboard/data-transfer-items-drop-file.html [ Pass ]
+editing/pasteboard/data-transfer-items-drop-file-promise.html [ Pass ]
 editing/pasteboard/data-transfer-set-data-sanitize-html-when-dragging-in-null-origin.html [ Pass ]
 editing/pasteboard/data-transfer-set-data-sanitize-url-when-dragging-in-null-origin.html [ Pass ]
-editing/pasteboard/drag-end-crash-accessing-item-list.html [ Pass ]
 editing/pasteboard/data-transfer-item-list-add-file-on-drag.html [ Pass ]
-editing/pasteboard/data-transfer-items-drop-file.html [ Pass ]
+editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html [ Pass ]
+editing/pasteboard/datatransfer-types-dropping-text-file-promise.html [ Pass ]
+editing/pasteboard/drag-drop-href-as-url.html [ Pass ]
+editing/pasteboard/drag-end-crash-accessing-item-list.html [ Pass ]
+editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html [ Pass ]
+editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html [ Pass ]
+editing/pasteboard/file-input-files-access-promise.html [ Pass ]
 http/tests/security/clipboard/drag-drop-html-cross-origin-iframe-in-same-origin.html [ Pass ]
 
 #//////////////////////////////////////////////////////////////////////////////////////////
index 88e8ee5..e1f658b 100644 (file)
@@ -133,6 +133,13 @@ fast/repaint/overflow-scroll-touch-repaint.html [ Skip ]
 
 # TODO Drag & Drop doesn't work correctly in DRT <rdar://5621244>
 editing/pasteboard/datatransfer-items-drop-plaintext-file.html
+editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html [ Skip ]
+editing/pasteboard/datatransfer-types-dropping-text-file-promise.html [ Skip ]
+editing/pasteboard/data-transfer-items-drag-drop-file-promise.html [ Skip ]
+editing/pasteboard/data-transfer-items-drop-file-promise.html [ Skip ]
+editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html [ Skip ]
+editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html [ Skip ]
+editing/pasteboard/file-input-files-access-promise.html [ Skip ]
 fast/events/drop-handler-should-not-stop-navigate.html [ Skip ]
 fast/events/drag-in-frames.html [ Skip ]
 fast/events/drag-to-navigate.html [ Skip ]
index dcd3550..16b6d04 100644 (file)
@@ -1,5 +1,50 @@
 2018-03-05  Andy Estes  <aestes@apple.com>
 
+        [Mac] Teach WebCore::Pasteboard about file promise drags
+        https://bugs.webkit.org/show_bug.cgi?id=183314
+        <rdar://problem/38105493>
+
+        Reviewed by Darin Adler.
+
+        While WebKit does support receiving file promise drags (since r210360), WebCore::Pasteboard
+        has not been instructed on how to read their file paths. When the various pasteboard readers
+        ask for file paths after a file promise drop, they receive an empty vector. This impacts
+        various features, most notably the DataTransfer API.
+
+        Pasteboard actually cannot learn about promised file paths from the pasteboard itself, as
+        the pasteboard only contains the dragged files' UTIs. Promised file paths aren't known until
+        the WebKits call -[NSFilePromiseReceiver receivePromisedFilesAtDestination:...], at which
+        point the file paths are passed to WebCore as part of WebCore::DragData.
+
+        When we construct new Pasteboards for drag and drop, we need to store any promised file
+        paths from the DragData. Then, when the various pasteboard readers ask for file paths and
+        NSFilesPromisePboardType is on the pasteboard, we can return these promised file paths.
+
+        Tests: editing/pasteboard/data-transfer-items-drag-drop-file-promise.html
+               editing/pasteboard/data-transfer-items-drop-file-promise.html
+               editing/pasteboard/datatransfer-items-drop-plaintext-file-promise.html
+               editing/pasteboard/datatransfer-types-dropping-text-file-promise.html
+               editing/pasteboard/drag-file-promises-to-editable-element-as-URLs.html
+               editing/pasteboard/drag-file-promises-to-editable-element-as-attachment.html
+               editing/pasteboard/file-input-files-access-promise.html
+
+        * platform/FileSystem.h:
+        * platform/Pasteboard.h:
+        (WebCore::Pasteboard::Pasteboard):
+        * platform/mac/DragDataMac.mm:
+        (WebCore::DragData::containsPromise const):
+        * platform/mac/PasteboardMac.mm:
+        (WebCore::Pasteboard::Pasteboard):
+        (WebCore::Pasteboard::createForDragAndDrop):
+        (WebCore::toString):
+        (WebCore::Pasteboard::read):
+        (WebCore::Pasteboard::readFilePaths):
+        (WebCore::absoluteURLsFromPasteboardFilenames): Deleted.
+        * platform/mac/PlatformPasteboardMac.mm:
+        (WebCore::PlatformPasteboard::numberOfFiles const):
+
+2018-03-05  Andy Estes  <aestes@apple.com>
+
         [Mac] Fix the build
 
         * Modules/applepay/ApplePaySession.cpp:
index 1edce41..38bd3eb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006, 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -247,14 +247,17 @@ public:
 
 #if PLATFORM(IOS)
     explicit Pasteboard(long changeCount);
+    explicit Pasteboard(const String& pasteboardName);
 
     static NSArray *supportedWebContentPasteboardTypes();
     static String resourceMIMEType(NSString *mimeType);
 #endif
 
-#if PLATFORM(COCOA)
-    explicit Pasteboard(const String& pasteboardName);
+#if PLATFORM(MAC)
+    explicit Pasteboard(const String& pasteboardName, const Vector<String>& promisedFilePaths = { });
+#endif
 
+#if PLATFORM(COCOA)
     static bool shouldTreatCocoaTypeAsFile(const String&);
     WEBCORE_EXPORT static NSArray *supportedFileUploadPasteboardTypes();
     const String& name() const { return m_pasteboardName; }
@@ -313,6 +316,10 @@ private:
     std::optional<PasteboardCustomData> m_customDataCache;
 #endif
 
+#if PLATFORM(MAC)
+    Vector<String> m_promisedFilePaths;
+#endif
+
 #if PLATFORM(WIN)
     HWND m_owner;
     COMPtr<IDataObject> m_dataObject;
index 60b7c3a..cda6d24 100644 (file)
@@ -246,6 +246,8 @@ bool DragData::containsCompatibleContent(DraggingPurpose purpose) const
 
 bool DragData::containsPromise() const
 {
+    // FIXME: legacyFilesPromisePasteboardType() contains UTIs, not path names. Also, why do we
+    // think promises should only contain one file (or UTI)?
     Vector<String> files;
 #if PLATFORM(MAC)
     platformStrategies()->pasteboardStrategy()->getPathnamesForType(files, String(legacyFilesPromisePasteboardType()), m_pasteboardName);
index 3d5d37d..b503c76 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2006-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -88,9 +88,10 @@ Pasteboard::Pasteboard()
 {
 }
 
-Pasteboard::Pasteboard(const String& pasteboardName)
+Pasteboard::Pasteboard(const String& pasteboardName, const Vector<String>& promisedFilePaths)
     : m_pasteboardName(pasteboardName)
     , m_changeCount(platformStrategies()->pasteboardStrategy()->changeCount(m_pasteboardName))
+    , m_promisedFilePaths(promisedFilePaths)
 {
     ASSERT(pasteboardName);
 }
@@ -114,7 +115,7 @@ std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop()
 
 std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData)
 {
-    return std::make_unique<Pasteboard>(dragData.pasteboardName());
+    return std::make_unique<Pasteboard>(dragData.pasteboardName(), dragData.fileNames());
 }
 #endif
 
@@ -275,6 +276,20 @@ void Pasteboard::writeMarkup(const String&)
 {
 }
 
+// FIXME: This should be a general utility function for Vectors of Strings (or things that can be
+// converted to Strings). It could also be faster by computing the total length and reserving that
+// capacity in the StringBuilder.
+static String joinPathnames(const Vector<String>& pathnames)
+{
+    StringBuilder builder;
+    for (auto& path : pathnames) {
+        if (!builder.isEmpty())
+            builder.append('\n');
+        builder.append(path);
+    }
+    return builder.toString();
+}
+
 void Pasteboard::read(PasteboardPlainText& text)
 {
     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
@@ -317,13 +332,13 @@ void Pasteboard::read(PasteboardPlainText& text)
     if (types.contains(String(legacyFilenamesPasteboardType()))) {
         Vector<String> pathnames;
         strategy.getPathnamesForType(pathnames, legacyFilenamesPasteboardType(), m_pasteboardName);
-        StringBuilder builder;
-        for (size_t i = 0, size = pathnames.size(); i < size; i++) {
-            if (i)
-                builder.append('\n');
-            builder.append(pathnames[i]);
-        }
-        text.text = builder.toString();
+        text.text = joinPathnames(pathnames);
+        text.isURL = false;
+        return;
+    }
+
+    if (types.contains(String(legacyFilesPromisePasteboardType()))) {
+        text.text = joinPathnames(m_promisedFilePaths);
         text.isURL = false;
         return;
     }
@@ -356,6 +371,11 @@ void Pasteboard::read(PasteboardWebContentReader& reader, WebContentReadingPolic
             return;
     }
 
+    if (policy == WebContentReadingPolicy::AnyType && types.contains(String(legacyFilesPromisePasteboardType()))) {
+        if (m_changeCount != changeCount() || reader.readFilePaths(m_promisedFilePaths))
+            return;
+    }
+
     if (types.contains(String(legacyHTMLPasteboardType()))) {
         String string = strategy.stringForType(legacyHTMLPasteboardType(), m_pasteboardName);
         if (m_changeCount != changeCount() || (!string.isNull() && reader.readHTML(string)))
@@ -463,23 +483,6 @@ void Pasteboard::clear(const String& type)
     m_changeCount = platformStrategies()->pasteboardStrategy()->setStringForType(emptyString(), cocoaType, m_pasteboardName);
 }
 
-static Vector<String> absoluteURLsFromPasteboardFilenames(const String& pasteboardName, bool onlyFirstURL = false)
-{
-    Vector<String> fileList;
-    platformStrategies()->pasteboardStrategy()->getPathnamesForType(fileList, String(legacyFilenamesPasteboardType()), pasteboardName);
-
-    if (fileList.isEmpty())
-        return fileList;
-
-    size_t count = onlyFirstURL ? 1 : fileList.size();
-    Vector<String> urls;
-    for (size_t i = 0; i < count; i++) {
-        NSURL *url = [NSURL fileURLWithPath:fileList[i]];
-        urls.append(String([url absoluteString]));
-    }
-    return urls;
-}
-
 String Pasteboard::readPlatformValueAsString(const String& domType, long changeCount, const String& pasteboardName)
 {
     const String& cocoaType = cocoaTypeFromHTMLClipboardType(domType);
@@ -561,16 +564,21 @@ void Pasteboard::writeString(const String& type, const String& data)
 
 Vector<String> Pasteboard::readFilePaths()
 {
-    // FIXME: Seems silly to convert paths to URLs and then back to paths. Does that do anything helpful?
-    Vector<String> absoluteURLs = absoluteURLsFromPasteboardFilenames(m_pasteboardName);
-    Vector<String> paths;
-    paths.reserveCapacity(absoluteURLs.size());
-    for (size_t i = 0; i < absoluteURLs.size(); i++) {
-        NSURL *absoluteURL = [NSURL URLWithString:absoluteURLs[i]];
-        ASSERT([absoluteURL isFileURL]);
-        paths.uncheckedAppend([absoluteURL path]);
+    auto& strategy = *platformStrategies()->pasteboardStrategy();
+
+    Vector<String> types;
+    strategy.getTypes(types, m_pasteboardName);
+
+    if (types.contains(String(legacyFilenamesPasteboardType()))) {
+        Vector<String> filePaths;
+        strategy.getPathnamesForType(filePaths, legacyFilenamesPasteboardType(), m_pasteboardName);
+        return filePaths;
     }
-    return paths;
+
+    if (types.contains(String(legacyFilesPromisePasteboardType())))
+        return m_promisedFilePaths;
+    
+    return { };
 }
 
 #if ENABLE(DRAG_SUPPORT)
index 8eee8d0..8421131 100644 (file)
@@ -65,6 +65,10 @@ int PlatformPasteboard::numberOfFiles() const
 {
     Vector<String> files;
     getPathnamesForType(files, String(legacyFilenamesPasteboardType()));
+
+    // FIXME: legacyFilesPromisePasteboardType() contains UTIs, not path names. Also, it's not
+    // guaranteed that the count of UTIs equals the count of files, since some clients only write
+    // unique UTIs.
     if (!files.size())
         getPathnamesForType(files, String(legacyFilesPromisePasteboardType()));
     return files.size();
index 688c51f..b46fcf0 100644 (file)
@@ -1,3 +1,16 @@
+2018-03-05  Andy Estes  <aestes@apple.com>
+
+        [Mac] Teach WebCore::Pasteboard about file promise drags
+        https://bugs.webkit.org/show_bug.cgi?id=183314
+        <rdar://problem/38105493>
+
+        Reviewed by Darin Adler.
+
+        Added a FIXME comment.
+
+        * UIProcess/Cocoa/WebViewImpl.mm:
+        (WebKit::WebViewImpl::performDragOperation):
+
 2018-03-05  Jeff Miller  <jeffm@apple.com>
 
         Expose still more WKPreferences SPI to match C SPI
index bdfafb7..7b85e2d 100644 (file)
@@ -3711,6 +3711,10 @@ bool WebViewImpl::performDragOperation(id <NSDraggingInfo> draggingInfo)
             fileNames.append(file);
         m_page->createSandboxExtensionsIfNeeded(fileNames, sandboxExtensionHandle, sandboxExtensionForUpload);
     } else if (![types containsObject:PasteboardTypes::WebArchivePboardType] && [types containsObject:WebCore::legacyFilesPromisePasteboardType()]) {
+        
+        // FIXME: legacyFilesPromisePasteboardType() contains UTIs, not path names. Also, it's not
+        // guaranteed that the count of UTIs equals the count of files, since some clients only write
+        // unique UTIs.
         NSArray *files = [draggingInfo.draggingPasteboard propertyListForType:WebCore::legacyFilesPromisePasteboardType()];
         if (![files isKindOfClass:[NSArray class]]) {
             delete dragData;
index 8fff385..4d1398d 100644 (file)
@@ -1,3 +1,16 @@
+2018-03-05  Andy Estes  <aestes@apple.com>
+
+        [Mac] Teach WebCore::Pasteboard about file promise drags
+        https://bugs.webkit.org/show_bug.cgi?id=183314
+        <rdar://problem/38105493>
+
+        Reviewed by Darin Adler.
+
+        Added a FIXME comment.
+
+        * WebView/WebView.mm:
+        (-[WebView performDragOperation:]):
+
 2018-03-04  Yusuke Suzuki  <utatane.tea@gmail.com>
 
         [WTF] Move currentCPUTime and sleep(Seconds) to CPUTime.h and Seconds.h respectively
index 4d67265..ec26b2b 100644 (file)
@@ -6715,6 +6715,10 @@ static NSString * const backingPropertyOldScaleFactorKey = @"NSBackingPropertyOl
 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
     NSArray* types = draggingInfo.draggingPasteboard.types;
     if (![types containsObject:WebArchivePboardType] && ![types containsObject:legacyFilenamesPasteboardType()] && [types containsObject:legacyFilesPromisePasteboardType()]) {
+        
+        // FIXME: legacyFilesPromisePasteboardType() contains UTIs, not path names. Also, it's not
+        // guaranteed that the count of UTIs equals the count of files, since some clients only write
+        // unique UTIs.
         NSArray *files = [draggingInfo.draggingPasteboard propertyListForType:legacyFilesPromisePasteboardType()];
         if (![files isKindOfClass:[NSArray class]]) {
             delete dragData;
index 861c92e..d406911 100644 (file)
@@ -1,3 +1,62 @@
+2018-03-05  Andy Estes  <aestes@apple.com>
+
+        [Mac] Teach WebCore::Pasteboard about file promise drags
+        https://bugs.webkit.org/show_bug.cgi?id=183314
+        <rdar://problem/38105493>
+
+        Reviewed by Darin Adler.
+
+        * DumpRenderTree/DumpRenderTreeFileDraggingSource.h:
+        * DumpRenderTree/DumpRenderTreeFileDraggingSource.m:
+        (-[DumpRenderTreeFileDraggingSource initWithPromisedFileURLs:]):
+        (-[DumpRenderTreeFileDraggingSource dealloc]):
+
+        Taught DumpRenderTreeFileDraggingSource to store the promised file URLs.
+
+        * DumpRenderTree/mac/DumpRenderTree.mm:
+        (runTest):
+
+        Called +[DumpRenderTreeDraggingInfo clearAllFilePromiseReceivers] after running a test.
+
+        * DumpRenderTree/mac/DumpRenderTreeDraggingInfo.h:
+        * DumpRenderTree/mac/DumpRenderTreeDraggingInfo.mm:
+        (-[DumpRenderTreeFilePromiseReceiver initWithPromisedUTIs:]):
+        (-[DumpRenderTreeFilePromiseReceiver fileTypes]):
+        (-[DumpRenderTreeFilePromiseReceiver fileNames]):
+        (-[DumpRenderTreeFilePromiseReceiver dealloc]):
+        (copyFile):
+        (-[DumpRenderTreeFilePromiseReceiver receivePromisedFilesAtDestination:options:operationQueue:reader:]):
+
+        We can't instantiate real NSFilePromiseReceivers in DumpRenderTree. They rely on the
+        pasteboard server to generate unique file URLs, which is incompatible with our swizzled
+        NSPasteboard.
+
+        Instead, create a subclass of NSFilePromiseReceiver that implements its own promise resolution.
+        -receivePromisedFilesAtDestination:... asks its DumpRenderTreeFileDraggingSource for the
+        array of file URLs, then copies each to the destination directory on the specified operation
+        queue. It emulates how NSPasteboard tries to find a unique destination name by appending
+        numbers to the file name.
+
+        All receivers are collected in a global array that is cleared when each test finishes.
+        DumpRenderTreeFilePromiseReceiver will delete the files it copied in -dealloc.
+
+        (+[DumpRenderTreeDraggingInfo clearAllFilePromiseReceivers]):
+        (-[DumpRenderTreeDraggingInfo enumerateDraggingItemsWithOptions:forView:classes:searchOptions:usingBlock:]):
+        
+        If NSFilesPromisePboardType is on the pasteboard and classArray contains
+        NSFilePromiseReceiver, construct a DumpRenderTreeFilePromiseReceiver, add it to the array of
+        all file promise receivers, then wrap it in an NSDraggingItem and call block.
+
+        * DumpRenderTree/mac/EventSendingController.mm:
+        (+[EventSendingController isSelectorExcludedFromWebScript:]):
+        (+[EventSendingController webScriptNameForSelector:]):
+        (-[EventSendingController beginDragWithFilePromises:]):
+
+        Implement eventSender.beginDragWithFilePromises() by placing file UTIs on the pasteboard
+        with type NSFilesPromisePboardType, creating a DumpRenderTreeFileDraggingSource with the
+        file URLs, and creating a new DumpRenderTreeDraggingInfo and passing it to
+        -[WebView draggingEntered:].
+
 2018-03-05  Aakash Jain  <aakash_jain@apple.com>
 
         [webkitpy] Bugzilla class should use NetworkTransaction for network operations
index 1d42a47..89e1fd3 100644 (file)
@@ -1,4 +1,5 @@
 // Copyright (c) 2009, Google Inc. All rights reserved.
+// Copyright (C) 2018 Apple Inc. All rights reserved.
 // 
 // Redistribution and use in source and binary forms, with or without
 // modification, are permitted provided that the following conditions are
 // Used by -[EventSendingController beginDragWithFiles:]
 
 @interface DumpRenderTreeFileDraggingSource : NSObject {
+    NSArray<NSURL *> *_promisedFileURLs;
 }
 
+- (instancetype)initWithPromisedFileURLs:(NSArray<NSURL *> *)promisedFileURLs;
 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag;
+@property (nonatomic, readonly) NSArray<NSURL *> *promisedFileURLs;
 
 @end
 
index 14227c4..1830c3b 100644 (file)
@@ -1,4 +1,5 @@
 // Copyright (c) 2009, Google Inc. All rights reserved.
+// Copyright (C) 2018 Apple Inc. All rights reserved.
 // 
 // Redistribution and use in source and binary forms, with or without
 // modification, are permitted provided that the following conditions are
 
 @implementation DumpRenderTreeFileDraggingSource
 
+@synthesize promisedFileURLs=_promisedFileURLs;
+
+- (instancetype)initWithPromisedFileURLs:(NSArray<NSURL *> *)promisedFileURLs
+{
+    self = [super init];
+    if (!self)
+        return nil;
+
+    _promisedFileURLs = [promisedFileURLs copy];
+    return self;
+}
+
+- (void)dealloc
+{
+    [_promisedFileURLs release];
+    [super dealloc];
+}
+
 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
 {
     return NSDragOperationCopy;
index f98fb7e..2ed38d6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005-2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2005-2018 Apple Inc. All rights reserved.
  *           (C) 2007 Graham Dennis (graham.dennis@gmail.com)
  *
  * Redistribution and use in source and binary forms, with or without
@@ -2051,6 +2051,10 @@ static void runTest(const string& inputLine)
     gTestRunner->cleanup();
     gTestRunner = nullptr;
 
+#if PLATFORM(MAC)
+    [DumpRenderTreeDraggingInfo clearAllFilePromiseReceivers];
+#endif
+
     if (ignoreWebCoreNodeLeaks)
         [WebCoreStatistics stopIgnoringWebCoreNodeLeaks];
 
index 2a9cd74..811ae1c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005, 2006 Apple Inc.  All rights reserved.
+ * Copyright (C) 2005-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -51,6 +51,8 @@
 
 - (void)slideDraggedImageTo:(NSPoint)screenPoint;
 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination;
+
++ (void)clearAllFilePromiseReceivers;
 @end
 
 #endif
index f219d79..28a9e71 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005, 2006 Apple Inc.  All rights reserved.
+ * Copyright (C) 2005-2018 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 #if !PLATFORM(IOS)
 
 #import "DumpRenderTree.h"
+#import "DumpRenderTreeFileDraggingSource.h"
+#import "DumpRenderTreePasteboard.h"
 #import "EventSendingController.h"
 #import <WebKit/WebKit.h>
+#import <wtf/RetainPtr.h>
+
+@interface NSDraggingItem ()
+- (void)setItem:(id)item;
+@end
+
+@interface DumpRenderTreeFilePromiseReceiver : NSFilePromiseReceiver {
+    RetainPtr<NSArray<NSString *>> _promisedUTIs;
+    RetainPtr<NSMutableArray<NSURL *>> _destinationURLs;
+    DumpRenderTreeFileDraggingSource *_draggingSource;
+}
+
+- (instancetype)initWithPromisedUTIs:(NSArray<NSString *> *)promisedUTIs;
+
+@property (nonatomic, retain) DumpRenderTreeFileDraggingSource *draggingSource;
+
+@end
+
+@implementation DumpRenderTreeFilePromiseReceiver
+
+@synthesize draggingSource=_draggingSource;
+
+- (instancetype)initWithPromisedUTIs:(NSArray<NSString *> *)promisedUTIs
+{
+    if (!(self = [super init]))
+        return nil;
+
+    _promisedUTIs = adoptNS([promisedUTIs copy]);
+    _destinationURLs = adoptNS([NSMutableArray new]);
+    return self;
+}
+
+- (NSArray<NSString *> *)fileTypes
+{
+    return _promisedUTIs.get();
+}
+
+- (NSArray<NSString *> *)fileNames
+{
+    NSMutableArray *fileNames = [NSMutableArray arrayWithCapacity:[_destinationURLs count]];
+    for (NSURL *url in _destinationURLs.get())
+        [fileNames addObject:url.lastPathComponent];
+    return fileNames;
+}
+
+- (void)dealloc
+{
+    // WebKit does not delete promised files it receives into NSTemporaryDirectory() (it should!),
+    // so we need to. Failing to do so could result in unpredictable file names in a subsequent test
+    // that promises a file with the same name as one of these destination URLs.
+
+    for (NSURL *destinationURL in _destinationURLs.get()) {
+        assert([destinationURL.path hasPrefix:NSTemporaryDirectory()]);
+        [NSFileManager.defaultManager removeItemAtURL:destinationURL error:nil];
+    }
+
+    [_draggingSource release];
+    [super dealloc];
+}
+
+static NSURL *copyFile(NSURL *sourceURL, NSURL *destinationDirectory, NSError *&error)
+{
+    // Emulate how CFPasteboard finds unique destination file names by inserting " 2", " 3", and so
+    // on between the file name's base and extension until a new file is successfully created in
+    // the destination directory.
+
+    NSUInteger number = 2;
+    NSString *fileName = sourceURL.lastPathComponent;
+    NSURL *destinationURL = [NSURL fileURLWithPath:fileName relativeToURL:destinationDirectory];
+    while (![NSFileManager.defaultManager copyItemAtURL:sourceURL toURL:destinationURL error:&error]) {
+        if (error.domain != NSCocoaErrorDomain || error.code != NSFileWriteFileExistsError)
+            return nil;
+
+        NSString *newFileName = [NSString stringWithFormat:@"%@ %lu.%@", fileName.stringByDeletingPathExtension, (unsigned long)number++, fileName.pathExtension];
+        destinationURL = [NSURL fileURLWithPath:newFileName relativeToURL:destinationDirectory];
+        error = nil;
+    }
+
+    return destinationURL;
+}
+
+- (void)receivePromisedFilesAtDestination:(NSURL *)destinationDirectory options:(NSDictionary *)options operationQueue:(NSOperationQueue *)operationQueue reader:(void (^)(NSURL *fileURL, NSError * __nullable errorOrNil))reader
+{
+    // Layout tests need files to be received in a predictable order, so execute operations in serial.
+    operationQueue.maxConcurrentOperationCount = 1;
+
+    NSArray<NSURL *> *sourceURLs = _draggingSource.promisedFileURLs;
+    for (NSURL *sourceURL in sourceURLs) {
+        [operationQueue addOperationWithBlock:^{
+            NSError *error = nil;
+            NSURL *destinationURL = copyFile(sourceURL, destinationDirectory, error);
+            if (destinationURL) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    [_destinationURLs addObject:destinationURL];
+                });
+            }
+
+            reader(destinationURL, error);
+        }];
+    }
+}
+
+@end
 
 @implementation DumpRenderTreeDraggingInfo
 
     // Ignored.
 }
 
-- (void)enumerateDraggingItemsWithOptions:(NSEnumerationOptions)enumOpts forView:(NSView *)view classes:(NSArray *)classArray searchOptions:(NSDictionary *)searchOptions usingBlock:(void (^)(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop))block
+static NSMutableArray<NSFilePromiseReceiver *> *allFilePromiseReceivers()
 {
-    // Ignored.
+    static NSMutableArray<NSFilePromiseReceiver *> *allReceivers = [[NSMutableArray alloc] init];
+    return allReceivers;
+}
+
++ (void)clearAllFilePromiseReceivers
+{
+    [allFilePromiseReceivers() removeAllObjects];
+}
+
+- (void)enumerateDraggingItemsWithOptions:(NSEnumerationOptions)enumOptions forView:(NSView *)view classes:(NSArray *)classArray searchOptions:(NSDictionary *)searchOptions usingBlock:(void (^)(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop))block
+{
+    assert(!enumOptions);
+    assert(!searchOptions.count);
+
+    BOOL stop = NO;
+    for (Class classObject in classArray) {
+        if (classObject != NSFilePromiseReceiver.class)
+            continue;
+
+        id promisedUTIs = [draggingPasteboard propertyListForType:NSFilesPromisePboardType];
+        if (![promisedUTIs isKindOfClass:NSArray.class])
+            return;
+
+        for (id object in promisedUTIs) {
+            if (![object isKindOfClass:NSString.class])
+                return;
+        }
+
+        auto receiver = adoptNS([[DumpRenderTreeFilePromiseReceiver alloc] initWithPromisedUTIs:promisedUTIs]);
+        [receiver setDraggingSource:draggingSource];
+        [allFilePromiseReceivers() addObject:receiver.get()];
+
+        auto item = adoptNS([NSDraggingItem new]);
+        [item setItem:receiver.get()];
+
+        block(item.get(), 0, &stop);
+        if (stop)
+            return;
+    }
 }
 
 -(NSSpringLoadingHighlight)springLoadingHighlight
index 66b772e..31cd6b2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2005, 2006, 2007, 2008, 2014-2015 Apple Inc. All rights reserved.
+ * Copyright (C) 2005-2018 Apple Inc. All rights reserved.
  * Copyright (C) 2006 Jonas Witt <jonas.witt@gmail.com>
  * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
  * Copyright (C) 2006 Alexey Proskuryakov <ap@nypop.com>
 #import "DumpRenderTree.h"
 #import "DumpRenderTreeDraggingInfo.h"
 #import "DumpRenderTreeFileDraggingSource.h"
+#import "DumpRenderTreePasteboard.h"
 #import "WebCoreTestSupport.h"
 #import <WebKit/DOMPrivate.h>
 #import <WebKit/WebKit.h>
 #import <WebKit/WebViewPrivate.h>
 #import <functional>
+#import <wtf/RetainPtr.h>
 
 #if !PLATFORM(IOS)
 #import <Carbon/Carbon.h> // for GetCurrentEventTime()
@@ -255,6 +257,7 @@ static NSDraggingSession *drt_WebHTMLView_beginDraggingSessionWithItemsEventSour
             || aSelector == @selector(callAfterScrollingCompletes:)
 #if PLATFORM(MAC)
             || aSelector == @selector(beginDragWithFiles:)
+            || aSelector == @selector(beginDragWithFilePromises:)
 #endif
 #if PLATFORM(IOS)
             || aSelector == @selector(addTouchAtX:y:)
@@ -286,6 +289,8 @@ static NSDraggingSession *drt_WebHTMLView_beginDraggingSessionWithItemsEventSour
 #if PLATFORM(MAC)
     if (aSelector == @selector(beginDragWithFiles:))
         return @"beginDragWithFiles";
+    if (aSelector == @selector(beginDragWithFilePromises:))
+        return @"beginDragWithFilePromises";
 #endif
     if (aSelector == @selector(contextClick))
         return @"contextClick";
@@ -458,6 +463,43 @@ static NSEventType eventTypeForMouseButtonAndAction(int button, MouseAction acti
     dragMode = NO; // dragMode saves events and then replays them later.  We don't need/want that.
     leftMouseButtonDown = YES; // Make the rest of eventSender think a drag is in progress
 }
+
+- (void)beginDragWithFilePromises:(WebScriptObject *)filePaths
+{
+    assert(!draggingInfo);
+
+    NSPasteboard *pasteboard = [NSPasteboard pasteboardWithUniqueName];
+    [pasteboard declareTypes:@[NSFilesPromisePboardType] owner:nil];
+
+    NSURL *currentTestURL = [NSURL URLWithString:mainFrame.webView.mainFrameURL];
+
+    size_t i = 0;
+    NSMutableArray *fileURLs = [NSMutableArray array];
+    NSMutableArray *fileUTIs = [NSMutableArray array];
+    while (true) {
+        id filePath = [filePaths webScriptValueAtIndex:i++];
+        if (![filePath isKindOfClass:NSString.class])
+            break;
+
+        NSURL *fileURL = [NSURL fileURLWithPath:(NSString *)filePath relativeToURL:currentTestURL];
+        [fileURLs addObject:fileURL];
+
+        NSString *fileUTI;
+        if (![fileURL getResourceValue:&fileUTI forKey:NSURLTypeIdentifierKey error:nil])
+            break;
+        [fileUTIs addObject:fileUTI];
+    }
+
+    [pasteboard setPropertyList:fileUTIs forType:NSFilesPromisePboardType];
+    assert([pasteboard propertyListForType:NSFilesPromisePboardType]);
+
+    auto source = adoptNS([[DumpRenderTreeFileDraggingSource alloc] initWithPromisedFileURLs:fileURLs]);
+    draggingInfo = [[DumpRenderTreeDraggingInfo alloc] initWithImage:nil offset:NSZeroSize pasteboard:pasteboard source:source.get()];
+    [mainFrame.webView draggingEntered:draggingInfo];
+
+    dragMode = NO;
+    leftMouseButtonDown = YES;
+}
 #endif // !PLATFORM(IOS)
 
 - (void)updateClickCountForButton:(int)buttonNumber