Pasting from Excel no longer provides text/html data
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 9 Feb 2018 23:41:34 +0000 (23:41 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 9 Feb 2018 23:41:34 +0000 (23:41 +0000)
https://bugs.webkit.org/show_bug.cgi?id=182636
<rdar://problem/37087060>

Reviewed by Ryosuke Niwa.

Source/WebCore:

After r222656, we treat images on the pasteboard as files. However, we also have an existing policy which hides
text data ("text/uri-list", "text/html", "text/plain") from the page when files are present on the pasteboard.
When copying a table, Microsoft Excel writes a rendering of the table to the pasteboard as an image. This means
that we'll hide other data types (importantly, 'text/html') upon pasting, even though important clients (such as
Google Docs and Confluence) depend on the 'text/html' data in order to correctly handle the paste (rather than
paste as an image of a table).

To fix this, we add an exception to the DataTransfer.getData codepath when the pasteboard contains files.
Instead of always returning the empty string for text/html, we still allow pasteboard access, but only read
from a limited set of rich text types, i.e. web archive, RTF(D), and HTML markup. Importantly, this prevents
us from exposing any file paths that appear as plain text or URLs on the pasteboard. Just as in the regular
codepath for getData(), if the pasteboard data comes from the same origin, we allow unsanitized access;
otherwise, we use WebContentMarkupReader to extract markup from the pasteboard.

Tests:  PasteMixedContent.ImageFileAndPlainText
        PasteMixedContent.ImageFileAndWebArchive
        PasteMixedContent.ImageFileAndHTML
        PasteMixedContent.ImageFileAndRTF
        PasteMixedContent.ImageFileAndURL
        PasteMixedContent.ImageFileWithHTMLAndURL
        DataInteractionTests.DataTransferGetDataWhenDroppingImageAndMarkup

Also rebaselined some layout tests, which cover changes in behavior when dropping on macOS and pasting on iOS.

* dom/DataTransfer.cpp:
(WebCore::DataTransfer::getDataForItem const):

Augment the codepath handling the case where the pasteboard contains files, such that we allow reading
"text/html", but only from rich text types.

(WebCore::DataTransfer::readStringFromPasteboard const):

Factor out logic for reading from the pasteboard into a private helper. This is called in two places from
getDataForItem: in the normal (existing) path, and in the case where we allow 'text/html' to be read despite
files appearing in the pasteboard.

One important difference here is that this helper now takes a WebContentReadingPolicy, whose purpose is to
prevent reading from non-rich-text types when files appear in the pasteboard.

Another tweak here is that we now use `lowercaseType` instead of the original (unadjusted) `type` when reading
from the pasteboard. This doesn't seem to be intended in the first place.

(WebCore::DataTransfer::types const):

Tweak the implementation of DataTransfer.types() in the case where files exist on the pasteboard, such that we
also add "text/html" if it is present in the list of DOM-safe types.

* dom/DataTransfer.h:
* platform/Pasteboard.h:

Introduce WebContentReadingPolicy, which indicates whether or not we should limit web content reading from the
pasteboard to only rich text types upon paste or drop. Normally, we allow all types to be read as web content
(::AnyType), but when files appear on the pasteboard, we force OnlyRichTextTypes to ensure that no other types
can unintentionally be read back as web content.

* platform/StaticPasteboard.h:
* platform/gtk/PasteboardGtk.cpp:
(WebCore::Pasteboard::read):
* platform/ios/PasteboardIOS.mm:

Teach Pasteboard (on iOS) to respect WebContentReadingPolicy.

(WebCore::isTypeAllowedByReadingPolicy):
(WebCore::Pasteboard::read):
(WebCore::Pasteboard::readRespectingUTIFidelities):
* platform/mac/PasteboardMac.mm:

Teach Pasteboard (on macOS) to respect WebContentReadingPolicy.

(WebCore::Pasteboard::read):
* platform/win/PasteboardWin.cpp:
(WebCore::Pasteboard::read):
* platform/wpe/PasteboardWPE.cpp:
(WebCore::Pasteboard::read):

Adjust non-Cocoa Pasteboard implementations for an interface change.

Tools:

Add new API tests to exercise pasting images with various other content types on macOS, and when dropping images
and HTML markup on iOS. See the WebCore ChangeLog for more detail.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/DataTransfer.html: Added.

Add a new API test harness that dumps various bits of information about a DataTransfer upon paste and drop.
While somewhat similar to some existing harnesses, this makes a distinction between the raw HTML data on the
pasteboard and the actual result of inserting said HTML into the DOM. This allows us to check that the HTML has
been sanitized, while making checks for the actual content of the HTML robust against inline style changes.

* TestWebKitAPI/Tests/WebKitCocoa/PasteImage.mm:
* TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm: Added.

Add a new test suite to exercise pasting mixed content types. In these test cases, the pasteboard contains a
file, with some combination of plain text, rich text, and URLs.

(imagePath):
(writeTypesAndDataToPasteboard):

Add a helper to write a var-arg list of content types and data to the general NSPasteboard.

(setUpWebView):
(markupString):
(TestWebKitAPI::TEST):
* TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
(TestWebKitAPI::testIconImageData):
(TestWebKitAPI::TEST):
* TestWebKitAPI/cocoa/TestWKWebView.h:

Move a private declaration of -[WKWebView paste:] out to TestWKWebView.h, so that it can be shared across
multiple tests. Currently, it only resides in PasteImage.mm, but I need it in PasteMixedContent.mm as well.

LayoutTests:

Rebaseline some existing layout tests. We now expose "text/html" alongside "Files" on DataTransfer.types() in
some circumstances. This also provides some test coverage for ensuring that the paste codepath iOS allows the
page to request HTML, even if there are files on the pasteboard. See the WebCore ChangeLog for more detail.

* editing/pasteboard/data-transfer-item-list-add-file-multiple-times-expected.txt:
* editing/pasteboard/data-transfer-item-list-add-file-on-copy-expected.txt:
* editing/pasteboard/data-transfer-item-list-add-file-on-drag-expected.txt:

