Populate "text/uri-list" with multiple URLs when the pasteboard contains multiple...
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 4 Sep 2018 23:47:55 +0000 (23:47 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 4 Sep 2018 23:47:55 +0000 (23:47 +0000)
https://bugs.webkit.org/show_bug.cgi?id=188890
<rdar://problem/43648605>

Reviewed by Tim Horton.

Source/WebCore:

Adds support for exposing a newline separated list of URLs via DataTransfer's "text/uri-list" type when pasting
or dropping multiple items on the pasteboard that can be represented as URLs. Currently on iOS, only the URL of
the first item (if present) is exposed, and on macOS, only the first out of all the URLs in the pasteboard is
exposed.

To fix this, we introduce `Pasteboard::readAllStrings`, which reads a list of pasteboard strings collected from
all available items on the platform pasteboard. Currently, this is only used to provide a list of URL strings
when fetching data for the "text/uri-list" type when calling `DataTransfer.getData()` and
`DataTransferItem.getAsString()`.

Tests:  DragAndDropTests.ExposeMultipleURLsInDataTransfer
        UIPasteboardTests.DataTransferURIListContainsMultipleURLs
        PasteMixedContent.PasteOneOrMoreURLs

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

Add a helper method that reads all URL strings from the pasteboard (for the MIME type "text/uri-list", which
corresponds to NSURLPboardType and "public.url" on macOS and iOS, respectively) and returns a single string
containing all non-empty URLs joined by newline characters. Also takes a filtering block which may be used to
reject URLs from being included in "text/uri-list" output.

(WebCore::DataTransfer::getDataForItem const):
(WebCore::DataTransfer::readStringFromPasteboard const):

Insteading of reading a single string from the pasteboard for "text/uri-list", call the above helper function to
read all URL strings in the pasteboard. If there are files present in the pasteboard, we also filter out URLs
whose schemes are not in the set of schemes that are safe to expose to the page (i.e. http(s), blob, and data).

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

Add a default non-Cocoa implementation of readAllStrings() that returns a vector, which may contain the result
of readString().

* platform/Pasteboard.h:
* platform/PasteboardStrategy.h:
* platform/PlatformPasteboard.h:

Add plumbing to grab a list of strings from the pasteboard for a given type.

* platform/cocoa/PasteboardCocoa.mm:
(WebCore::Pasteboard::readAllStrings):
(WebCore::Pasteboard::readString):

Implement these two methods in terms of `readPlatformValuesAsStrings`. `readAllStrings` returns the full list of
results, while `readString` only returns the first result.

* platform/ios/PasteboardIOS.mm:
(WebCore::Pasteboard::readPlatformValuesAsStrings):
(WebCore::Pasteboard::readPlatformValueAsString): Deleted.

Refactor this Cocoa helper function to return a list of pasteboard string values for the given type, rather than
a single string.

* platform/ios/PlatformPasteboardIOS.mm:
(WebCore::PlatformPasteboard::allStringsForType const):

Grab a string for each item (represented by an NSItemProvider) in the pasteboard that has data for the given
type identifier.

(WebCore::PlatformPasteboard::readString const):

Return the absolute string of the NSURL, instead of WebCore::URL::string(). This is needed to handle the case
where the NSURL is constructed from absolute and relative parts using a Plist. While -absoluteString gets us the
full URL string, URL::string() only returns the relative portion.

* platform/mac/PasteboardMac.mm:
(WebCore::Pasteboard::readPlatformValuesAsStrings):
(WebCore::Pasteboard::readPlatformValueAsString): Deleted.

Also refactor this to retrieve a list of pasteboard strings, rather than a single result.

* platform/mac/PlatformPasteboardMac.mm:
(WebCore::typeIdentifierForPasteboardType):
(WebCore::PlatformPasteboard::allStringsForType const):

Add an implementation for `allStringsForType` on macOS. Unlike iOS, it's much trickier to get this right since
we need to maintain compatibility with legacy "NS*Pboard" types, and `NSPasteboardItem` can only provide data
for `NSPasteboardType`s (i.e. UTIs), so there's no way to just iterate through each pasteboard item and ask it
for data that matches the given type, if the types are not UTIs. However, in the case where we have multiple
items, the client must have used NSPasteboardWriting-conformant objects and/or NSPasteboardItem itself to write
data to the pasteboard. Since NSPasteboardWriting-conformant objects register modern pasteboard types when
writing to the pasteboard, we can simply iterate over these pasteboard items and ask for property lists using
type identifiers instead of having to worry about legacy pasteboard types. Furthermore, in the case where there
is only a single item in the pasteboard and we do need to handle legacy pasteboard types, using `-[NSPasteboard
stringForType:]` in the same way we do currently should yield the correct result.

As such, in the case where there is a single pasteboard item, we use `-[NSPasteboard stringForType:]` with the
original legacy type, and in the case where there are multiple items on the pasteboard, we iterate over each of
the pasteboard items and call `-[NSPasteboardItem propertyListForType:]` with the modern pasteboard type
corresponding to the given legacy pasteboard type.

The different corner cases in this logic are tested by the new API test, PasteMixedContent.PasteOneOrMoreURLs,
which exercises several different ways of writing one or more URLs to the pasteboard on macOS, which each result
in different legacy and modern pasteboard types being written to the pasteboard; our implementation of
`PlatformPasteboard::allStringsForType` on macOS handles all cases correctly.

Source/WebKit:

Add some plumbing through pasteboard classes to support `Pasteboard::readAllStrings`. See WebCore ChangeLog for
more detail.

* UIProcess/Cocoa/WebPasteboardProxyCocoa.mm:
(WebKit::WebPasteboardProxy::getPasteboardStringsForType):
* UIProcess/WebPasteboardProxy.h:
* UIProcess/WebPasteboardProxy.messages.in:
* WebProcess/WebCoreSupport/WebPlatformStrategies.cpp:
(WebKit::WebPlatformStrategies::allStringsForType):
* WebProcess/WebCoreSupport/WebPlatformStrategies.h:

Source/WebKitLegacy/mac:

Add some plumbing through pasteboard classes to support `Pasteboard::readAllStrings`. See WebCore ChangeLog for
more detail.

* WebCoreSupport/WebPlatformStrategies.h:
* WebCoreSupport/WebPlatformStrategies.mm:
(WebPlatformStrategies::allStringsForType):

Tools:

* DumpRenderTree/mac/DumpRenderTreePasteboard.mm:
(-[LocalPasteboard pasteboardItems]):

Implement this method to avoid crashing when running layout tests that access the pasteboard.

* TestWebKitAPI/Tests/WebKitCocoa/DragAndDropTests.mm:

Add a test to verify that on macOS and iOS, multiple URLs dropped onto the page are accessible via
"text/uri-list".

* TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm:

Add a test that exercises 5 different ways to write one or more URLs to the pasteboard on macOS; in all cases,
the URLs written to the pasteboard should be exposed to the page via "text/uri-list". In all of these different
cases, the results of using `-[NSPasteboardItem stringForType:]`, `-[NSURL URLFromPasteboard:]` and
`-[NSPasteboard stringForType:]` will yield different results, so the purpose of this API test is to ensure that
our logic for grabbing a list of URLs from the pasteboard on macOS is robust enough to handle all of these
different behaviors.

* TestWebKitAPI/Tests/ios/UIPasteboardTests.mm:

Add a test to verify that on iOS, using `-[UIPasteboard setURLs:]` to write to multiple URLs to the pasteboard
and then pasting results in "text/uri-list" exposing a list of all the URLs written to the pasteboard.

* WebKitTestRunner/mac/WebKitTestRunnerPasteboard.mm:
(-[LocalPasteboard pasteboardItems]):

Implement this method to avoid crashing when running layout tests that access the pasteboard.

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

26 files changed:
Source/WebCore/ChangeLog
Source/WebCore/dom/DataTransfer.cpp
Source/WebCore/platform/Pasteboard.cpp
Source/WebCore/platform/Pasteboard.h
Source/WebCore/platform/PasteboardStrategy.h
Source/WebCore/platform/PlatformPasteboard.h
Source/WebCore/platform/cocoa/PasteboardCocoa.mm
Source/WebCore/platform/ios/PasteboardIOS.mm
Source/WebCore/platform/ios/PlatformPasteboardIOS.mm
Source/WebCore/platform/mac/PasteboardMac.mm
Source/WebCore/platform/mac/PlatformPasteboardMac.mm
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mm
Source/WebKit/UIProcess/WebPasteboardProxy.h
Source/WebKit/UIProcess/WebPasteboardProxy.messages.in
Source/WebKit/WebProcess/WebCoreSupport/WebPlatformStrategies.cpp
Source/WebKit/WebProcess/WebCoreSupport/WebPlatformStrategies.h
Source/WebKitLegacy/mac/ChangeLog
Source/WebKitLegacy/mac/WebCoreSupport/WebPlatformStrategies.h
Source/WebKitLegacy/mac/WebCoreSupport/WebPlatformStrategies.mm
Tools/ChangeLog
Tools/DumpRenderTree/mac/DumpRenderTreePasteboard.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/DragAndDropTests.mm
Tools/TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm
Tools/TestWebKitAPI/Tests/ios/UIPasteboardTests.mm
Tools/WebKitTestRunner/mac/WebKitTestRunnerPasteboard.mm

index 8938233..41f8966 100644 (file)
@@ -1,3 +1,109 @@
+2018-09-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Populate "text/uri-list" with multiple URLs when the pasteboard contains multiple URLs
+        https://bugs.webkit.org/show_bug.cgi?id=188890
+        <rdar://problem/43648605>
+
+        Reviewed by Tim Horton.
+
+        Adds support for exposing a newline separated list of URLs via DataTransfer's "text/uri-list" type when pasting
+        or dropping multiple items on the pasteboard that can be represented as URLs. Currently on iOS, only the URL of
+        the first item (if present) is exposed, and on macOS, only the first out of all the URLs in the pasteboard is
+        exposed.
+
+        To fix this, we introduce `Pasteboard::readAllStrings`, which reads a list of pasteboard strings collected from
+        all available items on the platform pasteboard. Currently, this is only used to provide a list of URL strings
+        when fetching data for the "text/uri-list" type when calling `DataTransfer.getData()` and
+        `DataTransferItem.getAsString()`.
+
+        Tests:  DragAndDropTests.ExposeMultipleURLsInDataTransfer
+                UIPasteboardTests.DataTransferURIListContainsMultipleURLs
+                PasteMixedContent.PasteOneOrMoreURLs
+
+        * dom/DataTransfer.cpp:
+        (WebCore::readURLsFromPasteboardAsString):
+
+        Add a helper method that reads all URL strings from the pasteboard (for the MIME type "text/uri-list", which
+        corresponds to NSURLPboardType and "public.url" on macOS and iOS, respectively) and returns a single string
+        containing all non-empty URLs joined by newline characters. Also takes a filtering block which may be used to
+        reject URLs from being included in "text/uri-list" output.
+
+        (WebCore::DataTransfer::getDataForItem const):
+        (WebCore::DataTransfer::readStringFromPasteboard const):
+
+        Insteading of reading a single string from the pasteboard for "text/uri-list", call the above helper function to
+        read all URL strings in the pasteboard. If there are files present in the pasteboard, we also filter out URLs
+        whose schemes are not in the set of schemes that are safe to expose to the page (i.e. http(s), blob, and data).
+
+        * platform/Pasteboard.cpp:
+        (WebCore::Pasteboard::readAllStrings):
+
+        Add a default non-Cocoa implementation of readAllStrings() that returns a vector, which may contain the result
+        of readString().
+
+        * platform/Pasteboard.h:
+        * platform/PasteboardStrategy.h:
+        * platform/PlatformPasteboard.h:
+
+        Add plumbing to grab a list of strings from the pasteboard for a given type.
+
+        * platform/cocoa/PasteboardCocoa.mm:
+        (WebCore::Pasteboard::readAllStrings):
+        (WebCore::Pasteboard::readString):
+
+        Implement these two methods in terms of `readPlatformValuesAsStrings`. `readAllStrings` returns the full list of
+        results, while `readString` only returns the first result.
+
+        * platform/ios/PasteboardIOS.mm:
+        (WebCore::Pasteboard::readPlatformValuesAsStrings):
+        (WebCore::Pasteboard::readPlatformValueAsString): Deleted.
+
+        Refactor this Cocoa helper function to return a list of pasteboard string values for the given type, rather than
+        a single string.
+
+        * platform/ios/PlatformPasteboardIOS.mm:
+        (WebCore::PlatformPasteboard::allStringsForType const):
+
+        Grab a string for each item (represented by an NSItemProvider) in the pasteboard that has data for the given
+        type identifier.
+
+        (WebCore::PlatformPasteboard::readString const):
+
+        Return the absolute string of the NSURL, instead of WebCore::URL::string(). This is needed to handle the case
+        where the NSURL is constructed from absolute and relative parts using a Plist. While -absoluteString gets us the
+        full URL string, URL::string() only returns the relative portion.
+
+        * platform/mac/PasteboardMac.mm:
+        (WebCore::Pasteboard::readPlatformValuesAsStrings):
+        (WebCore::Pasteboard::readPlatformValueAsString): Deleted.
+
+        Also refactor this to retrieve a list of pasteboard strings, rather than a single result.
+
+        * platform/mac/PlatformPasteboardMac.mm:
+        (WebCore::typeIdentifierForPasteboardType):
+        (WebCore::PlatformPasteboard::allStringsForType const):
+
+        Add an implementation for `allStringsForType` on macOS. Unlike iOS, it's much trickier to get this right since
+        we need to maintain compatibility with legacy "NS*Pboard" types, and `NSPasteboardItem` can only provide data
+        for `NSPasteboardType`s (i.e. UTIs), so there's no way to just iterate through each pasteboard item and ask it
+        for data that matches the given type, if the types are not UTIs. However, in the case where we have multiple
+        items, the client must have used NSPasteboardWriting-conformant objects and/or NSPasteboardItem itself to write
+        data to the pasteboard. Since NSPasteboardWriting-conformant objects register modern pasteboard types when
+        writing to the pasteboard, we can simply iterate over these pasteboard items and ask for property lists using
+        type identifiers instead of having to worry about legacy pasteboard types. Furthermore, in the case where there
+        is only a single item in the pasteboard and we do need to handle legacy pasteboard types, using `-[NSPasteboard
+        stringForType:]` in the same way we do currently should yield the correct result.
+
+        As such, in the case where there is a single pasteboard item, we use `-[NSPasteboard stringForType:]` with the
+        original legacy type, and in the case where there are multiple items on the pasteboard, we iterate over each of
+        the pasteboard items and call `-[NSPasteboardItem propertyListForType:]` with the modern pasteboard type
+        corresponding to the given legacy pasteboard type.
+
+        The different corner cases in this logic are tested by the new API test, PasteMixedContent.PasteOneOrMoreURLs,
+        which exercises several different ways of writing one or more URLs to the pasteboard on macOS, which each result
+        in different legacy and modern pasteboard types being written to the pasteboard; our implementation of
+        `PlatformPasteboard::allStringsForType` on macOS handles all cases correctly.
+
 2018-09-04  Simon Fraser  <simon.fraser@apple.com>
 
         CSS reference filter that references a tiled feTurbulence is blank
index 5141f47..6d39da4 100644 (file)
@@ -47,6 +47,7 @@
 #include "WebContentReader.h"
 #include "WebCorePasteboardFileReader.h"
 #include "markup.h"
+#include <wtf/unicode/CharacterNames.h>
 
 namespace WebCore {
 
@@ -142,6 +143,19 @@ void DataTransfer::clearData(const String& type)
         m_itemList->didClearStringData(normalizedType);
 }
 
+static String readURLsFromPasteboardAsString(Pasteboard& pasteboard, Function<bool(const String&)>&& shouldIncludeURL)
+{
+    StringBuilder urlList;
+    for (auto urlString : pasteboard.readAllStrings("text/uri-list"_s)) {
+        if (!shouldIncludeURL(urlString))
+            continue;
+        if (!urlList.isEmpty())
+            urlList.append(newlineCharacter);
+        urlList.append(urlString);
+    }
+    return urlList.toString();
+}
+
 String DataTransfer::getDataForItem(Document& document, const String& type) const
 {
     if (!canReadData())
@@ -150,9 +164,9 @@ String DataTransfer::getDataForItem(Document& document, const String& type) cons
     auto lowercaseType = stripLeadingAndTrailingHTMLSpaces(type).convertToASCIILowercase();
     if (shouldSuppressGetAndSetDataToAvoidExposingFilePaths()) {
         if (lowercaseType == "text/uri-list") {
-            auto urlString = m_pasteboard->readString(lowercaseType);
-            if (Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(urlString))
-                return urlString;
+            return readURLsFromPasteboardAsString(*m_pasteboard, [] (auto& urlString) {
+                return Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(urlString);
+            });
         }
 
         if (lowercaseType == "text/html" && RuntimeEnabledFeatures::sharedFeatures().customPasteboardDataEnabled()) {
@@ -192,6 +206,12 @@ String DataTransfer::readStringFromPasteboard(Document& document, const String&
         return reader.markup;
     }
 
+    if (!is<StaticPasteboard>(*m_pasteboard) && lowercaseType == "text/uri-list") {
+        return readURLsFromPasteboardAsString(*m_pasteboard, [] (auto&) {
+            return true;
+        });
+    }
+
     return m_pasteboard->readString(lowercaseType);
 }
 
index 18fdeb2..737a390 100644 (file)
@@ -85,4 +85,17 @@ PasteboardCustomData PasteboardCustomData::fromSharedBuffer(const SharedBuffer&
     return result;
 }
 
+#if !PLATFORM(COCOA)
+
+Vector<String> Pasteboard::readAllStrings(const String& type)
+{
+    auto result = readString(type);
+    if (result.isEmpty())
+        return { };
+
+    return { result };
+}
+
+#endif
+
 };
index e098156..20635ab 100644 (file)
@@ -204,6 +204,7 @@ public:
     virtual WEBCORE_EXPORT String readOrigin();
     virtual WEBCORE_EXPORT String readString(const String& type);
     virtual WEBCORE_EXPORT String readStringInCustomData(const String& type);
+    virtual WEBCORE_EXPORT Vector<String> readAllStrings(const String& type);
 
     virtual WEBCORE_EXPORT void writeString(const String& type, const String& data);
     virtual WEBCORE_EXPORT void clear();
@@ -298,7 +299,7 @@ private:
 
 #if PLATFORM(COCOA)
     Vector<String> readFilePaths();
-    String readPlatformValueAsString(const String& domType, long changeCount, const String& pasteboardName);
+    Vector<String> readPlatformValuesAsStrings(const String& domType, long changeCount, const String& pasteboardName);
     static void addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, const String& cocoaType);
     String readStringForPlatformType(const String&);
     Vector<String> readTypesWithSecurityCheck();
index 9d827c0..99d8ddf 100644 (file)
@@ -61,6 +61,7 @@ public:
     virtual RefPtr<SharedBuffer> bufferForType(const String& pasteboardType, const String& pasteboardName) = 0;
     virtual void getPathnamesForType(Vector<String>& pathnames, const String& pasteboardType, const String& pasteboardName) = 0;
     virtual String stringForType(const String& pasteboardType, const String& pasteboardName) = 0;
+    virtual Vector<String> allStringsForType(const String& pasteboardType, const String& pasteboardName) = 0;
     virtual long changeCount(const String& pasteboardName) = 0;
     virtual String uniqueName() = 0;
     virtual Color color(const String& pasteboardName) = 0;
index dc650c3..8e13bf4 100644 (file)
@@ -73,6 +73,7 @@ public:
     WEBCORE_EXPORT RefPtr<SharedBuffer> bufferForType(const String& pasteboardType);
     WEBCORE_EXPORT void getPathnamesForType(Vector<String>& pathnames, const String& pasteboardType) const;
     WEBCORE_EXPORT String stringForType(const String& pasteboardType) const;
+    WEBCORE_EXPORT Vector<String> allStringsForType(const String& pasteboardType) const;
     WEBCORE_EXPORT long changeCount() const;
     WEBCORE_EXPORT Color color();
     WEBCORE_EXPORT URL url();
index f48e7b8..fa50e4c 100644 (file)
@@ -259,9 +259,15 @@ void Pasteboard::read(PasteboardFileReader& reader)
     }
 }
 
