tor-browser

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

test_middle_click_paste.html (31312B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4  <title>Test for paste with middle button click</title>
      5  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      6  <script src="/tests/SimpleTest/EventUtils.js"></script>
      7  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
      8 </head>
      9 <body>
     10 <p id="display"></p>
     11 <div id="content" style="display: none;">
     12 
     13 </div>
     14 
     15 <div id="container"></div>
     16 
     17 <textarea id="toCopyPlaintext" style="display: none;"></textarea>
     18 <iframe id="toCopyHTMLContent" srcdoc="<body></body>" style="display: none;"></iframe>
     19 
     20 <pre id="test">
     21 
     22 <script class="testbody" type="application/javascript">
     23 SimpleTest.waitForExplicitFinish();
     24 
     25 // TODO: This file should test complicated cases too.
     26 //       E.g., pasting into existing content, e.g., pasting invalid child
     27 //       element for the parent elements at insertion point.
     28 
     29 async function copyPlaintext(aText) {
     30  return new Promise(resolve => {
     31    SimpleTest.waitForClipboard(aText,
     32      () => {
     33        let element = document.getElementById("toCopyPlaintext");
     34        element.style.display = "block";
     35        element.focus();
     36        element.value = aText;
     37        synthesizeKey("a", {accelKey: true});
     38        synthesizeKey("c", {accelKey: true});
     39      },
     40      () => {
     41        ok(true, `Succeeded to copy "${aText}" to clipboard`);
     42        let element = document.getElementById("toCopyPlaintext");
     43        element.style.display = "none";
     44        resolve();
     45      },
     46      () => {
     47        ok(false, `Failed to copy "${aText}" to clipboard`);
     48        SimpleTest.finish();
     49      });
     50  });
     51 }
     52 
     53 async function copyHTMLContent(aInnerHTML) {
     54  let iframe = document.getElementById("toCopyHTMLContent");
     55  iframe.style.display = "block";
     56  iframe.contentDocument.body.scrollTop;
     57  iframe.contentDocument.body.innerHTML = aInnerHTML;
     58  iframe.contentWindow.focus();
     59  iframe.contentWindow.getSelection().selectAllChildren(iframe.contentDocument.body);
     60  return new Promise(resolve => {
     61    SimpleTest.waitForClipboard(
     62      () => { return true; },
     63      () => {
     64        synthesizeKey("c", {accelKey: true}, iframe.contentWindow);
     65      },
     66      () => {
     67        ok(true, `Succeeded to copy "${aInnerHTML}" to clipboard as HTML`);
     68        iframe.style.display = "none";
     69        resolve();
     70      },
     71      () => {
     72        ok(false, `Failed to copy "${aInnerHTML}" to clipboard`);
     73        SimpleTest.finish();
     74      },
     75      "text/html");
     76  });
     77 }
     78 
     79 function checkInputEvent(aEvent, aInputType, aData, aDataTransfer, aTargetRanges, aDescription) {
     80  ok(aEvent instanceof InputEvent,
     81     `"${aEvent.type}" event should be dispatched with InputEvent interface ${aDescription}`);
     82  is(aEvent.cancelable, aEvent.type === "beforeinput",
     83     `"${aEvent.type}" event should ${aEvent.type === "beforeinput" ? "be" : "be never"} cancelable ${aDescription}`);
     84  is(aEvent.bubbles, true,
     85     `"${aEvent.type}" event should always bubble ${aDescription}`);
     86  is(aEvent.inputType, aInputType,
     87     `inputType of "${aEvent.type}" event should be "${aInputType}" ${aDescription}`);
     88  is(aEvent.data, aData,
     89     `data of "${aEvent.type}" event should be ${aData} ${aDescription}`);
     90  if (aDataTransfer === null) {
     91    is(aEvent.dataTransfer, null,
     92       `dataTransfer of "${aEvent.type}" event should be null ${aDescription}`);
     93  } else {
     94    for (let dataTransfer of aDataTransfer) {
     95      is(aEvent.dataTransfer.getData(dataTransfer.type), dataTransfer.data,
     96         `dataTransfer of "${aEvent.type}" should have "${dataTransfer.data}" whose type is "${dataTransfer.type}" ${aDescription}`);
     97    }
     98  }
     99  let targetRanges = aEvent.getTargetRanges();
    100  if (aTargetRanges.length === 0) {
    101    is(targetRanges.length, 0,
    102       `getTargetRange() of "${aEvent.type}" event should return empty array: ${aDescription}`);
    103  } else {
    104    is(targetRanges.length, aTargetRanges.length,
    105       `getTargetRange() of "${aEvent.type}" event should return static range array: ${aDescription}`);
    106    if (targetRanges.length == aTargetRanges.length) {
    107      for (let i = 0; i < targetRanges.length; i++) {
    108        is(targetRanges[i].startContainer, aTargetRanges[i].startContainer,
    109           `startContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
    110        is(targetRanges[i].startOffset, aTargetRanges[i].startOffset,
    111           `startOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
    112        is(targetRanges[i].endContainer, aTargetRanges[i].endContainer,
    113           `endContainer of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
    114        is(targetRanges[i].endOffset, aTargetRanges[i].endOffset,
    115           `endOffset of getTargetRanges()[${i}] of "${aEvent.type}" event does not match: ${aDescription}`);
    116      }
    117    }
    118  }
    119 }
    120 
    121 async function doTextareaTests(aTextarea) {
    122  let beforeInputEvents = [];
    123  let inputEvents = [];
    124  function onBeforeInput(aEvent) {
    125    beforeInputEvents.push(aEvent);
    126  }
    127  function onInput(aEvent) {
    128    inputEvents.push(aEvent);
    129  }
    130  aTextarea.addEventListener("beforeinput", onBeforeInput);
    131  aTextarea.addEventListener("input", onInput);
    132 
    133  await copyPlaintext("abc\ndef\nghi");
    134  aTextarea.focus();
    135  beforeInputEvents = [];
    136  inputEvents = [];
    137  synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
    138  is(aTextarea.value,
    139     "> abc\n> def\n> ghi\n\n",
    140     "Pasted each line should start with \"> \"");
    141  is(beforeInputEvents.length, 1,
    142     'One "beforeinput" event should be fired #1');
    143  checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\nghi", null, [], "#1");
    144  is(inputEvents.length, 1,
    145     'One "input" event should be fired #1');
    146  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\nghi", null, [], "#1");
    147  aTextarea.value = "";
    148 
    149  await copyPlaintext("> abc\n> def\n> ghi");
    150  aTextarea.focus();
    151  beforeInputEvents = [];
    152  inputEvents = [];
    153  synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
    154  is(aTextarea.value,
    155     ">> abc\n>> def\n>> ghi\n\n",
    156     "Pasted each line should be start with \">> \" when already quoted one level");
    157  is(beforeInputEvents.length, 1,
    158     'One "beforeinput" event should be fired #2');
    159  checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n> ghi", null, [], "#2");
    160  is(inputEvents.length, 1,
    161     'One "input" event should be fired #2');
    162  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n> ghi", null, [], "#2");
    163  aTextarea.value = "";
    164 
    165  await copyPlaintext("> abc\n> def\n\nghi");
    166  aTextarea.focus();
    167  beforeInputEvents = [];
    168  inputEvents = [];
    169  synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
    170  is(aTextarea.value,
    171     ">> abc\n>> def\n> \n> ghi\n\n",
    172     "Pasted each line should be start with \">> \" when already quoted one level");
    173  is(beforeInputEvents.length, 1,
    174     'One "beforeinput" event should be fired #3');
    175  checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n\nghi", null, [], "#3");
    176  is(inputEvents.length, 1,
    177     'One "input" event should be fired #3');
    178  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "> abc\n> def\n\nghi", null, [], "#3");
    179  aTextarea.value = "";
    180 
    181  await copyPlaintext("abc\ndef\n\n");
    182  aTextarea.focus();
    183  beforeInputEvents = [];
    184  inputEvents = [];
    185  synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
    186  is(aTextarea.value,
    187     "> abc\n> def\n> \n",
    188     "If pasted text ends with \"\\n\", only the last line should not started with \">\"");
    189  is(beforeInputEvents.length, 1,
    190     'One "beforeinput" event should be fired #4');
    191  checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\n\n", null, [], "#4");
    192  is(inputEvents.length, 1,
    193     'One "input" event should be fired #4');
    194  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\n\n", null, [], "#4");
    195  aTextarea.value = "";
    196 
    197  await copyPlaintext("abc\ndef\n\n");
    198  aTextarea.addEventListener("paste", (event) => { event.preventDefault(); }, {once: true});
    199  aTextarea.focus();
    200  beforeInputEvents = [];
    201  inputEvents = [];
    202  synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
    203  is(aTextarea.value, "",
    204     'Pasting as quote should have been canceled if "paste" event was canceled');
    205  is(beforeInputEvents.length, 0,
    206     'No "beforeinput" event should be fired since "paste" event was canceled #5');
    207  is(inputEvents.length, 0,
    208     'No "input" event should be fired since "paste" was canceled #5');
    209  aTextarea.value = "";
    210 
    211  await copyPlaintext("abc\ndef\n\n");
    212  aTextarea.addEventListener("beforeinput", (event) => { event.preventDefault(); }, {once: true});
    213  aTextarea.focus();
    214  beforeInputEvents = [];
    215  inputEvents = [];
    216  synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
    217  is(aTextarea.value, "",
    218     'Pasting as quote should have been canceled if "beforeinput" event was canceled');
    219  is(beforeInputEvents.length, 1,
    220     'One "beforeinput" event should be fired #5');
    221  checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", "abc\ndef\n\n", null, [], "#6");
    222  is(inputEvents.length, 0,
    223     'No "input" event should be fired since "beforeinput" was canceled #6');
    224  aTextarea.value = "";
    225 
    226  let pasteEventCount = 0;
    227  function pasteEventLogger() {
    228    pasteEventCount++;
    229  }
    230  aTextarea.addEventListener("paste", pasteEventLogger);
    231 
    232  await copyPlaintext("abc");
    233  aTextarea.focus();
    234  document.body.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
    235  beforeInputEvents = [];
    236  inputEvents = [];
    237  synthesizeMouseAtCenter(aTextarea, {button: 1});
    238  is(aTextarea.value, "abc",
    239     "If 'click' event is consumed at capturing phase of the <body>, paste should not be canceled");
    240  is(pasteEventCount, 1,
    241     "If 'click' event is consumed at capturing phase of the <body>, 'paste' event should still be fired");
    242  is(beforeInputEvents.length, 1,
    243     '"beforeinput" event should be fired when the "click" event is canceled');
    244  checkInputEvent(beforeInputEvents[0], "insertFromPaste", "abc", null, [], 'when the "click" event is canceled');
    245  is(inputEvents.length, 1,
    246     '"input" event should be fired when the "click" event is canceled');
    247  checkInputEvent(inputEvents[0], "insertFromPaste", "abc", null, [], 'when the "click" event is canceled');
    248  aTextarea.value = "";
    249 
    250  await copyPlaintext("abc");
    251  aTextarea.focus();
    252  aTextarea.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
    253  pasteEventCount = 0;
    254  beforeInputEvents = [];
    255  inputEvents = [];
    256  synthesizeMouseAtCenter(aTextarea, {button: 1});
    257  is(aTextarea.value, "abc",
    258     "Even if 'mouseup' event is consumed, paste should be done");
    259  is(pasteEventCount, 1,
    260     "Even if 'mouseup' event is consumed, 'paste' event should be fired once");
    261  is(beforeInputEvents.length, 1,
    262     'One "beforeinput" event should be fired even if "mouseup" event is canceled');
    263  checkInputEvent(beforeInputEvents[0], "insertFromPaste", "abc", null, [], 'even if "mouseup" event is canceled');
    264  is(inputEvents.length, 1,
    265     'One "input" event should be fired even if "mouseup" event is canceled');
    266  checkInputEvent(inputEvents[0], "insertFromPaste", "abc", null, [], 'even if "mouseup" event is canceled');
    267  aTextarea.value = "";
    268 
    269  await copyPlaintext("abc");
    270  aTextarea.focus();
    271  aTextarea.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
    272  pasteEventCount = 0;
    273  beforeInputEvents = [];
    274  inputEvents = [];
    275  synthesizeMouseAtCenter(aTextarea, {button: 1});
    276  is(aTextarea.value, "abc",
    277     "If 'click' event handler is added to the <textarea>, paste should not be canceled");
    278  is(pasteEventCount, 1,
    279     "If 'click' event handler is added to the <textarea>, 'paste' event should be fired once");
    280  is(beforeInputEvents.length, 1,
    281     'One "beforeinput" event should be fired even if "click" event is canceled in bubbling phase');
    282  checkInputEvent(beforeInputEvents[0], "insertFromPaste", "abc", null, [], 'even if "click" event is canceled in bubbling phase');
    283  is(inputEvents.length, 1,
    284     'One "input" event should be fired even if "click" event is canceled in bubbling phase');
    285  checkInputEvent(inputEvents[0], "insertFromPaste", "abc", null, [], 'even if "click" event is canceled in bubbling phase');
    286  aTextarea.value = "";
    287 
    288  await copyPlaintext("abc");
    289  aTextarea.focus();
    290  aTextarea.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
    291  pasteEventCount = 0;
    292  beforeInputEvents = [];
    293  inputEvents = [];
    294  synthesizeMouseAtCenter(aTextarea, {button: 1});
    295  is(aTextarea.value, "",
    296     "If 'auxclick' event is consumed, paste should be canceled");
    297  is(pasteEventCount, 0,
    298     "If 'auxclick' event is consumed, 'paste' event should not be fired once");
    299  is(beforeInputEvents.length, 0,
    300     'No "beforeinput" event should be fired if "auxclick" event is canceled');
    301  is(inputEvents.length, 0,
    302     'No "input" event should be fired if "auxclick" event is canceled');
    303  aTextarea.value = "";
    304 
    305  await copyPlaintext("abc");
    306  aTextarea.focus();
    307  aTextarea.addEventListener("paste", (event) => { event.preventDefault(); }, {once: true});
    308  pasteEventCount = 0;
    309  beforeInputEvents = [];
    310  inputEvents = [];
    311  synthesizeMouseAtCenter(aTextarea, {button: 1});
    312  is(aTextarea.value, "",
    313     "If 'paste' event is consumed, paste should be canceled");
    314  is(pasteEventCount, 1,
    315     'One "paste" event should be fired for making it possible to consume');
    316  is(beforeInputEvents.length, 0,
    317     'No "beforeinput" event should be fired if "paste" event is canceled');
    318  is(inputEvents.length, 0,
    319     'No "input" event should be fired if "paste" event is canceled');
    320  aTextarea.value = "";
    321 
    322  await copyPlaintext("abc");
    323  aTextarea.focus();
    324  aTextarea.addEventListener("beforeinput", (event) => { event.preventDefault(); }, {once: true});
    325  pasteEventCount = 0;
    326  beforeInputEvents = [];
    327  inputEvents = [];
    328  synthesizeMouseAtCenter(aTextarea, {button: 1});
    329  is(aTextarea.value, "",
    330     "If 'beforeinput' event is consumed, paste should be canceled");
    331  is(pasteEventCount, 1,
    332     'One "paste" event should be fired before "beforeinput" event is consumed');
    333  is(beforeInputEvents.length, 1,
    334     'One "beforeinput" event should be fired for making it possible to consume');
    335  checkInputEvent(beforeInputEvents[0], "insertFromPaste", "abc", null, [], 'when "beforeinput" is canceled in bubbling phase');
    336  is(inputEvents.length, 0,
    337     'No "input" event should be fired if "paste" event is canceled');
    338  aTextarea.value = "";
    339 
    340  aTextarea.removeEventListener("paste", pasteEventLogger);
    341  aTextarea.removeEventListener("beforeinput", onBeforeInput);
    342  aTextarea.removeEventListener("input", onInput);
    343 }
    344 
    345 async function doContenteditableTests(aEditableDiv) {
    346  let beforeInputEvents = [];
    347  let inputEvents = [];
    348  let selectionRanges = [];
    349  function onBeforeInput(aEvent) {
    350    beforeInputEvents.push(aEvent);
    351    let selection = document.getSelection();
    352    selectionRanges = [];
    353    for (let i = 0; i < selection.rangeCount; i++) {
    354      let range = selection.getRangeAt(i);
    355      selectionRanges.push({startContainer: range.startContainer, startOffset: range.startOffset,
    356                            endContainer: range.endContainer, endOffset: range.endOffset});
    357    }
    358  }
    359  function onInput(aEvent) {
    360    inputEvents.push(aEvent);
    361  }
    362  aEditableDiv.addEventListener("beforeinput", onBeforeInput);
    363  aEditableDiv.addEventListener("input", onInput);
    364 
    365  await copyPlaintext("abc\ndef\nghi");
    366  aEditableDiv.focus();
    367  beforeInputEvents = [];
    368  inputEvents = [];
    369  synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
    370  is(aEditableDiv.innerHTML,
    371     "<blockquote type=\"cite\">abc<br>def<br>ghi</blockquote>",
    372     "Pasted plaintext should be in <blockquote> element and each linebreaker should be <br> element");
    373  is(beforeInputEvents.length, 1,
    374     'One "beforeinput" event should be fired on the editing host');
    375  checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", null,
    376                  [{type: "text/plain", data: "abc\ndef\nghi"}], selectionRanges, "(contenteditable)");
    377  is(inputEvents.length, 1,
    378     'One "input" event should be fired on the editing host');
    379  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", null,
    380                  [{type: "text/plain", data: "abc\ndef\nghi"}], [], "(contenteditable)");
    381  aEditableDiv.innerHTML = "";
    382 
    383  let pasteEventCount = 0;
    384  function pasteEventLogger() {
    385    pasteEventCount++;
    386  }
    387  aEditableDiv.addEventListener("paste", pasteEventLogger);
    388 
    389  await copyPlaintext("abc");
    390  aEditableDiv.focus();
    391  window.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
    392  beforeInputEvents = [];
    393  inputEvents = [];
    394  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
    395  is(aEditableDiv.innerHTML, "abc",
    396     "If 'click' event is consumed at capturing phase of the window, paste should not be canceled");
    397  is(pasteEventCount, 1,
    398     "If 'click' event is consumed at capturing phase of the window, 'paste' event should be fired once");
    399  is(beforeInputEvents.length, 1,
    400     '"beforeinput" event should still be fired when the "click" event is canceled (contenteditable)');
    401  checkInputEvent(beforeInputEvents[0], "insertFromPaste", null,
    402                  [{type: "text/plain", data: "abc"}], selectionRanges, 'when the "click" event is canceled (contenteditable)');
    403  is(inputEvents.length, 1,
    404     '"input" event should still be fired when the "click" event is canceled (contenteditable)');
    405  checkInputEvent(inputEvents[0], "insertFromPaste", null,
    406                  [{type: "text/plain", data: "abc"}], [], 'when the "click" event is canceled (contenteditable)');
    407  aEditableDiv.innerHTML = "";
    408 
    409  await copyPlaintext("abc");
    410  aEditableDiv.focus();
    411  aEditableDiv.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
    412  pasteEventCount = 0;
    413  beforeInputEvents = [];
    414  inputEvents = [];
    415  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
    416  is(aEditableDiv.innerHTML, "abc",
    417     "Even if 'mouseup' event is consumed, paste should be done");
    418  is(pasteEventCount, 1,
    419     "Even if 'mouseup' event is consumed, 'paste' event should be fired once");
    420  is(beforeInputEvents.length, 1,
    421     'One "beforeinput" event should be fired even if "mouseup" event is canceled (contenteditable)');
    422  checkInputEvent(beforeInputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], selectionRanges,
    423                  'even if "mouseup" event is canceled (contenteditable)');
    424  is(inputEvents.length, 1,
    425     'One "input" event should be fired even if "mouseup" event is canceled (contenteditable)');
    426  checkInputEvent(inputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], [],
    427                  'even if "mouseup" event is canceled (contenteditable)');
    428  aEditableDiv.innerHTML = "";
    429 
    430  await copyPlaintext("abc");
    431  aEditableDiv.focus();
    432  aEditableDiv.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
    433  pasteEventCount = 0;
    434  beforeInputEvents = [];
    435  inputEvents = [];
    436  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
    437  is(aEditableDiv.innerHTML, "abc",
    438     "Even if 'click' event handler is added to the editing host, paste should not be canceled");
    439  is(pasteEventCount, 1,
    440     "Even if 'click' event handler is added to the editing host, 'paste' event should be fired");
    441  is(beforeInputEvents.length, 1,
    442     'One "beforeinput" event should be fired even if "click" event is canceled in bubbling phase (contenteditable)');
    443  checkInputEvent(beforeInputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], selectionRanges,
    444                  'even if "click" event is canceled in bubbling phase (contenteditable)');
    445  is(inputEvents.length, 1,
    446     'One "input" event should be fired even if "click" event is canceled in bubbling phase (contenteditable)');
    447  checkInputEvent(inputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], [],
    448                  'even if "click" event is canceled in bubbling phase (contenteditable)');
    449  aEditableDiv.innerHTML = "";
    450 
    451  await copyPlaintext("abc");
    452  aEditableDiv.focus();
    453  aEditableDiv.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
    454  pasteEventCount = 0;
    455  beforeInputEvents = [];
    456  inputEvents = [];
    457  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
    458  is(aEditableDiv.innerHTML, "",
    459     "If 'auxclick' event is consumed, paste should be canceled");
    460  is(pasteEventCount, 0,
    461     "If 'auxclick' event is consumed, 'paste' event should not be fired");
    462  is(beforeInputEvents.length, 0,
    463     'No "beforeinput" event should be fired if "auxclick" event is canceled (contenteditable)');
    464  is(inputEvents.length, 0,
    465     'No "input" event should be fired if "auxclick" event is canceled (contenteditable)');
    466  aEditableDiv.innerHTML = "";
    467 
    468  await copyPlaintext("abc");
    469  aEditableDiv.focus();
    470  aEditableDiv.addEventListener("paste", (event) => { event.preventDefault(); }, {once: true});
    471  pasteEventCount = 0;
    472  beforeInputEvents = [];
    473  inputEvents = [];
    474  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
    475  is(aEditableDiv.innerHTML, "",
    476     "If 'paste' event is consumed, paste should be canceled");
    477  is(pasteEventCount, 1,
    478     'One "paste" event should be fired for making it possible to consume');
    479  is(beforeInputEvents.length, 0,
    480     'No "beforeinput" event should be fired if "paste" event is canceled (contenteditable)');
    481  is(inputEvents.length, 0,
    482     'No "input" event should be fired if "paste" event is canceled (contenteditable)');
    483  aEditableDiv.innerHTML = "";
    484 
    485  await copyPlaintext("abc");
    486  aEditableDiv.focus();
    487  aEditableDiv.addEventListener("beforeinput", (event) => { event.preventDefault(); }, {once: true});
    488  pasteEventCount = 0;
    489  beforeInputEvents = [];
    490  inputEvents = [];
    491  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
    492  is(aEditableDiv.innerHTML, "",
    493     "If 'paste' event is consumed, paste should be canceled");
    494  is(pasteEventCount, 1,
    495     'One "paste" event should be fired before "beforeinput" event');
    496  is(beforeInputEvents.length, 1,
    497     'One "beforeinput" event should be fired for making it possible to consume (contenteditable)');
    498  checkInputEvent(beforeInputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: "abc"}], selectionRanges,
    499                  'when "beforeinput" will be canceled (contenteditable)');
    500  is(inputEvents.length, 0,
    501     'No "input" event should be fired if "beforeinput" event is canceled (contenteditable)');
    502  aEditableDiv.innerHTML = "";
    503 
    504  // If clipboard event is disabled, InputEvent.dataTransfer should have only empty string.
    505  await SpecialPowers.pushPrefEnv({"set": [["dom.event.clipboardevents.enabled", false]]});
    506  await copyPlaintext("abc");
    507  aEditableDiv.focus();
    508  pasteEventCount = 0;
    509  beforeInputEvents = [];
    510  inputEvents = [];
    511  synthesizeMouseAtCenter(aEditableDiv, {button: 1});
    512  is(aEditableDiv.innerHTML, "abc",
    513     "Even if clipboard event is disabled, paste should be done");
    514  is(pasteEventCount, 0,
    515     "If clipboard event is disabled, 'paste' event shouldn't be fired once");
    516  is(beforeInputEvents.length, 1,
    517     'One "beforeinput" event should be fired even if clipboard event is disabled (contenteditable)');
    518  checkInputEvent(beforeInputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: ""}], selectionRanges,
    519                  "when clipboard event is disabled (contenteditable)");
    520  is(inputEvents.length, 1,
    521     'One "input" event should be fired even if clipboard event is disabled (contenteditable)');
    522  checkInputEvent(inputEvents[0], "insertFromPaste", null, [{type: "text/plain", data: ""}], [],
    523                  "when clipboard event is disabled (contenteditable)");
    524  await SpecialPowers.pushPrefEnv({"set": [["dom.event.clipboardevents.enabled", true]]});
    525  aEditableDiv.innerHTML = "";
    526 
    527  aEditableDiv.removeEventListener("paste", pasteEventLogger);
    528 
    529  // Oddly, copyHTMLContent fails randomly only on Linux.  Let's skip this.
    530  if (navigator.platform.startsWith("Linux")) {
    531    aEditableDiv.removeEventListener("input", onInput);
    532    return;
    533  }
    534 
    535  await copyHTMLContent("<p>abc</p><p>def</p><p>ghi</p>");
    536  aEditableDiv.focus();
    537  beforeInputEvents = [];
    538  inputEvents = [];
    539  synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
    540  if (!navigator.appVersion.includes("Android")) {
    541    is(aEditableDiv.innerHTML,
    542       "<blockquote type=\"cite\"><p>abc</p><p>def</p><p>ghi</p></blockquote>",
    543       "Pasted HTML content should be set to the <blockquote>");
    544  } else {
    545    // Oddly, on Android, we use <br> elements for pasting <p> elements.
    546    is(aEditableDiv.innerHTML,
    547       "<blockquote type=\"cite\">abc<br><br>def<br><br>ghi</blockquote>",
    548       "Pasted HTML content should be set to the <blockquote>");
    549  }
    550  // On windows, HTML clipboard includes extra data.
    551  // The values are from widget/windows/nsDataObj.cpp.
    552  const kHTMLPrefix = (navigator.platform.includes("Win")) ? kTextHtmlPrefixClipboardDataWindows : "";
    553  const kHTMLPostfix = (navigator.platform.includes("Win")) ? kTextHtmlSuffixClipboardDataWindows : "";
    554  is(beforeInputEvents.length, 1,
    555     'One "beforeinput" event should be fired when pasting HTML');
    556  checkInputEvent(beforeInputEvents[0], "insertFromPasteAsQuotation", null,
    557                  [{type: "text/html",
    558                    data: `${kHTMLPrefix}<p>abc</p><p>def</p><p>ghi</p>${kHTMLPostfix}`}],
    559                  selectionRanges,
    560                  "when pasting HTML");
    561  is(inputEvents.length, 1,
    562     'One "input" event should be fired when pasting HTML');
    563  checkInputEvent(inputEvents[0], "insertFromPasteAsQuotation", null,
    564                  [{type: "text/html",
    565                    data: `${kHTMLPrefix}<p>abc</p><p>def</p><p>ghi</p>${kHTMLPostfix}`}],
    566                  [],
    567                  "when pasting HTML");
    568  aEditableDiv.innerHTML = "";
    569 
    570  aEditableDiv.removeEventListener("beforeinput", onBeforeInput);
    571  aEditableDiv.removeEventListener("input", onInput);
    572 }
    573 
    574 async function doNestedEditorTests(aEditableDiv) {
    575  await copyPlaintext("CLIPBOARD TEXT");
    576  aEditableDiv.innerHTML = '<p id="p">foo</p><textarea id="textarea"></textarea>';
    577  aEditableDiv.focus();
    578  let textarea = document.getElementById("textarea");
    579  let pasteTarget = null;
    580  function onPaste(aEvent) {
    581    pasteTarget = aEvent.target;
    582  }
    583  document.addEventListener("paste", onPaste);
    584 
    585  synthesizeMouseAtCenter(textarea, {button: 1});
    586  is(pasteTarget.getAttribute("id"), "textarea",
    587     "Target of 'paste' event should be the clicked <textarea>");
    588  is(textarea.value, "CLIPBOARD TEXT",
    589     "Clicking in <textarea> in an editable <div> should paste the clipboard text into the <textarea>");
    590  is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea"></textarea>',
    591     "Pasting in the <textarea> shouldn't be handled by the HTMLEditor");
    592 
    593  textarea.value = "";
    594  textarea.readOnly = true;
    595  pasteTarget = null;
    596  synthesizeMouseAtCenter(textarea, {button: 1});
    597  is(pasteTarget, textarea,
    598     "Target of 'paste' event should be the clicked <textarea> even if it's read-only");
    599  is(textarea.value, "",
    600     "Clicking in read-only <textarea> in an editable <div> should not paste the clipboard text into the read-only <textarea>");
    601  // HTMLEditor thinks that read-only <textarea> is not modifiable.
    602  // Therefore, HTMLEditor does not paste the text.
    603  is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea" readonly=""></textarea>',
    604     "Clicking in read-only <textarea> shouldn't cause pasting the clipboard text into its parent HTMLEditor");
    605 
    606  textarea.value = "";
    607  textarea.readOnly = false;
    608  textarea.disabled = true;
    609  pasteTarget = null;
    610  synthesizeMouseAtCenter(textarea, {button: 1});
    611  // Although, this compares with <textarea>, I'm not sure it's proper event
    612  // target because of disabled <textarea>.
    613  todo_is(pasteTarget, textarea,
    614          "Target of 'paste' event should be the clicked <textarea> even if it's disabled");
    615  is(textarea.value, "",
    616     "Clicking in disabled <textarea> in an editable <div> should not paste the clipboard text into the disabled <textarea>");
    617  // HTMLEditor thinks that disabled <textarea> is not modifiable.
    618  // Therefore, HTMLEditor does not paste the text.
    619  is(aEditableDiv.innerHTML, '<p id="p">foo</p><textarea id="textarea" disabled=""></textarea>',
    620     "Clicking in disabled <textarea> shouldn't cause pasting the clipboard text into its parent HTMLEditor");
    621 
    622  document.removeEventListener("paste", onPaste);
    623  aEditableDiv.innerHTML = "";
    624 }
    625 
    626 async function doAfterRemoveOfClickedElementTest(aEditableDiv) {
    627  await copyPlaintext("CLIPBOARD TEXT");
    628  aEditableDiv.innerHTML = '<p id="p">foo<span id="span">bar</span></p>';
    629  aEditableDiv.focus();
    630  let span = document.getElementById("span");
    631  let pasteTarget = null;
    632  document.addEventListener("paste", (aEvent) => { pasteTarget = aEvent.target; }, {once: true});
    633  document.addEventListener("auxclick", (aEvent) => {
    634    is(aEvent.target.getAttribute("id"), "span",
    635       "Target of auxclick event should be the <span> element");
    636    span.parentElement.removeChild(span);
    637  }, {once: true});
    638  synthesizeMouseAtCenter(span, {button: 1});
    639  is(pasteTarget.getAttribute("id"), "p",
    640     "Target of 'paste' event should be the <p> element since <span> has gone");
    641  // XXX Currently, pasted to start of the <p> because EventStateManager
    642  //     do not recompute event target frame.
    643  todo_is(aEditableDiv.innerHTML, '<p id="p">fooCLIPBOARD TEXT</p>',
    644          "Clipbpard text should looks like replacing the <span> element");
    645  aEditableDiv.innerHTML = "";
    646 }
    647 
    648 async function doNotStartAutoscrollInContentEditable(aEditableDiv) {
    649  await SpecialPowers.pushPrefEnv({"set": [["general.autoScroll", true]]});
    650  await copyPlaintext("CLIPBOARD TEXT");
    651  aEditableDiv.innerHTML = '<p id="p">foo<span id="span">bar</span></p>';
    652  aEditableDiv.focus();
    653  let span = document.getElementById("span");
    654  synthesizeMouseAtCenter(span, {button: 1});
    655  ok(aEditableDiv.innerHTML.includes("CLIPBOARD TEXT"),
    656    "Clipbpard text should be inserted");
    657  aEditableDiv.innerHTML = "";
    658 }
    659 
    660 async function doTests() {
    661  await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", true],
    662                                           ["middlemouse.contentLoadURL", false],
    663                                           ["dom.event.clipboardevents.enabled", true]]});
    664  let container = document.getElementById("container");
    665  container.innerHTML = "<textarea id=\"editor\"></textarea>";
    666  await doTextareaTests(document.getElementById("editor"));
    667  container.innerHTML = "<div id=\"editor\" contenteditable style=\"min-height: 1em;\"></div>";
    668  await doContenteditableTests(document.getElementById("editor"));
    669  await doNestedEditorTests(document.getElementById("editor"));
    670  await doAfterRemoveOfClickedElementTest(document.getElementById("editor"));
    671  // NOTE: The following test sets `general.autoScroll` to true.
    672  await doNotStartAutoscrollInContentEditable(document.getElementById("editor"));
    673  SimpleTest.finish();
    674 }
    675 
    676 SimpleTest.waitForFocus(doTests);
    677 </script>
    678 </pre>
    679 </body>
    680 </html>