From b8ebea7f172334f5a5d9ac87860aec5f55becd26 Mon Sep 17 00:00:00 2001
From: Anupam Snigdha
Date: Mon, 6 Jun 2022 11:01:40 -0700
Subject: [PATCH] [Clipboard API] Clipboard Web Custom Formats implementation.
This patch addresses the changes proposed by the EditingWG[1] and
agreed upon by all browser vendors. We are removing the `unsanitized`
option, and instead, adding custom format support for MIME types that
have "web " prefix in them.
Added few wpt tests to test these changes.
Below is a summary of the changes in this CL:
1. Removed `unsanitized` option from read/write methods.
2. If the custom format doesn't have a "web " prefix, then clipboard
read/write fails.
3. Transient user activation is applicable to all supported formats -
text/html, text/plain, image/png and web custom formats.
4. There are two "buckets" of clipboard formats. One for the
well-known formats and the other for the web custom format. If the
author doesn't specify the web format explicitly, then they don't
get access to it. This means, we won't write web custom formats
for well-known types implicitly if authors have not indicated that
during the write call via a "web " prefix (e.g. "web text/html").
Same applies for reading web custom formats for well-known types-
if there aren't any formats in the web custom format map, then we
won't return any web custom formats i.e. text/html won't be
automatically converted into "web text/html".
Spec: https://github.com/w3c/clipboard-apis/pull/175
Explainer: https://github.com/w3c/editing/blob/gh-pages/docs/clipboard-pickling/explainer.md
i2p: https://groups.google.com/a/chromium.org/g/blink-dev/c/Lo7WBM_v_LY/m/LncCKkXeAwAJ
i2s: https://groups.google.com/a/chromium.org/g/blink-dev/c/k2rgX-4Cigc/m/P0RijrpzBAAJ?utm_medium=email&utm_source=footer&pli=1
1. Github issue: https://github.com/w3c/clipboard-apis/issues/165
Bug: 106449
Change-Id: I86aae6a662089efeede2a01ac87cb698e9646df5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3650952
Commit-Queue: Anupam Snigdha
Reviewed-by: Alexander Timin
Reviewed-by: Daniel Cheng
Reviewed-by: Austin Sullivan
Cr-Commit-Position: refs/heads/main@{#1011078}
---
...om-formats-write-read.tentative.https.html | 50 +++++++++++++------
.../async-html-script-removal.https.html | 6 ++-
...or-clipboard-read-resource-load.https.html | 4 ++
...vigator-clipboard-read-sanitize.https.html | 4 ++
...-promise-write-blobs-read-blobs.https.html | 5 +-
.../async-svg-script-removal.https.html | 4 ++
...ml-formats-write-read.tentative.https.html | 24 +++++----
...xt-formats-write-read.tentative.https.html | 25 ++++++----
.../async-write-blobs-read-blobs.https.html | 4 ++
.../async-write-html-read-html.https.html | 4 ++
.../async-write-image-read-image.https.html | 6 +++
.../async-write-svg-read-svg.https.html | 4 ++
.../write-read-on-detached-iframe.https.html | 2 +
...ext-readText-on-detached-iframe.https.html | 2 +
...bute-cross-origin-tentative.https.sub.html | 7 +--
...-policy-attribute-tentative.https.sub.html | 7 +--
...licy-cross-origin-tentative.https.sub.html | 7 +--
...by-feature-policy.tentative.https.sub.html | 8 +--
...by-feature-policy.tentative.https.sub.html | 14 ++++--
...bute-cross-origin-tentative.https.sub.html | 7 +--
...-policy-attribute-tentative.https.sub.html | 7 +--
...licy-cross-origin-tentative.https.sub.html | 7 +--
...by-feature-policy.tentative.https.sub.html | 8 +--
...by-feature-policy.tentative.https.sub.html | 10 ++--
.../permissions/readText-denied.https.html | 3 ++
.../permissions/readText-granted.https.html | 3 ++
.../permissions/writeText-denied.https.html | 3 ++
.../permissions/writeText-granted.https.html | 3 ++
.../async-write-read.https.html | 4 ++
.../async-write-readText.https.html | 4 ++
.../async-writeText-read.https.html | 4 ++
.../async-writeText-readText.https.html | 4 ++
32 files changed, 186 insertions(+), 68 deletions(-)
diff --git a/clipboard-apis/async-custom-formats-write-read.tentative.https.html b/clipboard-apis/async-custom-formats-write-read.tentative.https.html
index b6c368f75bc2f2..77991ec98aa098 100644
--- a/clipboard-apis/async-custom-formats-write-read.tentative.https.html
+++ b/clipboard-apis/async-custom-formats-write-read.tentative.https.html
@@ -14,26 +14,19 @@
promise_test(async t => {
await test_driver.set_permission({name: 'clipboard-read'}, 'granted');
await test_driver.set_permission({name: 'clipboard-write'}, 'granted');
- const format1 = 'application/x-custom-format-clipboard-test-format-1';
- const format2 = 'application/x-custom-format-clipboard-test-format-2';
+ const format1 = 'web application/x-custom-format-clipboard-test-format-1';
+ const format2 = 'web application/x-custom-format-clipboard-test-format-2';
const blobInput1 = new Blob(['input data 1'], {type: format1});
const blobInput2 = new Blob(['input data 2'], {type: format2});
const clipboardItemInput = new ClipboardItem(
- {[format1]: blobInput1, [format2]: blobInput2},
- {unsanitized: [format1, format2]});
+ {[format1]: blobInput1, [format2]: blobInput2});
await waitForUserActivation();
await navigator.clipboard.write([clipboardItemInput]);
- // Items may not be readable on the sanitized clipboard after custom format
- // write.
- await promise_rejects_dom(t, 'DataError',
- navigator.clipboard.read());
-
// Items should be readable on a custom format clipboard after custom format
// write.
await waitForUserActivation();
- const clipboardItems = await navigator.clipboard.read(
- {unsanitized: [format1, format2]});
+ const clipboardItems = await navigator.clipboard.read();
assert_equals(clipboardItems.length, 1);
const clipboardItem = clipboardItems[0];
assert_true(clipboardItem instanceof ClipboardItem);
@@ -58,15 +51,44 @@
const customFormatArray = [];
const customFormatMap = {};
for (let i = 0; i <= 100; i++) {
- customFormatArray.push("CustomFormat" + i);
+ customFormatArray.push("web text/CustomFormat" + i);
const blobInput = new Blob(['input data'], {type: customFormatArray[i]});
customFormatMap[customFormatArray[i]] = blobInput;
}
- const clipboardItemInput = new ClipboardItem(customFormatMap,
- {unsanitized: customFormatArray});
+ const clipboardItemInput = new ClipboardItem(customFormatMap);
await waitForUserActivation();
await promise_rejects_dom(t, 'NotAllowedError',
navigator.clipboard.write([clipboardItemInput]));
}, 'navigator.clipboard.write() fails for more than 100 custom formats');
+promise_test(async t => {
+ await test_driver.set_permission({name: 'clipboard-read'}, 'granted');
+ await test_driver.set_permission({name: 'clipboard-write'}, 'granted');
+
+ const format1 = 'application/x-custom-format-clipboard-test-format-1';
+ const format2 = 'application/x-custom-format-clipboard-test-format-2';
+ const blobInput1 = new Blob(['input data 1'], {type: format1});
+ const blobInput2 = new Blob(['input data 2'], {type: format2});
+ const clipboardItemInput = new ClipboardItem(
+ {[format1]: blobInput1, [format2]: blobInput2});
+ await waitForUserActivation();
+ await promise_rejects_dom(t, 'NotAllowedError',
+ navigator.clipboard.write([clipboardItemInput]));
+}, 'navigator.clipboard.write() fails for custom formats without web prefix');
+
+promise_test(async t => {
+ await test_driver.set_permission({name: 'clipboard-read'}, 'granted');
+ await test_driver.set_permission({name: 'clipboard-write'}, 'granted');
+
+ const format1 = 'web ';
+ const format2 = 'web a';
+ const blobInput1 = new Blob(['input data 1'], {type: format1});
+ const blobInput2 = new Blob(['input data 2'], {type: format2});
+ const clipboardItemInput = new ClipboardItem(
+ {[format1]: blobInput1, [format2]: blobInput2});
+ await waitForUserActivation();
+ await promise_rejects_dom(t, 'NotAllowedError',
+ navigator.clipboard.write([clipboardItemInput]));
+}, 'navigator.clipboard.write() fails for custom formats with web prefix, but invalid MIME types');
+
diff --git a/clipboard-apis/async-html-script-removal.https.html b/clipboard-apis/async-html-script-removal.https.html
index 90dc44c9db20e3..44c11add855137 100644
--- a/clipboard-apis/async-html-script-removal.https.html
+++ b/clipboard-apis/async-html-script-removal.https.html
@@ -4,10 +4,12 @@
Async Clipboard write ([text/html ClipboardItem]) -> readHtml (and remove scripts) tests
+Body needed for test_driver.click()
+
+
+Body needed for test_driver.click()
@@ -27,6 +29,7 @@
await test_driver.set_permission({name: 'clipboard-read'}, 'granted');
await test_driver.click(button);
+ await waitForUserActivation();
const items = await navigator.clipboard.read();
const htmlBlob = await items[0].getType("text/html");
const html = await htmlBlob.text();
@@ -38,3 +41,4 @@
assert_false(loadObserved, 'Should not observe resource loading');
});
+
diff --git a/clipboard-apis/async-navigator-clipboard-read-sanitize.https.html b/clipboard-apis/async-navigator-clipboard-read-sanitize.https.html
index 9e0ab2ee740f85..cc1836753478b7 100644
--- a/clipboard-apis/async-navigator-clipboard-read-sanitize.https.html
+++ b/clipboard-apis/async-navigator-clipboard-read-sanitize.https.html
@@ -7,7 +7,9 @@
+
+Body needed for test_driver.click()
@@ -29,6 +31,7 @@
await test_driver.set_permission({name: 'clipboard-read'}, 'granted');
await test_driver.click(button);
+ await waitForUserActivation();
const items = await navigator.clipboard.read();
const htmlBlob = await items[0].getType("text/html");
const html = await htmlBlob.text();
@@ -42,3 +45,4 @@
assert_false(testFailed);
});
+
diff --git a/clipboard-apis/async-promise-write-blobs-read-blobs.https.html b/clipboard-apis/async-promise-write-blobs-read-blobs.https.html
index e4b93c7c5ffb7c..12184c92e0777f 100644
--- a/clipboard-apis/async-promise-write-blobs-read-blobs.https.html
+++ b/clipboard-apis/async-promise-write-blobs-read-blobs.https.html
@@ -4,10 +4,12 @@
Async Clipboard write blobs -> read blobs with promise tests
+Body needed for test_driver.click()
+
+
diff --git a/clipboard-apis/async-unsanitized-plaintext-formats-write-read.tentative.https.html b/clipboard-apis/async-unsanitized-plaintext-formats-write-read.tentative.https.html
index f44ed22618e2fd..1c5638ca0a5faf 100644
--- a/clipboard-apis/async-unsanitized-plaintext-formats-write-read.tentative.https.html
+++ b/clipboard-apis/async-unsanitized-plaintext-formats-write-read.tentative.https.html
@@ -18,32 +18,35 @@
await test_driver.set_permission({name: 'clipboard-write'}, 'granted');
const dataToWrite = 'Test text.';
- const format = 'text/plain';
+ const format1 = 'web text/plain';
+ const format2 = 'text/plain';
- const blobInput = new Blob([dataToWrite], {type: format});
+ const blobInput1 = new Blob([dataToWrite], {type: format1});
+ const blobInput2 = new Blob([dataToWrite], {type: format2});
// Blob types are automatically converted to lower-case.
- assert_equals(blobInput.type, format.toLowerCase());
+ assert_equals(blobInput1.type, format1.toLowerCase());
+ assert_equals(blobInput2.type, format2.toLowerCase());
const clipboardItemInput = new ClipboardItem(
- {[format]: blobInput}, {unsanitized: [format]});
+ {[format1]: blobInput1, [format2]: blobInput2});
await waitForUserActivation();
await navigator.clipboard.write([clipboardItemInput]);
// Items should be readable on a system clipboard after custom format write.
await waitForUserActivation();
- const clipboardItems = await navigator.clipboard.read(
- {unsanitized: [format]});
+ const clipboardItems = await navigator.clipboard.read();
assert_equals(clipboardItems.length, 1);
const clipboardItem = clipboardItems[0];
assert_true(clipboardItem instanceof ClipboardItem);
- const blobOutput = await clipboardItem.getType(format);
- assert_equals(blobOutput.type, format);
- const data = await (new Response(blobOutput)).text();
- assert_equals(data, dataToWrite);
+ const blobOutput1 = await clipboardItem.getType(format1);
+ assert_equals(blobOutput1.type, format1);
+ const data1 = await (new Response(blobOutput1)).text();
+ assert_equals(data1, dataToWrite);
// These examples use native text formats, so these formats should be
// accessible as text.
+ await waitForUserActivation();
const textOutput = await navigator.clipboard.readText();
assert_equals(textOutput, dataToWrite);
-}, 'Verify write and read unsanitized content to the clipboard given standard format as input');
+}, 'Verify write and read unsanitized content to the clipboard given standard and custom formats as input');
diff --git a/clipboard-apis/async-write-blobs-read-blobs.https.html b/clipboard-apis/async-write-blobs-read-blobs.https.html
index 50d23a9c3625d5..8bec558b2b2de8 100644
--- a/clipboard-apis/async-write-blobs-read-blobs.https.html
+++ b/clipboard-apis/async-write-blobs-read-blobs.https.html
@@ -4,10 +4,12 @@
Async Clipboard write blobs -> read blobs tests
+Body needed for test_driver.click()
+
+
+
+Body needed for test_driver.click()
The bottom image should display the same image as the top image.
Original Image:
@@ -47,7 +49,9 @@
assert_equals(blobInput.type, 'image/png');
const clipboardItemInput = new ClipboardItem({'image/png' : blobInput});
+ await waitForUserActivation();
await navigator.clipboard.write([clipboardItemInput]);
+ await waitForUserActivation();
const clipboardItems = await navigator.clipboard.read();
assert_equals(clipboardItems.length, 1);
@@ -73,7 +77,9 @@
const invalidPngBlob = new Blob(['this text is not a valid png image'],
{type: 'image/png'});
const clipboardItemInput = new ClipboardItem({'image/png' : invalidPngBlob});
+ await waitForUserActivation();
await promise_rejects_dom(t, 'DataError',
navigator.clipboard.write([clipboardItemInput]));
}, 'Verify write error on malformed data [image/png ClipboardItem]');
+
diff --git a/clipboard-apis/async-write-svg-read-svg.https.html b/clipboard-apis/async-write-svg-read-svg.https.html
index 19c91597c111ee..42f6c547b29799 100644
--- a/clipboard-apis/async-write-svg-read-svg.https.html
+++ b/clipboard-apis/async-write-svg-read-svg.https.html
@@ -4,10 +4,12 @@
Async Clipboard write ([image/svg+xml ClipboardItem]) -> read and write svg tests
+Body needed for test_driver.click()
+
+
+
+
-
+Body needed for test_driver.click()
+
-
+Body needed for test_driver.click()
+
-
+Body needed for test_driver.click()
+
-
+Body needed for test_driver.click()
+
-
+Body needed for test_driver.click()
+
-
+Body needed for test_driver.click()
+
-
+Body needed for test_driver.click()
+
-
+Body needed for test_driver.click()
+
-
+Body needed for test_driver.click()
+
-Body needed for test_driver.click()Body needed for test_driver.click()Body needed for test_driver.click()