/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ function getLoadContext() { var Ci = SpecialPowers.Ci; return SpecialPowers.wrap(window).docShell.QueryInterface(Ci.nsILoadContext); } var clipboard = SpecialPowers.Services.clipboard; var documentViewer = SpecialPowers.wrap( window ).docShell.docViewer.QueryInterface(SpecialPowers.Ci.nsIDocumentViewerEdit); function getClipboardData(mime) { var transferable = SpecialPowers.Cc[ "@mozilla.org/widget/transferable;1" ].createInstance(SpecialPowers.Ci.nsITransferable); transferable.init(getLoadContext()); transferable.addDataFlavor(mime); clipboard.getData( transferable, 1, SpecialPowers.wrap(window).browsingContext.currentWindowContext ); var data = SpecialPowers.createBlankObject(); transferable.getTransferData(mime, data); return data; } function testClipboardValue(suppressHTMLCheck, mime, expected) { if (suppressHTMLCheck && mime == "text/html") { return null; } var data = SpecialPowers.wrap(getClipboardData(mime)); is( data.value == null ? data.value : data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data, expected, mime + " value in the clipboard" ); return data.value; } function testSelectionToString(expected) { const flags = SpecialPowers.Ci.nsIDocumentEncoder.SkipInvisibleContent | SpecialPowers.Ci.nsIDocumentEncoder.AllowCrossShadowBoundary; is( SpecialPowers.wrap(window) .getSelection() .toStringWithFormat("text/plain", flags, 0) .replace(/\r\n/g, "\n"), expected, "Selection.toString" ); } function testHtmlClipboardValue(suppressHTMLCheck, mime, expected) { // For Windows, navigator.platform returns "Win32". var expectedValue = expected; if (navigator.platform.includes("Win")) { // Windows has extra content. expectedValue = kTextHtmlPrefixClipboardDataWindows + expected.replace(/\n/g, "\n") + kTextHtmlSuffixClipboardDataWindows; } testClipboardValue(suppressHTMLCheck, mime, expectedValue); } function testPasteText(textarea, expected) { textarea.value = ""; textarea.focus(); textarea.editor.paste(1); is(textarea.value, expected, "value of the textarea after the paste"); } async function copySelectionToClipboard() { await SimpleTest.promiseClipboardChange( () => true, () => { documentViewer.copySelection(); } ); ok(clipboard.hasDataMatchingFlavors(["text/plain"], 1), "check text/plain"); ok(clipboard.hasDataMatchingFlavors(["text/html"], 1), "check text/html"); } async function testCopyPasteShadowDOM() { var textarea = SpecialPowers.wrap(document.getElementById("input")); function clear() { textarea.blur(); var sel = window.getSelection(); sel.removeAllRanges(); } async function copySelectionToClipboardShadow( anchorNode, anchorOffset, focusNode, focusOffset ) { clear(); var sel = window.getSelection(); sel.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset); await copySelectionToClipboard(); } let start1 = document.getElementById("start1"); let end1 = document.getElementById("end1"); let host1 = document.getElementById("host1"); info("Test 1: Start is in Light DOM and end is a slotted node."); await copySelectionToClipboardShadow( start1.firstChild, 2, end1.firstChild, 2 ); testSelectionToString("art\nEn"); testClipboardValue(false, "text/plain", "art\nEn"); testHtmlClipboardValue( false, "text/html", 'art\n
\n \n \n En
' ); testPasteText(textarea, "art\nEn"); info( "Test 2: Start is in Light DOM and end is a slotted node, while there's a Shadow DOM node before the slotted node." ); await copySelectionToClipboardShadow( start1.firstChild, 2, host1.shadowRoot.getElementById("inner1").firstChild, 3 ); testSelectionToString("art\nEnd Inn"); testClipboardValue(false, "text/plain", "art\nEnd Inn"); testHtmlClipboardValue( false, "text/html", 'art\n
\n \n \n End\n \n Inn
' ); testPasteText(textarea, "art\nEnd Inn"); info("Test 3: Start is a slotted node and end is in shadow DOM."); await copySelectionToClipboardShadow( end1.firstChild, 2, host1.shadowRoot.getElementById("inner1").firstChild, 3 ); testSelectionToString("d Inn"); testClipboardValue(false, "text/plain", "d Inn"); testHtmlClipboardValue( false, "text/html", 'd\n \n Inn' ); testPasteText(textarea, "d Inn"); let start2 = document.getElementById("start2"); let host2 = document.getElementById("host2"); let host2_slot2 = document.getElementById("host2_slot2"); let host2_slot4 = document.getElementById("host2_slot4"); info( "Test 4: start is in light DOM and end is a slotted node with multiple assigned nodes in the same slot.\n" ); await copySelectionToClipboardShadow( start2.firstChild, 2, host2_slot2.firstChild, 5 ); testSelectionToString("art\nSlotted1Slott"); testClipboardValue(false, "text/plain", "art\nSlotted1Slott"); testHtmlClipboardValue( false, "text/html", 'art\n
\n Slotted1Slott
' ); testPasteText(textarea, "art\nSlotted1Slott"); info( "Test 5: start is in light DOM and end is a slotted node with endOffset includes the entire slotted node\n" ); await copySelectionToClipboardShadow( start2.firstChild, 2, host2_slot2.firstChild, 8 ); testSelectionToString("art\nSlotted1Slotted2"); testClipboardValue(false, "text/plain", "art\nSlotted1Slotted2"); testHtmlClipboardValue( false, "text/html", 'art\n
\n Slotted1Slotted2
' ); testPasteText(textarea, "art\nSlotted1Slotted2"); info("Test 6: start is in light DOM and end is a shadow node.\n"); await copySelectionToClipboardShadow( start2.firstChild, 2, host2.shadowRoot.getElementById("inner2").firstChild, 3 ); testSelectionToString("art\nSlotted1Slotted2 Inn"); testClipboardValue(false, "text/plain", "art\nSlotted1Slotted2 Inn"); testHtmlClipboardValue( false, "text/html", 'art\n
\n Slotted1Slotted2\n Inn
' ); testPasteText(textarea, "art\nSlotted1Slotted2 Inn"); info("Test 7: start is in light DOM and end is a slotted node.\n"); await copySelectionToClipboardShadow( start2.firstChild, 2, host2_slot4.firstChild, 8 ); testSelectionToString("art\nSlotted1Slotted2 Inner Slotted3Slotted4"); testClipboardValue( false, "text/plain", "art\nSlotted1Slotted2 Inner Slotted3Slotted4" ); testHtmlClipboardValue( false, "text/html", 'art\n
\n Slotted1Slotted2\n Inner\n Slotted3Slotted4
' ); testPasteText(textarea, "art\nSlotted1Slotted2 Inner Slotted3Slotted4"); let host3 = document.getElementById("host3"); let host3_slot1 = document.getElementById("host3_slot1"); let host3_slot4 = document.getElementById("host3_slot4"); info( "Test 8: Both start and end are slotted nodes, and their DOM tree order is reversed compare to flat tree order.\n" ); await copySelectionToClipboardShadow( host3_slot1.firstChild, 2, host3_slot4.firstChild, 8 ); testSelectionToString("otted1 Slotted2 Inner Slotted3 Slotted4"); testClipboardValue( false, "text/plain", "otted1 Slotted2 Inner Slotted3 Slotted4" ); testHtmlClipboardValue( false, "text/html", 'otted1\n Slotted2\n Inner\n Slotted3\n Slotted4' ); testPasteText(textarea, "otted1 Slotted2 Inner Slotted3 Slotted4"); info("Test 9: start is in Shadow DOM and end is in Light DOM.\n"); await copySelectionToClipboardShadow( host3.shadowRoot.getElementById("inner2").firstChild, 3, host3_slot1.firstChild, 4 ); testSelectionToString("ted1 Slotted2 Inn"); testClipboardValue(false, "text/plain", "ted1 Slotted2 Inn"); testHtmlClipboardValue( false, "text/html", 'ted1\n Slotted2\n Inn' ); testPasteText(textarea, "ted1 Slotted2 Inn"); }