Title: [222885] trunk
Revision
222885
Author
wenson_hs...@apple.com
Date
2017-10-04 15:28:08 -0700 (Wed, 04 Oct 2017)

Log Message

Add basic support for the version of DataTransferItemList.add that takes a File
https://bugs.webkit.org/show_bug.cgi?id=177853
<rdar://problem/34807346>

Reviewed by Ryosuke Niwa.

Source/WebCore:

Adds very basic support for DataTransferItemList.add(File). So far, a File added in this way can only be read
back from the same DataTransfer, during dragstart or copy. This File isn't written to the platform pasteboard
yet, so even dropping or pasting in the same page will not transfer the File, but this brings us closer to
parity with other browsers. See per-method comments for details.

Tests: editing/pasteboard/data-transfer-item-list-add-file-multiple-times.html
       editing/pasteboard/data-transfer-item-list-add-file-on-copy.html
       editing/pasteboard/data-transfer-item-list-add-file-on-drag.html

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

Recompute the DataTransfer's FileList. This behaves the same way as destroying the FileList altogether and
building it from scratch, but we avoid that approach because the FileList object needs to maintain the same DOM
wrapper after a File-backed item is removed.

(WebCore::DataTransfer::itemListDidAddFile):

Add the newly appended DataTransferItem's File to the DataTransfer's FileList.

(WebCore::DataTransfer::types const):

Return only the "Files" type if there are file-backed items in the DataTransfer's item list.

(WebCore::DataTransfer::updatedFilesForFileList const):
(WebCore::DataTransfer::files const):
* dom/DataTransfer.h:
* dom/DataTransferItem.h:
(WebCore::DataTransferItem::file const):
* dom/DataTransferItemList.cpp:
(WebCore::DataTransferItemList::add):
(WebCore::DataTransferItemList::remove):
(WebCore::DataTransferItemList::clear):

When removing a File, only clear from the DataTransfer's pasteboard if the removed item is not a File (otherwise,
clearing a File that shares the same type as some other item in the pasteboard will erroneously clear that other
item as well). Additionally, call out to the DataTransfer to update the FileList.

* dom/DataTransferItemList.h:
(WebCore::DataTransferItemList::hasItems const):
(WebCore::DataTransferItemList::items const):

Add helpers for directly accessing an item list's items. items() should be used in conjunction with hasItems().
This route is taken to (1) avoid having to copy the vector of Files, and (2) to avoid generating m_items if it
doesn't already exist.

LayoutTests:

Add tests to verify that Files can be added to and removed from the DataTransferItemList, and also read back via
both the item list and the DataTransfer's FileList when copying and dragging. Additionally, adds a test that adds
and removes the same File to the DataTransferItemList multiple times.

* TestExpectations:
* editing/pasteboard/data-transfer-item-list-add-file-multiple-times-expected.txt: Added.
* editing/pasteboard/data-transfer-item-list-add-file-multiple-times.html: Added.
* editing/pasteboard/data-transfer-item-list-add-file-on-copy-expected.txt: Added.
* editing/pasteboard/data-transfer-item-list-add-file-on-copy.html: Added.
* editing/pasteboard/data-transfer-item-list-add-file-on-drag-expected.txt: Added.
* editing/pasteboard/data-transfer-item-list-add-file-on-drag.html: Added.
* platform/ios-simulator-wk1/TestExpectations:
* platform/mac-wk1/TestExpectations:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (222884 => 222885)


--- trunk/LayoutTests/ChangeLog	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/LayoutTests/ChangeLog	2017-10-04 22:28:08 UTC (rev 222885)
@@ -1,3 +1,25 @@
+2017-10-04  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Add basic support for the version of DataTransferItemList.add that takes a File
+        https://bugs.webkit.org/show_bug.cgi?id=177853
+        <rdar://problem/34807346>
+
+        Reviewed by Ryosuke Niwa.
+
+        Add tests to verify that Files can be added to and removed from the DataTransferItemList, and also read back via
+        both the item list and the DataTransfer's FileList when copying and dragging. Additionally, adds a test that adds
+        and removes the same File to the DataTransferItemList multiple times.
+
+        * TestExpectations:
+        * editing/pasteboard/data-transfer-item-list-add-file-multiple-times-expected.txt: Added.
+        * editing/pasteboard/data-transfer-item-list-add-file-multiple-times.html: Added.
+        * editing/pasteboard/data-transfer-item-list-add-file-on-copy-expected.txt: Added.
+        * editing/pasteboard/data-transfer-item-list-add-file-on-copy.html: Added.
+        * editing/pasteboard/data-transfer-item-list-add-file-on-drag-expected.txt: Added.
+        * editing/pasteboard/data-transfer-item-list-add-file-on-drag.html: Added.
+        * platform/ios-simulator-wk1/TestExpectations:
+        * platform/mac-wk1/TestExpectations:
+
 2017-10-04  Per Arne Vollan  <pvol...@apple.com>
 
         Mark http/wpt/cache-storage/cache-quota.any.html as flaky on Windows.

