25
25
} ) ;
26
26
}
27
27
28
+ let typesToSet_ = [ "text/html" , "web txt/csv" ] ;
28
29
button . onclick = ( ) => document . execCommand ( "copy" ) ;
29
30
document . oncopy = ( ev ) => {
30
31
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
+ }
32
37
} ;
33
38
34
- function triggerCopyToClipboard ( ) {
39
+ function triggerCopyToClipboard ( typesToSet ) {
40
+ if ( typesToSet ) {
41
+ typesToSet_ = typesToSet ;
42
+ }
35
43
return test_driver . click ( button ) ;
36
44
}
37
45
38
46
promise_test ( async ( test ) => {
39
47
let clipboardChangeEventCount = 0 ;
40
48
let eventType = "" ;
49
+ let capturedEventTypes = null ;
41
50
navigator . clipboard . addEventListener ( "clipboardchange" , ( ev ) => {
42
51
clipboardChangeEventCount ++ ;
43
52
eventType = ev . type ;
53
+ capturedEventTypes = ev . types ;
44
54
} ) ;
45
55
await triggerCopyToClipboard ( ) ;
46
56
assert_equals ( clipboardChangeEventCount , 1 , "clipboardchange event should be called exactly once" ) ;
47
57
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" ) ;
48
60
} , "clipboardchange event is invoked" ) ;
49
61
50
62
promise_test ( async ( test ) => {
51
63
await tryGrantWritePermission ( ) ;
52
64
let clipboardChangeEventCount = 0 ;
65
+ let capturedEventTypes = null ;
53
66
navigator . clipboard . addEventListener ( "clipboardchange" , ( ev ) => {
54
67
clipboardChangeEventCount ++ ;
68
+ capturedEventTypes = ev . types ;
55
69
} ) ;
56
70
await navigator . clipboard . writeText ( "Test text" ) ;
57
71
await waitForRender ( ) ;
58
72
assert_equals ( clipboardChangeEventCount , 1 , "clipboardchange event should be called exactly once" ) ;
73
+ assert_true ( capturedEventTypes . includes ( "text/plain" ) , "types should contain 'text/plain'" ) ;
59
74
} , "clipboardchange event is invoked with async clipboard API" ) ;
60
75
61
76
promise_test ( async ( test ) => {
62
77
let onClipboardChangeAttributeCount = 0 ;
63
- navigator . clipboard . onclipboardchange = ( ) => {
78
+ let capturedEventTypes = null ;
79
+ navigator . clipboard . onclipboardchange = ( ev ) => {
64
80
onClipboardChangeAttributeCount ++ ;
81
+ capturedEventTypes = ev . types ;
65
82
} ;
66
83
await triggerCopyToClipboard ( ) ;
67
84
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" ) ;
68
87
} , "clipboardchange event is invoked using onclipboardchange attribute" ) ;
69
88
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
+
70
101
promise_test ( async ( test ) => {
71
102
let listenerCallCount = 0 ;
72
103
function clipboardChangeListener ( ) {
89
120
assert_equals ( listenerCallCount , 2 , "Event listener should be called exactly once after re-adding" ) ;
90
121
} , "clipboardchange event listener behavior when adding, removing, and re-adding" ) ;
91
122
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
+
92
172
promise_test ( async ( test ) => {
93
173
// Focus the document and acquire permission to write to the clipboard
94
174
await test_driver . click ( document . body ) ;
97
177
const iframe = document . getElementById ( 'iframe' ) ;
98
178
99
179
let frameEventCount = 0 ;
180
+ let capturedEventTypes = null ;
100
181
let focusEventFired = false ;
101
182
iframe . contentWindow . addEventListener ( "focus" , ( ) => {
102
183
focusEventFired = true ;
106
187
iframe . contentWindow . navigator . clipboard . addEventListener ( "clipboardchange" , ( ) => {
107
188
assert_true ( focusEventFired , "focus event should fire before clipboardchange event" ) ;
108
189
frameEventCount ++ ;
190
+ capturedEventTypes = event . types ;
109
191
} ) ;
110
192
111
193
// Ensure iFrame doesn't have the focus
114
196
115
197
// Trigger multiple clipboard changes
116
198
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
+ ] ) ;
118
206
await waitForRender ( ) ;
119
207
120
208
assert_equals ( frameEventCount , 0 , "iframe should not recieve any clipboardchange event yet" ) ;
121
209
122
210
iframe . focus ( ) ;
123
211
assert_true ( iframe . contentWindow . document . hasFocus ( ) , "iFrame should have focus" ) ;
124
212
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" ) ;
125
215
} , "clipboardchange event should only fire in the focused context" ) ;
216
+
126
217
</ script >
127
218
</ body >
0 commit comments