copypaste_flat_tree.js (9658B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 function getLoadContext() { 5 var Ci = SpecialPowers.Ci; 6 return SpecialPowers.wrap(window).docShell.QueryInterface(Ci.nsILoadContext); 7 } 8 9 var clipboard = SpecialPowers.Services.clipboard; 10 var documentViewer = SpecialPowers.wrap( 11 window 12 ).docShell.docViewer.QueryInterface(SpecialPowers.Ci.nsIDocumentViewerEdit); 13 14 function getClipboardData(mime) { 15 var transferable = SpecialPowers.Cc[ 16 "@mozilla.org/widget/transferable;1" 17 ].createInstance(SpecialPowers.Ci.nsITransferable); 18 transferable.init(getLoadContext()); 19 transferable.addDataFlavor(mime); 20 clipboard.getData( 21 transferable, 22 1, 23 SpecialPowers.wrap(window).browsingContext.currentWindowContext 24 ); 25 var data = SpecialPowers.createBlankObject(); 26 transferable.getTransferData(mime, data); 27 return data; 28 } 29 30 function testClipboardValue(suppressHTMLCheck, mime, expected) { 31 if (suppressHTMLCheck && mime == "text/html") { 32 return null; 33 } 34 var data = SpecialPowers.wrap(getClipboardData(mime)); 35 is( 36 data.value == null 37 ? data.value 38 : data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data, 39 expected, 40 mime + " value in the clipboard" 41 ); 42 return data.value; 43 } 44 45 function testSelectionToString(expected) { 46 const flags = 47 SpecialPowers.Ci.nsIDocumentEncoder.SkipInvisibleContent | 48 SpecialPowers.Ci.nsIDocumentEncoder.AllowCrossShadowBoundary; 49 is( 50 SpecialPowers.wrap(window) 51 .getSelection() 52 .toStringWithFormat("text/plain", flags, 0) 53 .replace(/\r\n/g, "\n"), 54 expected, 55 "Selection.toString" 56 ); 57 } 58 59 function testHtmlClipboardValue(suppressHTMLCheck, mime, expected) { 60 // For Windows, navigator.platform returns "Win32". 61 var expectedValue = expected; 62 if (navigator.platform.includes("Win")) { 63 // Windows has extra content. 64 expectedValue = 65 kTextHtmlPrefixClipboardDataWindows + 66 expected.replace(/\n/g, "\n") + 67 kTextHtmlSuffixClipboardDataWindows; 68 } 69 testClipboardValue(suppressHTMLCheck, mime, expectedValue); 70 } 71 72 function testPasteText(textarea, expected) { 73 textarea.value = ""; 74 textarea.focus(); 75 textarea.editor.paste(1); 76 is(textarea.value, expected, "value of the textarea after the paste"); 77 } 78 79 async function copySelectionToClipboard() { 80 await SimpleTest.promiseClipboardChange( 81 () => true, 82 () => { 83 documentViewer.copySelection(); 84 } 85 ); 86 ok(clipboard.hasDataMatchingFlavors(["text/plain"], 1), "check text/plain"); 87 ok(clipboard.hasDataMatchingFlavors(["text/html"], 1), "check text/html"); 88 } 89 90 async function testCopyPasteShadowDOM() { 91 var textarea = SpecialPowers.wrap(document.getElementById("input")); 92 93 function clear() { 94 textarea.blur(); 95 var sel = window.getSelection(); 96 sel.removeAllRanges(); 97 } 98 99 async function copySelectionToClipboardShadow( 100 anchorNode, 101 anchorOffset, 102 focusNode, 103 focusOffset 104 ) { 105 clear(); 106 var sel = window.getSelection(); 107 sel.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset); 108 await copySelectionToClipboard(); 109 } 110 111 let start1 = document.getElementById("start1"); 112 let end1 = document.getElementById("end1"); 113 let host1 = document.getElementById("host1"); 114 115 info("Test 1: Start is in Light DOM and end is a slotted node."); 116 await copySelectionToClipboardShadow( 117 start1.firstChild, 118 2, 119 end1.firstChild, 120 2 121 ); 122 testSelectionToString("art\nEn"); 123 testClipboardValue(false, "text/plain", "art\nEn"); 124 testHtmlClipboardValue( 125 false, 126 "text/html", 127 '<span id="start1">art</span>\n <div id="host1">\n <slot>\n \n <span id="end1">En</span></slot></div>' 128 ); 129 testPasteText(textarea, "art\nEn"); 130 131 info( 132 "Test 2: Start is in Light DOM and end is a slotted node, while there's a Shadow DOM node before the slotted node." 133 ); 134 await copySelectionToClipboardShadow( 135 start1.firstChild, 136 2, 137 host1.shadowRoot.getElementById("inner1").firstChild, 138 3 139 ); 140 testSelectionToString("art\nEnd Inn"); 141 testClipboardValue(false, "text/plain", "art\nEnd Inn"); 142 testHtmlClipboardValue( 143 false, 144 "text/html", 145 '<span id="start1">art</span>\n <div id="host1">\n <slot>\n \n <span id="end1">End</span>\n </slot>\n <span id="inner1">Inn</span></div>' 146 ); 147 testPasteText(textarea, "art\nEnd Inn"); 148 149 info("Test 3: Start is a slotted node and end is in shadow DOM."); 150 await copySelectionToClipboardShadow( 151 end1.firstChild, 152 2, 153 host1.shadowRoot.getElementById("inner1").firstChild, 154 3 155 ); 156 testSelectionToString("d Inn"); 157 testClipboardValue(false, "text/plain", "d Inn"); 158 testHtmlClipboardValue( 159 false, 160 "text/html", 161 '<slot><span id="end1">d</span>\n </slot>\n <span id="inner1">Inn</span>' 162 ); 163 testPasteText(textarea, "d Inn"); 164 165 let start2 = document.getElementById("start2"); 166 let host2 = document.getElementById("host2"); 167 let host2_slot2 = document.getElementById("host2_slot2"); 168 let host2_slot4 = document.getElementById("host2_slot4"); 169 170 info( 171 "Test 4: start is in light DOM and end is a slotted node with multiple assigned nodes in the same slot.\n" 172 ); 173 await copySelectionToClipboardShadow( 174 start2.firstChild, 175 2, 176 host2_slot2.firstChild, 177 5 178 ); 179 testSelectionToString("art\nSlotted1Slott"); 180 testClipboardValue(false, "text/plain", "art\nSlotted1Slott"); 181 testHtmlClipboardValue( 182 false, 183 "text/html", 184 '<span id="start2">art</span>\n <div id="host2">\n <slot name="slot1"><span id="host2_slot1" slot="slot1">Slotted1</span><span id="host2_slot2" slot="slot1">Slott</span></slot></div>' 185 ); 186 testPasteText(textarea, "art\nSlotted1Slott"); 187 188 info( 189 "Test 5: start is in light DOM and end is a slotted node with endOffset includes the entire slotted node\n" 190 ); 191 await copySelectionToClipboardShadow( 192 start2.firstChild, 193 2, 194 host2_slot2.firstChild, 195 8 196 ); 197 testSelectionToString("art\nSlotted1Slotted2"); 198 testClipboardValue(false, "text/plain", "art\nSlotted1Slotted2"); 199 testHtmlClipboardValue( 200 false, 201 "text/html", 202 '<span id="start2">art</span>\n <div id="host2">\n <slot name="slot1"><span id="host2_slot1" slot="slot1">Slotted1</span><span id="host2_slot2" slot="slot1">Slotted2</span></slot></div>' 203 ); 204 testPasteText(textarea, "art\nSlotted1Slotted2"); 205 206 info("Test 6: start is in light DOM and end is a shadow node.\n"); 207 await copySelectionToClipboardShadow( 208 start2.firstChild, 209 2, 210 host2.shadowRoot.getElementById("inner2").firstChild, 211 3 212 ); 213 testSelectionToString("art\nSlotted1Slotted2 Inn"); 214 testClipboardValue(false, "text/plain", "art\nSlotted1Slotted2 Inn"); 215 testHtmlClipboardValue( 216 false, 217 "text/html", 218 '<span id="start2">art</span>\n <div id="host2">\n <slot name="slot1"><span id="host2_slot1" slot="slot1">Slotted1</span><span id="host2_slot2" slot="slot1">Slotted2</span></slot>\n <span id="inner2">Inn</span></div>' 219 ); 220 testPasteText(textarea, "art\nSlotted1Slotted2 Inn"); 221 222 info("Test 7: start is in light DOM and end is a slotted node.\n"); 223 await copySelectionToClipboardShadow( 224 start2.firstChild, 225 2, 226 host2_slot4.firstChild, 227 8 228 ); 229 testSelectionToString("art\nSlotted1Slotted2 Inner Slotted3Slotted4"); 230 testClipboardValue( 231 false, 232 "text/plain", 233 "art\nSlotted1Slotted2 Inner Slotted3Slotted4" 234 ); 235 testHtmlClipboardValue( 236 false, 237 "text/html", 238 '<span id="start2">art</span>\n <div id="host2">\n <slot name="slot1"><span id="host2_slot1" slot="slot1">Slotted1</span><span id="host2_slot2" slot="slot1">Slotted2</span></slot>\n <span id="inner2">Inner</span>\n <slot name="slot2"><span slot="slot2">Slotted3</span><span id="host2_slot4" slot="slot2">Slotted4</span></slot></div>' 239 ); 240 testPasteText(textarea, "art\nSlotted1Slotted2 Inner Slotted3Slotted4"); 241 242 let host3 = document.getElementById("host3"); 243 let host3_slot1 = document.getElementById("host3_slot1"); 244 let host3_slot4 = document.getElementById("host3_slot4"); 245 246 info( 247 "Test 8: Both start and end are slotted nodes, and their DOM tree order is reversed compare to flat tree order.\n" 248 ); 249 await copySelectionToClipboardShadow( 250 host3_slot1.firstChild, 251 2, 252 host3_slot4.firstChild, 253 8 254 ); 255 testSelectionToString("otted1 Slotted2 Inner Slotted3 Slotted4"); 256 testClipboardValue( 257 false, 258 "text/plain", 259 "otted1 Slotted2 Inner Slotted3 Slotted4" 260 ); 261 testHtmlClipboardValue( 262 false, 263 "text/html", 264 '<slot name="slot1"><span id="host3_slot1" slot="slot1">otted1</span></slot>\n <slot name="slot2"><span id="host3_slot2" slot="slot2">Slotted2</span></slot>\n <span id="inner2">Inner</span>\n <slot name="slot3"><span id="host3_slot3" slot="slot3">Slotted3</span></slot>\n <slot name="slot4"><span id="host3_slot4" slot="slot4">Slotted4</span></slot>' 265 ); 266 testPasteText(textarea, "otted1 Slotted2 Inner Slotted3 Slotted4"); 267 268 info("Test 9: start is in Shadow DOM and end is in Light DOM.\n"); 269 await copySelectionToClipboardShadow( 270 host3.shadowRoot.getElementById("inner2").firstChild, 271 3, 272 host3_slot1.firstChild, 273 4 274 ); 275 testSelectionToString("ted1 Slotted2 Inn"); 276 testClipboardValue(false, "text/plain", "ted1 Slotted2 Inn"); 277 testHtmlClipboardValue( 278 false, 279 "text/html", 280 '<slot name="slot1"><span id="host3_slot1" slot="slot1">ted1</span></slot>\n <slot name="slot2"><span id="host3_slot2" slot="slot2">Slotted2</span></slot>\n <span id="inner2">Inn</span>' 281 ); 282 testPasteText(textarea, "ted1 Slotted2 Inn"); 283 }