Modified: trunk/LayoutTests/TestExpectations (222884 => 222885)


--- trunk/LayoutTests/TestExpectations	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/LayoutTests/TestExpectations	2017-10-04 22:28:08 UTC (rev 222885)
@@ -74,6 +74,7 @@
 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/drag-end-crash-accessing-item-list.html [ Skip ]
+editing/pasteboard/data-transfer-item-list-add-file-on-drag.html [ Skip ]
 
 # Only iOS supports QuickLook
 quicklook [ Skip ]

Added: trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-multiple-times-expected.txt (0 => 222885)


--- trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-multiple-times-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-multiple-times-expected.txt	2017-10-04 22:28:08 UTC (rev 222885)
@@ -0,0 +1,401 @@
+Copy this text!
+To manually test, copy the above text. The output below dumps DataTransfer state following each operation,
+
+described directly above the output text for each step. The DataTransfer state should be consistent with the
+
+operation performed at each step.
+
+
+1. After adding all items
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/uri-list",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        },
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        },
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        },
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        }
+    ]
+}
+
+2. After removing at index 4
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/uri-list",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        },
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        },
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+3. After removing at index 1
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/uri-list",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        },
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+4. After removing at index 3
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/uri-list",
+            "kind": "string",
+            "file": null
+        }
+    ],
+    "files": [
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+5. After clearing items
+{
+    "data": {},
+    "items": [],
+    "files": []
+}
+
+6. After adding two files and some string data again
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        },
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        }
+    ]
+}
+
+7. After removing at index 2
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        },
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+8. After removing at index 2
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        }
+    ],
+    "files": [
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+9. After removing at index 1
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "file.txt",
+                "bytes": 20,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "file.txt",
+            "bytes": 20,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+10. After removing at index 0
+{
+    "data": {},
+    "items": [],
+    "files": []
+}
+removedItem.getAsFile() should be null: null
+

Added: trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-multiple-times.html (0 => 222885)


--- trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-multiple-times.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-multiple-times.html	2017-10-04 22:28:08 UTC (rev 222885)
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta charset="utf-8">
+<style>
+body, html {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+}
+</style>
+<body>
+    <div style="font-size: 40px;" id="source">Copy this text!</div>
+    <p>To manually test, copy the above text. The output below dumps DataTransfer state following each operation,</p>
+    <p>described directly above the output text for each step. The DataTransfer state should be consistent with the</p>
+    <p>operation performed at each step.</p>
+    <pre style="width: 100%; height: 100%" id="output"></pre>
+</body>
+<script>
+function write(message) {
+    output.textContent += `${message}\n`;
+}
+
+function representationForFile(file) {
+    return file ? {
+        name: file.name,
+        bytes: file.size,
+        type: file.type
+    } : null;
+}
+
+function removeAt(itemList, index) {
+    const removedItem = itemList[index];
+    itemList.remove(index);
+    return removedItem;
+}
+
+function updateOutputText(description, event, itemList, fileList) {
+    const dataInfo = {};
+    for (const type of event.clipboardData.types)
+        dataInfo[type] = event.clipboardData.getData(type);
+    const itemsInfo = []
+    for (const item of itemList) {
+        itemsInfo.push({
+            type: item.type,
+            kind: item.kind,
+            file: representationForFile(item.getAsFile())
+        });
+    }
+    write(`\n${description}\n${JSON.stringify({
+        data: dataInfo,
+        items: itemsInfo,
+        files: Array.from(fileList).map(representationForFile)
+    }, null, "    ")}`);
+}
+
+source.addEventListener("copy", event => {
+    const file = new File([ "This is a text file." ], "file.txt", { type: "text/plain" });
+
+    let itemList = event.clipboardData.items;
+    let fileList = event.clipboardData.files;
+    event.clipboardData.items.add(file);
+    event.clipboardData.items.add(file);
+    event.clipboardData.items.add("plain text string", "text/plain");
+    event.clipboardData.items.add("https://webkit.org", "text/uri-list");
+    event.clipboardData.items.add(file);
+    event.clipboardData.items.add(file);
+    updateOutputText("1. After adding all items", event, itemList, fileList);
+
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    let removedItem = removeAt(itemList, 4);
+    updateOutputText("2. After removing at index 4", event, itemList, fileList);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    removedItem = removeAt(itemList, 1);
+    updateOutputText("3. After removing at index 1", event, itemList, fileList);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    removedItem = removeAt(itemList, 3);
+    updateOutputText("4. After removing at index 3", event, itemList, fileList);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    event.clipboardData.items.clear();
+    updateOutputText("5. After clearing items", event, itemList, fileList);
+
+    event.clipboardData.items.add(file);
+    event.clipboardData.items.add("<strong>some styled text</strong>", "text/html");
+    event.clipboardData.items.add("some plain text", "text/plain");
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    event.clipboardData.items.add(file);
+    updateOutputText("6. After adding two files and some string data again", event, itemList, fileList);
+
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    removedItem = removeAt(itemList, 2);
+    updateOutputText("7. After removing at index 2", event, itemList, fileList);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    removedItem = removeAt(itemList, 2);
+    updateOutputText("8. After removing at index 2", event, itemList, fileList);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    removedItem = removeAt(itemList, 1);
+    updateOutputText("9. After removing at index 1", event, itemList, fileList);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    itemList = event.clipboardData.items;
+    fileList = event.clipboardData.files;
+    removedItem = removeAt(itemList, 0);
+    updateOutputText("10. After removing at index 0", event, itemList, fileList);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    event.preventDefault();
+});
+
+getSelection().setBaseAndExtent(source, 0, source, 1);
+
+if (window.testRunner && window.internals) {
+    internals.settings.setCustomPasteboardDataEnabled(true);
+    testRunner.dumpAsText();
+    document.execCommand("Copy");
+}
+</script>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-copy-expected.txt (0 => 222885)


--- trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-copy-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-copy-expected.txt	2017-10-04 22:28:08 UTC (rev 222885)
@@ -0,0 +1,304 @@
+Copy this text!
+To manually test, copy the above text. The output below dumps DataTransfer state following each operation,
+
+described directly above the output text for each step. The DataTransfer state should be consistent with the
+
+operation performed at each step.
+
+
+1. After adding a string
+{
+    "data": {
+        "text/plain": "hello world"
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        }
+    ],
+    "files": []
+}
+
+2. After adding a file of custom type
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        }
+    ]
+}
+
+3. After adding the first plain text file
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "first.txt",
+                "bytes": 72,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        },
+        {
+            "name": "first.txt",
+            "bytes": 72,
+            "type": "text/plain"
+        }
+    ]
+}
+
+4. After removing the last file
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+5. After adding an HTML string
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        }
+    ]
+}
+
+6. After adding another plain text file
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "second.txt",
+                "bytes": 27,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        },
+        {
+            "name": "second.txt",
+            "bytes": 27,
+            "type": "text/plain"
+        }
+    ]
+}
+
+7. After removing the custom file
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "second.txt",
+                "bytes": 27,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "second.txt",
+            "bytes": 27,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+8. After removing the HTML string
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "second.txt",
+                "bytes": 27,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "second.txt",
+            "bytes": 27,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+9. After removing the second text file
+{
+    "data": {
+        "text/plain": "hello world"
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        }
+    ],
+    "files": []
+}
+removedItem.getAsFile() should be null: null
+
+10. After removing the plain text string
+{
+    "data": {},
+    "items": [],
+    "files": []
+}
+removedItem.getAsFile() should be null: null
+The DataTransfer's FileList should be the same object: true
+

