1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
<!DOCTYPE html>
<meta charset="utf-8" />
<title>
'clipboardchange' event should be fired upon setting clipboard using JS
</title>
<link rel="help" href="https://www.w3.org/TR/clipboard-apis/#clipboard-event-clipboardchange" />
<body>
Body needed for test_driver.click()
<p><button id="button">Put payload in the clipboard</button></p>
<div id="output"></div>
<iframe id="iframe" srcdoc="<p>Some text</p>"></iframe>
<link rel="help" href="https://issues.chromium.org/issues/41442253" />
<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>
function waitForRender() {
return new Promise(resolve => {
requestAnimationFrame(() => requestAnimationFrame(resolve));
});
}
let typesToSet_ = ["text/html", "web txt/csv"];
button.onclick = () => document.execCommand("copy");
document.oncopy = (ev) => {
ev.preventDefault();
for (let i = 0; i < typesToSet_.length; i++) {
const type = typesToSet_[i];
const data = new Blob([`Test data for ${type}`], {type: type});
ev.clipboardData.setData(type, data);
}
};
function triggerCopyToClipboard(typesToSet) {
if (typesToSet) {
typesToSet_ = typesToSet;
}
return test_driver.click(button);
}
promise_test(async (test) => {
let clipboardChangeEventCount = 0;
let eventType = "";
let capturedEventTypes = null;
navigator.clipboard.addEventListener("clipboardchange", (ev) => {
clipboardChangeEventCount++;
eventType = ev.type;
capturedEventTypes = ev.types;
});
await triggerCopyToClipboard();
assert_equals(clipboardChangeEventCount, 1, "clipboardchange event should be called exactly once");
assert_equals(eventType, "clipboardchange", "Event type should be 'clipboardchange'");
assert_true(capturedEventTypes.includes("text/html"), "types should contain 'text/html'");
assert_false(capturedEventTypes.includes("web txt/csv"), "types should not contain custom MIME type");
}, "clipboardchange event is invoked");
promise_test(async (test) => {
await tryGrantWritePermission();
let clipboardChangeEventCount = 0;
let capturedEventTypes = null;
navigator.clipboard.addEventListener("clipboardchange", (ev) => {
clipboardChangeEventCount++;
capturedEventTypes = ev.types;
});
await navigator.clipboard.writeText("Test text");
await waitForRender();
assert_equals(clipboardChangeEventCount, 1, "clipboardchange event should be called exactly once");
assert_true(capturedEventTypes.includes("text/plain"), "types should contain 'text/plain'");
}, "clipboardchange event is invoked with async clipboard API");
promise_test(async (test) => {
let onClipboardChangeAttributeCount = 0;
let capturedEventTypes = null;
navigator.clipboard.onclipboardchange = (ev) => {
onClipboardChangeAttributeCount++;
capturedEventTypes = ev.types;
};
await triggerCopyToClipboard();
assert_equals(onClipboardChangeAttributeCount, 1, "onclipboardchange attribute should be called exactly once");
assert_true(capturedEventTypes.includes("text/html"), "types should contain 'text/html'");
assert_false(capturedEventTypes.includes("web txt/csv"), "types should not contain custom MIME type");
}, "clipboardchange event is invoked using onclipboardchange attribute");
promise_test(async (test) => {
let onClipboardChangeAttributeCount = 0;
let capturedEventTypes = null;
navigator.clipboard.onclipboardchange = (ev) => {
onClipboardChangeAttributeCount++;
capturedEventTypes = ev.types;
};
await triggerCopyToClipboard(["web txt/csv"]);
assert_equals(onClipboardChangeAttributeCount, 1, "onclipboardchange attribute should be called exactly once");
assert_equals(capturedEventTypes.length, 0, "clipboardchange event should have no types");
}, "clipboardchange event is invoked even when only custom MIME types are set");
promise_test(async (test) => {
let listenerCallCount = 0;
function clipboardChangeListener() {
listenerCallCount++;
}
// 1. Add listener and verify it's called
navigator.clipboard.addEventListener("clipboardchange", clipboardChangeListener);
await triggerCopyToClipboard();
assert_equals(listenerCallCount, 1, "Event listener should be called exactly once after adding");
// 2. Remove listener and verify it's not called
navigator.clipboard.removeEventListener("clipboardchange", clipboardChangeListener);
await triggerCopyToClipboard();
assert_equals(listenerCallCount, 1, "Event listener should not be called after removing");
// 3. Re-add listener and verify it's called again
navigator.clipboard.addEventListener("clipboardchange", clipboardChangeListener);
await triggerCopyToClipboard();
assert_equals(listenerCallCount, 2, "Event listener should be called exactly once after re-adding");
}, "clipboardchange event listener behavior when adding, removing, and re-adding");
promise_test(async (test) => {
// https://w3c.github.io/clipboard-apis/#mandatory-data-types-x
const standardTypes = [
"text/plain",
"text/html",
"image/png",
];
const unsupportedTypes = [
"web application/custom",
"web web/proprietary",
"web x-custom/type",
"txt/json",
"text/rtf",
"image/svg+xml",
"text/uri-list",
];
const allTypesToSet = [...standardTypes, ...unsupportedTypes];
let clipboardChangeEventCount = 0;
let capturedEventTypes = null;
navigator.clipboard.addEventListener("clipboardchange", (ev) => {
clipboardChangeEventCount++;
capturedEventTypes = ev.types;
});
await triggerCopyToClipboard(allTypesToSet);
assert_true(clipboardChangeEventCount == 1, "clipboardchange event should be invoked once");
// Check that types is a frozen array
assert_true(Array.isArray(capturedEventTypes), "types should be an array");
assert_true(Object.isFrozen(capturedEventTypes), "types should be frozen");
// Verify all standard types are included
for (const type of standardTypes) {
assert_true(capturedEventTypes.includes(type), `types should contain standard MIME type '${type}'`);
}
// Verify custom types are filtered out
for (const type of unsupportedTypes) {
assert_false(capturedEventTypes.includes(type), `types should not contain custom MIME type '${type}'`);
}
// Verify we have exactly the standard types and nothing else
assert_equals(capturedEventTypes.length, standardTypes.length,
"clipboardchange event types should contain exactly the standard MIME types");
}, "clipboardchange event exposes all standard MIME types and filters non-standard ones");
promise_test(async (test) => {
// Focus the document and acquire permission to write to the clipboard
await test_driver.click(document.body);
await tryGrantWritePermission();
const iframe = document.getElementById('iframe');
let frameEventCount = 0;
let capturedEventTypes = null;
let focusEventFired = false;
iframe.contentWindow.addEventListener("focus", () => {
focusEventFired = true;
});
// Add listener to iframe
iframe.contentWindow.navigator.clipboard.addEventListener("clipboardchange", () => {
assert_true(focusEventFired, "focus event should fire before clipboardchange event");
frameEventCount++;
capturedEventTypes = event.types;
});
// Ensure iFrame doesn't have the focus
assert_false(iframe.contentWindow.document.hasFocus(), "iFrame should not have focus");
assert_false(focusEventFired, "focus event should not have fired yet");
// Trigger multiple clipboard changes
await navigator.clipboard.writeText("Test text");
// Write HTML to clipboard to ensure the event captured only html and not txt
await navigator.clipboard.write([
new ClipboardItem({
"text/html": new Blob(["<p>Test HTML</p>"], {type: "text/html"})
})
]);
await waitForRender();
assert_equals(frameEventCount, 0, "iframe should not recieve any clipboardchange event yet");
iframe.focus();
assert_true(iframe.contentWindow.document.hasFocus(), "iFrame should have focus");
assert_equals(frameEventCount, 1, "iframe should receive event only 1 event after focus");
assert_equals(capturedEventTypes.length, 1, "clipboardchange event should only have one type");
assert_true(capturedEventTypes.includes("text/html"), "clipboardchange event should only have text/html type");
}, "clipboardchange event should only fire in the focused context");
</script>
</body>
|