From eb4e99a40e2b82ec4a8265dab81b0cc442706a58 Mon Sep 17 00:00:00 2001 From: snianu <45411397+snianu@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:48:49 -0700 Subject: [PATCH] Add Web Custom format (#175) * Add web custom format. * Add web custom format. * Add web prefix. * Address PR comments. * Updated model and read implementation (readText and write still pending). * Fixed spec errors. * Fixed readText() method and addressed comments. * Address PR comments. --------- Co-authored-by: Bo Cupp --- index.bs | 359 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 286 insertions(+), 73 deletions(-) diff --git a/index.bs b/index.bs index 6b4109d..8dadd7f 100644 --- a/index.bs +++ b/index.bs @@ -165,15 +165,11 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; The platform provides a system clipboard. - The [=system clipboard=] has a list of clipboard items that are collectively - called the system clipboard data. + The [=system clipboard=] has a list of [=system clipboard item=]s that are collectively called the system clipboard data. - * For the [[#clipboard-event-api]], the [=system clipboard data=] is - exposed as a {{DataTransfer}} object that mirrors the contents of the clipboard. + Each system clipboard item has a list of [=system clipboard representation=]s. - * For the [[#async-clipboard-api]], the [=system clipboard data=] is - exposed as a sequence of [=/clipboard item=] objects that mirrors the contents of - the clipboard. + Each system clipboard representation has a name, which is a string, and data, which is a sequence of bytes.

Clipboard Events

@@ -634,7 +630,7 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn;
Returns true if type is in [=mandatory data types=] or [=optional data types=], else, returns false.
- A clipboard item is conceptually data that the user has expressed a desire to make shareable by invoking a "cut" or "copy" command. A [=/clipboard item=] serves two purposes. First, it allows a website to read data copied by a user to the system clipboard. Second, it allows a website to write data to the system clipboard. + A clipboard item is conceptually data that the user has expressed a desire to make shareable by invoking a "cut" or "copy" command. A [=/clipboard item=] serves two purposes. First, it allows a website to read data copied by a user to the [=system clipboard=]. Second, it allows a website to write data to the [=system clipboard=].

For example, if a user copies a range of cells from a spreadsheet of a native application, it will result in one [=/clipboard item=]. If a user copies a set of files from their desktop, that list of files will be represented by multiple [=/clipboard item=]s. @@ -642,10 +638,11 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; Some platforms may support having more than one [=/clipboard item=] at a time on the [=clipboard=], while other platforms replace the previous [=/clipboard item=] with the new one. - A [=/clipboard item=] has a list of representations, each representation with an associated mime type (a [=/MIME type=]) and data (a {{ClipboardItemData}}). + A [=/clipboard item=] has a list of representations, each representation with an associated mime type (a [=/MIME type=]), an isCustom flag, initially |false|, that indicates if this [=representation=] should be treated as a [=web custom format=] (as opposed to a well-known format of the [=system clipboard=]), and data (a {{ClipboardItemData}}). + A web custom format has [=representation/isCustom=] set to |true|.

- In the example where the user copies a range of cells from a spreadsheet, it may be represented as an image (image/png), an HTML table (text/html), or plain text (text/plain). + In the example where the user copies a range of cells from a spreadsheet, it may be represented as an image (image/png), an HTML table (text/html), or plain text (text/plain), or a web custom format (web text/csv).

Each of these [=/MIME type=]s describe a different [=representation=] of the same [=/clipboard item=] at different levels of fidelity and make the [=/clipboard item=] more consumable by target applications during paste. @@ -688,13 +685,25 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. For each (|key|, |value|) in |items|: + 1. Let |representation| be a new [=representation=]. + + 1. Let |isCustom| be |false|. + + 1. If |key| has "web " ("web" followed by U+0020 SPACE) prefix, then + + 1. Remove "web " prefix and set the remaining string to |key|. + + 1. Set |isCustom| |true| + + 1. Set |representation|'s [=representation/isCustom=] flag to |isCustom|. + 1. Let |mimeType| be the result of [=parsing a MIME type=] given |key|. 1. If |mimeType| is failure, then throw a {{TypeError}}. - 1. If [=this=]'s [=ClipboardItem/clipboard item=]'s [=list of representations=] [=list/contains=] a [=representation=] whose [=representation/MIME type=] is |mimeType|, then throw a {{TypeError}}. + 1. If [=this=]'s [=ClipboardItem/clipboard item=]'s [=list of representations=] [=list/contains=] a [=representation=] whose [=representation/MIME type=] is |mimeType| and whose [representation/isCustom] is |isCustom|, then throw a {{TypeError}}. - 1. Let |representation| be a new [=representation=]. +

The step above prevents collision between mime-types well-known by the user agent and those that are intended by the author to be treated as a custom type. For example, it would be possible for the author's list of items to contain representations for "text/html" as well as "web text/html".

1. Set |representation|'s [=representation/MIME type=] to |mimeType|. @@ -704,6 +713,8 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. Let |mimeTypeString| be the result of [=serializing a MIME type=] with |mimeType|. + 1. If |isCustom| is |true|, prefix |mimeTypeString| with "web " ("web" followed by U+0020 SPACE). + 1. Add |mimeTypeString| to |types|. 1. Set [=this=]'s [=ClipboardItem/types array=] to the result of running [=create a frozen array=] from |types|. @@ -724,6 +735,14 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. Let |realm| be [=this=]'s [=relevant realm=]. + 1. Let |isCustom| be |false|. + + 1. If |type| has "web "("web" followed by U+0020 SPACE) prefix, then: + + 1. Remove "web " prefix and set the remaining string to |type|. + + 1. Set |isCustom| to |true|. + 1. Let |mimeType| be the result of [=parsing a MIME type=] given |type|. 1. If |mimeType| is failure, then throw a {{TypeError}}. @@ -734,7 +753,7 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. For each |representation| in |itemTypeList|: - 1. If |representation|'s [=representation/MIME type=] is |mimeType|, then: + 1. If |representation|'s [=representation/MIME type=] is |mimeType| and |representation|'s [=representation/isCustom=] is |isCustom|, then: 1. Let |representationDataPromise| be the |representation|'s [=representation/data=]. @@ -795,7 +814,7 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn;

A web author needs to create a |data| which is an array of {{ClipboardItem}}s in order to write content to [=system clipboard=] using the {{Clipboard/write(data)}} method. - {{Clipboard/read()}} returns a [=Promise=] to [=clipboard items=] object that represents contents of [=system clipboard=]. + {{Clipboard/read()}} returns a [=Promise=] to [=clipboard items=] object that represents contents of [=system clipboard data=].

The clipboard task source is triggered in response to reading or writing of [=system clipboard data=]. @@ -819,26 +838,60 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. Abort these steps. - 1. Let |data| be a copy of the [=system clipboard data=] represented as [=clipboard items=]. For the MIME types defined in the [=mandatory data types=] list, |data| MAY be sanitized, but image/png format has unsanitized payload to preserve meta data. + 1. Let |data| be a copy of the [=system clipboard data=]. - Issue: Some OSs contain multiple clipboard (e.g. Linux, "primary", "secondary", "selection"). Define from which of those data is read. + 1. Let |items| be a [=sequence=]<[=/clipboard item=]>. + + 1. For each |systemClipboardItem| in |data|: + + 1. Let |item| be a new [=/clipboard item=]. + + 1. For each |systemClipboardRepresentation| in |systemClipboardItem|: + + 1. Let |mimeType| be the result of running the [=well-known mime type from os specific format=] algorithm given |systemClipboardRepresentation|'s [=system clipboard representation/name=]. + + 1. If |mimeType| is null, continue this loop. + + 1. Let |representation| be a new [=representation=]. + + 1. Set |representation|'s [=representation/MIME type=] to |mimeType|. - Issue: Add definition of sanitized. + 1. Set |representation|'s [=representation/data=] to |systemClipboardRepresentation|'s [=system clipboard representation/data=]. + + Issue: It should be possible to read the data asynchronously from the system clipboard after the author calls getType, however, this set of steps implies that data will be provided at the time of read. + + 1. The user agent, MAY sanitize |representation|'s [=representation/data=], unless |representation|'s [=representation/MIME type=]'s essence is "image/png", which should remain unsanitized to preserve meta data. + + 1. Append |representation| to |item|'s [=list of representations=]. + + 1. If |item|'s [=list of representations=] size is greater than 0, append |item| to |items|. + + If |items| has a size > 0, then: + + 1. Let |firstItem| be |items|[0] + + 1. Run the [=read web custom format=] algorithm given |firstItem|. + + Else: + + 1. Let |customItem| be a new [=/clipboard item=]. + + 1. Run the [=read web custom format=] algorithm given |customItem|. + + 1. If |customItem|'s [=list of representations=] size is greater than 0, append |item| to |items|. - Note: As further described in [[#image-transcode]] this explicitly does not transcode images. - Rather the original unmodified image data should be exposed to the website. 1. [=Queue a global task=] on the [=clipboard task source=], given |realm|'s [=global object=], to perform the below steps: - 1. Let |items| be a [=sequence=]<{{ClipboardItem}}>. + 1. Let |clipboardItems| be a [=sequence=]<{{ClipboardItem}}>. - 1. For each [=/clipboard item=] |underlyingItem| of |data|: + 1. For each [=/clipboard item=] |underlyingItem| of |items|: - 1. Let |item| be the result of running the steps of [=create a ClipboardItem object=] given |underlyingItem| and |realm|. + 1. Let |clipboardItem| be the result of running the steps of [=create a ClipboardItem object=] given |underlyingItem| and |realm|. - 1. Append |item| to |items|. + 1. Append |clipboardItem| to |clipboardItems|. - 1. Resolve |p| with |items|. + 1. Resolve |p| with |clipboardItems|. 1. Return |p|. @@ -870,7 +923,7 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. Abort these steps. - 1. Let |data| be a copy of the [=system clipboard data=] represented as [=clipboard items=]. For the MIME types defined in the [=mandatory data types=] list, |data| MAY contain the sanitized copy of text/plain format. + 1. Let |data| be a copy of the [=system clipboard data=]. Issue: Some OSs contain multiple clipboard (e.g. Linux, "primary", "secondary", "selection"). Define from which of those data is read. @@ -878,37 +931,45 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. [=Queue a global task=] on the [=clipboard task source=], given |realm|'s [=global object=], to perform the below steps: - 1. Let |dataList| be a [=sequence=] of [=/clipboard item=]s. + 1. For each |systemClipboardItem| in |data|: - 1. If |data|'s [=list/size=] is greater than 1, then add |data|[0] to |dataList|. + 1. For each |systemClipboardRepresentation| in |systemClipboardItem|: - Issue: Reading multiple clipboard items should be fixed in https://github.com/w3c/clipboard-apis/issues/166. + 1. Let |mimeType| be the result of running the [=well-known mime type from os specific format=] algorithm given |systemClipboardRepresentation|'s [=system clipboard representation/name=]. + + 1. If |mimeType| is null, continue this loop. - 1. Let |itemTypeList| be |dataList|'s [=ClipboardItem/clipboard item=]'s [=list of representations=]. + 1. Let |representation| be a new [=representation=]. - 1. For each |representation| in |itemTypeList|: + 1. If |representation|'s [=representation/MIME type=] [=MIME type/essence=] is "text/plain", then: - 1. If |representation|'s [=representation/MIME type=] [=MIME type/essence=] is "text/plain", then: + 1. Set |representation|'s [=representation/MIME type=] to |mimeType|. - 1. Let |representationDataPromise| be the |representation|'s [=representation/data=]. + 1. Let |representationDataPromise| be the |representation|'s [=representation/data=]. - 1. [=promise/React=] to |representationDataPromise|: + 1. [=promise/React=] to |representationDataPromise|: - 1. If |representationDataPromise| was fulfilled with value |v|, then: + 1. If |representationDataPromise| was fulfilled with value |v|, then: - 1. If |v| is a {{DOMString}}, then follow the below steps: + 1. If |v| is a {{DOMString}}, then follow the below steps: - 1. Resolve |p| with |v|. + 1. Resolve |p| with |v|. - 1. If |v| is a {{Blob}}, then follow the below steps: + 1. Return |p|. - 1. Let |string| be the result of [=UTF-8 decoding=] |v|'s underlying byte sequence. + 1. If |v| is a {{Blob}}, then follow the below steps: - 1. Resolve |p| with |string|. + 1. Let |string| be the result of [=UTF-8 decoding=] |v|'s underlying byte sequence. - 1. If |representationDataPromise| was rejected, then: + 1. Resolve |p| with |string|. + + 1. Return |p|. + + 1. If |representationDataPromise| was rejected, then: + + 1. [=Reject=] |p| with {{"NotFoundError"}} {{DOMException}} in |realm|. - 1. [=Reject=] |p| with {{"NotFoundError"}} {{DOMException}} in |realm|. + 1. Return |p|. 1. [=Reject=] |p| with {{"NotFoundError"}} {{DOMException}} in |realm|. @@ -984,7 +1045,7 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. Let |type| be the |blob|'s {{Blob/type}}. - 1. If |type| is not in the [=mandatory data types=] list, then [=reject=] |p| with {{"NotAllowedError"}} {{DOMException}} in |realm| and abort these steps. + 1. If |type| is not in the [=mandatory data types=] or [=optional data types=] list, then [=reject=] |p| with {{"NotAllowedError"}} {{DOMException}} in |realm| and abort these steps. 1. Let |cleanItem| be an optionally sanitized copy of |blob|. @@ -1642,11 +1703,23 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; : Output :: None + 1. Let |webCustomFormats| be a [=sequence=]<{{Blob}}>. + 1. For each |item| in |items|: 1. Let |formatString| be the result of running [=os specific well-known format=] given |item|'s {{Blob/type}}. - 1. If |formatString| is empty then abort these steps. + 1. If |formatString| is empty then follow the below steps: + + 1. Let |webCustomFormatString| be the |item|'s {{Blob/type}}. + + 1. Let |webCustomFormat| be an empty {{Blob/type}}. + + 1. If |webCustomFormatString| has "web "("web" followed by U+0020 SPACE) prefix, then remove the "web " prefix and store the remaining string in |webCustomFormat|'s {{Blob/type}}'s [=MIME type/essence=], else abort all steps. + + 1. Set |item|'s {{Blob/type}} to |webCustomFormat|. + + 1. Append |webCustomFormat| to |webCustomFormats|. 1. Let |payload| be the result of [=UTF-8 decoding=] |item|'s underlying byte sequence. @@ -1654,6 +1727,8 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; Issue: some OSs contain multiple clipboard (e.g. Linux, "primary", "secondary", "selection"). Define to which of those data is written. + 1. [=Write web custom formats=] given |webCustomFormats|. +
@@ -1671,7 +1746,7 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; For Mac see https://developer.apple.com/documentation/appkit/nspasteboardtype

- 1. Let |wellKnownFormat| be a string. + 1. Let |wellKnownFormat| be an empty string. 1. If |mimeType|'s [=MIME type/essence=] is "text/plain", then @@ -1679,75 +1754,213 @@ urlPrefix: https://w3c.github.io/FileAPI/#dfn-; type: dfn; 1. Assign CF_UNICODETEXT to |wellKnownFormat|. - 1. Return |wellKnownFormat|. - On MacOS, follow the convention described below: 1. Assign NSPasteboardTypeString to |wellKnownFormat|. - 1. Return |wellKnownFormat|. - On Linux, ChromeOS, and Android, follow the convention described below: 1. Assign "text/plain" to |wellKnownFormat|. - 1. Return |wellKnownFormat|. - 1. Else, if |mimeType|'s [=MIME type/essence=] is "text/html", then On Windows, follow the convention described below: 1. Assign CF_HTML to |wellKnownFormat|. - 1. Return |wellKnownFormat|. - On MacOS, follow the convention described below: 1. Assign NSHTMLPboardType to |wellKnownFormat|. - 1. Return |wellKnownFormat|. - On Linux, ChromeOS, and Android, follow the convention described below: 1. Assign "text/html" to |wellKnownFormat|. - 1. Return |wellKnownFormat|. - 1. Else, if |mimeType|'s [=MIME type/essence=] is "image/png", then On Windows, follow the convention described below: 1. Assign "PNG" to |wellKnownFormat|. - 1. Return |wellKnownFormat|. - On MacOS, follow the convention described below: 1. Assign NSPasteboardTypePNG to |wellKnownFormat|. - 1. Return |wellKnownFormat|. - On Linux, ChromeOS, and Android, follow the convention described below: 1. Assign "image/png" to |wellKnownFormat|. - 1. Return |wellKnownFormat|. + 1. Return |wellKnownFormat|. + +
+ +
+

well-known mime type from os specific format

+ + : Input + :: |osFormatName|, a platform specific string type. On Mac it's NSPasteboardType, on Windows it's LPCWSTR and Linux it's a const char*. + + : Output + :: |mimeType|, a [=/MIME type=] + +

+ For Windows see https://docs.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats and + https://docs.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables?redirectedfrom=MSDN + For Mac see https://developer.apple.com/documentation/appkit/nspasteboardtype +

+ + On Windows, follow the convention described below: + + 1. If |osFormatName| "UnicodeText", then set |mimeTypeString| to "text/plain". + + 1. Else, if |osFormatName| "HTML Format", then set |mimeTypeString| to "text/html". + + 1. Else, if |osFormatName| "PNG", then set |mimeTypeString| to "image/png". + + + On MacOS, follow the convention described below: + + 1. If |osFormatName| is NSPasteboardTypeString, then set |mimeTypeString| to "text/plain". + + 1. Else, if |osFormatName| is NSHTMLPboardType, then set |mimeTypeString| to "text/html". + + 1. Else, if |osFormatName| NSPasteboardTypePNG, then set |mimeTypeString| to "image/png". + + + On Linux, ChromeOS, and Android, follow the convention described below: + + 1. If |osFormatName| is "text/plain", then set |mimeTypeString| to "text/plain". + + 1. Else, if |osFormatName| "text/html", then set |mimeTypeString| to "text/html". + + 1. Else, if |osFormatName| "image/png", then set |mimeTypeString| to "image/png". + + + 1. Let |mimeType| be the result of [=parsing a MIME type=] given |mimeTypeString|. + + 1. Return |mimeType|. + +
+ +
+

read web custom format

+ + : Input + :: |item|, a [=/clipboard item=] + + 1. Let |webCustomFormatMap| be the [=os specific custom map name=]. + + 1. Read |webCustomFormatMap| from the [=system clipboard=]. + + Issue: specify in more detail the process of reading a |webCustomFormatMap|. + + 1. If |webCustomFormatMap| is empty, then return |item|. + + 1. Let |webCustomFormatMapString| be the JSON string deserialized from |webCustomFormatMap|. + + Note: Need a JSON reader to deserialize the content from the |webCustomFormatMap|. + + 1. For each (|key|, |value|) in |webCustomFormatMapString|: + + 1. Let |mimeType| be the result of [=parsing a MIME type=] given |key|. + + 1. If |mimeType| is failure, then continue the loop. + + 1. Let |representation| be a new [=representation=]. + + 1. Set |representation|'s [=representation/MIME type=] to |mimeType|. + + 1. Set |representation|'s [=representation/isCustom=] flag to |true|. + + 1. Read |webCustomFormat| from the [=system clipboard=] given |value|. + + 1. If the read is successful, then set |representation|'s [=representation/data=] to the resulting data from the [=system clipboard=], else continue the loop. + + 1. Append |representation| to |item|'s [=list of representations=]. + +
+ +

write web custom formats

+ + : Input + :: |items|, a [=sequence=]<{{Blob}}> + + 1. Let |idx| be a number initialized to 0. + + 1. Let |webCustomFormatMap| be the [=os specific custom map name=]. + + 1. Let |webCustomFormatMapString| be an empty JSON string. + + 1. For each |item| in |items|: + + 1. Let |webCustomFormat| be the [=os specific custom name=]. + + 1. Let |webCustomFormatIdx| be the result of appending |idx| to |webCustomFormat|. + + 1. Insert |item|'s {{Blob/type}} as key and |webCustomFormatIdx| as value into the |webCustomFormatMapString| using a JSON serializer. + + Note: Need a JSON writer to serialize the content into the |webCustomFormatMapString|. + + 1. Insert the |item| into the [=system clipboard=] using |webCustomFormatIdx| as the format. + + 1. Increment |idx|. + + 1. If |idx| is greater than 100, then break from this loop. + + 1. Insert the |webCustomFormatMapString| into the [=system clipboard=] using |webCustomFormatMap| as the format. + + + + +
+

os specific custom map name

+ + : Output + :: |webCustomFormatMap|, a string + + On Windows, follow the convention described below: + + 1. Assign "Web Custom Format Map" to |webCustomFormatMap|. + + 1. Return |webCustomFormatMap|. + + On MacOS, follow the convention described below: + + 1. Assign "com.web.custom.format.map" to |webCustomFormatMap|. + + 1. Return |webCustomFormatMap|. + + On Linux, ChromeOS, and Android, follow the convention described below: + + 1. Assign "application/web;type=\"custom/formatmap\"" to |webCustomFormatMap|. + + 1. Return |webCustomFormatMap|. + +
+ +
+

os specific custom name

+ + : Output + :: |webCustomFormat|, a string + + On Windows, follow the convention described below: + + 1. Assign "Web Custom Format" to |webCustomFormat|. + + 1. Return |webCustomFormat|. + + On MacOS, follow the convention described below: + + 1. Assign "com.web.custom.format" to |webCustomFormat|. - 1. Else + 1. Return |webCustomFormat|. - 1. This is left to the implementation... + On Linux, ChromeOS, and Android, follow the convention described below: - Issue: It's not good to leave things up to the - implementation. What should happen here? + 1. Assign "application/web;type="custom/format" to |webCustomFormat|. - Note: Due to limitations in the implementation of operating - system clipboards, scripts should not assume that custom - formats will be available to other applications on the - system. For example, there is a limit to how many custom - clipboard formats can be registered in Microsoft Windows. - While it is possible to use any string for - setData()'s type argument, sticking to the - [=mandatory data types=] is strongly recommended. + 1. Return |webCustomFormat|.