tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

copypaste_shadow_dom.js (10616B)


      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  info(
    112    "Test 1: Both start and end are in light DOM, the range has contents in Shadow DOM."
    113  );
    114  await copySelectionToClipboardShadow(
    115    document.getElementById("title"),
    116    0,
    117    document.getElementById("host1"),
    118    1
    119  );
    120  testSelectionToString("This is a draggable bit of text.\nShadow Content1 ");
    121  testClipboardValue(
    122    false,
    123    "text/plain",
    124    "This is a draggable bit of text.\nShadow Content1 "
    125  );
    126  testHtmlClipboardValue(
    127    false,
    128    "text/html",
    129    '<div id="title" title="title to have a long HTML line">This is a <em>draggable</em> bit of text.</div>\n  <div id="host1">\n      <span id="shadow-content">Shadow Content1</span>\n    </div>'
    130  );
    131  testPasteText(textarea, "This is a draggable bit of text.\nShadow Content1 ");
    132 
    133  info("Test 2: Start is in Shadow DOM and end is in light DOM.");
    134  await copySelectionToClipboardShadow(
    135    document.getElementById("host1").shadowRoot.getElementById("shadow-content")
    136      .firstChild,
    137    3,
    138    document.getElementById("light-content").firstChild,
    139    5
    140  );
    141  testSelectionToString("dow Content1\nLight");
    142  testClipboardValue(false, "text/plain", "dow Content1\nLight");
    143  testHtmlClipboardValue(
    144    false,
    145    "text/html",
    146    '<div id="host1"><span id="shadow-content">dow Content1</span>\n    </div>\n\n  <span id="light-content">Light</span>'
    147  );
    148 
    149  info("Test 3: Start is in light DOM and end is in shadow DOM.");
    150  await copySelectionToClipboardShadow(
    151    document.getElementById("light-content").firstChild,
    152    3,
    153    document.getElementById("host2").shadowRoot.getElementById("shadow-content")
    154      .firstChild,
    155    5
    156  );
    157  testSelectionToString("ht Content\nShado");
    158  testClipboardValue(false, "text/plain", "ht Content\nShado");
    159  testHtmlClipboardValue(
    160    false,
    161    "text/html",
    162    '<span id="light-content">ht Content</span>\n\n  <div id="host2">\n      <span id="shadow-content">Shado</span></div>'
    163  );
    164 
    165  info("Test 4: start is in light DOM and end is a nested shadow DOM.\n");
    166  await copySelectionToClipboardShadow(
    167    document.getElementById("light-content").firstChild,
    168    3,
    169    document
    170      .getElementById("host2")
    171      .shadowRoot.getElementById("nested-host")
    172      .shadowRoot.getElementById("nested-shadow-content").firstChild,
    173    5
    174  );
    175  testSelectionToString("ht Content\nShadow Content2\nNeste");
    176  testClipboardValue(false, "text/plain", "ht Content\nShadow Content2\nNeste");
    177  testHtmlClipboardValue(
    178    false,
    179    "text/html",
    180    '<span id="light-content">ht Content</span>\n\n  <div id="host2">\n      <span id="shadow-content">Shadow Content2</span>\n      <div id="nested-host">\n          <span id="nested-shadow-content">Neste</span></div></div>'
    181  );
    182 
    183  info("Test 5: Both start and end are in shadow DOM but in different trees.");
    184  await copySelectionToClipboardShadow(
    185    document.getElementById("host1").shadowRoot.getElementById("shadow-content")
    186      .firstChild,
    187    3,
    188    document
    189      .getElementById("host2")
    190      .shadowRoot.getElementById("nested-host")
    191      .shadowRoot.getElementById("nested-shadow-content").firstChild,
    192    5
    193  );
    194  testSelectionToString("dow Content1\nLight Content\nShadow Content2\nNeste");
    195  testClipboardValue(
    196    false,
    197    "text/plain",
    198    "dow Content1\nLight Content\nShadow Content2\nNeste"
    199  );
    200  testHtmlClipboardValue(
    201    false,
    202    "text/html",
    203    '<div id="host1"><span id="shadow-content">dow Content1</span>\n    </div>\n\n  <span id="light-content">Light Content</span>\n\n  <div id="host2">\n      <span id="shadow-content">Shadow Content2</span>\n      <div id="nested-host">\n          <span id="nested-shadow-content">Neste</span></div></div>'
    204  );
    205 
    206  info(
    207    "Test 6: Start is in a shadow tree and end is in a nested shadow tree within the same shadow tree."
    208  );
    209  await copySelectionToClipboardShadow(
    210    document.getElementById("host2").shadowRoot.getElementById("shadow-content")
    211      .firstChild,
    212    3,
    213    document
    214      .getElementById("host2")
    215      .shadowRoot.getElementById("nested-host")
    216      .shadowRoot.getElementById("nested-shadow-content").firstChild,
    217    5
    218  );
    219  testSelectionToString("dow Content2\nNeste");
    220  testClipboardValue(false, "text/plain", "dow Content2\nNeste");
    221  testHtmlClipboardValue(
    222    false,
    223    "text/html",
    224    '<span id="shadow-content">dow Content2</span>\n      <div id="nested-host">\n          <span id="nested-shadow-content">Neste</span></div>'
    225  );
    226 
    227  info(
    228    "Test 7: End is at a slotted content where the slot element is before the regular shadow dom contents."
    229  );
    230  await copySelectionToClipboardShadow(
    231    document.getElementById("light-content2").firstChild,
    232    3,
    233    document.getElementById("slotted1").firstChild,
    234    8
    235  );
    236  testSelectionToString("ht Content\nslotted1");
    237  testClipboardValue(false, "text/plain", "ht Content\nslotted1");
    238  testHtmlClipboardValue(
    239    false,
    240    "text/html",
    241    '<span id=\"light-content2\">ht Content</span>\n  <div id=\"host3\">\n      <slot name=\"slot1\"><span slot=\"slot1\" id=\"slotted1\">slotted1</span></slot></div>'
    242  );
    243 
    244  info(
    245    "Test 8: End is at a slotted content where the slot element is after the regular shadow dom contents"
    246  );
    247  await copySelectionToClipboardShadow(
    248    document.getElementById("light-content2").firstChild,
    249    3,
    250    document.getElementById("slotted2").firstChild,
    251    8
    252  );
    253  testSelectionToString("ht Content\nslotted1 Shadow Content2 slotted2");
    254  testClipboardValue(
    255    false,
    256    "text/plain",
    257    "ht Content\nslotted1 Shadow Content2 slotted2"
    258  );
    259  testHtmlClipboardValue(
    260    false,
    261    "text/html",
    262    '<span id=\"light-content2\">ht Content</span>\n  <div id=\"host3\">\n      <slot name=\"slot1\"><span slot=\"slot1\" id=\"slotted1\">slotted1</span></slot>\n      <span id=\"shadow-content\">Shadow Content2</span>\n      <slot name=\"slot2\"><span slot=\"slot2\" id=\"slotted2\">slotted2</span></slot></div>'
    263  );
    264 
    265  info(
    266    "Test 9: things still work as expected with a more complex shadow tree."
    267  );
    268  await copySelectionToClipboardShadow(
    269    document.getElementById("slotted3").firstChild,
    270    3,
    271    document.getElementById("slotted4").firstChild,
    272    8
    273  );
    274  testSelectionToString(
    275    "tted1 Shadow Content2\nNested Slotted ShadowNested\nslotted2"
    276  );
    277  testClipboardValue(
    278    false,
    279    "text/plain",
    280    "tted1 Shadow Content2\nNested Slotted ShadowNested\nslotted2"
    281  );
    282  testHtmlClipboardValue(
    283    false,
    284    "text/html",
    285    '<slot name=\"slot1\"><span slot=\"slot1\" id=\"slotted3\">tted1</span></slot>\n      <span id=\"shadow-content\">Shadow Content2</span>\n      <div id=\"nestedHost\">\n          <slot>\n        \n        <span>Nested Slotted</span>\n      </slot>\n          <span>ShadowNested</span>\n        </div>\n      <slot name=\"slot2\"><span slot=\"slot2\" id=\"slotted4\">slotted2</span></slot>'
    286  );
    287 
    288  info("Test 10: Slot element is always serialized even if it's not visible");
    289  await copySelectionToClipboardShadow(
    290    document.getElementById("light-content3").firstChild,
    291    0,
    292    document.getElementById("host5").shadowRoot.querySelector("span")
    293      .firstChild,
    294    5
    295  );
    296  testSelectionToString("Light Content\nSlotted Shado");
    297  testClipboardValue(false, "text/plain", "Light Content\nSlotted Shado");
    298  testHtmlClipboardValue(
    299    false,
    300    "text/html",
    301    '<span id=\"light-content3\">Light Content</span>\n  \n  <div id=\"host5\">\n      <slot>\n    \n    <span>Slotted</span>\n  </slot>\n      <span>Shado</span></div>'
    302  );
    303 }