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 ( ) {
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 ) ;
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 ;
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
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