Skip to content

Commit a238c9e

Browse files
rorajachromium-wpt-export-bot
authored andcommitted
[Clipboardchange] - Event handler contains native mime types
As per the latest explainer changes, to make clipboardchange event interop, the API should let it's clients read native clipboard mime types using the event handler, without making any additional API calls. To reduce the risk of fingerprinting attacks, this API intentionally omits support for exposing custom MIME types. This CL ensures that the clipboardchange event contains native mime type as part of event payload. Just before renderer is about to dispatch the event (after focus check), the renderer requests the browser process for available mime types using an existing method. The list is filtered to only contain Chromium supported native mime types (a hard-coded list). Considered alternative: Instead of renderer requesting for types just before event dispatch, the browser could instead send types as part of OnClipboardChange MOJOM call, reducing 1 mojo call. However this is not efficient when there are multiple frames listening to the clipboard since all the frames will try to read the available types in quick succession which can cause performance issue. Bug: 41442253 Change-Id: I5a0d5335c1a007f496aacbd039a59382db596904 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6511791 Reviewed-by: Dan Clark <[email protected]> Commit-Queue: Rohan Raja <[email protected]> Reviewed-by: Sambamurthy Bandaru <[email protected]> Cr-Commit-Position: refs/heads/main@{#1463867}
1 parent 0344c6f commit a238c9e

File tree

1 file changed

+95
-4
lines changed

1 file changed

+95
-4
lines changed

clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,48 +25,79 @@
2525
});
2626
}
2727

28+
let typesToSet_ = ["text/html", "web txt/csv"];
2829
button.onclick = () => document.execCommand("copy");
2930
document.oncopy = (ev) => {
3031
ev.preventDefault();
31-
ev.clipboardData.setData("text/html", `<div>Test html</div>`);
32+
for (let i = 0; i < typesToSet_.length; i++) {
33+
const type = typesToSet_[i];
34+
const data = new Blob([`Test data for ${type}`], {type: type});
35+
ev.clipboardData.setData(type, data);
36+
}
3237
};
3338

