diff --git a/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html b/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html index 37e2e6a7bde3ef..bf32f4851b341c 100644 --- a/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html +++ b/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html @@ -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", `
Test html
`); + 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() { @@ -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); @@ -97,6 +177,7 @@ const iframe = document.getElementById('iframe'); let frameEventCount = 0; + let capturedEventTypes = null; let focusEventFired = false; iframe.contentWindow.addEventListener("focus", () => { focusEventFired = true; @@ -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 @@ -114,7 +196,13 @@ // 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(["

Test HTML

"], {type: "text/html"}) + }) + ]); await waitForRender(); assert_equals(frameEventCount, 0, "iframe should not recieve any clipboardchange event yet"); @@ -122,6 +210,9 @@ 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"); + \ No newline at end of file