tor-browser

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

window_clipboard_events.html (46557B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <meta charset="utf-8">
      5  <title>Test for Clipboard Events</title>
      6  <script>
      7    var SimpleTest = opener.SimpleTest;
      8    var SpecialPowers = opener.SpecialPowers;
      9    var ok = opener.ok;
     10    var is = opener.is;
     11    var isnot = opener.isnot;
     12    var todo = opener.todo;
     13    var todo_is = opener.todo_is;
     14    var add_task = opener.add_task;
     15  </script>
     16  <script src="/tests/SimpleTest/EventUtils.js"></script>
     17  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
     18 </head>
     19 <body>
     20 <p id="display"></p>
     21 <div id="content" style="border: 3px solid black; padding: 3em;">CONTENT TEXT<input id="content-input" value="INPUT TEXT"></div>
     22 <img id="image" src="image_50.png">
     23 <button id="button">Button</button>
     24 
     25 <div id="syntheticSpot" oncut="compareSynthetic(event, 'cut')"
     26                        oncopy="compareSynthetic(event, 'copy')"
     27                        onpaste="compareSynthetic(event, 'paste')">Spot</div>
     28 
     29 <div id="contenteditableContainer"></div>
     30 
     31 <pre id="test">
     32 <script class="testbody" type="text/javascript">
     33 var content = document.getElementById("content");
     34 var contentInput = document.getElementById("content-input");
     35 var contenteditableContainer = document.getElementById("contenteditableContainer");
     36 var clipboardInitialValue = "empty";
     37 
     38 var cachedCutData, cachedCopyData, cachedPasteData;
     39 
     40 ok(SpecialPowers.getBoolPref("dom.events.dataTransfer.protected.enabled"),
     41   "The following require dom.events.dataTransfer.protected.enabled is enabled");
     42 isnot(typeof window.onbeforeinput, "undefined",
     43      "The following tests require onbeforeinput attribute");
     44 
     45 // Before each test function is run, the clipboard is initialized
     46 // to clipboardInitialValue, and the contents of div#content are
     47 // set as the window's selection.
     48 
     49 add_task(async function initialize_for_tests() {
     50  disableNonTestMouseEvents(true);
     51 
     52  await SimpleTest.promiseFocus(window);
     53 
     54  // Test that clearing and reading the clipboard works.  A random number
     55  // is used to make sure that leftover clipboard values from a previous
     56  // test run don't cause a false-positive test.
     57  try {
     58    var cb_text = "empty_" + Math.random();
     59    await putOnClipboard(cb_text, () => { setClipboardText(cb_text) },
     60                         "Failed to initial set/get clipboard text");
     61  } catch (e) {
     62    ok(false, e.toString());
     63  }
     64 });
     65 
     66 var eventListeners = [];
     67 
     68 // Note that don't use EventTarget.addEventListener directly in this file
     69 // because if it's remained in next test, it makes developers harder to
     70 // investigate such oranges. addEventListenerTo cleans up when `reset()`
     71 // is called by first of next test and then, all event listeners including
     72 // marked as "once" are removed automatically.
     73 function addEventListenerTo(aEventTarget, aEventType, aListener, aOptions) {
     74  eventListeners.push({
     75    target: aEventTarget,
     76    type: aEventType,
     77    listener: aListener,
     78    options: aOptions
     79  });
     80  aEventTarget.addEventListener(aEventType, aListener, aOptions);
     81 }
     82 
     83 async function reset() {
     84  [content, contentInput, document, document.documentElement].forEach(eventTarget => {
     85    ["oncut", "oncopy", "onpaste", "oninput", "onbeforeinput"].forEach(attr => {
     86      eventTarget[attr] = null;
     87    });
     88  });
     89  eventListeners.forEach(data => {
     90    data.target.removeEventListener(data.type, data.listener, data.options);
     91  });
     92  eventListeners = [];
     93 
     94  // Init clipboard
     95  await putOnClipboard(clipboardInitialValue,
     96                       () => { setClipboardText(clipboardInitialValue) },
     97                       "reset clipboard");
     98 
     99  // Reset value of editors.
    100  contentInput.value = "INPUT TEXT";
    101  contenteditableContainer.innerHTML = "";
    102 }
    103 
    104 function getClipboardText() {
    105  return SpecialPowers.getClipboardData("text/plain");
    106 }
    107 
    108 function getHTMLEditor() {
    109  let editingSession = SpecialPowers.wrap(window).docShell.editingSession;
    110  if (!editingSession) {
    111    return null;
    112  }
    113  let editor = editingSession.getEditorForWindow(window);
    114  if (!editor) {
    115    return null;
    116  }
    117  return editor.QueryInterface(SpecialPowers.Ci.nsIHTMLEditor);
    118 }
    119 
    120 async function putOnClipboard(expected, operationFn, desc, type) {
    121  try {
    122    await SimpleTest.promiseClipboardChange(expected, operationFn, type, 1000);
    123  } catch (e) {
    124    throw `Failed "${desc}" due to "${e.toString()}"`
    125  }
    126 }
    127 
    128 async function wontPutOnClipboard(unexpectedData, operationFn, desc, type) {
    129  try {
    130    // SimpleTest.promiseClipboardChange() doesn't throw exception when
    131    // it unexpectedly succeeds to copy something.  Therefore, we need
    132    // to throw an exception by ourselves.
    133    await SimpleTest.promiseClipboardChange(null, operationFn, type, 300, true, false)
    134                    .then(aData => {
    135                      if (aData == unexpectedData) {
    136                        throw `Failed "${desc}", the clipboard data is modified to "${aData.toString()}"`;
    137                      }
    138                    });
    139  } catch (e) {
    140    throw `Failed "${desc}" due to "${e.toString()}"`
    141  }
    142 }
    143 
    144 function setClipboardText(text) {
    145  var helper = SpecialPowers.Cc["@mozilla.org/widget/clipboardhelper;1"]
    146    .getService(SpecialPowers.Ci.nsIClipboardHelper);
    147  helper.copyString(text);
    148 }
    149 
    150 function selectContentDiv() {
    151  // Set selection
    152  var selection = window.getSelection();
    153  selection.removeAllRanges();
    154  selection.selectAllChildren(content);
    155 }
    156 
    157 function selectContentInput() {
    158  contentInput.focus();
    159  contentInput.select();
    160 }
    161 
    162 add_task(async function test_dom_oncopy() {
    163  try {
    164    await reset();
    165  } catch (e) {
    166    ok(false, `Failed to reset (${e.toString()})`);
    167    return;
    168  }
    169 
    170  // Setup an oncopy event handler, fire copy.  Ensure that the event
    171  // handler was called, and the clipboard contents have set to CONTENT TEXT.
    172  // Test firing oncopy event on ctrl-c:
    173  selectContentDiv();
    174 
    175  var oncopy_fired = false;
    176  content.oncopy = function() { oncopy_fired = true; };
    177  try {
    178    await putOnClipboard("CONTENT TEXT", () => {
    179      synthesizeKey("c", {accelKey: 1});
    180    }, "copy on DOM element set clipboard correctly");
    181    ok(oncopy_fired, "copy event firing on DOM element");
    182  } catch (e) {
    183    ok(false, e.toString());
    184  }
    185 });
    186 
    187 add_task(async function test_dom_oncut() {
    188  try {
    189    await reset();
    190  } catch (e) {
    191    ok(false, `Failed to reset (${e.toString()})`);
    192    return;
    193  }
    194 
    195  // Setup an oncut event handler, fire cut.  Ensure that the event handler
    196  // was called.  The <div> doesn't handle a cut, so ensure that the
    197  // clipboard text shouldn't be "CONTENT TEXT".
    198  selectContentDiv();
    199  var oncut_fired = false;
    200  content.oncut = function() { oncut_fired = true; };
    201  try {
    202    await wontPutOnClipboard("CONTENT TEXT", () => {
    203      synthesizeKey("x", {accelKey: 1});
    204    }, "cut on DOM element set clipboard correctly");
    205    ok(oncut_fired, "cut event firing on DOM element")
    206  } catch (e) {
    207    ok(false, e.toString());
    208  }
    209 });
    210 
    211 add_task(async function test_dom_onpaste() {
    212  try {
    213    await reset();
    214  } catch (e) {
    215    ok(false, `Failed to reset (${e.toString()})`);
    216    return;
    217  }
    218 
    219  // Setup an onpaste event handler, fire paste.  Ensure that the event
    220  // handler was called.
    221  selectContentDiv();
    222  var onpaste_fired = false;
    223  content.onpaste = function() { onpaste_fired = true; };
    224  synthesizeKey("v", {accelKey: 1});
    225  ok(onpaste_fired, "paste event firing on DOM element");
    226 });
    227 
    228 add_task(async function test_dom_oncopy_abort() {
    229  try {
    230    await reset();
    231  } catch (e) {
    232    ok(false, `Failed to reset (${e.toString()})`);
    233    return;
    234  }
    235 
    236  // Setup an oncopy event handler that aborts the copy, and fire the copy
    237  // event.  Ensure that the event handler was fired, and the clipboard
    238  // contents have not been modified.
    239  selectContentDiv();
    240  var oncopy_fired = false;
    241  content.oncopy = function() { oncopy_fired = true; return false; };
    242  try {
    243    await wontPutOnClipboard("CONTENT TEXT", () => {
    244      synthesizeKey("c", {accelKey: 1});
    245    }, "aborted copy on DOM element did not modify clipboard");
    246    ok(oncopy_fired, "copy event (to-be-cancelled) firing on DOM element");
    247  } catch (e) {
    248    ok(false, e.toString());
    249  }
    250 });
    251 
    252 add_task(async function test_input_oncopy() {
    253  try {
    254    await reset();
    255  } catch (e) {
    256    ok(false, `Failed to reset (${e.toString()})`);
    257    return;
    258  }
    259 
    260  // Setup an oncopy event handler, fire copy.  Ensure that the event
    261  // handler was called, and the clipboard contents have been set to 'PUT TE',
    262  // which is the part that is selected below.
    263  selectContentInput();
    264  contentInput.focus();
    265  contentInput.setSelectionRange(2, 8);
    266 
    267  let oncopy_fired = false;
    268  let onbeforeinput_fired = false;
    269  let oninput_fired = false;
    270  contentInput.oncopy = () => { oncopy_fired = true; };
    271  contentInput.onbeforeinput = () => { onbeforeinput = true; };
    272  contentInput.oninput = () => { oninput_fired = true; };
    273  try {
    274    await putOnClipboard("PUT TE", () => {
    275      synthesizeKey("c", {accelKey: 1});
    276    }, "copy on plaintext editor set clipboard correctly");
    277    ok(oncopy_fired, "copy event firing on plaintext editor");
    278    ok(!onbeforeinput_fired, "beforeinput event shouldn't be fired on plaintext editor by copy");
    279    ok(!oninput_fired, "input event shouldn't be fired on plaintext editor by copy");
    280  } catch (e) {
    281    ok(false, e.toString());
    282  }
    283 });
    284 
    285 add_task(async function test_input_oncut() {
    286  try {
    287    await reset();
    288  } catch (e) {
    289    ok(false, `Failed to reset (${e.toString()})`);
    290    return;
    291  }
    292 
    293  // Setup an oncut event handler, and fire cut.  Ensure that the event
    294  // handler was fired, the clipboard contains the INPUT TEXT, and
    295  // that the input itself is empty.
    296  selectContentInput();
    297  let oncut_fired = false;
    298  let beforeInputEvents = [];
    299  let inputEvents = [];
    300  contentInput.oncut = () => { oncut_fired = true; };
    301  contentInput.onbeforeinput = (aEvent) => { beforeInputEvents.push(aEvent); }
    302  contentInput.oninput = (aEvent) => { inputEvents.push(aEvent); }
    303  try {
    304    await putOnClipboard("INPUT TEXT", () => {
    305      synthesizeKey("x", {accelKey: 1});
    306    }, "cut on plaintext editor set clipboard correctly");
    307    ok(oncut_fired, "cut event firing on plaintext editor");
    308    is(beforeInputEvents.length, 1, '"beforeinput" event should be fired once by cut');
    309    if (beforeInputEvents.length) {
    310      is(beforeInputEvents[0].inputType, "deleteByCut", '"inputType" of "beforeinput" event should be "deleteByCut"');
    311      is(beforeInputEvents[0].cancelable, true, '"beforeinput" event for "deleteByCut" should be cancelable');
    312      is(beforeInputEvents[0].data, null, '"data" of "beforeinput" event for "deleteByCut" should be null');
    313      is(beforeInputEvents[0].dataTransfer, null, '"dataTransfer" of "beforeinput" event for "deleteByCut" should be null');
    314      is(beforeInputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "beforeinput" event for "deleteByCut" should return empty array');
    315    }
    316    is(inputEvents.length, 1, '"input" event should be fired once by cut');
    317    if (inputEvents.length) {
    318      is(inputEvents[0].inputType, "deleteByCut", '"inputType" of "input" event should be "deleteByCut"');
    319      is(inputEvents[0].cancelable, false, '"input" event for "deleteByCut" should not be cancelable');
    320      is(inputEvents[0].data, null, '"data" of "input" event for "deleteByCut" should be null');
    321      is(inputEvents[0].dataTransfer, null, '"dataTransfer" of "input" event for "deleteByCut" should be null');
    322      is(inputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "input" event for "deleteByCut" should return empty array');
    323    }
    324    is(contentInput.value, "",
    325      "cut on plaintext editor emptied editor");
    326  } catch (e) {
    327    ok(false, e.toString());
    328  }
    329 });
    330 
    331 add_task(async function test_input_onpaste() {
    332  try {
    333    await reset();
    334  } catch (e) {
    335    ok(false, `Failed to reset (${e.toString()})`);
    336    return;
    337  }
    338 
    339  // Setup an onpaste event handler, and fire paste.  Ensure that the event
    340  // handler was fired, the clipboard contents didn't change, and that the
    341  // input value did change (ie. paste succeeded).
    342  selectContentInput();
    343  let onpaste_fired = false;
    344  let beforeInputEvents = [];
    345  let inputEvents = [];
    346  contentInput.onpaste = () => { onpaste_fired = true; };
    347  contentInput.onbeforeinput = (aEvent) => { beforeInputEvents.push(aEvent); }
    348  contentInput.oninput = (aEvent) => { inputEvents.push(aEvent); }
    349 
    350  synthesizeKey("v", {accelKey: 1});
    351  ok(onpaste_fired, "paste event firing on plaintext editor");
    352  is(getClipboardText(), clipboardInitialValue,
    353    "paste on plaintext editor did not modify clipboard contents");
    354  is(beforeInputEvents.length, 1, '"beforeinput" event should be fired once by paste');
    355  if (beforeInputEvents.length) {
    356    is(beforeInputEvents[0].inputType, "insertFromPaste", '"inputType" of "beforeinput" event should be "insertFromPaste"');
    357    is(beforeInputEvents[0].cancelable, true, '"beforeinput" event for "insertFromPaste" should be cancelable');
    358    is(beforeInputEvents[0].data, clipboardInitialValue, `"data" of "beforeinput" event for "insertFromPaste" should be "${clipboardInitialValue}"`);
    359    is(beforeInputEvents[0].dataTransfer, null, '"dataTransfer" of "beforeinput" event for "insertFromPaste" should be null');
    360    is(beforeInputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "beforeinput" event for "insertFromPaste" should return empty array');
    361  }
    362  is(inputEvents.length, 1, '"input" event should be fired once by paste');
    363  if (inputEvents.length) {
    364    is(inputEvents[0].inputType, "insertFromPaste", '"inputType" of "input" event should be "insertFromPaste"');
    365    is(inputEvents[0].cancelable, false, '"input" event for "insertFromPaste" should not be cancelable');
    366    is(inputEvents[0].data, clipboardInitialValue, `"data" of "input" event for "insertFromPaste" should be "${clipboardInitialValue}"`);
    367    is(inputEvents[0].dataTransfer, null, '"dataTransfer" of "input" event for "insertFromPaste" should be null');
    368    is(inputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "input" event for "insertFromPaste" should return empty array');
    369  }
    370  is(contentInput.value, clipboardInitialValue,
    371    "paste on plaintext editor did modify editor value");
    372 });
    373 
    374 add_task(async function test_input_oncopy_abort() {
    375  try {
    376    await reset();
    377  } catch (e) {
    378    ok(false, `Failed to reset (${e.toString()})`);
    379    return;
    380  }
    381 
    382  // Setup an oncopy event handler, fire copy.  Ensure that the event
    383  // handler was called, and that the clipboard value did NOT change.
    384  selectContentInput();
    385  let oncopy_fired = false;
    386  contentInput.oncopy = () => { oncopy_fired = true; return false; };
    387  contentInput.onbeforeinput = () => {
    388    ok(false, '"beforeinput" event should not be fired by copy but canceled');
    389  };
    390  contentInput.oninput = function() {
    391    ok(false, '"input" event should not be fired by copy but canceled');
    392  };
    393  try {
    394    await wontPutOnClipboard("CONTENT TEXT", () => {
    395      synthesizeKey("c", {accelKey: 1});
    396    }, "aborted copy on plaintext editor did not modify clipboard");
    397    ok(oncopy_fired, "copy event (to-be-cancelled) firing on plaintext editor");
    398  } catch (e) {
    399    ok(false, e.toString());
    400  }
    401 });
    402 
    403 add_task(async function test_input_oncut_abort() {
    404  try {
    405    await reset();
    406  } catch (e) {
    407    ok(false, `Failed to reset (${e.toString()})`);
    408    return;
    409  }
    410 
    411  // Setup an oncut event handler, and fire cut.  Ensure that the event
    412  // handler was fired, the clipboard contains the INPUT TEXT, and
    413  // that the input itself is empty.
    414  selectContentInput();
    415  let oncut_fired = false;
    416  contentInput.oncut = () => { oncut_fired = true; return false; };
    417  contentInput.onbeforeinput = () => {
    418    ok(false, '"beforeinput" event should not be fired by cut but canceled by "cut" event listener');
    419  };
    420  contentInput.oninput = () => {
    421    ok(false, '"input" event should not be fired by cut but canceled by "cut" event listener');
    422  };
    423  try {
    424    await wontPutOnClipboard("CONTENT TEXT", () => {
    425      synthesizeKey("x", {accelKey: 1});
    426    }, "aborted cut on plaintext editor did not modify clipboard");
    427    ok(oncut_fired, "cut event (to-be-cancelled) firing on plaintext editor");
    428    is(contentInput.value, "INPUT TEXT",
    429      "aborted cut on plaintext editor did not modify editor contents");
    430  } catch (e) {
    431    ok(false, e.toString());
    432  }
    433 });
    434 
    435 add_task(async function test_input_oncut_beforeinput_abort() {
    436  try {
    437    await reset();
    438  } catch (e) {
    439    ok(false, `Failed to reset (${e.toString()})`);
    440    return;
    441  }
    442 
    443  // Setup an oncut event handler, and fire cut.  Ensure that the event
    444  // handler was fired, the clipboard contains the INPUT TEXT, and
    445  // that the input itself is empty.
    446  selectContentInput();
    447  let oncut_fired = false;
    448  let beforeInputEvents = [];
    449  let inputEvents = [];
    450  contentInput.oncut = () => { oncut_fired = true; };
    451  contentInput.onbeforeinput = (aEvent) => { beforeInputEvents.push(aEvent); aEvent.preventDefault(); }
    452  contentInput.oninput = (aEvent) => { inputEvents.push(aEvent); }
    453  try {
    454    await putOnClipboard("INPUT TEXT", () => {
    455      synthesizeKey("x", {accelKey: 1});
    456    }, "cut on plaintext editor set clipboard correctly");
    457    ok(oncut_fired, "cut event firing on plaintext editor");
    458    is(beforeInputEvents.length, 1, '"beforeinput" event should be fired once by cut');
    459    if (beforeInputEvents.length) {
    460      is(beforeInputEvents[0].inputType, "deleteByCut", '"inputType" of "beforeinput" event should be "deleteByCut"');
    461      is(beforeInputEvents[0].cancelable, true, '"beforeinput" event for "deleteByCut" should be cancelable');
    462      is(beforeInputEvents[0].data, null, '"data" of "beforeinput" event for "deleteByCut" should be null');
    463      is(beforeInputEvents[0].dataTransfer, null, '"dataTransfer" of "beforeinput" event for "deleteByCut" should be null');
    464      is(beforeInputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "beforeinput" event for "deleteByCut" should return empty array');
    465    }
    466    is(inputEvents.length, 0, '"input" event should not be fired by cut if "beforeinput" event is canceled');
    467    is(contentInput.value, "INPUT TEXT",
    468      'cut on plaintext editor should not change editor since "beforeinput" event was canceled');
    469  } catch (e) {
    470    ok(false, e.toString());
    471  }
    472 });
    473 
    474 add_task(async function test_input_onpaste_abort() {
    475  try {
    476    await reset();
    477  } catch (e) {
    478    ok(false, `Failed to reset (${e.toString()})`);
    479    return;
    480  }
    481 
    482  // Setup an onpaste event handler, and fire paste.  Ensure that the event
    483  // handler was fired, the clipboard contents didn't change, and that the
    484  // input value did change (ie. paste succeeded).
    485  selectContentInput();
    486  let onpaste_fired = false;
    487  contentInput.onpaste = () => { onpaste_fired = true; return false; };
    488  contentInput.onbeforeinput = () => {
    489    ok(false, '"beforeinput" event should not be fired by paste but canceled');
    490  };
    491  contentInput.oninput = () => {
    492    ok(false, '"input" event should not be fired by paste but canceled');
    493  };
    494  synthesizeKey("v", {accelKey: 1});
    495  ok(onpaste_fired,
    496    "paste event (to-be-cancelled) firing on plaintext editor");
    497  is(getClipboardText(), clipboardInitialValue,
    498    "aborted paste on plaintext editor did not modify clipboard");
    499  is(contentInput.value, "INPUT TEXT",
    500    "aborted paste on plaintext editor did not modify modified editor value");
    501 });
    502 
    503 add_task(async function test_input_onpaste_beforeinput_abort() {
    504  try {
    505    await reset();
    506  } catch (e) {
    507    ok(false, `Failed to reset (${e.toString()})`);
    508    return;
    509  }
    510 
    511  // Setup an onpaste event handler, and fire paste.  Ensure that the event
    512  // handler was fired, the clipboard contents didn't change, and that the
    513  // input value did change (ie. paste succeeded).
    514  selectContentInput();
    515  let onpaste_fired = false;
    516  let beforeInputEvents = [];
    517  let inputEvents = [];
    518  contentInput.onpaste = () => { onpaste_fired = true; };
    519  contentInput.onbeforeinput = (aEvent) => { beforeInputEvents.push(aEvent); aEvent.preventDefault(); }
    520  contentInput.oninput = (aEvent) => { inputEvents.push(aEvent); }
    521 
    522  synthesizeKey("v", {accelKey: 1});
    523  ok(onpaste_fired, "paste event firing on plaintext editor");
    524  is(getClipboardText(), clipboardInitialValue,
    525    "paste on plaintext editor did not modify clipboard contents");
    526  is(beforeInputEvents.length, 1, '"beforeinput" event should be fired once by paste');
    527  if (beforeInputEvents.length) {
    528    is(beforeInputEvents[0].inputType, "insertFromPaste", '"inputType" of "beforeinput" event should be "insertFromPaste"');
    529    is(beforeInputEvents[0].cancelable, true, '"beforeinput" event for "insertFromPaste" should be cancelable');
    530    is(beforeInputEvents[0].data, clipboardInitialValue, `"data" of "beforeinput" event for "insertFromPaste" should be "${clipboardInitialValue}"`);
    531    is(beforeInputEvents[0].dataTransfer, null, '"dataTransfer" of "beforeinput" event for "insertFromPaste" should be null');
    532    is(beforeInputEvents[0].getTargetRanges().length, 0, 'getTargetRanges() of "beforeinput" event for "insertFromPaste" should return empty array');
    533  }
    534  is(inputEvents.length, 0, '"input" event should not be fired by paste when "beforeinput" is canceled');
    535  is(contentInput.value, "INPUT TEXT",
    536    "paste on plaintext editor did modify editor value");
    537 });
    538 
    539 add_task(async function test_input_cut_dataTransfer() {
    540  try {
    541    await reset();
    542  } catch (e) {
    543    ok(false, `Failed to reset (${e.toString()})`);
    544    return;
    545  }
    546 
    547  // Cut using event.dataTransfer. The event is not cancelled so the default
    548  // cut should occur
    549  selectContentInput();
    550  contentInput.oncut = function(event) {
    551    ok(event instanceof ClipboardEvent, "cut event is a ClipboardEvent");
    552    ok(event.clipboardData instanceof DataTransfer, "cut event dataTransfer is a DataTransfer");
    553    is(event.target, contentInput, "cut event target");
    554    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "cut event mozItemCount");
    555    is(event.clipboardData.getData("text/plain"), "", "cut event getData");
    556    event.clipboardData.setData("text/plain", "This is some dataTransfer text");
    557    cachedCutData = event.clipboardData;
    558  };
    559  try {
    560    await putOnClipboard("INPUT TEXT", () => {
    561      synthesizeKey("x", {accelKey: 1});
    562    }, "cut using dataTransfer on plaintext editor set clipboard correctly");
    563    is(contentInput.value, "",
    564      "cut using dataTransfer on plaintext editor cleared input");
    565  } catch (e) {
    566    ok(false, e.toString());
    567  }
    568 });
    569 
    570 add_task(async function test_input_cut_abort_dataTransfer() {
    571  try {
    572    await reset();
    573  } catch (e) {
    574    ok(false, `Failed to reset (${e.toString()})`);
    575    return;
    576  }
    577 
    578  // Cut using event.dataTransfer but cancel the event. The data should be
    579  // put on the clipboard but since we don't modify the input value, the input
    580  // should have the same value.
    581  selectContentInput();
    582  contentInput.oncut = function(event) {
    583    event.clipboardData.setData("text/plain", "Cut dataTransfer text");
    584    return false;
    585  };
    586  try {
    587    await putOnClipboard("Cut dataTransfer text", () => {
    588      synthesizeKey("x", {accelKey: 1});
    589    }, "aborted cut using dataTransfer on plaintext editor set clipboard correctly");
    590    is(contentInput.value, "INPUT TEXT",
    591      "aborted cut using dataTransfer on plaintext editor did not modify input");
    592  } catch (e) {
    593    ok(false, e.toString());
    594  }
    595 });
    596 
    597 add_task(async function test_input_copy_dataTransfer() {
    598  try {
    599    await reset();
    600  } catch (e) {
    601    ok(false, `Failed to reset (${e.toString()})`);
    602    return;
    603  }
    604 
    605  // Copy using event.dataTransfer
    606  selectContentInput();
    607  contentInput.oncopy = function(event) {
    608    ok(event instanceof ClipboardEvent, "copy event is a ClipboardEvent");
    609    ok(event.clipboardData instanceof DataTransfer, "copy event dataTransfer is a DataTransfer");
    610    is(event.target, contentInput, "copy event target");
    611    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "copy event mozItemCount");
    612    is(event.clipboardData.getData("text/plain"), "", "copy event getData");
    613    event.clipboardData.setData("text/plain", "Copied dataTransfer text");
    614    cachedCopyData = event.clipboardData;
    615  };
    616  try {
    617    await putOnClipboard("INPUT TEXT", () => {
    618      synthesizeKey("c", {accelKey: 1});
    619    }, "copy using dataTransfer on plaintext editor set clipboard correctly");
    620    is(contentInput.value, "INPUT TEXT",
    621      "copy using dataTransfer on plaintext editor did not modify input");
    622  } catch (e) {
    623    ok(false, e.toString());
    624  }
    625 });
    626 
    627 add_task(async function test_input_copy_abort_dataTransfer() {
    628  try {
    629    await reset();
    630  } catch (e) {
    631    ok(false, `Failed to reset (${e.toString()})`);
    632    return;
    633  }
    634 
    635  // Copy using event.dataTransfer but cancel the event.
    636  selectContentInput();
    637  contentInput.oncopy = function(event) {
    638    event.clipboardData.setData("text/plain", "Copy dataTransfer text");
    639    return false;
    640  };
    641  try {
    642    await putOnClipboard("Copy dataTransfer text", () => {
    643      synthesizeKey("c", {accelKey: 1});
    644    }, "aborted copy using dataTransfer on plaintext editor set clipboard correctly");
    645    is(contentInput.value, "INPUT TEXT",
    646      "aborted copy using dataTransfer on plaintext editor did not modify input");
    647  } catch (e) {
    648    ok(false, e.toString());
    649  }
    650 });
    651 
    652 add_task(async function test_input_paste_dataTransfer() {
    653  try {
    654    await reset();
    655  } catch (e) {
    656    ok(false, `Failed to reset (${e.toString()})`);
    657    return;
    658  }
    659 
    660  // Paste using event.dataTransfer
    661  selectContentInput();
    662  contentInput.onpaste = function(event) {
    663    ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent");
    664    ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer");
    665    is(event.target, contentInput, "paste event target");
    666    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "paste event mozItemCount");
    667    is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "paste event getData");
    668    cachedPasteData = event.clipboardData;
    669  };
    670  synthesizeKey("v", {accelKey: 1});
    671  is(getClipboardText(), clipboardInitialValue,
    672    "paste using dataTransfer on plaintext editor did not modify clipboard contents");
    673  is(contentInput.value, clipboardInitialValue,
    674    "paste using dataTransfer on plaintext editor modified input");
    675 });
    676 
    677 add_task(async function test_input_paste_abort_dataTransfer() {
    678  try {
    679    await reset();
    680  } catch (e) {
    681    ok(false, `Failed to reset (${e.toString()})`);
    682    return;
    683  }
    684 
    685  // Paste using event.dataTransfer but cancel the event
    686  selectContentInput();
    687  contentInput.onpaste = function(event) {
    688    is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "get data on aborted paste");
    689    contentInput.value = "Alternate Paste";
    690    return false;
    691  };
    692  synthesizeKey("v", {accelKey: 1});
    693  is(getClipboardText(), clipboardInitialValue,
    694     "aborted paste using dataTransfer on plaintext editor did not modify clipboard contents");
    695  is(contentInput.value, "Alternate Paste",
    696     "aborted paste using dataTransfer on plaintext editor modified input");
    697 });
    698 
    699 add_task(async function test_input_copypaste_dataTransfer_multiple() {
    700  try {
    701    await reset();
    702  } catch (e) {
    703    ok(false, `Failed to reset (${e.toString()})`);
    704    return;
    705  }
    706 
    707  // Cut several types of data and paste it again
    708  contentInput.value = "This is a line of text";
    709  contentInput.oncopy = function(event) {
    710    var cd = event.clipboardData;
    711    cd.setData("text/plain", "would be a phrase");
    712 
    713    var exh = false;
    714    try { SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; }
    715    ok(exh, "exception occured mozSetDataAt 1");
    716    exh = false;
    717    try { SpecialPowers.wrap(cd).mozTypesAt(1); } catch (ex) { exh = true; }
    718    ok(exh, "exception occured mozTypesAt 1");
    719    exh = false;
    720    try { SpecialPowers.wrap(cd).mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; }
    721    ok(exh, "exception occured mozGetDataAt 1");
    722    exh = false;
    723    try { cd.mozClearDataAt("text/plain", 1); } catch (ex) { exh = true; }
    724    ok(exh, "exception occured mozClearDataAt 1");
    725 
    726    cd.setData("text/x-moz-url", "http://www.mozilla.org\nMozilla Page Title");
    727    SpecialPowers.wrap(cd).mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0);
    728    is(SpecialPowers.wrap(cd).mozItemCount, 1, "mozItemCount after set multiple types");
    729    return false;
    730  };
    731 
    732  try {
    733    selectContentInput();
    734 
    735    await putOnClipboard("would be a phrase", () => {
    736      synthesizeKey("c", {accelKey: 1});
    737    }, "copy multiple types text");
    738    contentInput.oncopy = null;  // XXX Not sure why this is required...
    739  } catch (e) {
    740    ok(false, e.toString());
    741    return;
    742  }
    743 
    744  contentInput.setSelectionRange(5, 14);
    745 
    746  contentInput.onpaste = function(event) {
    747    var cd = event.clipboardData;
    748    is(SpecialPowers.wrap(cd).mozItemCount, 1, "paste after copy multiple types mozItemCount");
    749    is(cd.getData("text/plain"), "would be a phrase", "paste text/plain multiple types");
    750 
    751    // Firefox for Android's clipboard code doesn't handle x-moz-url. Therefore
    752    // disabling the following test. Enable this once bug #840101 is fixed.
    753    if (!navigator.appVersion.includes("Android")) {
    754      is(cd.getData("text/x-moz-url"), "http://www.mozilla.org\nMozilla Page Title", "paste text/x-moz-url multiple types");
    755      is(cd.getData("text/x-custom"), "Custom Text with \u0000 null", "paste text/custom multiple types");
    756    } else {
    757      is(cd.getData("text/x-custom"), "", "paste text/custom multiple types");
    758    }
    759 
    760    is(cd.getData("application/x-moz-custom-clipdata"), "", "application/x-moz-custom-clipdata is not present");
    761 
    762    exh = false;
    763    try { cd.setData("application/x-moz-custom-clipdata", "Some Data"); } catch (ex) { exh = true; }
    764    ok(exh, "exception occured setData with application/x-moz-custom-clipdata");
    765 
    766    exh = false;
    767    try { cd.setData("text/plain", "Text on Paste"); } catch (ex) { exh = true; }
    768    ok(exh, "exception occured setData on paste");
    769 
    770    is(cd.getData("text/plain"), "would be a phrase", "text/plain data unchanged");
    771  };
    772  synthesizeKey("v", {accelKey: 1});
    773  is(contentInput.value, "This would be a phrase of text",
    774    "default paste after copy multiple types");
    775 });
    776 
    777 add_task(async function test_input_copy_button_dataTransfer() {
    778  try {
    779    await reset();
    780  } catch (e) {
    781    ok(false, `Failed to reset (${e.toString()})`);
    782    return;
    783  }
    784 
    785  // Copy using event.dataTransfer when a button is focused.
    786  var button = document.getElementById("button");
    787  button.focus();
    788  button.oncopy = function(event) {
    789    ok(false, "should not be firing copy event on button");
    790    return false;
    791  };
    792  try {
    793    // copy should not occur here because buttons don't have any controller
    794    // for the copy command
    795    await wontPutOnClipboard("", () => {
    796      synthesizeKey("c", {accelKey: 1});
    797    }, "Accel-C on the `<button>` shouldn't modify the clipboard data");
    798    ok(true, "Accel-C on the <button> shouldn't modify the clipboard data");
    799  } catch (e) {
    800    ok(false, e.toString());
    801  }
    802 
    803  try {
    804    selectContentDiv();
    805 
    806    await putOnClipboard("CONTENT TEXT", () => {
    807      synthesizeKey("c", {accelKey: 1});
    808    }, "Accel-C with selecting the content <div> should modify the clipboard data with text in it");
    809  } catch (e) {
    810    ok(false, e.toString());
    811  }
    812 });
    813 
    814 add_task(async function test_eventspref_disabled() {
    815  try {
    816    await reset();
    817  } catch (e) {
    818    ok(false, `Failed to reset (${e.toString()})`);
    819    return;
    820  }
    821 
    822  // Disable clipboard events
    823  try {
    824    await SpecialPowers.pushPrefEnv({
    825      set: [['dom.event.clipboardevents.enabled', false]]
    826    });
    827 
    828    var event_fired = false;
    829    var input_data = undefined;
    830    contentInput.oncut = function() { event_fired = true; };
    831    contentInput.oncopy = function() { event_fired = true; };
    832    contentInput.onpaste = function() { event_fired = true; };
    833    contentInput.oninput = function(event) { input_data = event.data; };
    834 
    835    selectContentInput();
    836    contentInput.setSelectionRange(1, 4);
    837 
    838    await putOnClipboard("NPU", () => {
    839      synthesizeKey("x", {accelKey: 1});
    840    }, "cut changed clipboard when preference is disabled");
    841    is(contentInput.value, "IT TEXT", "cut changed text when preference is disabled");
    842    ok(!event_fired, "cut event did not fire when preference is disabled");
    843    is(input_data, null, "cut should cause input event whose data value is null");
    844 
    845    event_fired = false;
    846    input_data = undefined;
    847    contentInput.setSelectionRange(3, 6);
    848    await putOnClipboard("TEX", () => {
    849      synthesizeKey("c", {accelKey: 1});
    850    }, "copy changed clipboard when preference is disabled");
    851    ok(!event_fired, "copy event did not fire when preference is disabled")
    852    is(input_data, undefined, "copy shouldn't cause input event");
    853 
    854    event_fired = false;
    855    contentInput.setSelectionRange(0, 2);
    856    synthesizeKey("v", {accelKey: 1});
    857    is(contentInput.value, "TEX TEXT", "paste changed text when preference is disabled");
    858    ok(!event_fired, "paste event did not fire when preference is disabled");
    859    is(input_data, "",
    860       "paste should cause input event but whose data value should be empty string if clipboard event is disabled");
    861  } catch (e) {
    862    ok(false, e.toString());
    863  } finally {
    864    await SpecialPowers.popPrefEnv();
    865  }
    866 });
    867 
    868 let expectedData = [];
    869 
    870 // Check to make that synthetic events do not change the clipboard
    871 add_task(async function test_synthetic_events() {
    872  try {
    873    await reset();
    874  } catch (e) {
    875    ok(false, `Failed to reset (${e.toString()})`);
    876    return;
    877  }
    878 
    879  let syntheticSpot = document.getElementById("syntheticSpot");
    880 
    881  // No dataType specified
    882  let event = new ClipboardEvent("cut", { data: "something" });
    883  expectedData = { type: "cut", data: null }
    884  compareSynthetic(event, "before");
    885  syntheticSpot.dispatchEvent(event);
    886  ok(expectedData.eventFired, "cut event fired");
    887  compareSynthetic(event, "after");
    888 
    889  event = new ClipboardEvent("cut", { dataType: "text/plain", data: "something" });
    890  expectedData = { type: "cut", dataType: "text/plain", data: "something" }
    891  compareSynthetic(event, "before");
    892  syntheticSpot.dispatchEvent(event);
    893  ok(expectedData.eventFired, "cut event fired");
    894  compareSynthetic(event, "after");
    895 
    896  event = new ClipboardEvent("copy", { dataType: "text/plain", data: "something" });
    897  expectedData = { type: "copy", dataType: "text/plain", data: "something" }
    898  compareSynthetic(event, "before");
    899  syntheticSpot.dispatchEvent(event);
    900  ok(expectedData.eventFired, "copy event fired");
    901  compareSynthetic(event, "after");
    902 
    903  event = new ClipboardEvent("copy", { dataType: "text/plain" });
    904  expectedData = { type: "copy", dataType: "text/plain", data: "" }
    905  compareSynthetic(event, "before");
    906  syntheticSpot.dispatchEvent(event);
    907  ok(expectedData.eventFired, "copy event fired");
    908  compareSynthetic(event, "after");
    909 
    910  event = new ClipboardEvent("paste", { dataType: "text/plain", data: "something" });
    911  expectedData = { type: "paste", dataType: "text/plain", data: "something" }
    912  compareSynthetic(event, "before");
    913  syntheticSpot.dispatchEvent(event);
    914  ok(expectedData.eventFired, "paste event fired");
    915  compareSynthetic(event, "after");
    916 
    917  event = new ClipboardEvent("paste", { dataType: "application/unknown", data: "unknown" });
    918  expectedData = { type: "paste", dataType: "application/unknown", data: "unknown" }
    919  compareSynthetic(event, "before");
    920  syntheticSpot.dispatchEvent(event);
    921  ok(expectedData.eventFired, "paste event fired");
    922  compareSynthetic(event, "after");
    923 });
    924 
    925 function compareSynthetic(event, eventtype) {
    926  let step = (eventtype == "cut" || eventtype == "copy" || eventtype == "paste") ? "during" : eventtype;
    927  if (step == "during") {
    928    is(eventtype, expectedData.type, "synthetic " + eventtype + " event fired");
    929  }
    930 
    931  ok(event.clipboardData instanceof DataTransfer, "clipboardData is assigned");
    932 
    933  is(event.type, expectedData.type, "synthetic " + eventtype + " event type");
    934  if (expectedData.data === null) {
    935    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 0, "synthetic " + eventtype + " empty data");
    936  }
    937  else {
    938    is(SpecialPowers.wrap(event.clipboardData).mozItemCount, 1, "synthetic " + eventtype + " item count");
    939    is(event.clipboardData.types.length, 1, "synthetic " + eventtype + " types length");
    940    is(event.clipboardData.getData(expectedData.dataType), expectedData.data,
    941       "synthetic " + eventtype + " data");
    942  }
    943 
    944  is(getClipboardText(), "empty", "event does not change the clipboard " + step + " dispatch");
    945 
    946  if (step == "during") {
    947    expectedData.eventFired = true;
    948  }
    949 }
    950 
    951 async function checkCachedDataTransfer(cd, eventtype) {
    952  var testprefix = "cached " + eventtype + " dataTransfer";
    953 
    954  try {
    955    await putOnClipboard("Some Clipboard Text", () => { setClipboardText("Some Clipboard Text") },
    956                         "change clipboard outside of event");
    957  } catch (e) {
    958    ok(false, e.toString());
    959    return;
    960  }
    961 
    962  var oldtext = cd.getData("text/plain");
    963  ok(!oldtext, "clipboard get using " + testprefix);
    964 
    965  try {
    966    SpecialPowers.wrap(cd).mozSetDataAt("text/plain", "Test Cache Data", 0);
    967  } catch (ex) {}
    968  ok(!cd.getData("text/plain"), "clipboard set using " + testprefix);
    969 
    970  is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix);
    971 
    972  try {
    973    cd.mozClearDataAt("text/plain", 0);
    974  } catch (ex) {}
    975  ok(!cd.getData("text/plain"), "clipboard clear using " + testprefix);
    976 
    977  is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix);
    978 }
    979 
    980 add_task(async function test_modify_datatransfer_outofevent() {
    981  try {
    982    await reset();
    983  } catch (e) {
    984    ok(false, `Failed to reset (${e.toString()})`);
    985    return;
    986  }
    987 
    988  // Check if the cached clipboard data can be accessed or modified
    989  // and whether it modifies the real clipboard
    990 
    991  // XXX Depends on test_input_cut_dataTransfer()
    992  if (cachedCutData) {
    993    await checkCachedDataTransfer(cachedCutData, "cut");
    994  } else {
    995    todo(false, "test_input_cut_dataTransfer must have been failed, skipping tests with its dataTransfer");
    996  }
    997  // XXX Depends on test_input_copy_dataTransfer()
    998  if (cachedCopyData) {
    999    await checkCachedDataTransfer(cachedCopyData, "copy");
   1000  } else {
   1001    todo(false, "test_input_copy_dataTransfer must have been failed, skipping tests with its dataTransfer");
   1002  }
   1003  // XXX Depends on test_input_paste_dataTransfer()
   1004  if (cachedPasteData) {
   1005    await checkCachedDataTransfer(cachedPasteData, "paste");
   1006  } else {
   1007    todo(false, "test_input_paste_dataTransfer must have been failed, skipping tests with its dataTransfer");
   1008  }
   1009 });
   1010 
   1011 add_task(async function test_input_cut_disallowed_types_dataTransfer() {
   1012  try {
   1013    await reset();
   1014  } catch (e) {
   1015    ok(false, `Failed to reset (${e.toString()})`);
   1016    return;
   1017  }
   1018 
   1019  selectContentInput();
   1020  let oncutExecuted = false;
   1021  contentInput.oncut = function(event) {
   1022    // Setting an arbitrary type should be OK
   1023    try {
   1024      event.clipboardData.setData("apple/cider", "Anything your heart desires");
   1025      ok(true, "We should have successfully executed the setData call");
   1026    } catch(e) {
   1027      ok(false, "We should not have gotten an exception for trying to set that data");
   1028    }
   1029 
   1030    // Unless that type happens to be application/x-moz-custom-clipdata
   1031    try {
   1032      event.clipboardData.setData("application/x-moz-custom-clipdata", "Anything your heart desires");
   1033      ok(false, "We should not have successfully executed the setData call");
   1034    } catch(e) {
   1035      is(e.name, "NotSupportedError",
   1036         "We should have gotten an NotSupportedError exception for trying to set that data");
   1037    }
   1038    oncutExecuted = true;
   1039  };
   1040 
   1041  try {
   1042    await putOnClipboard("INPUT TEXT", () => {
   1043      synthesizeKey("x", {accelKey: 1});
   1044    }, "The oncut handler should have been executed data");
   1045    ok(oncutExecuted, "The oncut handler should have been executed");
   1046  } catch (e) {
   1047    ok(false, "Failed to copy the data given by the oncut");
   1048  }
   1049 });
   1050 
   1051 // Try copying an image to the clipboard and make sure that it looks correct when pasting it.
   1052 add_task(async function test_image_dataTransfer() {
   1053  // cmd_copyImageContents errors on Android (bug 1299578).
   1054  if (navigator.userAgent.includes("Android")) {
   1055    return;
   1056  }
   1057 
   1058  try {
   1059    await reset();
   1060  } catch (e) {
   1061    ok(false, `Failed to reset (${e.toString()})`);
   1062    return;
   1063  }
   1064 
   1065  // Copy the image's data to the clipboard
   1066  try {
   1067    await putOnClipboard("", () => {
   1068      SpecialPowers.setCommandNode(window, document.getElementById("image"));
   1069      SpecialPowers.doCommand(window, "cmd_copyImageContents");
   1070    }, "copy changed clipboard when preference is disabled");
   1071  } catch (e) {
   1072    ok(false, e.toString());
   1073  }
   1074 
   1075  let onpasteCalled = false;
   1076  document.onpaste = function(event) {
   1077    ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent");
   1078    ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer");
   1079    let items = event.clipboardData.items;
   1080    let foundData = false;
   1081    for (let i = 0; i < items.length; ++i)  {
   1082      if (items[i].kind == "file") {
   1083        foundData = true;
   1084        is(items[i].type, "image/png", "The type of the data must be image/png");
   1085        is(items[i].getAsFile().type, "image/png", "The attached file must be image/png");
   1086      }
   1087    }
   1088    ok(foundData, "Should have found a file entry in the DataTransferItemList");
   1089    let files = event.clipboardData.files;
   1090    is(files.length, 1, "There should only be one file on the DataTransfer");
   1091    is(files[0].type, "image/png", "The only file should be an image/png");
   1092    onpasteCalled = true;
   1093  }
   1094 
   1095  synthesizeKey("v", {accelKey: 1});
   1096  ok(onpasteCalled, "The paste event listener must have been called");
   1097 });
   1098 
   1099 add_task(async function test_event_target() {
   1100  try {
   1101    await reset();
   1102  } catch (e) {
   1103    ok(false, `Failed to reset (${e.toString()})`);
   1104    return;
   1105  }
   1106 
   1107  let copyTarget = null;
   1108  addEventListenerTo(document, "copy", (event) => { copyTarget = event.target; }, {once: true});
   1109 
   1110  if (document.activeElement) {
   1111    document.activeElement.blur();
   1112  }
   1113 
   1114  let selection = document.getSelection();
   1115  selection.setBaseAndExtent(content.firstChild, "CONTENT ".length,
   1116                            content.firstChild, "CONTENT TEXT".length);
   1117 
   1118  try {
   1119    await putOnClipboard("TEXT", () => {
   1120      synthesizeKey("c", {accelKey: 1});
   1121    }, "copy text from non-editable element");
   1122  } catch (e) {
   1123    ok(false, e.toString());
   1124  }
   1125 
   1126  is(copyTarget.getAttribute("id"), "content", "Copy event's target should be always an element");
   1127 
   1128  // Create a contenteditable element to check complicated event target.
   1129  contenteditableContainer.innerHTML = '<div contenteditable><p id="p1">foo</p><p id="p2">bar</p></div>';
   1130  contenteditableContainer.firstChild.focus();
   1131 
   1132  let p1 = document.getElementById("p1");
   1133  let p2 = document.getElementById("p2");
   1134  selection.setBaseAndExtent(p1.firstChild, 1, p2.firstChild, 1);
   1135 
   1136  let pasteTarget = null;
   1137  let pasteEventCount = 0;
   1138  function pasteEventLogger(event) {
   1139    pasteTarget = event.target;
   1140    pasteEventCount++;
   1141  }
   1142  addEventListenerTo(document, "paste", pasteEventLogger);
   1143  synthesizeKey("v", {accelKey: 1});
   1144  is(pasteTarget.getAttribute("id"), "p1",
   1145    "'paste' event's target should be always an element which includes start container of the first Selection range");
   1146  is(pasteEventCount, 1,
   1147    "'paste' event should be fired only once when Accel+'v' is pressed");
   1148 });
   1149 
   1150 add_task(async function test_paste_event_for_middle_click_without_HTMLEditor() {
   1151  await SpecialPowers.pushPrefEnv({"set": [["middlemouse.paste", true],
   1152                                           ["middlemouse.contentLoadURL", false]]});
   1153 
   1154  try {
   1155    await reset();
   1156  } catch (e) {
   1157    ok(false, `Failed to reset (${e.toString()})`);
   1158    return;
   1159  }
   1160 
   1161  contenteditableContainer.innerHTML = '<div id="non-editable-target">non-editable</div>';
   1162  let noneditableDiv = document.getElementById("non-editable-target");
   1163 
   1164  ok(!getHTMLEditor(), "There should not be HTMLEditor");
   1165 
   1166  let selection = document.getSelection();
   1167  selection.setBaseAndExtent(content.firstChild, 0,
   1168                             content.firstChild, "CONTENT".length);
   1169 
   1170  try {
   1171    await putOnClipboard("CONTENT", () => {
   1172      synthesizeKey("c", {accelKey: 1});
   1173    }, "copy text from non-editable element");
   1174  } catch (e) {
   1175    ok(false, e.toString());
   1176    return;
   1177  }
   1178 
   1179  let auxclickFired = false;
   1180  function onAuxClick(event) {
   1181    auxclickFired = true;
   1182  }
   1183  addEventListenerTo(document, "auxclick", onAuxClick);
   1184 
   1185  let pasteEventCount = 0;
   1186  function onPaste(event) {
   1187    pasteEventCount++;
   1188    ok(auxclickFired, "'auxclick' event should be fired before 'paste' event");
   1189    is(event.target, noneditableDiv,
   1190      "'paste' event should be fired on the clicked element");
   1191  }
   1192  addEventListenerTo(document, "paste", onPaste);
   1193 
   1194  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
   1195  is(pasteEventCount, 1, "'paste' event should be fired just once");
   1196 
   1197  pasteEventCount = 0;
   1198  auxclickFired = false;
   1199  addEventListenerTo(document, "mouseup", (event) => { event.preventDefault(); }, {once: true});
   1200  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
   1201  is(pasteEventCount, 1,
   1202    "Even if 'mouseup' event is consumed, 'paste' event should be fired");
   1203 
   1204  pasteEventCount = 0;
   1205  auxclickFired = false;
   1206  addEventListenerTo(document, "auxclick", (event) => { event.preventDefault(); }, {once: true, capture: true});
   1207  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
   1208  ok(auxclickFired, "'auxclickFired' fired");
   1209  is(pasteEventCount, 0,
   1210    "If 'auxclick' event is consumed at capturing phase at the document node, 'paste' event should not be fired");
   1211 
   1212  pasteEventCount = 0;
   1213  auxclickFired = false;
   1214  addEventListenerTo(noneditableDiv, "auxclick", (event) => { event.preventDefault(); }, {once: true});
   1215  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
   1216  ok(auxclickFired, "'auxclick' fired");
   1217  is(pasteEventCount, 0,
   1218    "If 'auxclick' event listener is added to the click event target, 'paste' event should not be fired");
   1219 
   1220  pasteEventCount = 0;
   1221  auxclickFired = false;
   1222  addEventListenerTo(document, "auxclick", (event) => { event.preventDefault(); }, {once: true});
   1223  synthesizeMouseAtCenter(noneditableDiv, {button: 1});
   1224  ok(auxclickFired, "'auxclick' fired");
   1225  is(pasteEventCount, 0,
   1226    "If 'auxclick' event is consumed, 'paste' event should be not be fired");
   1227 });
   1228 
   1229 add_task(function cleaning_up() {
   1230  try {
   1231    disableNonTestMouseEvents(false);
   1232  } finally {
   1233    window.close();
   1234  }
   1235 });
   1236 </script>
   1237 </pre>
   1238 </body>
   1239 </html>