Skip to content

[Clipboardchange] - Event handler contains native mime types #52714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 22, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,48 +25,79 @@
});
}

let typesToSet_ = ["text/html", "web txt/csv"];
button.onclick = () => document.execCommand("copy");
document.oncopy = (ev) => {
ev.preventDefault();
ev.clipboardData.setData("text/html", `<div>Test html</div>`);
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() {
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;
navigator.clipboard.onclipboardchange = () => {
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() {
Expand All @@ -89,6 +120,55 @@
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);
Expand All @@ -97,6 +177,7 @@
const iframe = document.getElementById('iframe');

let frameEventCount = 0;
let capturedEventTypes = null;
let focusEventFired = false;
iframe.contentWindow.addEventListener("focus", () => {
focusEventFired = true;
Expand All @@ -106,6 +187,7 @@
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
Expand All @@ -114,14 +196,23 @@

// Trigger multiple clipboard changes
await navigator.clipboard.writeText("Test text");
await navigator.clipboard.writeText("Test text 2");

// 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>