clipboard-apis icon indicating copy to clipboard operation
clipboard-apis copied to clipboard

What should `clipboard.readText()` return when the clipboard contains multiple clipboard items?

Open mbrodesser opened this issue 3 years ago • 7 comments

Currently, clipboard.readText() returns a Promise<DOMString>. However, when clipboard.read() would return multiple ClipboardItems, multiple of those items may contain a text/plain representation.

I see the following answers to above question:

  1. Specify to return either the first or the last item's text/plain representation.
  2. Specify to throw an error and a method like clipboard.getNumberOfClipboardItems().
  3. Specify to return a sequence of DOMStrings.

Since multiple ClipboardItems are only supported on Apple's OSs, Apple's view on this would be appreciated. cc @whsieh

mbrodesser avatar Jan 31 '22 11:01 mbrodesser

CC @snianu because of #158 .

mbrodesser avatar Jan 31 '22 11:01 mbrodesser

I like the first option - it seems intuitive and there are APIs that seem to fit the behavior you described in the Apple Developer docs.

Interestingly, that is not the API that Chromium seems to use for readText. The stringForType documentation says:

A concatenation of the strings for the specified type from all the items in the receiver that contain the type, or nil if none of the items contain strings of the specified type.

That strikes me as odd behavior, but in practice maybe it works out to be the same as returning the first item's text/plain representation? The only scenario I know that produces multiple clipboard items is copying multiple files from the Finder (admittedly I just learned that scenario from Wenson in our last WG call, so there may be many more). In that case, only the first item has a text/plain representation: a newline separated list of the names of the files you copied. Subsequent items only have a public.file-url representation containing the path to the copied file.

BoCupp-Microsoft avatar Feb 02 '22 00:02 BoCupp-Microsoft

The only scenario I know that produces multiple clipboard items is copying multiple files from the Finder (admittedly I just learned that scenario from Wenson in our last WG call, so there may be many more). In that case, only the first item has a text/plain representation: a newline separated list of the names of the files you copied.

That's odd. Then one couldn't get only the first file name, without further processing.

It would be helpful if @whsieh or @rniwa could shed light on this.

mbrodesser avatar Feb 02 '22 09:02 mbrodesser

A concatenation of the strings for the specified type from all the items in the receiver that contain the type, or nil if none of the items contain strings of the specified type.

I'm not sure, but that might yield unexpected results when some items don't contain a text/plain representation; and there's no guarantee that they do.

Solution 2 and 3 proposed in https://github.com/w3c/clipboard-apis/issues/166#issue-1119331749 don't yield such unexpected results (specifying that the third solution returns empty strings for those items not containing text/plain representations).

mbrodesser avatar Feb 02 '22 10:02 mbrodesser

I wrote a quick Objective-C program to dump pasteboard items (and the list of types per item), and tested it out in a couple of scenarios (against the latest macOS Monterey beta):

@import AppKit;
@import UniformTypeIdentifiers;

int main(int argc, const char * argv[]) {
    __auto_type filterDeclaredTypes = [NSPredicate predicateWithBlock:^BOOL(NSString *typeIdentifier, NSDictionary<NSString *,id> *unused) {
        return [UTType typeWithIdentifier:typeIdentifier].declared;
    }];

    [[[NSPasteboard generalPasteboard] pasteboardItems] enumerateObjectsUsingBlock:^(NSPasteboardItem *item, NSUInteger index, BOOL *unused) {
        NSLog(@"Item at index [%zu] contains types: %@", index, [item.types filteredArrayUsingPredicate:filterDeclaredTypes]);
    }];
}

When copying 4 files in Finder, I see multiple items (though only the first item has a plain text representation that contains a space-separated list of file paths):

2022-02-02 08:26:30.567 PasteboardTest[5673:30388857] Item at index [0] contains types: (
    "public.file-url",
    "public.utf16-external-plain-text",
    "public.utf8-plain-text"
)
2022-02-02 08:26:30.567 PasteboardTest[5673:30388857] Item at index [1] contains types: (
    "public.file-url"
)
2022-02-02 08:26:30.567 PasteboardTest[5673:30388857] Item at index [2] contains types: (
    "public.file-url"
)
2022-02-02 08:26:30.568 PasteboardTest[5673:30388857] Item at index [3] contains types: (
    "public.file-url"
)

When copying 3 images from the Photos app, all 3 items correspond to pasteboard items:

2022-02-02 08:27:59.027 PasteboardTest[5678:30390067] Item at index [0] contains types: (
    "com.apple.photos.object-reference.asset",
    "public.jpeg",
    "public.file-url"
)
2022-02-02 08:27:59.028 PasteboardTest[5678:30390067] Item at index [1] contains types: (
    "com.apple.photos.object-reference.asset",
    "public.jpeg",
    "public.file-url"
)
2022-02-02 08:27:59.028 PasteboardTest[5678:30390067] Item at index [2] contains types: (
    "com.apple.photos.object-reference.asset",
    "public.jpeg",
    "public.file-url"
)

whsieh avatar Feb 02 '22 16:02 whsieh

I augmented the test app a bit so that it instead writes two strings as separate items to the pasteboard, and then queries for just the string on the pasteboard (without specifying an item).

int main(int argc, const char * argv[]) {
    __auto_type item1 = [NSPasteboardItem new];
    __auto_type item2 = [NSPasteboardItem new];

    [item1 setString:@"Hello world" forType:NSPasteboardTypeString];
    [item2 setString:@"Foo bar" forType:NSPasteboardTypeString];

    [[NSPasteboard generalPasteboard] clearContents];
    [[NSPasteboard generalPasteboard] writeObjects:@[item1, item2]];

    NSLog(@"The string is: \"%@\"", [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]);
}

…with output:

2022-02-02 08:34:44.587825-0800 PasteboardTest[5784:30400159] The string is: "Hello world
Foo bar"

So on macOS at least, the platform behavior for the overall text representation when there are multiple items is that it concatenates all the strings together with a newline in between each item.

whsieh avatar Feb 02 '22 16:02 whsieh

@whsieh thanks for the programs, output and analysis.

To specify what's most reasonable for clipboard.readText() to return, it would be helpful to know web-devs' use-cases of that method.

CC @johanneswilm

mbrodesser avatar Feb 03 '22 14:02 mbrodesser