Added: trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-copy.html (0 => 222885)


--- trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-copy.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-copy.html	2017-10-04 22:28:08 UTC (rev 222885)
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta charset="utf-8">
+<style>
+body, html {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+}
+</style>
+<body>
+    <div style="font-size: 40px;" id="source">Copy this text!</div>
+    <p>To manually test, copy the above text. The output below dumps DataTransfer state following each operation,</p>
+    <p>described directly above the output text for each step. The DataTransfer state should be consistent with the</p>
+    <p>operation performed at each step.</p>
+    <pre style="height: 100%; width: 100%;" id="output"></pre>
+</body>
+<script>
+function write(message) {
+    output.textContent += `${message}\n`;
+}
+
+function representationForFile(file) {
+    return file ? {
+        name: file.name,
+        bytes: file.size,
+        type: file.type
+    } : null;
+}
+
+function updateOutputText(description, event) {
+    const dataInfo = {};
+    for (const type of event.clipboardData.types)
+        dataInfo[type] = event.clipboardData.getData(type);
+    const itemsInfo = [];
+    for (const item of event.clipboardData.items) {
+        itemsInfo.push({
+            type: item.type,
+            kind: item.kind,
+            file: representationForFile(item.getAsFile())
+        });
+    }
+    write(`\n${description}\n${JSON.stringify({
+        data: dataInfo,
+        items: itemsInfo,
+        files: Array.from(event.clipboardData.files).map(representationForFile)
+    }, null, "    ")}`);
+}
+
+function removeAt(itemList, index) {
+    const removedItem = itemList[index];
+    itemList.remove(index);
+    return removedItem;
+}
+
+source.addEventListener("copy", event => {
+    const fileList = event.clipboardData.files;
+    event.clipboardData.items.add("hello world", "text/plain");
+    updateOutputText("1. After adding a string", event);
+
+    const buffer = new ArrayBuffer(64);
+    const array = new Int8Array(buffer);
+    array.fill(15);
+    event.clipboardData.items.add(new File([ buffer ], "foo", { type: "custom" }));
+    updateOutputText("2. After adding a file of custom type", event);
+
+    event.clipboardData.items.add(new File([
+        new Blob(["This part is from a _javascript_ Blob"], { type : "text/plain" }),
+        "This part is just from a plain string"
+    ], "first.txt", { type: "text/plain" }));
+    updateOutputText("3. After adding the first plain text file", event);
+
+    removedItem = removeAt(event.clipboardData.items, 2);
+    updateOutputText("4. After removing the last file", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    event.clipboardData.items.add("<a>goodbye world</a>", "text/html");
+    updateOutputText("5. After adding an HTML string", event);
+
+    event.clipboardData.items.add(new File([ "This is just a plain string" ], "second.txt", { type: "text/plain" }));
+    updateOutputText("6. After adding another plain text file", event);
+
+    removedItem = removeAt(event.clipboardData.items, 1);
+    updateOutputText("7. After removing the custom file", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    removedItem = removeAt(event.clipboardData.items, 1);
+    updateOutputText("8. After removing the HTML string", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    removedItem = removeAt(event.clipboardData.items, 1);
+    updateOutputText("9. After removing the second text file", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    removedItem = removeAt(event.clipboardData.items, 0);
+    updateOutputText("10. After removing the plain text string", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+    write(`The DataTransfer's FileList should be the same object: ${fileList == event.clipboardData.files}`);
+
+    event.preventDefault();
+});
+
+getSelection().setBaseAndExtent(source, 0, source, 1);
+
+if (window.testRunner && window.internals) {
+    internals.settings.setCustomPasteboardDataEnabled(true);
+    testRunner.dumpAsText();
+    document.execCommand("Copy");
+}
+</script>
+</html>

Added: trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-drag-expected.txt (0 => 222885)


--- trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-drag-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-drag-expected.txt	2017-10-04 22:28:08 UTC (rev 222885)
@@ -0,0 +1,304 @@
+Drag me out.
+To manually test, drag the above text. The output below dumps DataTransfer state following each operation,
+
+described directly above the output text for each step. The DataTransfer state should be consistent with the
+
+operation performed at each step.
+
+
+1. After adding a string
+{
+    "data": {
+        "text/plain": "hello world"
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        }
+    ],
+    "files": []
+}
+
+2. After adding a file of custom type
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        }
+    ]
+}
+
+3. After adding the first plain text file
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "first.txt",
+                "bytes": 72,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        },
+        {
+            "name": "first.txt",
+            "bytes": 72,
+            "type": "text/plain"
+        }
+    ]
+}
+
+4. After removing the last file
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+5. After adding an HTML string
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        }
+    ]
+}
+
+6. After adding another plain text file
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "custom",
+            "kind": "file",
+            "file": {
+                "name": "foo",
+                "bytes": 64,
+                "type": "custom"
+            }
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "second.txt",
+                "bytes": 27,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "foo",
+            "bytes": 64,
+            "type": "custom"
+        },
+        {
+            "name": "second.txt",
+            "bytes": 27,
+            "type": "text/plain"
+        }
+    ]
+}
+
+7. After removing the custom file
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/html",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "second.txt",
+                "bytes": 27,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "second.txt",
+            "bytes": 27,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+8. After removing the HTML string
+{
+    "data": {
+        "Files": ""
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        },
+        {
+            "type": "text/plain",
+            "kind": "file",
+            "file": {
+                "name": "second.txt",
+                "bytes": 27,
+                "type": "text/plain"
+            }
+        }
+    ],
+    "files": [
+        {
+            "name": "second.txt",
+            "bytes": 27,
+            "type": "text/plain"
+        }
+    ]
+}
+removedItem.getAsFile() should be null: null
+
+9. After removing the second text file
+{
+    "data": {
+        "text/plain": "hello world"
+    },
+    "items": [
+        {
+            "type": "text/plain",
+            "kind": "string",
+            "file": null
+        }
+    ],
+    "files": []
+}
+removedItem.getAsFile() should be null: null
+
+10. After removing the plain text string
+{
+    "data": {},
+    "items": [],
+    "files": []
+}
+removedItem.getAsFile() should be null: null
+The DataTransfer's FileList should be the same object: true
+