+Vector<String> Pasteboard::readAllStrings(const String& type)
+{
+    return readPlatformValuesAsStrings(type, m_changeCount, m_pasteboardName);
+}
+
 String Pasteboard::readString(const String& type)
 {
-    return readPlatformValueAsString(type, m_changeCount, m_pasteboardName);
+    auto values = readPlatformValuesAsStrings(type, m_changeCount, m_pasteboardName);
+    return values.isEmpty() ? String() : values.first();
 }
 
 String Pasteboard::readStringInCustomData(const String& type)
index d1c02ed..9f6914e 100644 (file)
@@ -373,38 +373,28 @@ void Pasteboard::clear()
     platformStrategies()->pasteboardStrategy()->writeToPasteboard(String(), String(), m_pasteboardName);
 }
 
-String Pasteboard::readPlatformValueAsString(const String& domType, long changeCount, const String& pasteboardName)
+Vector<String> Pasteboard::readPlatformValuesAsStrings(const String& domType, long changeCount, const String& pasteboardName)
 {
-    PasteboardStrategy& strategy = *platformStrategies()->pasteboardStrategy();
-
-    int numberOfItems = strategy.getPasteboardItemsCount(pasteboardName);
-
-    if (!numberOfItems)
-        return String();
+    auto& strategy = *platformStrategies()->pasteboardStrategy();
 
     // Grab the value off the pasteboard corresponding to the cocoaType.
-    RetainPtr<NSString> cocoaType = cocoaTypeFromHTMLClipboardType(domType);
-
-    NSString *cocoaValue = nil;
+    auto cocoaType = cocoaTypeFromHTMLClipboardType(domType);
+    if (!cocoaType)
+        return { };
 
-    if ([cocoaType isEqualToString:(NSString *)kUTTypeURL]) {
-        String title;
-        URL url = strategy.readURLFromPasteboard(0, pasteboardName, title);
-        if (!url.isNull())
-            cocoaValue = [(NSURL *)url absoluteString];
-    } else if ([cocoaType isEqualToString:(NSString *)kUTTypePlainText]) {
-        String value = strategy.readStringFromPasteboard(0, kUTTypePlainText, pasteboardName);
-        if (!value.isNull())
-            cocoaValue = [(NSString *)value precomposedStringWithCanonicalMapping];
-    } else if (cocoaType)
-        cocoaValue = (NSString *)strategy.readStringFromPasteboard(0, cocoaType.get(), pasteboardName);
+    auto values = strategy.allStringsForType(cocoaType.get(), pasteboardName);
+    if ([cocoaType isEqualToString:(__bridge NSString *)kUTTypePlainText]) {
+        values = values.map([&] (auto& value) -> String {
+            return [value precomposedStringWithCanonicalMapping];
+        });
+    }
 
     // Enforce changeCount ourselves for security. We check after reading instead of before to be
     // sure it doesn't change between our testing the change count and accessing the data.
-    if (cocoaValue && changeCount == changeCountForPasteboard(pasteboardName))
-        return cocoaValue;
+    if (changeCount != changeCountForPasteboard(pasteboardName))
+        return { };
 
-    return String();
+    return values;
 }
 
 void Pasteboard::addHTMLClipboardTypesForCocoaType(ListHashSet<String>& resultTypes, const String& cocoaType)
