Skip to content

Commit

Permalink
[Clipboard API] Clipboard Web Custom Formats implementation.
Browse files Browse the repository at this point in the history
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: w3c/clipboard-apis#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: w3c/clipboard-apis#165

Bug: 106449

Change-Id: I86aae6a662089efeede2a01ac87cb698e9646df5
  • Loading branch information
snianu authored and chromium-wpt-export-bot committed Jun 4, 2022
1 parent b8f0622 commit bf1f8ab
Show file tree
Hide file tree
Showing 32 changed files with 171 additions and 68 deletions.
35 changes: 21 additions & 14 deletions clipboard-apis/async-custom-formats-write-read.tentative.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -58,15 +51,29 @@
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');

</script>
6 changes: 5 additions & 1 deletion clipboard-apis/async-html-script-removal.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
Async Clipboard write ([text/html ClipboardItem]) -> readHtml (and remove scripts) tests
</title>
<link rel="help" href="https://w3c.github.io/clipboard-apis/#async-clipboard-api">
<body>Body needed for test_driver.click()</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>
<script>
'use strict';
// This function removes extra spaces between tags in html. For example, the
Expand Down Expand Up @@ -36,8 +38,10 @@
await test_driver.set_permission({name: 'clipboard-write'}, 'granted');
const blobInput = new Blob([html_with_script], {type: 'text/html'});
const clipboardItem = new ClipboardItem({'text/html': blobInput});
await waitForUserActivation();
await navigator.clipboard.write([clipboardItem]);
const clipboardItems = await navigator.clipboard.read({type: 'text/html'});
await waitForUserActivation();
const clipboardItems = await navigator.clipboard.read();

const html = clipboardItems[0];
assert_equals(html.types.length, 1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>

<body>Body needed for test_driver.click()
<p><button id="button">Put payload in the clipboard</button></p>
<div id="output"></div>

Expand All @@ -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();
Expand All @@ -38,3 +41,4 @@
assert_false(loadObserved, 'Should not observe resource loading');
});
</script>
</body>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>

<body>Body needed for test_driver.click()
<p><button id="button">Put payload in the clipboard</button></p>
<div id="output"></div>

Expand All @@ -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();
Expand All @@ -42,3 +45,4 @@
assert_false(testFailed);
});
</script>
</body>
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
Async Clipboard write blobs -> read blobs with promise tests
</title>
<link rel="help" href="https://w3c.github.io/clipboard-apis/#async-clipboard-api">
<body>Body needed for test_driver.click()</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>

<script>
async function loadBlob(fileName) {
Expand All @@ -26,8 +28,9 @@

const clipboardItemInput = new ClipboardItem(
{'text/plain' : blobText, 'image/png' : promise1});

await waitForUserActivation();
await navigator.clipboard.write([clipboardItemInput]);
await waitForUserActivation();
const clipboardItems = await navigator.clipboard.read();

assert_equals(clipboardItems.length, 1);
Expand Down
4 changes: 4 additions & 0 deletions clipboard-apis/async-svg-script-removal.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
Async Clipboard write ([image/svg+xml ClipboardItem]) -> readSvg (and remove scripts) tests
</title>
<link rel="help" href="https://w3c.github.io/clipboard-apis/#async-clipboard-api">
<body>Body needed for test_driver.click()</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>
<script>
'use strict';
// This function removes extra spaces between tags in svg. For example, the
Expand Down Expand Up @@ -36,7 +38,9 @@
await test_driver.set_permission({name: 'clipboard-write'}, 'granted');
const blobInput = new Blob([svg_with_script], {type: 'image/svg+xml'});
const clipboardItem = new ClipboardItem({'image/svg+xml': blobInput});
await waitForUserActivation();
await navigator.clipboard.write([clipboardItem]);
await waitForUserActivation();
const clipboardItems =
await navigator.clipboard.read({type: 'image/svg+xml'});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,18 @@

// Create and write unsanitized version of standard HTML and custom formats.
const format1 = 'text/html';
const format2 = 'application/x-custom-format-clipboard-test-format-2';
const format2 = 'web text/html';
const textInput = '<style>p {color:blue}</style><p>Hello World</p>';
const blobInput1 = new Blob([textInput], {type: 'text/html'});
const blobInput2 = new Blob(['input data 2'], {type: format2});
const blobInput1 = new Blob([textInput], {type: format1});
const blobInput2 = new Blob([textInput], {type: format2});
const clipboardItemInput = new ClipboardItem(
{[format1]: blobInput1, [format2]: blobInput2},
{unsanitized: [format1, format2]});
{[format1]: blobInput1, [format2]: blobInput2});
await waitForUserActivation();
await navigator.clipboard.write([clipboardItemInput]);