Added: trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-drag.html (0 => 222885)


--- trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-drag.html	                        (rev 0)
+++ trunk/LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-drag.html	2017-10-04 22:28:08 UTC (rev 222885)
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta charset="utf-8">
+<style>
+body, html {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+}
+</style>
+<body>
+    <div style="font-size: 40px;" id="source" draggable="true">Drag me out.</div>
+    <p>To manually test, drag the above text. The output below dumps DataTransfer state following each operation,</p>
+    <p>described directly above the output text for each step. The DataTransfer state should be consistent with the</p>
+    <p>operation performed at each step.</p>
+    <pre style="width: 100%; height: 100%" id="output"></pre>
+</body>
+<script>
+function write(message) {
+    output.textContent += `${message}\n`;
+}
+
+function representationForFile(file) {
+    return file ? {
+        name: file.name,
+        bytes: file.size,
+        type: file.type
+    } : null;
+}
+
+function updateOutputText(description, event) {
+    const dataInfo = {};
+    for (const type of event.dataTransfer.types)
+        dataInfo[type] = event.dataTransfer.getData(type);
+    const itemsInfo = []
+    for (const item of event.dataTransfer.items) {
+        itemsInfo.push({
+            type: item.type,
+            kind: item.kind,
+            file: representationForFile(item.getAsFile())
+        });
+    }
+    write(`\n${description}\n${JSON.stringify({
+        data: dataInfo,
+        items: itemsInfo,
+        files: Array.from(event.dataTransfer.files).map(representationForFile)
+    }, null, "    ")}`);
+}
+
+function removeAt(itemList, index) {
+    const removedItem = itemList[index];
+    itemList.remove(index);
+    return removedItem;
+}
+
+output.addEventListener("dragover", event => event.preventDefault());
+output.addEventListener("drop", event => event.preventDefault());
+source.addEventListener("dragstart", event => {
+    const fileList = event.dataTransfer.files;
+    event.dataTransfer.items.add("hello world", "text/plain");
+    updateOutputText("1. After adding a string", event);
+
+    const buffer = new ArrayBuffer(64);
+    const array = new Int8Array(buffer);
+    array.fill(15);
+    event.dataTransfer.items.add(new File([ buffer ], "foo", { type: "custom" }));
+    updateOutputText("2. After adding a file of custom type", event);
+
+    event.dataTransfer.items.add(new File([
+        new Blob(["This part is from a _javascript_ Blob"], { type : "text/plain" }),
+        "This part is just from a plain string"
+    ], "first.txt", { type: "text/plain" }));
+    updateOutputText("3. After adding the first plain text file", event);
+
+    removedItem = removeAt(event.dataTransfer.items, 2);
+    updateOutputText("4. After removing the last file", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    event.dataTransfer.items.add("<a>goodbye world</a>", "text/html");
+    updateOutputText("5. After adding an HTML string", event);
+
+    event.dataTransfer.items.add(new File([ "This is just a plain string" ], "second.txt", { type: "text/plain" }));
+    updateOutputText("6. After adding another plain text file", event);
+
+    removedItem = removeAt(event.dataTransfer.items, 1);
+    updateOutputText("7. After removing the custom file", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    removedItem = removeAt(event.dataTransfer.items, 1);
+    updateOutputText("8. After removing the HTML string", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    removedItem = removeAt(event.dataTransfer.items, 1);
+    updateOutputText("9. After removing the second text file", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+
+    removedItem = removeAt(event.dataTransfer.items, 0);
+    updateOutputText("10. After removing the plain text string", event);
+    write(`removedItem.getAsFile() should be null: ${removedItem.getAsFile()}`);
+    write(`The DataTransfer's FileList should be the same object: ${fileList == event.dataTransfer.files}`);
+
+    event.preventDefault();
+});
+
+if (window.testRunner && window.eventSender && window.internals) {
+    internals.settings.setCustomPasteboardDataEnabled(true);
+    testRunner.dumpAsText();
+    eventSender.mouseMoveTo(100, 25);
+    eventSender.mouseDown();
+    eventSender.leapForward(1000);
+    eventSender.mouseMoveTo(100, 400);
+    eventSender.mouseUp();
+}
+</script>
+</html>

Modified: trunk/LayoutTests/platform/ios-simulator-wk1/TestExpectations (222884 => 222885)


--- trunk/LayoutTests/platform/ios-simulator-wk1/TestExpectations	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/LayoutTests/platform/ios-simulator-wk1/TestExpectations	2017-10-04 22:28:08 UTC (rev 222885)
@@ -11,3 +11,5 @@
 editing/pasteboard/data-transfer-get-data-on-paste-plain-text.html [ Pass ]
 editing/pasteboard/data-transfer-get-data-on-paste-rich-text.html [ Pass ]
 editing/pasteboard/data-transfer-get-data-non-normalized-types.html [ Pass ]
+editing/pasteboard/data-transfer-item-list-add-file-on-copy.html [ Pass ]
+editing/pasteboard/data-transfer-item-list-add-file-multiple-times.html [ Pass ]

Modified: trunk/LayoutTests/platform/mac-wk1/TestExpectations (222884 => 222885)


--- trunk/LayoutTests/platform/mac-wk1/TestExpectations	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/LayoutTests/platform/mac-wk1/TestExpectations	2017-10-04 22:28:08 UTC (rev 222885)
@@ -12,6 +12,7 @@
 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/drag-end-crash-accessing-item-list.html [ Pass ]
+editing/pasteboard/data-transfer-item-list-add-file-on-drag.html [ Pass ]
 
 #//////////////////////////////////////////////////////////////////////////////////////////
 # End platform-specific directories.

Modified: trunk/Source/WebCore/ChangeLog (222884 => 222885)


--- trunk/Source/WebCore/ChangeLog	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/Source/WebCore/ChangeLog	2017-10-04 22:28:08 UTC (rev 222885)
@@ -1,3 +1,57 @@
+2017-10-04  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Add basic support for the version of DataTransferItemList.add that takes a File
+        https://bugs.webkit.org/show_bug.cgi?id=177853
+        <rdar://problem/34807346>
+
+        Reviewed by Ryosuke Niwa.
+
+        Adds very basic support for DataTransferItemList.add(File). So far, a File added in this way can only be read
+        back from the same DataTransfer, during dragstart or copy. This File isn't written to the platform pasteboard
+        yet, so even dropping or pasting in the same page will not transfer the File, but this brings us closer to
+        parity with other browsers. See per-method comments for details.
+
+        Tests: editing/pasteboard/data-transfer-item-list-add-file-multiple-times.html
+               editing/pasteboard/data-transfer-item-list-add-file-on-copy.html
+               editing/pasteboard/data-transfer-item-list-add-file-on-drag.html
+
+        * dom/DataTransfer.cpp:
+        (WebCore::DataTransfer::updateFileList):
+
+        Recompute the DataTransfer's FileList. This behaves the same way as destroying the FileList altogether and
+        building it from scratch, but we avoid that approach because the FileList object needs to maintain the same DOM
+        wrapper after a File-backed item is removed.
+
+        (WebCore::DataTransfer::itemListDidAddFile):
+
+        Add the newly appended DataTransferItem's File to the DataTransfer's FileList.
+
+        (WebCore::DataTransfer::types const):
+
+        Return only the "Files" type if there are file-backed items in the DataTransfer's item list.
+
+        (WebCore::DataTransfer::updatedFilesForFileList const):
+        (WebCore::DataTransfer::files const):
+        * dom/DataTransfer.h:
+        * dom/DataTransferItem.h:
+        (WebCore::DataTransferItem::file const):
+        * dom/DataTransferItemList.cpp:
+        (WebCore::DataTransferItemList::add):
+        (WebCore::DataTransferItemList::remove):
+        (WebCore::DataTransferItemList::clear):
+
+        When removing a File, only clear from the DataTransfer's pasteboard if the removed item is not a File (otherwise,
+        clearing a File that shares the same type as some other item in the pasteboard will erroneously clear that other
+        item as well). Additionally, call out to the DataTransfer to update the FileList.
+
+        * dom/DataTransferItemList.h:
+        (WebCore::DataTransferItemList::hasItems const):
+        (WebCore::DataTransferItemList::items const):
+
+        Add helpers for directly accessing an item list's items. items() should be used in conjunction with hasItems().
+        This route is taken to (1) avoid having to copy the vector of Files, and (2) to avoid generating m_items if it
+        doesn't already exist.
+
 2017-10-04  Zalan Bujtas  <za...@apple.com>
 
         RenderMultiColumnFlow populate/evacuate should not disable layout state.

Modified: trunk/Source/WebCore/dom/DataTransfer.cpp (222884 => 222885)


--- trunk/Source/WebCore/dom/DataTransfer.cpp	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/Source/WebCore/dom/DataTransfer.cpp	2017-10-04 22:28:08 UTC (rev 222885)
@@ -28,6 +28,7 @@
 
 #include "CachedImage.h"
 #include "CachedImageClient.h"
+#include "DataTransferItem.h"
 #include "DataTransferItemList.h"
 #include "DragData.h"
 #include "Editor.h"
@@ -161,6 +162,26 @@
         m_itemList->didSetStringData(normalizedType);
 }
 
+void DataTransfer::updateFileList()
+{
+    // If we're removing an item, then the item list must exist, which implies that the file list must have been initialized already.
+    ASSERT(m_fileList);
+    ASSERT(canWriteData());
+
+    m_fileList->m_files = filesFromPasteboardAndItemList();
+}
+
+void DataTransfer::didAddFileToItemList()
+{
+    ASSERT(canWriteData());
+    if (!m_fileList)
+        return;
+
+    auto& newItem = m_itemList->items().last();
+    ASSERT(newItem->isFile());
+    m_fileList->append(*newItem->file());
+}
+
 DataTransferItemList& DataTransfer::items()
 {
     if (!m_itemList)
@@ -181,6 +202,9 @@
         return types;
     }
 
+    if (m_itemList && m_itemList->hasItems() && m_itemList->items().findMatching([] (const auto& item) { return item->isFile(); }) != notFound)
+        return { "Files" };
+
     if (m_pasteboard->containsFiles()) {
         ASSERT(!m_pasteboard->typesSafeForBindings().contains("Files"));
         return { "Files" };
@@ -191,6 +215,31 @@
     return types;
 }
 
+Vector<Ref<File>> DataTransfer::filesFromPasteboardAndItemList() const
+{
+    bool addedFilesFromPasteboard = false;
+    Vector<Ref<File>> files;
+    if (!forDrag() || forFileDrag()) {
+        WebCorePasteboardFileReader reader;
+        m_pasteboard->read(reader);
+        files = WTFMove(reader.files);
+        addedFilesFromPasteboard = !files.isEmpty();
+    }
+
+    bool itemListContainsItems = false;
+    if (m_itemList && m_itemList->hasItems()) {
+        for (auto& item : m_itemList->items()) {
+            if (auto file = item->file()) {
+                files.append(*file);
+            }
+        }
+        itemListContainsItems = true;
+    }
+
+    ASSERT(!itemListContainsItems || !addedFilesFromPasteboard);
+    return files;
+}
+
 FileList& DataTransfer::files() const
 {
     if (!canReadData()) {
@@ -201,19 +250,9 @@
         return *m_fileList;
     }
 
-    if (forDrag() && !forFileDrag()) {
-        if (m_fileList)
-            ASSERT(m_fileList->isEmpty());
-        else
-            m_fileList = FileList::create();
-        return *m_fileList;
-    }
+    if (!m_fileList)
+        m_fileList = FileList::create(filesFromPasteboardAndItemList());
 
-    if (!m_fileList) {
-        WebCorePasteboardFileReader reader;
-        m_pasteboard->read(reader);
-        m_fileList = FileList::create(WTFMove(reader.files));
-    }
     return *m_fileList;
 }
 

Modified: trunk/Source/WebCore/dom/DataTransfer.h (222884 => 222885)


--- trunk/Source/WebCore/dom/DataTransfer.h	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/Source/WebCore/dom/DataTransfer.h	2017-10-04 22:28:08 UTC (rev 222885)
@@ -36,6 +36,7 @@
 class DragImageLoader;
 class Element;
 class FileList;
+class File;
 class Pasteboard;
 
 class DataTransfer : public RefCounted<DataTransfer> {
@@ -99,6 +100,9 @@
     bool hasDragImage() const;
 #endif
 
+    void didAddFileToItemList();
+    void updateFileList();
+
 private:
     enum class Type { CopyAndPaste, DragAndDropData, DragAndDropFiles, InputEvent };
     DataTransfer(StoreMode, std::unique_ptr<Pasteboard>, Type = Type::CopyAndPaste);
@@ -111,6 +115,8 @@
     bool forFileDrag() const { return false; }
 #endif
 
+    Vector<Ref<File>> filesFromPasteboardAndItemList() const;
+
     StoreMode m_storeMode;
     std::unique_ptr<Pasteboard> m_pasteboard;
     std::unique_ptr<DataTransferItemList> m_itemList;

Modified: trunk/Source/WebCore/dom/DataTransferItem.h (222884 => 222885)


--- trunk/Source/WebCore/dom/DataTransferItem.h	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/Source/WebCore/dom/DataTransferItem.h	2017-10-04 22:28:08 UTC (rev 222885)
@@ -54,6 +54,7 @@
 
     ~DataTransferItem();
 
+    RefPtr<File> file() const { return m_file; }
     void clearListAndPutIntoDisabledMode();
 
     bool isFile() const { return m_file; }

Modified: trunk/Source/WebCore/dom/DataTransferItemList.cpp (222884 => 222885)


--- trunk/Source/WebCore/dom/DataTransferItemList.cpp	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/Source/WebCore/dom/DataTransferItemList.cpp	2017-10-04 22:28:08 UTC (rev 222885)
@@ -84,9 +84,14 @@
     return RefPtr<DataTransferItem> { m_items->last().copyRef() };
 }
 
-RefPtr<DataTransferItem> DataTransferItemList::add(Ref<File>&&)
+RefPtr<DataTransferItem> DataTransferItemList::add(Ref<File>&& file)
 {
-    return nullptr;
+    if (!m_dataTransfer.canWriteData())
+        return nullptr;
+
+    ensureItems().append(DataTransferItem::create(m_weakPtrFactory.createWeakPtr(*this), file->type(), file.copyRef()));
+    m_dataTransfer.didAddFileToItemList();
+    return RefPtr<DataTransferItem> { m_items->last().ptr() };
 }
 
 ExceptionOr<void> DataTransferItemList::remove(unsigned index)
@@ -98,13 +103,16 @@
     if (items.size() <= index)
         return Exception { IndexSizeError }; // Matches Gecko. See https://github.com/whatwg/html/issues/2925
 
-    // FIXME: Handle the removal of files once we added the support for writing a File.
-    ASSERT(!items[index]->isFile());
-
+    // Since file-backed DataTransferItems are not actually written to the pasteboard yet, we don't need to remove any
+    // temporary files. When we support writing file-backed DataTransferItems to the platform pasteboard, we will need
+    // to clean up here.
     auto& removedItem = items[index].get();
-    m_dataTransfer.pasteboard().clear(removedItem.type());
+    if (!removedItem.isFile())
+        m_dataTransfer.pasteboard().clear(removedItem.type());
     removedItem.clearListAndPutIntoDisabledMode();
     items.remove(index);
+    if (removedItem.isFile())
+        m_dataTransfer.updateFileList();
 
     return { };
 }
@@ -112,11 +120,17 @@
 void DataTransferItemList::clear()
 {
     m_dataTransfer.pasteboard().clear();
+    bool removedItemContainingFile = false;
     if (m_items) {
-        for (auto& item : *m_items)
+        for (auto& item : *m_items) {
+            removedItemContainingFile |= item->isFile();
             item->clearListAndPutIntoDisabledMode();
+        }
         m_items->clear();
     }
+
+    if (removedItemContainingFile)
+        m_dataTransfer.updateFileList();
 }
 
 Vector<Ref<DataTransferItem>>& DataTransferItemList::ensureItems() const

Modified: trunk/Source/WebCore/dom/DataTransferItemList.h (222884 => 222885)


--- trunk/Source/WebCore/dom/DataTransferItemList.h	2017-10-04 22:13:58 UTC (rev 222884)
+++ trunk/Source/WebCore/dom/DataTransferItemList.h	2017-10-04 22:28:08 UTC (rev 222885)
@@ -65,6 +65,12 @@
 
     void didClearStringData(const String& type);
     void didSetStringData(const String& type);
+    bool hasItems() const { return m_items.has_value(); }
+    const Vector<Ref<DataTransferItem>>& items() const
+    {
+        ASSERT(m_items);
+        return *m_items;
+    }
 
 private:
     Vector<Ref<DataTransferItem>>& ensureItems() const;
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to