index e02893e..da7f621 100644 (file)
@@ -603,6 +603,19 @@ int PlatformPasteboard::count() const
     return [m_pasteboard numberOfItems];
 }
 
+Vector<String> PlatformPasteboard::allStringsForType(const String& type) const
+{
+    auto numberOfItems = count();
+    Vector<String> strings;
+    strings.reserveInitialCapacity(numberOfItems);
+    for (int index = 0; index < numberOfItems; ++index) {
+        String value = readString(index, type);
+        if (!value.isEmpty())
+            strings.uncheckedAppend(WTFMove(value));
+    }
+    return strings;
+}
+
 RefPtr<SharedBuffer> PlatformPasteboard::readBuffer(int index, const String& type) const
 {
     NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:index];
@@ -618,7 +631,7 @@ String PlatformPasteboard::readString(int index, const String& type) const
 {
     if (type == String(kUTTypeURL)) {
         String title;
-        return readURL(index, title);
+        return [(NSURL *)readURL(index, title) absoluteString];
     }
 
     NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:index];
index fd6e929..1dd676c 100644 (file)
@@ -488,22 +488,26 @@ void Pasteboard::clear(const String& type)
     m_changeCount = platformStrategies()->pasteboardStrategy()->setStringForType(emptyString(), cocoaType, m_pasteboardName);
 }
 