34-
function triggerCopyToClipboard() {
39+
function triggerCopyToClipboard(typesToSet) {
40+
if (typesToSet) {
41+
typesToSet_ = typesToSet;
42+
}
3543
return test_driver.click(button);
3644
}
3745

3846
promise_test(async (test) => {
3947
let clipboardChangeEventCount = 0;
4048
let eventType = "";
49+
let capturedEventTypes = null;
4150
navigator.clipboard.addEventListener("clipboardchange", (ev) => {
4251
clipboardChangeEventCount++;
4352
eventType = ev.type;
53+
capturedEventTypes = ev.types;
4454
});
4555
await triggerCopyToClipboard();
4656
assert_equals(clipboardChangeEventCount, 1, "clipboardchange event should be called exactly once");
4757
assert_equals(eventType, "clipboardchange", "Event type should be 'clipboardchange'");
58+
assert_true(capturedEventTypes.includes("text/html"), "types should contain 'text/html'");
59+
assert_false(capturedEventTypes.includes("web txt/csv"), "types should not contain custom MIME type");
4860
}, "clipboardchange event is invoked");
4961

5062
promise_test(async (test) => {
5163
await tryGrantWritePermission();
5264
let clipboardChangeEventCount = 0;
65+
let capturedEventTypes = null;
5366
navigator.clipboard.addEventListener("clipboardchange", (ev) => {
5467
clipboardChangeEventCount++;
68+
capturedEventTypes = ev.types;
5569
});
5670
await navigator.clipboard.writeText("Test text");
5771
await waitForRender();
5872
assert_equals(clipboardChangeEventCount, 1, "clipboardchange event should be called exactly once");
73+
assert_true(capturedEventTypes.includes("text/plain"), "types should contain 'text/plain'");
5974
}, "clipboardchange event is invoked with async clipboard API");
6075

6176
promise_test(async (test) => {
6277
let onClipboardChangeAttributeCount = 0;
63-
navigator.clipboard.onclipboardchange = () => {
78+
let capturedEventTypes = null;
79+
navigator.clipboard.onclipboardchange = (ev) => {
6480
onClipboardChangeAttributeCount++;
81+
capturedEventTypes = ev.types;
6582
};
6683
await triggerCopyToClipboard();
6784
assert_equals(onClipboardChangeAttributeCount, 1, "onclipboardchange attribute should be called exactly once");
85+
assert_true(capturedEventTypes.includes("text/html"), "types should contain 'text/html'");
86+
assert_false(capturedEventTypes.includes("web txt/csv"), "types should not contain custom MIME type");
6887
}, "clipboardchange event is invoked using onclipboardchange attribute");
6988

89+
promise_test(async (test) => {
90+
let onClipboardChangeAttributeCount = 0;
91+
let capturedEventTypes = null;
92+
navigator.clipboard.onclipboardchange = (ev) => {
93+
onClipboardChangeAttributeCount++;
94+
capturedEventTypes = ev.types;
95+
};
96+
await triggerCopyToClipboard(["web txt/csv"]);
97+
assert_equals(onClipboardChangeAttributeCount, 1, "onclipboardchange attribute should be called exactly once");
98+
assert_equals(capturedEventTypes.length, 0, "clipboardchange event should have no types");
99+
}, "clipboardchange event is invoked even when only custom MIME types are set");
100+
70101
promise_test(async (test) => {
71102
let listenerCallCount = 0;
72103
function clipboardChangeListener() {
@@ -89,6 +120,55 @@
89120
assert_equals(listenerCallCount, 2, "Event listener should be called exactly once after re-adding");
90121
}, "clipboardchange event listener behavior when adding, removing, and re-adding");
91122

123+
promise_test(async (test) => {
124+
// https://w3c.github.io/clipboard-apis/#mandatory-data-types-x
125+
const standardTypes = [
126+
"text/plain",
127+
"text/html",
128+
"image/png",
129+
];
130+
const unsupportedTypes = [
131+
"web application/custom",
132+
"web web/proprietary",
133+
"web x-custom/type",
134+
"txt/json",
135+
"text/rtf",
136+
"image/svg+xml",
137+
"text/uri-list",
138+
];
139+
const allTypesToSet = [...standardTypes, ...unsupportedTypes];
140+
141+
let clipboardChangeEventCount = 0;
142+
let capturedEventTypes = null;
143+
144+
navigator.clipboard.addEventListener("clipboardchange", (ev) => {
145+
clipboardChangeEventCount++;
146+
capturedEventTypes = ev.types;
147+
});
148+
149+
await triggerCopyToClipboard(allTypesToSet);
150+
151+
assert_true(clipboardChangeEventCount == 1, "clipboardchange event should be invoked once");
152+
153+
// Check that types is a frozen array
154+
assert_true(Array.isArray(capturedEventTypes), "types should be an array");
155+
assert_true(Object.isFrozen(capturedEventTypes), "types should be frozen");
156+
157+
// Verify all standard types are included
158+
for (const type of standardTypes) {
159+
assert_true(capturedEventTypes.includes(type), `types should contain standard MIME type '${type}'`);
160+
}
161+
162+
// Verify custom types are filtered out
163+
for (const type of unsupportedTypes) {
164+
assert_false(capturedEventTypes.includes(type), `types should not contain custom MIME type '${type}'`);
165+
}
166+
167+
// Verify we have exactly the standard types and nothing else
168+
assert_equals(capturedEventTypes.length, standardTypes.length,
169+
"clipboardchange event types should contain exactly the standard MIME types");
170+
}, "clipboardchange event exposes all standard MIME types and filters non-standard ones");
171+
92172
promise_test(async (test) => {
93173
// Focus the document and acquire permission to write to the clipboard
94174
await test_driver.click(document.body);
@@ -97,6 +177,7 @@
97177
const iframe = document.getElementById('iframe');
98178

99179
let frameEventCount = 0;
180+
let capturedEventTypes = null;
100181
let focusEventFired = false;
101182
iframe.contentWindow.addEventListener("focus", () => {
102183
focusEventFired = true;
@@ -106,6 +187,7 @@
106187
iframe.contentWindow.navigator.clipboard.addEventListener("clipboardchange", () => {
107188
assert_true(focusEventFired, "focus event should fire before clipboardchange event");
108189
frameEventCount++;
190+
capturedEventTypes = event.types;
109191
});
110192

111193
// Ensure iFrame doesn't have the focus
@@ -114,14 +196,23 @@
114196

115197
// Trigger multiple clipboard changes
116198
await navigator.clipboard.writeText("Test text");
117-
await navigator.clipboard.writeText("Test text 2");
199+
200+
// Write HTML to clipboard to ensure the event captured only html and not txt
201+
await navigator.clipboard.write([
202+
new ClipboardItem({
203+
"text/html": new Blob(["<p>Test HTML</p>"], {type: "text/html"})
204+
})
205+
]);
118206
await waitForRender();
119207

120208
assert_equals(frameEventCount, 0, "iframe should not recieve any clipboardchange event yet");
121209

122210
iframe.focus();
123211
assert_true(iframe.contentWindow.document.hasFocus(), "iFrame should have focus");
124212
assert_equals(frameEventCount, 1, "iframe should receive event only 1 event after focus");
213+
assert_equals(capturedEventTypes.length, 1, "clipboardchange event should only have one type");
214+
assert_true(capturedEventTypes.includes("text/html"), "clipboardchange event should only have text/html type");
125215
}, "clipboardchange event should only fire in the focused context");
216+
126217
</script>
127218
</body>

0 commit comments

Comments
 (0)