tor-browser

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

copypaste.js (18372B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 function modifySelection(s) {
      5  var g = window.getSelection();
      6  var l = g.getRangeAt(0);
      7  var d = document.createElement("p");
      8  d.innerHTML = s;
      9  d.appendChild(l.cloneContents());
     10 
     11  var e = document.createElement("div");
     12  document.body.appendChild(e);
     13  e.appendChild(d);
     14  var a = document.createRange();
     15  a.selectNode(d);
     16  g.removeAllRanges();
     17  g.addRange(a);
     18  window.setTimeout(function () {
     19    e.remove();
     20    g.removeAllRanges();
     21    g.addRange(l);
     22  }, 0);
     23 }
     24 
     25 function getLoadContext() {
     26  var Ci = SpecialPowers.Ci;
     27  return SpecialPowers.wrap(window).docShell.QueryInterface(Ci.nsILoadContext);
     28 }
     29 
     30 async function testCopyPaste(isXHTML) {
     31  var suppressUnicodeCheckIfHidden = !!isXHTML;
     32  var suppressHTMLCheck = !!isXHTML;
     33 
     34  var docShell = SpecialPowers.wrap(window).docShell;
     35 
     36  var documentViewer = docShell.docViewer.QueryInterface(
     37    SpecialPowers.Ci.nsIDocumentViewerEdit
     38  );
     39 
     40  var clipboard = SpecialPowers.Services.clipboard;
     41 
     42  var textarea = SpecialPowers.wrap(document.getElementById("input"));
     43 
     44  async function copySelectionToClipboard(suppressUnicodeCheck) {
     45    await SimpleTest.promiseClipboardChange(
     46      () => true,
     47      () => {
     48        documentViewer.copySelection();
     49      }
     50    );
     51    if (!suppressUnicodeCheck) {
     52      ok(
     53        clipboard.hasDataMatchingFlavors(["text/plain"], 1),
     54        "check text/plain"
     55      );
     56    }
     57    if (!suppressHTMLCheck) {
     58      ok(clipboard.hasDataMatchingFlavors(["text/html"], 1), "check text/html");
     59    }
     60  }
     61  function clear() {
     62    textarea.blur();
     63    var sel = window.getSelection();
     64    sel.removeAllRanges();
     65  }
     66  async function copyToClipboard(node, suppressUnicodeCheck) {
     67    clear();
     68    var r = document.createRange();
     69    r.selectNode(node);
     70    window.getSelection().addRange(r);
     71    await copySelectionToClipboard(suppressUnicodeCheck);
     72  }
     73  function addRange(startNode, startIndex, endNode, endIndex) {
     74    var sel = window.getSelection();
     75    var r = document.createRange();
     76    r.setStart(startNode, startIndex);
     77    r.setEnd(endNode, endIndex);
     78    sel.addRange(r);
     79  }
     80  async function copyRangeToClipboard(
     81    startNode,
     82    startIndex,
     83    endNode,
     84    endIndex,
     85    suppressUnicodeCheck
     86  ) {
     87    clear();
     88    addRange(startNode, startIndex, endNode, endIndex);
     89    await copySelectionToClipboard(suppressUnicodeCheck);
     90  }
     91  async function copyChildrenToClipboard(id) {
     92    clear();
     93    window.getSelection().selectAllChildren(document.getElementById(id));
     94    await copySelectionToClipboard();
     95  }
     96  function getClipboardData(mime) {
     97    var transferable = SpecialPowers.Cc[
     98      "@mozilla.org/widget/transferable;1"
     99    ].createInstance(SpecialPowers.Ci.nsITransferable);
    100    transferable.init(getLoadContext());
    101    transferable.addDataFlavor(mime);
    102    clipboard.getData(
    103      transferable,
    104      1,
    105      SpecialPowers.wrap(window).browsingContext.currentWindowContext
    106    );
    107    var data = SpecialPowers.createBlankObject();
    108    transferable.getTransferData(mime, data);
    109    return data;
    110  }
    111  function testHtmlClipboardValue(mime, expected) {
    112    // For Windows, navigator.platform returns "Win32".
    113    var expectedValue = expected;
    114    if (navigator.platform.includes("Win")) {
    115      // Windows has extra content.
    116      expectedValue =
    117        kTextHtmlPrefixClipboardDataWindows +
    118        expected.replace(/\n/g, "\n") +
    119        kTextHtmlSuffixClipboardDataWindows;
    120    }
    121    testClipboardValue(mime, expectedValue);
    122  }
    123  function testClipboardValue(mime, expected) {
    124    if (suppressHTMLCheck && mime == "text/html") {
    125      return null;
    126    }
    127    var data = SpecialPowers.wrap(getClipboardData(mime));
    128    is(
    129      data.value == null
    130        ? data.value
    131        : data.value.QueryInterface(SpecialPowers.Ci.nsISupportsString).data,
    132      expected,
    133      mime + " value in the clipboard"
    134    );
    135    return data.value;
    136  }
    137  function testPasteText(expected) {
    138    textarea.value = "";
    139    textarea.focus();
    140    textarea.editor.paste(1);
    141    is(textarea.value, expected, "value of the textarea after the paste");
    142  }
    143  function testPasteHTML(id, expected) {
    144    var contentEditable = $(id);
    145    contentEditable.focus();
    146    synthesizeKey("v", { accelKey: true });
    147    is(contentEditable.innerHTML, expected, id + ".innerHtml after the paste");
    148  }
    149  function testSelectionToString(expected) {
    150    is(
    151      window.getSelection().toString().replace(/\r\n/g, "\n"),
    152      expected,
    153      "Selection.toString"
    154    );
    155  }
    156  function testInnerHTML(id, expected) {
    157    var value = document.getElementById(id).innerHTML;
    158    is(value, expected, id + ".innerHTML");
    159  }
    160 
    161  const includeCommonAncestor = SpecialPowers.getBoolPref(
    162    "dom.serializer.includeCommonAncestor.enabled"
    163  );
    164 
    165  await copyChildrenToClipboard("draggable");
    166  testSelectionToString("This is a draggable bit of text.");
    167  testClipboardValue("text/plain", "This is a draggable bit of text.");
    168  testHtmlClipboardValue(
    169    "text/html",
    170    `${includeCommonAncestor ? '<div id="draggable" title="title to have a long HTML line">' : ""}` +
    171      `This is a <em>draggable</em> bit of text.` +
    172      `${includeCommonAncestor ? "</div>" : ""}`
    173  );
    174  testPasteText("This is a draggable bit of text.");
    175 
    176  await copyChildrenToClipboard("alist");
    177  testSelectionToString(" bla\n\n    foo\n    bar\n\n");
    178  testClipboardValue("text/plain", " bla\n\n    foo\n    bar\n\n");
    179  testHtmlClipboardValue(
    180    "text/html",
    181    `${includeCommonAncestor ? '<div id="alist">' : ""}` +
    182      `\n    bla\n    <ul>\n      <li>foo</li>\n      \n      <li>bar</li>\n    </ul>\n  ` +
    183      `${includeCommonAncestor ? "</div>" : ""}`
    184  );
    185  testPasteText(" bla\n\n    foo\n    bar\n\n");
    186 
    187  await copyChildrenToClipboard("blist");
    188  testSelectionToString(" mozilla\n\n    foo\n    bar\n\n");
    189  testClipboardValue("text/plain", " mozilla\n\n    foo\n    bar\n\n");
    190  testHtmlClipboardValue(
    191    "text/html",
    192    `${includeCommonAncestor ? '<div id="blist">' : ""}` +
    193      `\n    mozilla\n    <ol>\n      <li>foo</li>\n      \n      <li>bar</li>\n    </ol>\n  ` +
    194      `${includeCommonAncestor ? "</div>" : ""}`
    195  );
    196  testPasteText(" mozilla\n\n    foo\n    bar\n\n");
    197 
    198  await copyChildrenToClipboard("clist");
    199  testSelectionToString(" mzla\n\n    foo\n        bazzinga!\n    bar\n\n");
    200  testClipboardValue(
    201    "text/plain",
    202    " mzla\n\n    foo\n        bazzinga!\n    bar\n\n"
    203  );
    204  testHtmlClipboardValue(
    205    "text/html",
    206    `${includeCommonAncestor ? '<div id="clist">' : ""}` +
    207      `\n    mzla\n    <ul>\n      <li>foo<ul>\n        <li>bazzinga!</li>\n      </ul></li>\n      \n      <li>bar</li>\n    </ul>\n  ` +
    208      `${includeCommonAncestor ? "</div>" : ""}`
    209  );
    210  testPasteText(" mzla\n\n    foo\n        bazzinga!\n    bar\n\n");
    211 
    212  await copyChildrenToClipboard("div4");
    213  testSelectionToString(" Tt t t ");
    214  testClipboardValue("text/plain", " Tt t t ");
    215  if (isXHTML) {
    216    testHtmlClipboardValue(
    217      "text/html",
    218      '<div id="div4">\n  T<textarea xmlns="http://www.w3.org/1999/xhtml">t t t</textarea>\n</div>'
    219    );
    220    testInnerHTML(
    221      "div4",
    222      '\n  T<textarea xmlns="http://www.w3.org/1999/xhtml">t t t</textarea>\n'
    223    );
    224  } else {
    225    testHtmlClipboardValue(
    226      "text/html",
    227      `${includeCommonAncestor ? '<div id="div4">' : ""}` +
    228        `\n  T<textarea>t t t</textarea>\n` +
    229        `${includeCommonAncestor ? "</div>" : ""}`
    230    );
    231    testInnerHTML("div4", "\n  T<textarea>t t t</textarea>\n");
    232  }
    233  testPasteText(" Tt t t ");
    234 
    235  await copyChildrenToClipboard("div5");
    236  testSelectionToString(" T     ");
    237  testClipboardValue("text/plain", " T     ");
    238  if (isXHTML) {
    239    testHtmlClipboardValue(
    240      "text/html",
    241      '<div id="div5">\n  T<textarea xmlns="http://www.w3.org/1999/xhtml">     </textarea>\n</div>'
    242    );
    243    testInnerHTML(
    244      "div5",
    245      '\n  T<textarea xmlns="http://www.w3.org/1999/xhtml">     </textarea>\n'
    246    );
    247  } else {
    248    testHtmlClipboardValue(
    249      "text/html",
    250      `${includeCommonAncestor ? '<div id="div5">' : ""}` +
    251        `\n  T<textarea>     </textarea>\n` +
    252        `${includeCommonAncestor ? "</div>" : ""}`
    253    );
    254    testInnerHTML("div5", "\n  T<textarea>     </textarea>\n");
    255  }
    256  testPasteText(" T     ");
    257 
    258  await copyRangeToClipboard(
    259    $("div6").childNodes[0],
    260    0,
    261    $("div6").childNodes[1],
    262    1,
    263    suppressUnicodeCheckIfHidden
    264  );
    265  testSelectionToString("");
    266  // XXX: disabled due to bug 564688
    267  // testClipboardValue("text/plain", "");
    268  // testClipboardValue("text/html", "");
    269  testInnerHTML("div6", "div6");
    270 
    271  await copyRangeToClipboard(
    272    $("div7").childNodes[0],
    273    0,
    274    $("div7").childNodes[0],
    275    4,
    276    suppressUnicodeCheckIfHidden
    277  );
    278  testSelectionToString("");
    279  // XXX: disabled due to bug 564688
    280  // testClipboardValue("text/plain", "");
    281  // testClipboardValue("text/html", "");
    282  testInnerHTML("div7", "div7");
    283 
    284  await copyRangeToClipboard(
    285    $("div8").childNodes[0],
    286    0,
    287    $("div8").childNodes[0],
    288    4,
    289    suppressUnicodeCheckIfHidden
    290  );
    291  testSelectionToString("");
    292  // XXX: disabled due to bug 564688
    293  // testClipboardValue("text/plain", "");
    294  // testClipboardValue("text/html", "");
    295  testInnerHTML("div8", "div8");
    296 
    297  await copyRangeToClipboard(
    298    $("div9").childNodes[0],
    299    0,
    300    $("div9").childNodes[0],
    301    4,
    302    suppressUnicodeCheckIfHidden
    303  );
    304  testSelectionToString("div9");
    305  testClipboardValue("text/plain", "div9");
    306  testHtmlClipboardValue("text/html", "div9");
    307  testInnerHTML("div9", "div9");
    308 
    309  await copyToClipboard($("div10"), suppressUnicodeCheckIfHidden);
    310  testSelectionToString("");
    311  testInnerHTML("div10", "div10");
    312 
    313  await copyToClipboard($("div10").firstChild, suppressUnicodeCheckIfHidden);
    314  testSelectionToString("");
    315 
    316  await copyRangeToClipboard(
    317    $("div10").childNodes[0],
    318    0,
    319    $("div10").childNodes[0],
    320    1,
    321    suppressUnicodeCheckIfHidden
    322  );
    323  testSelectionToString("");
    324 
    325  await copyRangeToClipboard(
    326    $("div10").childNodes[1],
    327    0,
    328    $("div10").childNodes[1],
    329    1,
    330    suppressUnicodeCheckIfHidden
    331  );
    332  testSelectionToString("");
    333 
    334  if (!isXHTML) {
    335    // ============ copy/paste multi-range selection (bug 1123505)
    336    // with text start node
    337    var sel = window.getSelection();
    338    sel.removeAllRanges();
    339    var r = document.createRange();
    340    var ul = $("ul1");
    341    var parent = ul.parentNode;
    342    r.setStart(parent, 0);
    343    r.setEnd(parent.firstChild, 15);
    344    sel.addRange(r); // <div>{Copy1then Paste]<ul id="ul1"><li>LI</li>\n</ul></div>
    345 
    346    r = document.createRange();
    347    r.setStart(ul, 1);
    348    r.setEnd(parent, 2);
    349    sel.addRange(r); // <div>Copy1then Paste<ul id="ul1"><li>LI{</li>\n</ul>}</div>
    350    await copySelectionToClipboard(true);
    351    testPasteHTML("contentEditable1", "Copy1then Paste"); // The <ul> should not appear because it has no <li>s
    352 
    353    // with text end node
    354    sel = window.getSelection();
    355    sel.removeAllRanges();
    356    r = document.createRange();
    357    ul = $("ul2");
    358    parent = ul.parentNode;
    359    r.setStart(parent, 0);
    360    r.setEnd(ul, 1);
    361    sel.addRange(r); // <div>{<ul id="ul2">\n}<li>LI</li></ul>Copy2then Paste</div>
    362 
    363    r = document.createRange();
    364    r.setStart(parent.childNodes[1], 0);
    365    r.setEnd(parent, 2);
    366    sel.addRange(r); // <div><ul id="ul2">\n<li>LI</li></ul>[Copy2then Paste}</div>
    367    await copySelectionToClipboard(true);
    368    testPasteHTML("contentEditable2", "Copy2then Paste"); // The <ul> should not appear because it has no <li>s
    369 
    370    // with text end node and non-empty start
    371    sel = window.getSelection();
    372    sel.removeAllRanges();
    373    r = document.createRange();
    374    ul = $("ul3");
    375    parent = ul.parentNode;
    376    r.setStart(parent, 0);
    377    r.setEnd(ul, 1);
    378    sel.addRange(r); // <div>{<ul id="ul3"><li>\n</li>}<li>LI</li></ul>Copy3then Paste</div>
    379 
    380    r = document.createRange();
    381    r.setStart(parent.childNodes[1], 0);
    382    r.setEnd(parent, 2);
    383    sel.addRange(r); // <div><ul id="ul3"><li>\n</li><li>LI</li></ul>[Copy3then Paste}</div>
    384    await copySelectionToClipboard(true);
    385    testPasteHTML(
    386      "contentEditable3",
    387      // The <ul> should appear because it has a <li>
    388      // The preceding linefeed of the <br> in the empty <li> is an invisible
    389      // white-space.  Thus, it's not important whether it appears or not in the result.
    390      '<ul id="ul3"><li><br></li></ul>Copy3then Paste'
    391    );
    392 
    393    // with elements of different depth
    394    sel = window.getSelection();
    395    sel.removeAllRanges();
    396    r = document.createRange();
    397    var div1 = $("div1s");
    398    parent = div1.parentNode;
    399    r.setStart(parent, 0);
    400    r.setEnd(document.getElementById("div1se1"), 1); // after the "inner" DIV
    401    sel.addRange(r);
    402 
    403    r = document.createRange();
    404    r.setStart(div1.childNodes[1], 0); // the start of "after"
    405    r.setEnd(parent, 1);
    406    sel.addRange(r);
    407    await copySelectionToClipboard(true);
    408    testPasteHTML(
    409      "contentEditable4",
    410      '<div id="div1s"><div id="div1se1">before</div></div><div id="div1s">after</div>'
    411    );
    412 
    413    // with elements of different depth, and a text node at the end
    414    sel = window.getSelection();
    415    sel.removeAllRanges();
    416    r = document.createRange();
    417    div1 = $("div2s");
    418    parent = div1.parentNode;
    419    r.setStart(parent, 0);
    420    r.setEnd(document.getElementById("div2se1"), 1); // after the "inner" DIV
    421    sel.addRange(r);
    422 
    423    r = document.createRange();
    424    r.setStart(div1.childNodes[1], 0); // the start of "after"
    425    r.setEnd(parent, 1);
    426    sel.addRange(r);
    427    await copySelectionToClipboard(true);
    428    testPasteHTML(
    429      "contentEditable5",
    430      '<div id="div2s"><div id="div2se1">before</div></div><div id="div2s">after</div>'
    431    );
    432 
    433    // crash test for bug 1127835
    434    var e1 = document.getElementById("1127835crash1");
    435    var e2 = document.getElementById("1127835crash2");
    436    var e3 = document.getElementById("1127835crash3");
    437    var t1 = e1.childNodes[0];
    438    var t3 = e3.childNodes[0];
    439 
    440    sel = window.getSelection();
    441    sel.removeAllRanges();
    442 
    443    r = document.createRange();
    444    r.setStart(t1, 1);
    445    r.setEnd(e2, 0);
    446    sel.addRange(r); // <div>\n<span id="1127835crash1">1[</span><div id="1127835crash2">}<div>\n</div></div><a href="..." id="1127835crash3">3</a>\n</div>
    447 
    448    r = document.createRange();
    449    r.setStart(e2, 1);
    450    r.setEnd(t3, 0);
    451    sel.addRange(r); // <div>\n<span id="1127835crash1">1</span><div id="1127835crash2"><div>\n</div>{</div><a href="..." id="1127835crash3">]3</a>\n</div>
    452    await copySelectionToClipboard(true);
    453    testPasteHTML(
    454      "contentEditable6",
    455      '<span id="1127835crash1"></span><div id="1127835crash2"><div>\n</div></div><a href="http://www.mozilla.org/" id="1127835crash3"><br></a>'
    456    ); // Don't strip the empty `<a href="...">` element because of avoiding any dataloss provided by the element
    457  }
    458 
    459  // ============ copy/paste test from/to a textarea
    460 
    461  var val = "1\n 2\n  3";
    462  textarea.value = val;
    463  textarea.select();
    464  await SimpleTest.promiseClipboardChange(textarea.value, () => {
    465    textarea.editor.copy();
    466  });
    467  textarea.value = "";
    468  textarea.editor.paste(1);
    469  is(textarea.value, val);
    470  textarea.value = "";
    471 
    472  // ============ NOSCRIPT should not be copied
    473 
    474  await copyChildrenToClipboard("div13");
    475  testSelectionToString("__");
    476  testClipboardValue("text/plain", "__");
    477  testHtmlClipboardValue(
    478    "text/html",
    479    `${includeCommonAncestor ? '<div id="div13">' : ""}` +
    480      `__` +
    481      `${includeCommonAncestor ? "</div>" : ""}`
    482  );
    483  testPasteText("__");
    484 
    485  // ============ converting cell boundaries to tabs in tables
    486 
    487  await copyToClipboard($("tr1"));
    488  testClipboardValue("text/plain", "foo\tbar");
    489 
    490  if (!isXHTML) {
    491    // ============ spanning multiple rows
    492 
    493    await copyRangeToClipboard($("tr2"), 0, $("tr3"), 0);
    494    testClipboardValue("text/plain", "1\t2\n3\t4\n");
    495    testHtmlClipboardValue(
    496      "text/html",
    497      '<table><tbody><tr id="tr2"><tr id="tr2"><td>1</td><td>2</td></tr><tr><td>3</td><td>4</td></tr><tr id="tr3"></tr></tr></tbody></table>'
    498    );
    499 
    500    // ============ spanning multiple rows in multi-range selection
    501 
    502    clear();
    503    addRange($("tr2"), 0, $("tr2"), 2);
    504    addRange($("tr3"), 0, $("tr3"), 2);
    505    await copySelectionToClipboard();
    506    testClipboardValue("text/plain", "1\t2\n5\t6");
    507    testHtmlClipboardValue(
    508      "text/html",
    509      '<table><tbody><tr id="tr2"><td>1</td><td>2</td></tr><tr id="tr3"><td>5</td><td>6</td></tr></tbody></table>'
    510    );
    511  }
    512 
    513  // ============ manipulating Selection in oncopy
    514 
    515  await copyRangeToClipboard(
    516    $("div11").childNodes[0],
    517    0,
    518    $("div11").childNodes[1],
    519    2
    520  );
    521  testClipboardValue("text/plain", "Xdiv11");
    522  testHtmlClipboardValue(
    523    "text/html",
    524    `${includeCommonAncestor ? "<div>" : ""}` +
    525      `<p>X<span>div</span>11</p>` +
    526      `${includeCommonAncestor ? "</div>" : ""}`
    527  );
    528 
    529  await new Promise(resolve => {
    530    setTimeout(resolve, 0);
    531  });
    532  testSelectionToString("div11");
    533 
    534  await new Promise(resolve => {
    535    setTimeout(resolve, 0);
    536  });
    537  await copyRangeToClipboard(
    538    $("div12").childNodes[0],
    539    0,
    540    $("div12").childNodes[1],
    541    2
    542  );
    543 
    544  testClipboardValue("text/plain", "Xdiv12");
    545  testHtmlClipboardValue(
    546    "text/html",
    547    `${includeCommonAncestor ? "<div>" : ""}` +
    548      `<p>X<span>div</span>12</p>` +
    549      `${includeCommonAncestor ? "</div>" : ""}`
    550  );
    551  await new Promise(resolve => {
    552    setTimeout(resolve, 0);
    553  });
    554  testSelectionToString("div12");
    555 
    556  await new Promise(resolve => {
    557    setTimeout(resolve, 0);
    558  });
    559 
    560  if (!isXHTML) {
    561    // ============ copy from ruby
    562 
    563    const ruby1 = $("ruby1");
    564    const ruby1Container = ruby1.parentNode;
    565 
    566    // Ruby annotation is included when selecting inside ruby.
    567    await copyRangeToClipboard(ruby1, 0, ruby1, 6);
    568    testClipboardValue("text/plain", "aabb(AABB)");
    569 
    570    // Ruby annotation is ignored when selecting across ruby.
    571    await copyRangeToClipboard(ruby1Container, 0, ruby1Container, 3);
    572    testClipboardValue("text/plain", "XaabbY");
    573 
    574    // ... unless converter.html2txt.always_include_ruby is set
    575    await SpecialPowers.pushPrefEnv({
    576      set: [["converter.html2txt.always_include_ruby", true]],
    577    });
    578    await copyRangeToClipboard(ruby1Container, 0, ruby1Container, 3);
    579    testClipboardValue("text/plain", "Xaabb(AABB)Y");
    580    await SpecialPowers.popPrefEnv();
    581  }
    582 }