-String Pasteboard::readPlatformValueAsString(const String& domType, long changeCount, const String& pasteboardName)
+Vector<String> Pasteboard::readPlatformValuesAsStrings(const String& domType, long changeCount, const String& pasteboardName)
 {
-    const String& cocoaType = cocoaTypeFromHTMLClipboardType(domType);
-    String cocoaValue;
+    auto& strategy = *platformStrategies()->pasteboardStrategy();
+    auto cocoaType = cocoaTypeFromHTMLClipboardType(domType);
+    if (cocoaType.isEmpty())
+        return { };
 
-    if (cocoaType == String(legacyStringPasteboardType()))
-        cocoaValue = [platformStrategies()->pasteboardStrategy()->stringForType(cocoaType, pasteboardName) precomposedStringWithCanonicalMapping];
-    else if (!cocoaType.isEmpty())
-        cocoaValue = platformStrategies()->pasteboardStrategy()->stringForType(cocoaType, pasteboardName);
+    auto values = strategy.allStringsForType(cocoaType, pasteboardName);
+    if (cocoaType == String(legacyStringPasteboardType())) {
+        values = values.map([&] (auto& value) -> String {
+            return [value precomposedStringWithCanonicalMapping];
+        });
+    }
 
     // Enforce changeCount ourselves for security.  We check after reading instead of before to be
     // sure it doesn't change between our testing the change count and accessing the data.
-    if (!cocoaValue.isEmpty() && changeCount == platformStrategies()->pasteboardStrategy()->changeCount(pasteboardName))
-        return cocoaValue;
+    if (changeCount != platformStrategies()->pasteboardStrategy()->changeCount(pasteboardName))
+        return { };
 
-    return String();
+    return values;
 }
 
 static String utiTypeFromCocoaType(const String& type)