// Read unsanitized version of HTML format.
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];
Expand All @@ -55,12 +53,20 @@
assert_equals(blobOutput1.type, format1);
const data1 = await (new Response(blobOutput1)).text();
const outputHtml = reformatHtml(data1);
const inputHtml = reformatHtml(textInput);
const expectedHtml = '<p style="color: blue; font-size: medium; font-style: normal; ' +
'font-variant-ligatures: normal; font-variant-caps: normal; ' +
'font-weight: 400; letter-spacing: normal; orphans: 2; ' +
'text-align: start; text-indent: 0px; text-transform: none; '+
'white-space: normal; widows: 2; word-spacing: 0px; ' +
'-webkit-text-stroke-width: 0px; text-decoration-thickness: initial; ' +
'text-decoration-style: initial; text-decoration-color: initial;">' +
'Hello World</p>';
const inputHtml = reformatHtml(expectedHtml);
assert_equals(outputHtml, inputHtml);

const blobOutput2 = await clipboardItem.getType(format2);
assert_equals(blobOutput2.type, format2);
const data2 = await (new Response(blobOutput2)).text();
assert_equals(data2, 'input data 2');
assert_equals(data2, textInput);
}, 'Verify write and read unsanitized content to the clipboard given text/html format as input');
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -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');
</script>
4 changes: 4 additions & 0 deletions clipboard-apis/async-write-blobs-read-blobs.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
Async Clipboard write blobs -> read blobs tests
</title>
<link rel="help" href="https://w3c.github.io/clipboard-apis/#async-clipboard-api">
<body>Body needed for test_driver.click()</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>

<script>
async function loadBlob(fileName) {
Expand All @@ -28,7 +30,9 @@
const clipboardItemInput = new ClipboardItem(
{'text/plain' : blobText, 'image/png' : blobImage});

await waitForUserActivation();
await navigator.clipboard.write([clipboardItemInput]);
await waitForUserActivation();
const clipboardItems = await navigator.clipboard.read();

assert_equals(clipboardItems.length, 1);
Expand Down
4 changes: 4 additions & 0 deletions clipboard-apis/async-write-html-read-html.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
Async Clipboard write ([text/html ClipboardItem]) -> readHtml tests
</title>
<link rel="help" href="https://w3c.github.io/clipboard-apis/#async-clipboard-api">
<body>Body needed for test_driver.click()</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>
<script>
'use strict';
// This function removes extra spaces between tags in html. For example, the
Expand All @@ -28,7 +30,9 @@
await test_driver.set_permission({name: 'clipboard-write'}, 'granted');
const blobInput = new Blob([textInput], {type: 'text/html'});
const clipboardItem = new ClipboardItem({'text/html': blobInput});
await waitForUserActivation();
await navigator.clipboard.write([clipboardItem]);
await waitForUserActivation();
const clipboardItems = await navigator.clipboard.read({type: 'text/html'});

const html = clipboardItems[0];
Expand Down
6 changes: 6 additions & 0 deletions clipboard-apis/async-write-image-read-image.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>
<body>Body needed for test_driver.click()
<p>
<p>The bottom image should display the same image as the top image.</p>
<p>Original Image:</p>
Expand Down Expand Up @@ -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);
Expand All @@ -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]');
</script>
</body>
4 changes: 4 additions & 0 deletions clipboard-apis/async-write-svg-read-svg.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
Async Clipboard write ([image/svg+xml ClipboardItem]) -> read and write svg tests
</title>
<link rel="help" href="https://w3c.github.io/clipboard-apis/#async-clipboard-api">
<body>Body needed for test_driver.click()</body>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="resources/user-activation.js"></script>
<script>
'use strict';
// This function removes extra spaces between tags in svg. For example, the
Expand All @@ -28,7 +30,9 @@
await test_driver.set_permission({name: 'clipboard-write'}, 'granted');
const blobInput = new Blob([textInput], {type: 'image/svg+xml'});
const clipboardItem = new ClipboardItem({'image/svg+xml': blobInput});
await waitForUserActivation();
await navigator.clipboard.write([clipboardItem]);
await waitForUserActivation();
const clipboardItems =
await navigator.clipboard.read({type: 'image/svg+xml'});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="../resources/user-activation.js"></script>
<iframe id="iframe"></iframe>
<script>
'use strict';
Expand All @@ -21,6 +22,7 @@
const iframeClipboard = iframe.contentWindow.navigator.clipboard;
const blobInput = new Blob(['test string'], {type: 'text/plain'});
const clipboardItemInput = new ClipboardItem({'text/plain': blobInput});
await waitForUserActivation();
// Clipboard API must only be available in focused documents.
// reference: https://www.w3.org/TR/clipboard-apis/#privacy-async
iframe.focus();
Expand Down
Loading

0 comments on commit bf1f8ab

Please sign in to comment.