Adjust test expectations for the additional "text/html" type.

* editing/pasteboard/paste-image-does-not-reveal-file-url-expected.txt:
* editing/pasteboard/paste-image-does-not-reveal-file-url.html:

Instead of checking that types is [ "Files" ], just check that types contains "Files". On iOS, copying a
selected image does not also copy HTML, but on macOS it does; this covers both cases.

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

23 files changed:
LayoutTests/ChangeLog
LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-multiple-times-expected.txt
LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-copy-expected.txt
LayoutTests/editing/pasteboard/data-transfer-item-list-add-file-on-drag-expected.txt
LayoutTests/editing/pasteboard/paste-image-does-not-reveal-file-url-expected.txt
LayoutTests/editing/pasteboard/paste-image-does-not-reveal-file-url.html
Source/WebCore/ChangeLog
Source/WebCore/dom/DataTransfer.cpp
Source/WebCore/dom/DataTransfer.h
Source/WebCore/platform/Pasteboard.h
Source/WebCore/platform/StaticPasteboard.h
Source/WebCore/platform/gtk/PasteboardGtk.cpp
Source/WebCore/platform/ios/PasteboardIOS.mm
Source/WebCore/platform/mac/PasteboardMac.mm
Source/WebCore/platform/win/PasteboardWin.cpp
Source/WebCore/platform/wpe/PasteboardWPE.cpp
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/DataTransfer.html [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteImage.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm [new file with mode: 0644]
Tools/TestWebKitAPI/Tests/ios/DataInteractionTests.mm
Tools/TestWebKitAPI/cocoa/TestWKWebView.h

index 2e1b5e3..06a6a2d 100644 (file)
@@ -1,3 +1,27 @@
+2018-02-08  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Pasting from Excel no longer provides text/html data
+        https://bugs.webkit.org/show_bug.cgi?id=182636
+        <rdar://problem/37087060>
+
+        Reviewed by Ryosuke Niwa.
+
+        Rebaseline some existing layout tests. We now expose "text/html" alongside "Files" on DataTransfer.types() in
+        some circumstances. This also provides some test coverage for ensuring that the paste codepath iOS allows the
+        page to request HTML, even if there are files on the pasteboard. See the WebCore ChangeLog for more detail.
+
+        * editing/pasteboard/data-transfer-item-list-add-file-multiple-times-expected.txt:
+        * editing/pasteboard/data-transfer-item-list-add-file-on-copy-expected.txt:
+        * editing/pasteboard/data-transfer-item-list-add-file-on-drag-expected.txt:
+
+        Adjust test expectations for the additional "text/html" type.
+
+        * editing/pasteboard/paste-image-does-not-reveal-file-url-expected.txt:
+        * editing/pasteboard/paste-image-does-not-reveal-file-url.html:
+
+        Instead of checking that types is [ "Files" ], just check that types contains "Files". On iOS, copying a
+        selected image does not also copy HTML, but on macOS it does; this covers both cases.
+
 2018-02-09  Matt Baker  <mattbaker@apple.com>
 
         Web Inspector: Object.shallowEqual always fails when comparing array property values
index 1f80c59..6ef1038 100644 (file)
@@ -247,7 +247,8 @@ removedItem.getAsFile() should be null: null
 6. After adding two files and some string data again
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<strong>some styled text</strong>"
     },
     "items": [
         {
@@ -296,7 +297,8 @@ removedItem.getAsFile() should be null: null
 7. After removing at index 2
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<strong>some styled text</strong>"
     },
     "items": [
         {
@@ -341,7 +343,8 @@ removedItem.getAsFile() should be null: null
 8. After removing at index 2
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<strong>some styled text</strong>"
     },
     "items": [
         {
index ffdf67c..29bc949 100644 (file)
@@ -129,7 +129,8 @@ removedItem.getAsFile() should be null: null
 5. After adding an HTML string
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<a>goodbye world</a>"
     },
     "items": [
         {
@@ -164,7 +165,8 @@ removedItem.getAsFile() should be null: null
 6. After adding another plain text file
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<a>goodbye world</a>"
     },
     "items": [
         {
@@ -213,7 +215,8 @@ removedItem.getAsFile() should be null: null
 7. After removing the custom file
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<a>goodbye world</a>"
     },
     "items": [
         {
index 7ada654..07c06cc 100644 (file)
@@ -129,7 +129,8 @@ removedItem.getAsFile() should be null: null
 5. After adding an HTML string
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<a>goodbye world</a>"
     },
     "items": [
         {
@@ -164,7 +165,8 @@ removedItem.getAsFile() should be null: null
 6. After adding another plain text file
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<a>goodbye world</a>"
     },
     "items": [
         {
@@ -213,7 +215,8 @@ removedItem.getAsFile() should be null: null
 7. After removing the custom file
 {
     "data": {
-        "Files": ""
+        "Files": "",
+        "text/html": "<a>goodbye world</a>"
     },
     "items": [
         {
index df64b01..4c94931 100644 (file)
@@ -3,7 +3,7 @@ Tests that pasting images do not reveal its file URL. To manually test, copy the
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
 
-PASS JSON.stringify(event.clipboardData.types) is "[\"Files\"]"
+PASS event.clipboardData.types.includes("Files") is true
 PASS event.clipboardData.getData("url") is ""
 PASS event.clipboardData.getData("text/plain") is ""
 PASS event.clipboardData.getData("text/uri-list") is ""
index 2d5e8ee..05592af 100644 (file)
@@ -31,7 +31,7 @@ function runTest()
 
 function check(event)
 {
-    shouldBeEqualToString('JSON.stringify(event.clipboardData.types)', '["Files"]');
+    shouldBeTrue('event.clipboardData.types.includes("Files")');
     shouldBeEqualToString('event.clipboardData.getData("url")', '');
     shouldBeEqualToString('event.clipboardData.getData("text/plain")', '');
     shouldBeEqualToString('event.clipboardData.getData("text/uri-list")', '');
index b4ea18f..254e56e 100644 (file)
@@ -1,3 +1,88 @@
+2018-02-09  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Pasting from Excel no longer provides text/html data
+        https://bugs.webkit.org/show_bug.cgi?id=182636
+        <rdar://problem/37087060>
+
+        Reviewed by Ryosuke Niwa.
+
+        After r222656, we treat images on the pasteboard as files. However, we also have an existing policy which hides
+        text data ("text/uri-list", "text/html", "text/plain") from the page when files are present on the pasteboard.
+        When copying a table, Microsoft Excel writes a rendering of the table to the pasteboard as an image. This means
+        that we'll hide other data types (importantly, 'text/html') upon pasting, even though important clients (such as
+        Google Docs and Confluence) depend on the 'text/html' data in order to correctly handle the paste (rather than
+        paste as an image of a table).
+
+        To fix this, we add an exception to the DataTransfer.getData codepath when the pasteboard contains files.
+        Instead of always returning the empty string for text/html, we still allow pasteboard access, but only read
+        from a limited set of rich text types, i.e. web archive, RTF(D), and HTML markup. Importantly, this prevents
+        us from exposing any file paths that appear as plain text or URLs on the pasteboard. Just as in the regular
+        codepath for getData(), if the pasteboard data comes from the same origin, we allow unsanitized access;
+        otherwise, we use WebContentMarkupReader to extract markup from the pasteboard.
+
+        Tests:  PasteMixedContent.ImageFileAndPlainText
+                PasteMixedContent.ImageFileAndWebArchive
+                PasteMixedContent.ImageFileAndHTML
+                PasteMixedContent.ImageFileAndRTF
+                PasteMixedContent.ImageFileAndURL
+                PasteMixedContent.ImageFileWithHTMLAndURL
+                DataInteractionTests.DataTransferGetDataWhenDroppingImageAndMarkup
+
+        Also rebaselined some layout tests, which cover changes in behavior when dropping on macOS and pasting on iOS.
+
+        * dom/DataTransfer.cpp:
+        (WebCore::DataTransfer::getDataForItem const):
+
+        Augment the codepath handling the case where the pasteboard contains files, such that we allow reading
+        "text/html", but only from rich text types.
+
+        (WebCore::DataTransfer::readStringFromPasteboard const):
+
+        Factor out logic for reading from the pasteboard into a private helper. This is called in two places from
+        getDataForItem: in the normal (existing) path, and in the case where we allow 'text/html' to be read despite
+        files appearing in the pasteboard.
+
+        One important difference here is that this helper now takes a WebContentReadingPolicy, whose purpose is to
+        prevent reading from non-rich-text types when files appear in the pasteboard.
+
+        Another tweak here is that we now use `lowercaseType` instead of the original (unadjusted) `type` when reading
+        from the pasteboard. This doesn't seem to be intended in the first place.
+
+        (WebCore::DataTransfer::types const):
+
+        Tweak the implementation of DataTransfer.types() in the case where files exist on the pasteboard, such that we
+        also add "text/html" if it is present in the list of DOM-safe types.
+
+        * dom/DataTransfer.h:
+        * platform/Pasteboard.h:
+
+        Introduce WebContentReadingPolicy, which indicates whether or not we should limit web content reading from the
+        pasteboard to only rich text types upon paste or drop. Normally, we allow all types to be read as web content
+        (::AnyType), but when files appear on the pasteboard, we force OnlyRichTextTypes to ensure that no other types
+        can unintentionally be read back as web content.
+
+        * platform/StaticPasteboard.h:
+        * platform/gtk/PasteboardGtk.cpp:
+        (WebCore::Pasteboard::read):
+        * platform/ios/PasteboardIOS.mm:
+
+        Teach Pasteboard (on iOS) to respect WebContentReadingPolicy.
+
+        (WebCore::isTypeAllowedByReadingPolicy):
+        (WebCore::Pasteboard::read):
+        (WebCore::Pasteboard::readRespectingUTIFidelities):
+        * platform/mac/PasteboardMac.mm:
+
+        Teach Pasteboard (on macOS) to respect WebContentReadingPolicy.
+
+        (WebCore::Pasteboard::read):
+        * platform/win/PasteboardWin.cpp:
+        (WebCore::Pasteboard::read):
+        * platform/wpe/PasteboardWPE.cpp:
+        (WebCore::Pasteboard::read):
+
+        Adjust non-Cocoa Pasteboard implementations for an interface change.
+
 2018-02-09  Zalan Bujtas  <zalan@apple.com>
 
         [RenderTreeBuilder] Move RenderRubyAsInline/AsBlock::takeChild mutation to a RenderTreeBuilder
index c109423..3e71a73 100644 (file)
@@ -154,9 +154,23 @@ String DataTransfer::getDataForItem(Document& document, const String& type) cons
             if (Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(urlString))
                 return urlString;
         }
+
+        if (lowercaseType == "text/html" && RuntimeEnabledFeatures::sharedFeatures().customPasteboardDataEnabled()) {
+            // If the pasteboard contains files and the page requests 'text/html', we only read from rich text types to prevent file
+            // paths from leaking (e.g. from plain text data on the pasteboard) since we sanitize cross-origin markup. However, if
+            // custom pasteboard data is disabled, then we can't ensure that the markup we deliver is sanitized, so we fall back to
+            // current behavior and return an empty string.
+            return readStringFromPasteboard(document, lowercaseType, WebContentReadingPolicy::OnlyRichTextTypes);
+        }
+
         return { };
     }
 
+    return readStringFromPasteboard(document, lowercaseType, WebContentReadingPolicy::AnyType);
+}
+
+String DataTransfer::readStringFromPasteboard(Document& document, const String& lowercaseType, WebContentReadingPolicy policy) const
+{
     if (!RuntimeEnabledFeatures::sharedFeatures().customPasteboardDataEnabled())
         return m_pasteboard->readString(lowercaseType);
 
@@ -170,15 +184,15 @@ String DataTransfer::getDataForItem(Document& document, const String& type) cons
     if (!Pasteboard::isSafeTypeForDOMToReadAndWrite(lowercaseType))
         return { };
 
-    if (!is<StaticPasteboard>(*m_pasteboard) && type == "text/html") {
+    if (!is<StaticPasteboard>(*m_pasteboard) && lowercaseType == "text/html") {
         if (!document.frame())
             return { };
         WebContentMarkupReader reader { *document.frame() };
-        m_pasteboard->read(reader);
+        m_pasteboard->read(reader, policy);
         return reader.markup;
     }
 
-    return m_pasteboard->readString(type);
+    return m_pasteboard->readString(lowercaseType);
 }
 
 String DataTransfer::getData(Document& document, const String& type) const
@@ -293,6 +307,8 @@ Vector<String> DataTransfer::types(AddFilesType addFilesType) const
             types.append(ASCIILiteral("Files"));
         if (safeTypes.contains("text/uri-list"))
             types.append(ASCIILiteral("text/uri-list"));
+        if (safeTypes.contains("text/html") && RuntimeEnabledFeatures::sharedFeatures().customPasteboardDataEnabled())
+            types.append(ASCIILiteral("text/html"));
         return types;
     }
 
index 31dc799..00e9ef4 100644 (file)
@@ -39,6 +39,7 @@ class Element;
 class FileList;
 class File;
 class Pasteboard;
+enum class WebContentReadingPolicy;
 
 class DataTransfer : public RefCounted<DataTransfer> {
 public:
@@ -121,6 +122,7 @@ private:
     bool forFileDrag() const { return false; }
 #endif
 
+    String readStringFromPasteboard(Document&, const String& lowercaseType, WebContentReadingPolicy) const;
     bool shouldSuppressGetAndSetDataToAvoidExposingFilePaths() const;
 
     enum class AddFilesType { No, Yes };
index e917cc3..1edce41 100644 (file)
@@ -64,6 +64,7 @@ class Range;
 class SelectionData;
 class SharedBuffer;
 
+enum class WebContentReadingPolicy { AnyType, OnlyRichTextTypes };
 enum ShouldSerializeSelectedTextForDataTransfer { DefaultSelectedTextType, IncludeImageAltTextForDataTransfer };
 
 // For writing to the pasteboard. Generally sorted with the richest formats on top.
@@ -209,7 +210,7 @@ public:
     virtual WEBCORE_EXPORT void clear(const String& type);
 
     virtual WEBCORE_EXPORT void read(PasteboardPlainText&);
-    virtual WEBCORE_EXPORT void read(PasteboardWebContentReader&);
+    virtual WEBCORE_EXPORT void read(PasteboardWebContentReader&, WebContentReadingPolicy = WebContentReadingPolicy::AnyType);
     virtual WEBCORE_EXPORT void read(PasteboardFileReader&);
 
     virtual WEBCORE_EXPORT void write(const PasteboardURL&);
@@ -273,7 +274,7 @@ public:
 private:
 #if PLATFORM(IOS)
     bool respectsUTIFidelities() const;
-    void readRespectingUTIFidelities(PasteboardWebContentReader&);
+    void readRespectingUTIFidelities(PasteboardWebContentReader&, WebContentReadingPolicy);
 
     enum class ReaderResult {
         ReadType,
index 6a3934d..007e43a 100644 (file)
@@ -53,7 +53,7 @@ public:
     void clear(const String& type) final;
 
     void read(PasteboardPlainText&) final { }
-    void read(PasteboardWebContentReader&) final { }
+    void read(PasteboardWebContentReader&, WebContentReadingPolicy) final { }
 
     void write(const PasteboardURL&) final { }
     void write(const PasteboardImage&) final { }
index a8d5e59..99d1203 100644 (file)
@@ -236,7 +236,7 @@ void Pasteboard::read(PasteboardPlainText& text)
     text.text = m_selectionData->text();
 }
 
-void Pasteboard::read(PasteboardWebContentReader&)
+void Pasteboard::read(PasteboardWebContentReader&, WebContentReadingPolicy)
 {
 }
 
index b887984..c5132e3 100644 (file)
@@ -163,6 +163,15 @@ static NSArray* supportedImageTypes()
     return @[(id)kUTTypePNG, (id)kUTTypeTIFF, (id)kUTTypeJPEG, (id)kUTTypeGIF];
 }
 
+static bool isTypeAllowedByReadingPolicy(NSString *type, WebContentReadingPolicy policy)
+{
+    return policy == WebContentReadingPolicy::AnyType
+        || [type isEqualToString:WebArchivePboardType]
+        || [type isEqualToString:(NSString *)kUTTypeHTML]
+        || [type isEqualToString:(NSString *)kUTTypeRTF]
+        || [type isEqualToString:(NSString *)kUTTypeFlatRTFD];
+}
+
 Pasteboard::ReaderResult Pasteboard::readPasteboardWebContentDataForType(PasteboardWebContentReader& reader, PasteboardStrategy& strategy, NSString *type, int itemIndex)
 {
     if ([type isEqualToString:WebArchivePboardType]) {
@@ -225,11 +234,11 @@ Pasteboard::ReaderResult Pasteboard::readPasteboardWebContentDataForType(Pastebo
     return ReaderResult::DidNotReadType;
 }
 
-void Pasteboard::read(PasteboardWebContentReader& reader)
+void Pasteboard::read(PasteboardWebContentReader& reader, WebContentReadingPolicy policy)
 {
     reader.contentOrigin = readOrigin();
     if (respectsUTIFidelities()) {
-        readRespectingUTIFidelities(reader);
+        readRespectingUTIFidelities(reader, policy);
         return;
     }
 
@@ -245,9 +254,14 @@ void Pasteboard::read(PasteboardWebContentReader& reader)
 
     for (int i = 0; i < numberOfItems; i++) {
         for (int typeIndex = 0; typeIndex < numberOfTypes; typeIndex++) {
-            auto itemResult = readPasteboardWebContentDataForType(reader, strategy, [types objectAtIndex:typeIndex], i);
+            NSString *type = [types objectAtIndex:typeIndex];
+            if (!isTypeAllowedByReadingPolicy(type, policy))
+                continue;
+
+            auto itemResult = readPasteboardWebContentDataForType(reader, strategy, type, i);
             if (itemResult == ReaderResult::PasteboardWasChangedExternally)
                 return;
+
             if (itemResult == ReaderResult::ReadType)
                 break;
         }
@@ -262,14 +276,14 @@ bool Pasteboard::respectsUTIFidelities() const
     return m_pasteboardName == "data interaction pasteboard";
 }
 
-void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader)
+void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader, WebContentReadingPolicy policy)
 {
     ASSERT(respectsUTIFidelities());
     auto& strategy = *platformStrategies()->pasteboardStrategy();
     for (NSUInteger index = 0, numberOfItems = strategy.getPasteboardItemsCount(m_pasteboardName); index < numberOfItems; ++index) {
 #if ENABLE(ATTACHMENT_ELEMENT)
         auto info = strategy.informationForItemAtIndex(index, m_pasteboardName);
-        bool canReadAttachment = RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled() && !info.pathForFileUpload.isEmpty();
+        bool canReadAttachment = policy == WebContentReadingPolicy::AnyType && RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled() && !info.pathForFileUpload.isEmpty();
         if (canReadAttachment && info.preferredPresentationStyle == PasteboardItemPresentationStyle::Attachment) {
             reader.readFilePaths({ info.pathForFileUpload });
             continue;
@@ -281,6 +295,9 @@ void Pasteboard::readRespectingUTIFidelities(PasteboardWebContentReader& reader)
         strategy.getTypesByFidelityForItemAtIndex(typesForItemInOrderOfFidelity, index, m_pasteboardName);
         ReaderResult result = ReaderResult::DidNotReadType;
         for (auto& type : typesForItemInOrderOfFidelity) {
+            if (!isTypeAllowedByReadingPolicy(type, policy))
+                continue;
+
             result = readPasteboardWebContentDataForType(reader, strategy, type, index);
             if (result == ReaderResult::PasteboardWasChangedExternally)
                 return;
index 9448e91..6376496 100644 (file)
@@ -331,7 +331,7 @@ void Pasteboard::read(PasteboardPlainText& text)
     text.isURL = !text.text.isNull();
 }
 
-void Pasteboard::read(PasteboardWebContentReader& reader)
+void Pasteboard::read(PasteboardWebContentReader& reader, WebContentReadingPolicy policy)
 {
     PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
 
@@ -347,7 +347,7 @@ void Pasteboard::read(PasteboardWebContentReader& reader)
         }
     }
 
-    if (types.contains(String(legacyFilenamesPasteboardType()))) {
+    if (policy == WebContentReadingPolicy::AnyType && types.contains(String(legacyFilenamesPasteboardType()))) {
         Vector<String> paths;
         strategy.getPathnamesForType(paths, legacyFilenamesPasteboardType(), m_pasteboardName);
         if (m_changeCount != changeCount() || reader.readFilePaths(paths))
@@ -374,6 +374,9 @@ void Pasteboard::read(PasteboardWebContentReader& reader)
         }
     }
 
+    if (policy == WebContentReadingPolicy::OnlyRichTextTypes)
+        return;
+
     if (types.contains(String(legacyTIFFPasteboardType()))) {
         if (RefPtr<SharedBuffer> buffer = strategy.bufferForType(legacyTIFFPasteboardType(), m_pasteboardName)) {
             if (m_changeCount != changeCount() || reader.readImage(buffer.releaseNonNull(), ASCIILiteral("image/tiff")))
index c9a0d2d..46650f0 100644 (file)
@@ -1073,7 +1073,7 @@ void Pasteboard::write(const PasteboardWebContent&)
 {
 }
 
-void Pasteboard::read(PasteboardWebContentReader&)
+void Pasteboard::read(PasteboardWebContentReader&, WebContentReadingPolicy)
 {
 }
 
index 247af55..1eb2d29 100644 (file)
@@ -97,7 +97,7 @@ void Pasteboard::read(PasteboardPlainText& text)
     text.text = platformStrategies()->pasteboardStrategy()->readStringFromPasteboard(0, "text/plain;charset=utf-8");
 }
 
-void Pasteboard::read(PasteboardWebContentReader&)
+void Pasteboard::read(PasteboardWebContentReader&, WebContentReadingPolicy)
 {
     notImplemented();
 }
index e77d273..7000e77 100644 (file)
@@ -1,3 +1,44 @@
+2018-02-08  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Pasting from Excel no longer provides text/html data
+        https://bugs.webkit.org/show_bug.cgi?id=182636
+        <rdar://problem/37087060>
+
+        Reviewed by Ryosuke Niwa.
+
+        Add new API tests to exercise pasting images with various other content types on macOS, and when dropping images
+        and HTML markup on iOS. See the WebCore ChangeLog for more detail.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/DataTransfer.html: Added.
+
+        Add a new API test harness that dumps various bits of information about a DataTransfer upon paste and drop.
+        While somewhat similar to some existing harnesses, this makes a distinction between the raw HTML data on the
+        pasteboard and the actual result of inserting said HTML into the DOM. This allows us to check that the HTML has
+        been sanitized, while making checks for the actual content of the HTML robust against inline style changes.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/PasteImage.mm:
+        * TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm: Added.
+
+        Add a new test suite to exercise pasting mixed content types. In these test cases, the pasteboard contains a
+        file, with some combination of plain text, rich text, and URLs.
+
+        (imagePath):
+        (writeTypesAndDataToPasteboard):
+
+        Add a helper to write a var-arg list of content types and data to the general NSPasteboard.
+
+        (setUpWebView):
+        (markupString):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/Tests/ios/DataInteractionTests.mm:
+        (TestWebKitAPI::testIconImageData):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+
+        Move a private declaration of -[WKWebView paste:] out to TestWKWebView.h, so that it can be shared across
+        multiple tests. Currently, it only resides in PasteImage.mm, but I need it in PasteMixedContent.mm as well.
+
 2018-02-09  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         Add a way to check if a host is an IP address
index 3af8d24..a2c7516 100644 (file)
                F44D064A1F3962F2001A0E29 /* EditingTestHarness.mm in Sources */ = {isa = PBXBuildFile; fileRef = F44D06491F3962E3001A0E29 /* EditingTestHarness.mm */; };
                F4512E131F60C44600BB369E /* DataTransferItem-getAsEntry.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */; };
                F4538EF71E8473E600B5C953 /* large-red-square.png in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4538EF01E846B4100B5C953 /* large-red-square.png */; };
+               F457A9B8202D5CDC00F7E9D5 /* PasteMixedContent.mm in Sources */ = {isa = PBXBuildFile; fileRef = F457A9B6202D5CDC00F7E9D5 /* PasteMixedContent.mm */; };
+               F457A9D6202D68AF00F7E9D5 /* DataTransfer.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F457A9B3202D535300F7E9D5 /* DataTransfer.html */; };
                F45B63FB1F197F4A009D38B9 /* image-map.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F45B63FA1F197F33009D38B9 /* image-map.html */; };
                F45B63FE1F19D410009D38B9 /* ActionSheetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F45B63FC1F19D410009D38B9 /* ActionSheetTests.mm */; };
                F46849BE1EEF58E400B937FE /* UIPasteboardTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F46849BD1EEF58E400B937FE /* UIPasteboardTests.mm */; };
                                F4AB578A1F65165400DB0DA1 /* custom-draggable-div.html in Copy Resources */,
                                290F4275172A221C00939FF0 /* custom-protocol-sync-xhr.html in Copy Resources */,
                                F486B1D01F67952300F34BDD /* DataTransfer-setDragImage.html in Copy Resources */,
+                               F457A9D6202D68AF00F7E9D5 /* DataTransfer.html in Copy Resources */,
                                F4512E131F60C44600BB369E /* DataTransferItem-getAsEntry.html in Copy Resources */,
                                C07E6CB213FD73930038B22B /* devicePixelRatio.html in Copy Resources */,
                                0799C34B1EBA3301003B7532 /* disableGetUserMedia.html in Copy Resources */,
                F44D06491F3962E3001A0E29 /* EditingTestHarness.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EditingTestHarness.mm; sourceTree = "<group>"; };
                F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "DataTransferItem-getAsEntry.html"; sourceTree = "<group>"; };
                F4538EF01E846B4100B5C953 /* large-red-square.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "large-red-square.png"; sourceTree = "<group>"; };
+               F457A9B3202D535300F7E9D5 /* DataTransfer.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = DataTransfer.html; sourceTree = "<group>"; };
+               F457A9B6202D5CDC00F7E9D5 /* PasteMixedContent.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PasteMixedContent.mm; sourceTree = "<group>"; };
                F45B63FA1F197F33009D38B9 /* image-map.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "image-map.html"; sourceTree = "<group>"; };
                F45B63FC1F19D410009D38B9 /* ActionSheetTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ActionSheetTests.mm; sourceTree = "<group>"; };
                F46849BD1EEF58E400B937FE /* UIPasteboardTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = UIPasteboardTests.mm; sourceTree = "<group>"; };
                                CEBCA12E1E3A660100C73293 /* OverrideContentSecurityPolicy.mm */,
                                9BCB7C2620130600003E7C0C /* PasteHTML.mm */,
                                9BDCCD851F7D0B0700009A18 /* PasteImage.mm */,
+                               F457A9B6202D5CDC00F7E9D5 /* PasteMixedContent.mm */,
                                9BDD95561F83683600D20C60 /* PasteRTFD.mm */,
                                9B2346411F943A2400DB1D23 /* PasteWebArchive.mm */,
                                3FCC4FE41EC4E8520076E37C /* PictureInPictureDelegate.mm */,
                                9B62630B1F8C2510007EE29B /* copy-url.html */,
                                F4AB57891F65164B00DB0DA1 /* custom-draggable-div.html */,
                                F486B1CF1F6794FF00F34BDD /* DataTransfer-setDragImage.html */,
+                               F457A9B3202D535300F7E9D5 /* DataTransfer.html */,
                                F4512E121F60C43400BB369E /* DataTransferItem-getAsEntry.html */,
                                0799C34A1EBA32F4003B7532 /* disableGetUserMedia.html */,
                                63A61B8A1FAD204D00F06885 /* display-mode.html */,
                                7CCE7F0A1A411AE600447C4C /* PasteboardNotifications.mm in Sources */,
                                9BCB7C2820130600003E7C0C /* PasteHTML.mm in Sources */,
                                9BDCCD871F7D0B0700009A18 /* PasteImage.mm in Sources */,
+                               F457A9B8202D5CDC00F7E9D5 /* PasteMixedContent.mm in Sources */,
                                9BDD95581F83683600D20C60 /* PasteRTFD.mm in Sources */,
                                9B2346421F943E2700DB1D23 /* PasteWebArchive.mm in Sources */,
                                7C83E0531D0A643A00FEBCF3 /* PendingAPIRequestURL.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/DataTransfer.html b/Tools/TestWebKitAPI/Tests/WebKitCocoa/DataTransfer.html
new file mode 100644 (file)
index 0000000..c91d606
--- /dev/null
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<head>
+    <style>
+        body, html {
+            width: 100%;
+            height: 100%;
+            font-family: monospace;
+        }
+
+        .heading {
+            display: block;
+            width: 100%;
+            border-top: 2px gray dashed;
+            margin-top: 20px;
+            box-sizing: border-box;
+        }
+    </style>
+    <script>
+        function setup()
+        {
+            document.body.addEventListener("paste", event => {
+                dumpAsOutput(event.clipboardData);
+                event.preventDefault();
+            });
+            document.body.addEventListener("drop", event => {
+                dumpAsOutput(event.dataTransfer);
+                event.preventDefault();
+            });
+            document.body.addEventListener("beforeinput", event => event.preventDefault());
+            document.body.focus();
+        }
+
+        function dumpAsOutput(dataTransfer)
+        {
+            document.body.contentEditable = false;
+            types.textContent = dataTransfer.types.join(", ");
+            items.textContent = Array.from(dataTransfer.items).map(item => `(${item.kind.toUpperCase()}, ${item.type})`).join(", ");
+            files.textContent = Array.from(dataTransfer.files).map(file => `('${file.name}', ${file.type})`).join(", ");
+            urlData.textContent = dataTransfer.getData("text/uri-list");
+            textData.textContent = dataTransfer.getData("text/plain");
+            let markup = dataTransfer.getData("text/html");
+            htmlData.insertAdjacentHTML("beforeend", markup);
+            rawHTMLData.textContent = markup;
+        }
+
+        function reset()
+        {
+            Array.from(document.getElementsByClassName("output")).map(element => {
+                element.innerHTML = "";
+            });
+            document.body.contentEditable = true;
+        }
+    </script>
+</head>
+<body contenteditable onload=setup()>
+    <h3 class="heading">Types</h3>
+    <div class="output" id="types"></div>
+
+    <h3 class="heading">Items</h3>
+    <div class="output" id="items"></div>
+
+    <h3 class="heading">Files</h3>
+    <div class="output" id="files"></div>
+
+    <h3 class="heading">text/plain</h3>
+    <div class="output" id="textData"></div>
+
+    <h3 class="heading">text/uri-list</h3>
+    <div class="output" id="urlData"></div>
+
+    <h3 class="heading">text/html</h3>
+    <div class="output" id="htmlData"></div>
+    <div class="output" id="rawHTMLData"></div>
+</body>
+</html>
index b22896b..89bc05a 100644 (file)
 #include <MobileCoreServices/MobileCoreServices.h>
 #endif
 
-@interface WKWebView ()
-- (void)paste:(id)sender;
-@end
-
 #if PLATFORM(MAC)
 void writeImageDataToPasteboard(NSString *type, NSData *data)
 {
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm
new file mode 100644 (file)
index 0000000..0dd33e1
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * 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 met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if PLATFORM(MAC) && WK_API_ENABLED
+
+#import "PlatformUtilities.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesRef.h>
+#import <WebKit/WKPreferencesRefPrivate.h>
+#import <wtf/RetainPtr.h>
+#import <wtf/text/WTFString.h>
+
+static NSString *imagePath()
+{
+    return [[NSBundle mainBundle] pathForResource:@"icon" ofType:@"png" inDirectory:@"TestWebKitAPI.resources"];
+}
+
+void writeTypesAndDataToPasteboard(id type, ...)
+{
+    NSMutableArray<NSString *> *types = [NSMutableArray array];
+    NSMutableArray *data = [NSMutableArray array];
+    va_list argumentList;
+    if (type) {
+        ASSERT([type isKindOfClass:[NSString class]]);
+        [types addObject:type];
+        va_start(argumentList, type);
+        NSUInteger index = 1;
+        id object = va_arg(argumentList, id);
+        while (object) {
+            if (index % 2)
+                [data addObject:object];
+            else {
+                ASSERT([object isKindOfClass:[NSString class]]);
+                [types addObject:object];
+            }
+            ++index;
+            object = va_arg(argumentList, id);
+        }
+        va_end(argumentList);
+    }
+
+    if (types.count != data.count) {
+        NSLog(@"Expected number of types: %tu to match number of data items: %tu", types.count, data.count);
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    [[NSPasteboard generalPasteboard] declareTypes:types owner:nil];
+    [types enumerateObjectsUsingBlock:[&] (NSString *type, NSUInteger index, BOOL *) {
+        id dataToWrite = data[index];
+        if ([dataToWrite isKindOfClass:[NSData class]])
+            [[NSPasteboard generalPasteboard] setData:dataToWrite forType:type];
+        else if ([dataToWrite isKindOfClass:[NSString class]])
+            [[NSPasteboard generalPasteboard] setString:dataToWrite forType:type];
+        else
+            [[NSPasteboard generalPasteboard] setPropertyList:dataToWrite forType:type];
+    }];
+}
+
+static RetainPtr<TestWKWebView> setUpWebView()
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 400, 400)]);
+    WKPreferencesSetCustomPasteboardDataEnabled((WKPreferencesRef)[webView configuration].preferences, true);
+    [webView synchronouslyLoadTestPageNamed:@"DataTransfer"];
+    return webView;
+}
+
+static NSString *markupString()
+{
+    // The script tag and mouseover listener attribute in the markup string below should be omitted during
+    // sanitization while pasting.
+    return @"<script>foo()</script><strong onmouseover='javascript:void(0)'>HELLO WORLD</strong>";
+}
+
+namespace TestWebKitAPI {
+
+TEST(PasteMixedContent, ImageFileAndPlainText)
+{
+    auto webView = setUpWebView();
+    writeTypesAndDataToPasteboard(NSFilenamesPboardType, @[ imagePath() ], NSPasteboardTypeString, imagePath(), nil);
+    [webView paste:nil];
+
+    EXPECT_WK_STREQ("Files", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(FILE, image/png)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("('icon.png', image/png)", [webView stringByEvaluatingJavaScript:@"files.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"rawHTMLData.textContent"]);
+}
+
+TEST(PasteMixedContent, ImageFileAndWebArchive)
+{
+    auto webView = setUpWebView();
+    NSURL *mainResourceURL = [NSURL fileURLWithPath:@"/some/nonexistent/file.html"];
+    auto mainResource = adoptNS([[WebResource alloc] initWithData:[markupString() dataUsingEncoding:NSUTF8StringEncoding] URL:mainResourceURL MIMEType:@"text/html" textEncodingName:@"utf-8" frameName:nil]);
+    auto archive = adoptNS([[WebArchive alloc] initWithMainResource:mainResource.get() subresources:nil subframeArchives:nil]);
+    writeTypesAndDataToPasteboard(NSFilenamesPboardType, @[ imagePath() ], WebArchivePboardType, [archive data], nil);
+    [webView paste:nil];
+
+    EXPECT_WK_STREQ("Files, text/html", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(STRING, text/html), (FILE, image/png)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("('icon.png', image/png)", [webView stringByEvaluatingJavaScript:@"files.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+    EXPECT_WK_STREQ("HELLO WORLD", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]);
+    EXPECT_FALSE([[webView stringByEvaluatingJavaScript:@"rawHTMLData.textContent"] containsString:@"script"]);
+}
+
+TEST(PasteMixedContent, ImageFileAndHTML)
+{
+    auto webView = setUpWebView();
+    writeTypesAndDataToPasteboard(NSFilenamesPboardType, @[ imagePath() ], NSPasteboardTypeHTML, markupString(), nil);
+    [webView paste:nil];
+
+    EXPECT_WK_STREQ("Files, text/html", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(STRING, text/html), (FILE, image/png)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("('icon.png', image/png)", [webView stringByEvaluatingJavaScript:@"files.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+    EXPECT_WK_STREQ("HELLO WORLD", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]);
+    EXPECT_FALSE([[webView stringByEvaluatingJavaScript:@"rawHTMLData.textContent"] containsString:@"script"]);
+}
+
+TEST(PasteMixedContent, ImageFileAndRTF)
+{
+    auto webView = setUpWebView();
+    auto text = adoptNS([[NSMutableAttributedString alloc] init]);
+    [text appendAttributedString:[[NSAttributedString alloc] initWithString:@"link to "]];
+    [text appendAttributedString:[[NSAttributedString alloc] initWithString:@"apple" attributes:@{ NSLinkAttributeName: [NSURL URLWithString:@"https://www.apple.com/"] }]];
+    NSData *rtfData = [text RTFFromRange:NSMakeRange(0, [text length]) documentAttributes:@{ }];
+    writeTypesAndDataToPasteboard(NSFilenamesPboardType, @[ imagePath() ], NSPasteboardTypeRTF, rtfData, nil);
+    [webView paste:nil];
+
+    EXPECT_WK_STREQ("Files, text/html", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(STRING, text/html), (FILE, image/png)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("('icon.png', image/png)", [webView stringByEvaluatingJavaScript:@"files.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+    EXPECT_WK_STREQ("link to apple", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]);
+    EXPECT_WK_STREQ("https://www.apple.com/", [webView stringByEvaluatingJavaScript:@"htmlData.querySelector('a').href"]);
+}
+
+TEST(PasteMixedContent, ImageFileAndURL)
+{
+    auto webView = setUpWebView();
+    writeTypesAndDataToPasteboard(NSURLPboardType, @"https://www.webkit.org/", NSFilenamesPboardType, @[ imagePath() ], nil);
+    [webView paste:nil];
+
+    EXPECT_WK_STREQ("Files, text/uri-list", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(STRING, text/uri-list), (FILE, image/png)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("('icon.png', image/png)", [webView stringByEvaluatingJavaScript:@"files.textContent"]);
+    EXPECT_WK_STREQ("https://www.webkit.org/", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]);
+}
+
+TEST(PasteMixedContent, ImageFileWithHTMLAndURL)
+{
+    auto webView = setUpWebView();
+    writeTypesAndDataToPasteboard(NSURLPboardType, @"https://www.webkit.org/", NSPasteboardTypeHTML, markupString(), NSFilenamesPboardType, @[ imagePath() ], nil);
+    [webView paste:nil];
+
+    EXPECT_WK_STREQ("Files, text/uri-list, text/html", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(STRING, text/uri-list), (STRING, text/html), (FILE, image/png)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("('icon.png', image/png)", [webView stringByEvaluatingJavaScript:@"files.textContent"]);
+    EXPECT_WK_STREQ("https://www.webkit.org/", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+    EXPECT_WK_STREQ("HELLO WORLD", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]);
+    EXPECT_FALSE([[webView stringByEvaluatingJavaScript:@"rawHTMLData.textContent"] containsString:@"script"]);
+}
+
+} // namespace TestWebKitAPI
+
+#endif // PLATFORM(MAC) && WK_API_ENABLED
index 4085fab..cd21c02 100644 (file)
@@ -1499,6 +1499,35 @@ TEST(DataInteractionTests, DragLiftPreviewDataTransferSetDragImage)
 
 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110300
 
+static NSData *testIconImageData()
+{
+    return [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"icon" ofType:@"png" inDirectory:@"TestWebKitAPI.resources"]];
+}
+
+TEST(DataInteractionTests, DataTransferGetDataWhenDroppingImageAndMarkup)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    WKPreferencesSetCustomPasteboardDataEnabled((WKPreferencesRef)[webView configuration].preferences, true);
+    [webView synchronouslyLoadTestPageNamed:@"DataTransfer"];
+
+    auto simulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
+    auto itemProvider = adoptNS([[UIItemProvider alloc] init]);
+    [itemProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypePNG withData:testIconImageData()];
+    NSString *markupString = @"<script>bar()</script><strong onmousedown=javascript:void(0)>HELLO WORLD</strong>";
+    [itemProvider registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypeHTML withData:[markupString dataUsingEncoding:NSUTF8StringEncoding]];
+    [itemProvider setSuggestedName:@"icon"];
+    [simulator setExternalItemProviders:@[ itemProvider.get() ]];
+    [simulator runFrom:CGPointZero to:CGPointMake(50, 100)];
+
+    EXPECT_WK_STREQ("Files, text/html", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(STRING, text/html), (FILE, image/png)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("('icon.png', image/png)", [webView stringByEvaluatingJavaScript:@"files.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+    EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+    EXPECT_WK_STREQ("HELLO WORLD", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]);
+    EXPECT_FALSE([[webView stringByEvaluatingJavaScript:@"rawHTMLData.textContent"] containsString:@"script"]);
+}
+
 TEST(DataInteractionTests, DataTransferGetDataWhenDroppingPlainText)
 {
     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
index daa3b26..908e052 100644 (file)
 @class _WKActivatedElementInfo;
 #endif
 
+@interface WKWebView (AdditionalDeclarations)
+#if PLATFORM(MAC)
+- (void)paste:(id)sender;
+#endif
+@end
+
 @interface TestMessageHandler : NSObject <WKScriptMessageHandler>
 - (void)addMessage:(NSString *)message withHandler:(dispatch_block_t)handler;
 @end