index a1925ba..b1dffb6 100644 (file)
@@ -116,6 +116,68 @@ String PlatformPasteboard::stringForType(const String& pasteboardType) const
     return [m_pasteboard stringForType:pasteboardType];
 }
 
+static Vector<String> urlStringsFromPasteboard(NSPasteboard *pasteboard)
+{
+    NSArray<NSPasteboardItem *> *items = pasteboard.pasteboardItems;
+    Vector<String> urlStrings;
+    urlStrings.reserveInitialCapacity(items.count);
+    if (items.count > 1) {
+        for (NSPasteboardItem *item in items) {
+            if (id propertyList = [item propertyListForType:(__bridge NSString *)kUTTypeURL]) {
+                if (auto urlFromItem = adoptNS([[NSURL alloc] initWithPasteboardPropertyList:propertyList ofType:(__bridge NSString *)kUTTypeURL]))
+                    urlStrings.uncheckedAppend([urlFromItem absoluteString]);
+            }
+        }
+    } else if (NSURL *urlFromPasteboard = [NSURL URLFromPasteboard:pasteboard])
+        urlStrings.uncheckedAppend(urlFromPasteboard.absoluteString);
+    else if (NSString *urlStringFromPasteboard = [pasteboard stringForType:legacyURLPasteboardType()])
+        urlStrings.uncheckedAppend(urlStringFromPasteboard);
+
+    bool mayContainFiles = pasteboardMayContainFilePaths(pasteboard);
+    urlStrings.removeAllMatching([&] (auto& urlString) {
+        return urlString.isEmpty() || (mayContainFiles && !Pasteboard::canExposeURLToDOMWhenPasteboardContainsFiles(urlString));
+    });
+
+    return urlStrings;
+}
+
+static String typeIdentifierForPasteboardType(const String& pasteboardType)
+{
+    if (UTTypeIsDeclared(pasteboardType.createCFString().get()))
+        return pasteboardType;
+
+    if (pasteboardType == String(legacyStringPasteboardType()))
+        return kUTTypeUTF8PlainText;
+
+    if (pasteboardType == String(legacyHTMLPasteboardType()))
+        return kUTTypeHTML;
+
+    if (pasteboardType == String(legacyURLPasteboardType()))
+        return kUTTypeURL;
+
+    return { };
+}
+
+Vector<String> PlatformPasteboard::allStringsForType(const String& pasteboardType) const
+{
+    auto typeIdentifier = typeIdentifierForPasteboardType(pasteboardType);
+    if (typeIdentifier == String(kUTTypeURL))
+        return urlStringsFromPasteboard(m_pasteboard.get());
+
+    NSArray<NSPasteboardItem *> *items = [m_pasteboard pasteboardItems];
+    Vector<String> strings;
+    strings.reserveInitialCapacity(items.count);
+    if (items.count > 1 && !typeIdentifier.isNull()) {
+        for (NSPasteboardItem *item in items) {
+            if (NSString *stringFromItem = [item stringForType:typeIdentifier])
+                strings.append(stringFromItem);
+        }
+    } else if (NSString *stringFromPasteboard = [m_pasteboard stringForType:pasteboardType])
+        strings.append(stringFromPasteboard);
+
+    return strings;
+}
+
 static const char* safeTypeForDOMToReadAndWriteForPlatformType(const String& platformType)
 {
     if (platformType == String(legacyStringPasteboardType()) || platformType == String(NSPasteboardTypeString))
index b924630..d3dc54a 100644 (file)
@@ -1,3 +1,22 @@
+2018-09-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Populate "text/uri-list" with multiple URLs when the pasteboard contains multiple URLs
+        https://bugs.webkit.org/show_bug.cgi?id=188890
+        <rdar://problem/43648605>
+
+        Reviewed by Tim Horton.
+
+        Add some plumbing through pasteboard classes to support `Pasteboard::readAllStrings`. See WebCore ChangeLog for
+        more detail.
+
+        * UIProcess/Cocoa/WebPasteboardProxyCocoa.mm:
+        (WebKit::WebPasteboardProxy::getPasteboardStringsForType):
+        * UIProcess/WebPasteboardProxy.h:
+        * UIProcess/WebPasteboardProxy.messages.in:
+        * WebProcess/WebCoreSupport/WebPlatformStrategies.cpp:
+        (WebKit::WebPlatformStrategies::allStringsForType):
+        * WebProcess/WebCoreSupport/WebPlatformStrategies.h:
+
 2018-09-04  Youenn Fablet  <youenn@apple.com>
 
         Disable WebRTC unified plan runtime flag by default
index c992440..ae64707 100644 (file)
@@ -70,6 +70,11 @@ void WebPasteboardProxy::getPasteboardStringForType(const String& pasteboardName
     string = PlatformPasteboard(pasteboardName).stringForType(pasteboardType);
 }
 
+void WebPasteboardProxy::getPasteboardStringsForType(const String& pasteboardName, const String& pasteboardType, Vector<String>& strings)
+{
+    strings = PlatformPasteboard(pasteboardName).allStringsForType(pasteboardType);
+}
+
 void WebPasteboardProxy::getPasteboardBufferForType(const String& pasteboardName, const String& pasteboardType, SharedMemory::Handle& handle, uint64_t& size)
 {
     RefPtr<SharedBuffer> buffer = PlatformPasteboard(pasteboardName).bufferForType(pasteboardType);
index e5ec209..3ae2603 100644 (file)
@@ -87,6 +87,7 @@ private:
     void getPasteboardTypes(const String& pasteboardName, Vector<String>& pasteboardTypes);
     void getPasteboardPathnamesForType(IPC::Connection&, const String& pasteboardName, const String& pasteboardType, Vector<String>& pathnames, SandboxExtension::HandleArray&);
     void getPasteboardStringForType(const String& pasteboardName, const String& pasteboardType, String&);
+    void getPasteboardStringsForType(const String& pasteboardName, const String& pasteboardType, Vector<String>&);
     void getPasteboardBufferForType(const String& pasteboardName, const String& pasteboardType, SharedMemory::Handle&, uint64_t& size);
     void pasteboardCopy(const String& fromPasteboard, const String& toPasteboard, uint64_t& newChangeCount);
     void getPasteboardChangeCount(const String& pasteboardName, uint64_t& changeCount);
index b92cb81..5dd6988 100644 (file)
@@ -45,6 +45,7 @@ messages -> WebPasteboardProxy {
     GetPasteboardTypes(String pasteboardName) -> (Vector<String> types)
     GetPasteboardPathnamesForType(String pasteboardName, String pasteboardType) -> (Vector<String> pathnames, WebKit::SandboxExtension::HandleArray sandboxExtensions) WantsConnection
     GetPasteboardStringForType(String pasteboardName, String pasteboardType) -> (String string)
+    GetPasteboardStringsForType(String pasteboardName, String pasteboardType) -> (Vector<String> strings)
     GetPasteboardBufferForType(String pasteboardName, String pasteboardType) -> (WebKit::SharedMemory::Handle handle, uint64_t size)
     PasteboardCopy(String fromPasteboard, String toPasteboard) -> (uint64_t changeCount)
     GetPasteboardChangeCount(String pasteboardName) -> (uint64_t changeCount)
index 7602f5c..243298e 100644 (file)
@@ -207,6 +207,13 @@ String WebPlatformStrategies::stringForType(const String& pasteboardType, const
     return value;
 }
 
+Vector<String> WebPlatformStrategies::allStringsForType(const String& pasteboardType, const String& pasteboardName)
+{
+    Vector<String> values;
+    WebProcess::singleton().parentProcessConnection()->sendSync(Messages::WebPasteboardProxy::GetPasteboardStringsForType(pasteboardName, pasteboardType), Messages::WebPasteboardProxy::GetPasteboardStringsForType::Reply(values), 0);
+    return values;
+}
+
 long WebPlatformStrategies::changeCount(const WTF::String &pasteboardName)
 {
     uint64_t changeCount { 0 };
index 591d78f..6eec09b 100644 (file)
@@ -76,6 +76,7 @@ private:
     RefPtr<WebCore::SharedBuffer> bufferForType(const String& pasteboardType, const String& pasteboardName) override;
     void getPathnamesForType(Vector<String>& pathnames, const String& pasteboardType, const String& pasteboardName) override;
     String stringForType(const String& pasteboardType, const String& pasteboardName) override;
+    Vector<String> allStringsForType(const String& pasteboardType, const String& pasteboardName) override;
     long changeCount(const String& pasteboardName) override;
     String uniqueName() override;
     WebCore::Color color(const String& pasteboardName) override;
index b4139bf..b12e132 100644 (file)
@@ -1,3 +1,18 @@
+2018-09-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Populate "text/uri-list" with multiple URLs when the pasteboard contains multiple URLs
+        https://bugs.webkit.org/show_bug.cgi?id=188890
+        <rdar://problem/43648605>
+
+        Reviewed by Tim Horton.
+
+        Add some plumbing through pasteboard classes to support `Pasteboard::readAllStrings`. See WebCore ChangeLog for
+        more detail.
+
+        * WebCoreSupport/WebPlatformStrategies.h:
+        * WebCoreSupport/WebPlatformStrategies.mm:
+        (WebPlatformStrategies::allStringsForType):
+
 2018-09-01  Darin Adler  <darin@apple.com>
 
         [CFNetwork] Update CFNetwork SPI use to use CFNetworkSPI.h more consistently
index a8d9626..739c951 100644 (file)
@@ -76,6 +76,7 @@ private:
     RefPtr<WebCore::SharedBuffer> bufferForType(const String& pasteboardType, const String& pasteboardName) override;
     void getPathnamesForType(Vector<String>& pathnames, const String& pasteboardType, const String& pasteboardName) override;
     String stringForType(const String& pasteboardType, const String& pasteboardName) override;
+    Vector<String> allStringsForType(const String& pasteboardType, const String& pasteboardName) override;
     long changeCount(const String& pasteboardName) override;
     String uniqueName() override;
     WebCore::Color color(const String& pasteboardName) override;
index 9d578a0..bd7581f 100644 (file)
@@ -123,6 +123,11 @@ void WebPlatformStrategies::getPathnamesForType(Vector<String>& pathnames, const
     PlatformPasteboard(pasteboardName).getPathnamesForType(pathnames, pasteboardType);
 }
 
+Vector<String> WebPlatformStrategies::allStringsForType(const String& pasteboardType, const String& pasteboardName)
+{
+    return PlatformPasteboard(pasteboardName).allStringsForType(pasteboardType);
+}
+
 String WebPlatformStrategies::stringForType(const String& pasteboardType, const String& pasteboardName)
 {
     return PlatformPasteboard(pasteboardName).stringForType(pasteboardType);
index 8d550db..9a359a6 100644 (file)
@@ -1,3 +1,40 @@
+2018-09-04  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        Populate "text/uri-list" with multiple URLs when the pasteboard contains multiple URLs
+        https://bugs.webkit.org/show_bug.cgi?id=188890
+        <rdar://problem/43648605>
+
+        Reviewed by Tim Horton.
+
+        * DumpRenderTree/mac/DumpRenderTreePasteboard.mm:
+        (-[LocalPasteboard pasteboardItems]):
+
+        Implement this method to avoid crashing when running layout tests that access the pasteboard.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/DragAndDropTests.mm:
+
+        Add a test to verify that on macOS and iOS, multiple URLs dropped onto the page are accessible via
+        "text/uri-list".
+
+        * TestWebKitAPI/Tests/WebKitCocoa/PasteMixedContent.mm:
+
+        Add a test that exercises 5 different ways to write one or more URLs to the pasteboard on macOS; in all cases,
+        the URLs written to the pasteboard should be exposed to the page via "text/uri-list". In all of these different
+        cases, the results of using `-[NSPasteboardItem stringForType:]`, `-[NSURL URLFromPasteboard:]` and
+        `-[NSPasteboard stringForType:]` will yield different results, so the purpose of this API test is to ensure that
+        our logic for grabbing a list of URLs from the pasteboard on macOS is robust enough to handle all of these
+        different behaviors.
+
+        * TestWebKitAPI/Tests/ios/UIPasteboardTests.mm:
+
+        Add a test to verify that on iOS, using `-[UIPasteboard setURLs:]` to write to multiple URLs to the pasteboard
+        and then pasting results in "text/uri-list" exposing a list of all the URLs written to the pasteboard.
+
+        * WebKitTestRunner/mac/WebKitTestRunnerPasteboard.mm:
+        (-[LocalPasteboard pasteboardItems]):
+
+        Implement this method to avoid crashing when running layout tests that access the pasteboard.
+
 2018-09-04  Simon Fraser  <simon.fraser@apple.com>
 
         REGRESSION(r235408): GTK bots exiting early
index cdd31b3..ae29a94 100644 (file)
@@ -237,6 +237,17 @@ static RetainPtr<CFStringRef> toUTI(NSString *type)
     return YES;
 }
 
+- (NSArray<NSPasteboardItem *> *)pasteboardItems
+{
+    auto item = adoptNS([[NSPasteboardItem alloc] init]);
+    for (auto typeAndData : _data) {
+        NSData *data = (__bridge NSData *)typeAndData.value.get();
+        NSString *type = (__bridge NSString *)typeAndData.key.get();
+        [item setData:data forType:type];
+    }
+    return @[ item.get() ];
+}
+
 @end
 
 #endif // PLATFORM(MAC)
index 15e4148..94b5f70 100644 (file)
@@ -43,6 +43,37 @@ TEST(DragAndDropTests, DragImageLocationForLinkInSubframe)
 #endif
 }
 
+TEST(DragAndDropTests, ExposeMultipleURLsInDataTransfer)
+{
+    auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:CGRectMake(0, 0, 320, 500)]);
+    auto webView = [simulator webView];
+    [webView synchronouslyLoadTestPageNamed:@"DataTransfer"];
+
+    NSString *stringData = @"Hello world";
+    NSURL *firstURL = [NSURL URLWithString:@"https://webkit.org/"];
+    NSURL *secondURL = [NSURL URLWithString:@"https://apple.com/"];
+
+#if PLATFORM(MAC)
+    NSPasteboard *pasteboard = [NSPasteboard pasteboardWithUniqueName];
+    [pasteboard writeObjects:@[ stringData, firstURL, secondURL ]];
+    [simulator setExternalDragPasteboard:pasteboard];
+#else
+    auto stringItem = adoptNS([[NSItemProvider alloc] initWithObject:stringData]);
+    auto firstURLItem = adoptNS([[NSItemProvider alloc] initWithObject:firstURL]);
+    auto secondURLItem = adoptNS([[NSItemProvider alloc] initWithObject:secondURL]);
+    for (NSItemProvider *item in @[ stringItem.get(), firstURLItem.get(), secondURLItem.get() ])
+        item.preferredPresentationStyle = UIPreferredPresentationStyleInline;
+    [simulator setExternalItemProviders:@[ stringItem.get(), firstURLItem.get(), secondURLItem.get() ]];
+#endif
+
+    [simulator runFrom:CGPointMake(0, 0) to:CGPointMake(100, 100)];
+
+    EXPECT_WK_STREQ("text/plain, text/uri-list", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(STRING, text/plain), (STRING, text/uri-list)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("Hello world", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+    EXPECT_WK_STREQ("https://webkit.org/\nhttps://apple.com/", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+}
+
 #if ENABLE(INPUT_TYPE_COLOR)
 
 TEST(DragAndDropTests, ColorInputToColorInput)
index c5e50e2..7132a2e 100644 (file)
@@ -255,6 +255,50 @@ TEST(PasteMixedContent, PasteURLWrittenToPasteboardUsingWriteObjects)
     EXPECT_WK_STREQ([webView stringByEvaluatingJavaScript:@"document.querySelector('a').textContent"], urlToCopy);
 }
 
+TEST(PasteMixedContent, PasteOneOrMoreURLs)
+{
+    NSURL *appleURL = [NSURL URLWithString:@"https://www.apple.com/"];
+    NSURL *webKitURL = [NSURL URLWithString:@"https://webkit.org/"];
+
+    auto webView = setUpWebView();
+    auto runTest = [webView] (NSString *description, NSString *expectedURLString, Function<void(NSPasteboard *)>&& writeURLsToPasteboard) {
+        NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+
+        [pasteboard clearContents];
+        writeURLsToPasteboard(pasteboard);
+        [webView stringByEvaluatingJavaScript:@"reset(); document.body.focus()"];
+        [webView paste:nil];
+
+        EXPECT_WK_STREQ(expectedURLString, [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+        EXPECT_WK_STREQ("(STRING, text/uri-list)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+        EXPECT_WK_STREQ("text/uri-list", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    };
+
+    runTest(@"Write multiple URLs.", @"https://www.apple.com/\nhttps://webkit.org/", ^(NSPasteboard *pasteboard) {
+        [pasteboard writeObjects:@[appleURL, webKitURL]];
+    });
+
+    runTest(@"Declare legacy URL and write URL to pasteboard.", @"https://www.apple.com/", ^(NSPasteboard *pasteboard) {
+        [pasteboard declareTypes:@[NSURLPboardType] owner:nil];
+        [appleURL writeToPasteboard:pasteboard];
+    });
+
+    runTest(@"Declare legacy URL and set a URL string.", @"https://www.apple.com/", ^(NSPasteboard *pasteboard) {
+        [pasteboard declareTypes:@[NSURLPboardType] owner:nil];
+        [pasteboard setString:appleURL.absoluteString forType:NSURLPboardType];
+    });
+
+    runTest(@"Declare legacy URL and set a property list.", @"https://www.apple.com/", ^(NSPasteboard *pasteboard) {
+        [pasteboard declareTypes:@[NSURLPboardType] owner:nil];
+        [pasteboard setPropertyList:@[@"/", @"https://www.apple.com"] forType:NSURLPboardType];
+    });
+
+    runTest(@"Declare URL UTI and set a URL string.", @"https://www.apple.com/", ^(NSPasteboard *pasteboard) {
+        [pasteboard declareTypes:@[(__bridge NSString *)kUTTypeURL] owner:nil];
+        [pasteboard setString:appleURL.absoluteString forType:(__bridge NSString *)kUTTypeURL];
+    });
+}
+
 #endif // PLATFORM(MAC)
 
 TEST(PasteMixedContent, CopyAndPasteWithCustomPasteboardDataOnly)
index 4334855..1c19a37 100644 (file)
@@ -267,6 +267,22 @@ TEST(UIPasteboardTests, DataTransferGetDataCannotReadArbitraryPlatformTypes)
     });
 }
 
+TEST(UIPasteboardTests, DataTransferURIListContainsMultipleURLs)
+{
+    auto webView = setUpWebViewForPasteboardTests(@"DataTransfer");
+
+    NSURL *firstURL = [NSURL URLWithString:@"https://www.apple.com/"];
+    NSURL *secondURL = [NSURL URLWithString:@"https://webkit.org/"];
+    [UIPasteboard generalPasteboard].URLs = @[ firstURL, secondURL ];
+
+    [webView paste:nil];
+
+    EXPECT_WK_STREQ("text/uri-list, text/plain", [webView stringByEvaluatingJavaScript:@"types.textContent"]);
+    EXPECT_WK_STREQ("(STRING, text/uri-list), (STRING, text/plain)", [webView stringByEvaluatingJavaScript:@"items.textContent"]);
+    EXPECT_WK_STREQ("https://www.apple.com/\nhttps://webkit.org/", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]);
+    EXPECT_WK_STREQ("https://www.apple.com/", [webView stringByEvaluatingJavaScript:@"textData.textContent"]);
+}
+
 #endif // __IPHONE_OS_VERSION_MIN_REQUIRED >= 110300
 
 } // namespace TestWebKitAPI
index abcdf41..5e5cac4 100644 (file)
@@ -29,6 +29,7 @@
 #include "WebKitTestRunnerPasteboard.h"
 
 #include <objc/runtime.h>
+#include <wtf/RetainPtr.h>
 
 @interface LocalPasteboard : NSPasteboard
 {
@@ -197,4 +198,12 @@ static NSMutableDictionary *localPasteboards;
     return [self setData:[string dataUsingEncoding:NSUTF8StringEncoding] forType:dataType];
 }
 
+- (NSArray<NSPasteboardItem *> *)pasteboardItems
+{
+    auto item = adoptNS([[NSPasteboardItem alloc] init]);
+    for (NSString *type in dataByType)
+        [item setData:dataByType[type] forType:type];
+    return @[ item.get() ];
+}
+
 @end