dom/tests/mochitest/general/test_clipboard_events.html
author Bogdan Tara <btara@mozilla.com>
Sat, 11 Aug 2018 03:21:43 +0300
changeset 486141 ecd5d7bcee04b97d2a4b1b87e301105c11b34cc7
parent 486115 485fdf8e37e801c10fa32190481d8c1f3f7310b3
child 488012 db6e8f0ee6b45d11799fbf9fa2924d49b7c14480
permissions -rw-r--r--
Backed out changeset 485fdf8e37e8 (bug 1453153) for clipboard failures on test_findbar.xul CLOSED TREE

<!DOCTYPE HTML>
<html>
<head>
  <title>Test for Clipboard Events</title>
  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="border: 3px solid black; padding: 3em;">CONTENT TEXT<input id="content-input" value="INPUT TEXT"></div>
<img id="image" src="image_50.png">
<button id="button">Button</button>

<div id="syntheticSpot" oncut="compareSynthetic(event, 'cut')"
                        oncopy="compareSynthetic(event, 'copy')"
                        onpaste="compareSynthetic(event, 'paste')">Spot</div>

<pre id="test">
<script class="testbody" type="text/javascript">
var content = document.getElementById("content");
var contentInput = document.getElementById("content-input");
var clipboardInitialValue = "empty";

var cachedCutData, cachedCopyData, cachedPasteData;

// Before each test function is run, the clipboard is initialized
// to clipboardInitialValue, and the contents of div#content are
// set as the window's selection.

add_task(async function initialize_for_tests() {
  await SimpleTest.promiseFocus();

  await new Promise(resolve => {
    SpecialPowers.pushPrefEnv({
      // NOTE: These tests operate under the assumption that the protected mode of
      // DataTransfer is enabled.
      "set": [["dom.events.dataTransfer.protected.enabled", true]]
    }, resolve);
  });

  // Test that clearing and reading the clipboard works.  A random number
  // is used to make sure that leftover clipboard values from a previous
  // test run don't cause a false-positive test.
  var cb_text = "empty_" + Math.random();

  await putOnClipboard(cb_text, () => { setClipboardText(cb_text) },
                        "initial set/get clipboard text");
});

async function reset() {
  // Init clipboard
  await putOnClipboard(clipboardInitialValue,
                       () => { setClipboardText(clipboardInitialValue) },
                       "reset clipboard");

  // Reset value of contentInput.
  contentInput.value = "INPUT TEXT";
}

function getClipboardText() {
  return SpecialPowers.getClipboardData("text/unicode");
}

async function putOnClipboard(expected, operationFn, desc, type) {
  await SimpleTest.promiseClipboardChange(expected, operationFn, type);
  ok(true, desc);
}

async function wontPutOnClipboard(expected, operationFn, desc, type) {
  await SimpleTest.promiseClipboardChange(null, operationFn, type, 300, true);
  ok(SpecialPowers.getClipboardData(type || "text/unicode")
                  .includes("waitForClipboard-known-value"), desc + " data");
}

function setClipboardText(text) {
  var helper = SpecialPowers.Cc["@mozilla.org/widget/clipboardhelper;1"]
    .getService(SpecialPowers.Ci.nsIClipboardHelper);
  helper.copyString(text);
}

function selectContentDiv() {
  // Set selection
  var selection = window.getSelection();
  selection.removeAllRanges();
  selection.selectAllChildren(content);
}

function selectContentInput() {
  contentInput.select();
  contentInput.focus();
}

add_task(async function test_dom_oncopy() {
  await reset();

  // Setup an oncopy event handler, fire copy.  Ensure that the event
  // handler was called, and the clipboard contents have set to CONTENT TEXT.
  // Test firing oncopy event on ctrl-c:
  selectContentDiv();

  var oncopy_fired = false;
  content.oncopy = function() { oncopy_fired = true; };
  try {
    await putOnClipboard("CONTENT TEXT", () => {
      synthesizeKey("c", {accelKey: 1});
    }, "copy on DOM element set clipboard correctly");
    ok(oncopy_fired, "copy event firing on DOM element");
  } finally {
    content.oncopy = null;
  }
});

add_task(async function test_dom_oncut() {
  await reset();

  // Setup an oncut event handler, fire cut.  Ensure that the event handler
  // was called.  The <div> doesn't handle a cut, so ensure that the
  // clipboard text is clipboardInitialValue, NOT "CONTENT TEXT".
  selectContentDiv();
  var oncut_fired = false;
  content.oncut = function() { oncut_fired = true; };
  try {
    await wontPutOnClipboard(clipboardInitialValue, () => {
      synthesizeKey("x", {accelKey: 1});
    }, "cut on DOM element set clipboard correctly");
    ok(oncut_fired, "cut event firing on DOM element")
  } finally {
    content.oncut = null;
  }
});

add_task(async function test_dom_onpaste() {
  await reset();

  // Setup an onpaste event handler, fire paste.  Ensure that the event
  // handler was called.
  selectContentDiv();
  var onpaste_fired = false;
  content.onpaste = function() { onpaste_fired = true; };
  try {
    synthesizeKey("v", {accelKey: 1});
    ok(onpaste_fired, "paste event firing on DOM element");
  } finally {
    content.onpaste = null;
  }
});

add_task(async function test_dom_oncopy_abort() {
  await reset();

  // Setup an oncopy event handler that aborts the copy, and fire the copy
  // event.  Ensure that the event handler was fired, and the clipboard
  // contents have not been modified.
  selectContentDiv();
  var oncopy_fired = false;
  content.oncopy = function() { oncopy_fired = true; return false; };
  try {
    await wontPutOnClipboard(clipboardInitialValue, () => {
      synthesizeKey("c", {accelKey: 1});
    }, "aborted copy on DOM element did not modify clipboard");
    ok(oncopy_fired, "copy event (to-be-cancelled) firing on DOM element");
  } finally {
    content.oncopy = null;
  }
});

add_task(async function test_input_oncopy() {
  await reset();

  // Setup an oncopy event handler, fire copy.  Ensure that the event
  // handler was called, and the clipboard contents have been set to 'PUT TE',
  // which is the part that is selected below.
  selectContentInput();
  contentInput.focus();
  contentInput.setSelectionRange(2, 8);

  var oncopy_fired = false;
  contentInput.oncopy = function() { oncopy_fired = true; };
  try {
    await putOnClipboard("PUT TE", () => {
      synthesizeKey("c", {accelKey: 1});
    }, "copy on plaintext editor set clipboard correctly");
    ok(oncopy_fired, "copy event firing on plaintext editor");
  } finally {
    contentInput.oncopy = null;
  }
});

add_task(async function test_input_oncut() {
  await reset();

  // Setup an oncut event handler, and fire cut.  Ensure that the event
  // handler was fired, the clipboard contains the INPUT TEXT, and
  // that the input itself is empty.
  selectContentInput();
  var oncut_fired = false;
  contentInput.oncut = function() { oncut_fired = true; };
  try {
    await putOnClipboard("INPUT TEXT", () => {
      synthesizeKey("x", {accelKey: 1});
    }, "cut on plaintext editor set clipboard correctly");
    ok(oncut_fired, "cut event firing on plaintext editor");
    is(contentInput.value, "",
      "cut on plaintext editor emptied editor");
  } finally {
    contentInput.oncut = null;
  }
});

add_task(async function test_input_onpaste() {
  await reset();

  // Setup an onpaste event handler, and fire paste.  Ensure that the event
  // handler was fired, the clipboard contents didn't change, and that the
  // input value did change (ie. paste succeeded).
  selectContentInput();
  var onpaste_fired = false;
  contentInput.onpaste = function() { onpaste_fired = true; };
  try {
    synthesizeKey("v", {accelKey: 1});
    ok(onpaste_fired, "paste event firing on plaintext editor");
    is(getClipboardText(), clipboardInitialValue,
      "paste on plaintext editor did not modify clipboard contents");
    is(contentInput.value, clipboardInitialValue,
      "paste on plaintext editor did modify editor value");
  } finally {
    contentInput.onpaste = null;
  }
});

add_task(async function test_input_oncopy_abort() {
  await reset();

  // Setup an oncopy event handler, fire copy.  Ensure that the event
  // handler was called, and that the clipboard value did NOT change.
  selectContentInput();
  var oncopy_fired = false;
  contentInput.oncopy = function() { oncopy_fired = true; return false; };
  try {
    await wontPutOnClipboard(clipboardInitialValue, () => {
      synthesizeKey("c", {accelKey: 1});
    }, "aborted copy on plaintext editor did not modify clipboard");
    ok(oncopy_fired, "copy event (to-be-cancelled) firing on plaintext editor");
  } finally {
    contentInput.oncopy = null;
  }
});

add_task(async function test_input_oncut_abort() {
  await reset();

  // Setup an oncut event handler, and fire cut.  Ensure that the event
  // handler was fired, the clipboard contains the INPUT TEXT, and
  // that the input itself is empty.
  selectContentInput();
  var oncut_fired = false;
  contentInput.oncut = function() { oncut_fired = true; return false; };
  try {
    await wontPutOnClipboard(clipboardInitialValue, () => {
      synthesizeKey("x", {accelKey: 1});
    }, "aborted cut on plaintext editor did not modify clipboard");
    ok(oncut_fired, "cut event (to-be-cancelled) firing on plaintext editor");
    is(contentInput.value, "INPUT TEXT",
      "aborted cut on plaintext editor did not modify editor contents");
  } finally {
    contentInput.oncut = null;
  }
});

add_task(async function test_input_onpaste_abort() {
  await reset();

  // Setup an onpaste event handler, and fire paste.  Ensure that the event
  // handler was fired, the clipboard contents didn't change, and that the
  // input value did change (ie. paste succeeded).
  selectContentInput();
  var onpaste_fired = false;
  contentInput.onpaste = function() { onpaste_fired = true; return false; };
  try {
    synthesizeKey("v", {accelKey: 1});
    ok(onpaste_fired,
      "paste event (to-be-cancelled) firing on plaintext editor");
    is(getClipboardText(), clipboardInitialValue,
      "aborted paste on plaintext editor did not modify clipboard");
    is(contentInput.value, "INPUT TEXT",
      "aborted paste on plaintext editor did not modify modified editor value");
  } finally {
    contentInput.onpaste = null;
  }
});

add_task(async function test_input_cut_dataTransfer() {
  await reset();

  // Cut using event.dataTransfer. The event is not cancelled so the default
  // cut should occur
  selectContentInput();
  contentInput.oncut = function(event) {
    ok(event instanceof ClipboardEvent, "cut event is a ClipboardEvent");
    ok(event.clipboardData instanceof DataTransfer, "cut event dataTransfer is a DataTransfer");
    is(event.target, contentInput, "cut event target");
    is(event.clipboardData.mozItemCount, 0, "cut event mozItemCount");
    is(event.clipboardData.getData("text/plain"), "", "cut event getData");
    event.clipboardData.setData("text/plain", "This is some dataTransfer text");
    cachedCutData = event.clipboardData;
  };
  try {
    await putOnClipboard("INPUT TEXT", () => {
      synthesizeKey("x", {accelKey: 1});
    }, "cut using dataTransfer on plaintext editor set clipboard correctly");
    is(contentInput.value, "",
      "cut using dataTransfer on plaintext editor cleared input");
  } finally {
    contentInput.oncut = null;
  }
});

add_task(async function test_input_cut_abort_dataTransfer() {
  await reset();

  // Cut using event.dataTransfer but cancel the event. The data should be
  // put on the clipboard but since we don't modify the input value, the input
  // should have the same value.
  selectContentInput();
  contentInput.oncut = function(event) {
    event.clipboardData.setData("text/plain", "Cut dataTransfer text");
    return false;
  };
  try {
    await putOnClipboard("Cut dataTransfer text", () => {
      synthesizeKey("x", {accelKey: 1});
    }, "aborted cut using dataTransfer on plaintext editor set clipboard correctly");
    is(contentInput.value, "INPUT TEXT",
      "aborted cut using dataTransfer on plaintext editor did not modify input");
  } finally {
    contentInput.oncut = null;
  }
});

add_task(async function test_input_copy_dataTransfer() {
  await reset();

  // Copy using event.dataTransfer
  selectContentInput();
  contentInput.oncopy = function(event) {
    ok(event instanceof ClipboardEvent, "copy event is a ClipboardEvent");
    ok(event.clipboardData instanceof DataTransfer, "copy event dataTransfer is a DataTransfer");
    is(event.target, contentInput, "copy event target");
    is(event.clipboardData.mozItemCount, 0, "copy event mozItemCount");
    is(event.clipboardData.getData("text/plain"), "", "copy event getData");
    event.clipboardData.setData("text/plain", "Copied dataTransfer text");
    cachedCopyData = event.clipboardData;
  };
  try {
    await putOnClipboard("INPUT TEXT", () => {
      synthesizeKey("c", {accelKey: 1});
    }, "copy using dataTransfer on plaintext editor set clipboard correctly");
    is(contentInput.value, "INPUT TEXT",
      "copy using dataTransfer on plaintext editor did not modify input");
  } finally {
    contentInput.oncopy = null;
  }
});

add_task(async function test_input_copy_abort_dataTransfer() {
  await reset();

  // Copy using event.dataTransfer but cancel the event.
  selectContentInput();
  contentInput.oncopy = function(event) {
    event.clipboardData.setData("text/plain", "Copy dataTransfer text");
    return false;
  };
  try {
    await putOnClipboard("Copy dataTransfer text", () => {
      synthesizeKey("c", {accelKey: 1});
    }, "aborted copy using dataTransfer on plaintext editor set clipboard correctly");
    is(contentInput.value, "INPUT TEXT",
      "aborted copy using dataTransfer on plaintext editor did not modify input");
  } finally {
    contentInput.oncopy = null;
  }
});

add_task(async function test_input_paste_dataTransfer() {
  await reset();

  // Paste using event.dataTransfer
  selectContentInput();
  contentInput.onpaste = function(event) {
    ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent");
    ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer");
    is(event.target, contentInput, "paste event target");
    is(event.clipboardData.mozItemCount, 1, "paste event mozItemCount");
    is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "paste event getData");
    cachedPasteData = event.clipboardData;
  };
  try {
    synthesizeKey("v", {accelKey: 1});
    is(getClipboardText(), clipboardInitialValue,
      "paste using dataTransfer on plaintext editor did not modify clipboard contents");
    is(contentInput.value, clipboardInitialValue,
      "paste using dataTransfer on plaintext editor modified input");
  } finally {
    contentInput.onpaste = null;
  }
});

add_task(async function test_input_paste_abort_dataTransfer() {
  await reset();

  // Paste using event.dataTransfer but cancel the event
  selectContentInput();
  contentInput.onpaste = function(event) {
    is(event.clipboardData.getData("text/plain"), clipboardInitialValue, "get data on aborted paste");
    contentInput.value = "Alternate Paste";
    return false;
  };
  try {
    synthesizeKey("v", {accelKey: 1});
    is(getClipboardText(), clipboardInitialValue,
      "aborted paste using dataTransfer on plaintext editor did not modify clipboard contents");
    is(contentInput.value, "Alternate Paste",
      "aborted paste using dataTransfer on plaintext editor modified input");
  } finally {
    contentInput.onpaste = null;
  }
});

add_task(async function test_input_copypaste_dataTransfer_multiple() {
  await reset();

  // Cut several types of data and paste it again
  contentInput.value = "This is a line of text";
  contentInput.oncopy = function(event) {
    var cd = event.clipboardData;
    cd.setData("text/plain", "would be a phrase");

    var exh = false;
    try { cd.mozSetDataAt("text/plain", "Text", 1); } catch (ex) { exh = true; }
    ok(exh, "exception occured mozSetDataAt 1");
    exh = false;
    try { cd.mozTypesAt(1); } catch (ex) { exh = true; }
    ok(exh, "exception occured mozTypesAt 1");
    exh = false;
    try { cd.mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; }
    ok(exh, "exception occured mozGetDataAt 1");
    exh = false;
    try { cd.mozClearDataAt("text/plain", 1); } catch (ex) { exh = true; }
    ok(exh, "exception occured mozClearDataAt 1");

    cd.setData("text/x-moz-url", "http://www.mozilla.org");
    cd.mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0);
    is(cd.mozItemCount, 1, "mozItemCount after set multiple types");
    return false;
  };

  try {
    selectContentInput();

    await putOnClipboard("would be a phrase", () => {
      synthesizeKey("c", {accelKey: 1});
    }, "copy multiple types text");
  }
  finally {
    contentInput.oncopy = null;
  }

  contentInput.setSelectionRange(5, 14);

  contentInput.onpaste = function(event) {
    var cd = event.clipboardData;
    is(cd.mozItemCount, 1, "paste after copy multiple types mozItemCount");
    is(cd.getData("text/plain"), "would be a phrase", "paste text/plain multiple types");

    // Firefox for Android's clipboard code doesn't handle x-moz-url. Therefore
    // disabling the following test. Enable this once bug #840101 is fixed.
    if (!navigator.appVersion.includes("Android")) {
      is(cd.getData("text/x-moz-url"), "http://www.mozilla.org", "paste text/x-moz-url multiple types");
      is(cd.getData("text/x-custom"), "Custom Text with \u0000 null", "paste text/custom multiple types");
    } else {
      is(cd.getData("text/x-custom"), "", "paste text/custom multiple types");
    }

    is(cd.getData("application/x-moz-custom-clipdata"), "", "application/x-moz-custom-clipdata is not present");

    exh = false;
    try { cd.setData("application/x-moz-custom-clipdata", "Some Data"); } catch (ex) { exh = true; }
    ok(exh, "exception occured setData with application/x-moz-custom-clipdata");

    exh = false;
    try { cd.setData("text/plain", "Text on Paste"); } catch (ex) { exh = true; }
    ok(exh, "exception occured setData on paste");

    is(cd.getData("text/plain"), "would be a phrase", "text/plain data unchanged");
  };
  try {
    synthesizeKey("v", {accelKey: 1});
    is(contentInput.value, "This would be a phrase of text",
      "default paste after copy multiple types");
  } finally {
    contentInput.onpaste = null;
  }
});

add_task(async function test_input_copy_button_dataTransfer() {
  await reset();

  // Copy using event.dataTransfer when a button is focused.
  var button = document.getElementById("button");
  button.focus();
  button.oncopy = function(event) {
    ok(false, "should not be firing copy event on button");
    return false;
  };
  try {
    // copy should not occur here because buttons don't have any controller
    // for the copy command
    await wontPutOnClipboard(clipboardInitialValue, () => {
      synthesizeKey("c", {accelKey: 1});
    }, "copy using dataTransfer on plaintext editor set clipboard correctly for button");

    selectContentDiv();

    await putOnClipboard("CONTENT TEXT", () => {
      synthesizeKey("c", {accelKey: 1});
    }, "copy using dataTransfer with selection on plaintext editor set clipboard correctly for button");
  } finally {
    document.documentElement.oncopy = null;
  }
});

add_task(async function test_eventspref_disabled() {
  await reset();

  // Disable clipboard events
  await new Promise(resolve => {
    SpecialPowers.pushPrefEnv({"set": [['dom.event.clipboardevents.enabled', false]]}, resolve);
  });

  var event_fired = false;
  contentInput.oncut = function() { event_fired = true; };
  contentInput.oncopy = function() { event_fired = true; };
  contentInput.onpaste = function() { event_fired = true; };
  try {
    selectContentInput();
    contentInput.setSelectionRange(1, 4);

    await putOnClipboard("NPU", () => {
      synthesizeKey("x", {accelKey: 1});
    }, "cut changed clipboard when preference is disabled");
    is(contentInput.value, "IT TEXT", "cut changed text when preference is disabled");
    ok(!event_fired, "cut event did not fire when preference is disabled")

    event_fired = false;
    contentInput.setSelectionRange(3, 6);
    await putOnClipboard("TEX", () => {
      synthesizeKey("c", {accelKey: 1});
    }, "copy changed clipboard when preference is disabled");
    ok(!event_fired, "copy event did not fire when preference is disabled")

    event_fired = false;
    contentInput.setSelectionRange(0, 2);
    synthesizeKey("v", {accelKey: 1});
    is(contentInput.value, "TEX TEXT", "paste changed text when preference is disabled");
    ok(!event_fired, "paste event did not fire when preference is disabled")
  } finally {
    contentInput.oncut = null;
    contentInput.oncopy = null;
    contentInput.onpaste = null;
  }

  await new Promise(resolve => {
    SpecialPowers.popPrefEnv(resolve);
  });
});

let expectedData = [];

// Check to make that synthetic events do not change the clipboard
add_task(async function test_synthetic_events() {
  await reset();

  let syntheticSpot = document.getElementById("syntheticSpot");

  // No dataType specified
  let event = new ClipboardEvent("cut", { data: "something" });
  expectedData = { type: "cut", data: null }
  compareSynthetic(event, "before");
  syntheticSpot.dispatchEvent(event);
  ok(expectedData.eventFired, "cut event fired");
  compareSynthetic(event, "after");

  event = new ClipboardEvent("cut", { dataType: "text/plain", data: "something" });
  expectedData = { type: "cut", dataType: "text/plain", data: "something" }
  compareSynthetic(event, "before");
  syntheticSpot.dispatchEvent(event);
  ok(expectedData.eventFired, "cut event fired");
  compareSynthetic(event, "after");

  event = new ClipboardEvent("copy", { dataType: "text/plain", data: "something" });
  expectedData = { type: "copy", dataType: "text/plain", data: "something" }
  compareSynthetic(event, "before");
  syntheticSpot.dispatchEvent(event);
  ok(expectedData.eventFired, "copy event fired");
  compareSynthetic(event, "after");

  event = new ClipboardEvent("copy", { dataType: "text/plain" });
  expectedData = { type: "copy", dataType: "text/plain", data: "" }
  compareSynthetic(event, "before");
  syntheticSpot.dispatchEvent(event);
  ok(expectedData.eventFired, "copy event fired");
  compareSynthetic(event, "after");

  event = new ClipboardEvent("paste", { dataType: "text/plain", data: "something" });
  expectedData = { type: "paste", dataType: "text/plain", data: "something" }
  compareSynthetic(event, "before");
  syntheticSpot.dispatchEvent(event);
  ok(expectedData.eventFired, "paste event fired");
  compareSynthetic(event, "after");

  event = new ClipboardEvent("paste", { dataType: "application/unknown", data: "unknown" });
  expectedData = { type: "paste", dataType: "application/unknown", data: "unknown" }
  compareSynthetic(event, "before");
  syntheticSpot.dispatchEvent(event);
  ok(expectedData.eventFired, "paste event fired");
  compareSynthetic(event, "after");
});

function compareSynthetic(event, eventtype) {
  let step = (eventtype == "cut" || eventtype == "copy" || eventtype == "paste") ? "during" : eventtype;
  if (step == "during") {
    is(eventtype, expectedData.type, "synthetic " + eventtype + " event fired");
  }

  ok(event.clipboardData instanceof DataTransfer, "clipboardData is assigned");

  is(event.type, expectedData.type, "synthetic " + eventtype + " event type");
  if (expectedData.data === null) {
    is(event.clipboardData.mozItemCount, 0, "synthetic " + eventtype + " empty data");
  }
  else {
    is(event.clipboardData.mozItemCount, 1, "synthetic " + eventtype + " item count");
    is(event.clipboardData.types.length, 1, "synthetic " + eventtype + " types length");
    is(event.clipboardData.getData(expectedData.dataType), expectedData.data,
       "synthetic " + eventtype + " data");
  }

  is(getClipboardText(), "empty", "event does not change the clipboard " + step + " dispatch");

  if (step == "during") {
    expectedData.eventFired = true;
  }
}

async function checkCachedDataTransfer(cd, eventtype) {
  var testprefix = "cached " + eventtype + " dataTransfer";

  await putOnClipboard("Some Clipboard Text", () => { setClipboardText("Some Clipboard Text") },
                       "change clipboard outside of event");

  var oldtext = cd.getData("text/plain");
  ok(!oldtext, "clipboard get using " + testprefix);

  try {
    cd.mozSetDataAt("text/plain", "Test Cache Data", 0);
  } catch (ex) {}
  ok(!cd.getData("text/plain"), "clipboard set using " + testprefix);

  is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix);

  try {
    cd.mozClearDataAt("text/plain", 0);
  } catch (ex) {}
  ok(!cd.getData("text/plain"), "clipboard clear using " + testprefix);

  is(getClipboardText(), "Some Clipboard Text", "clipboard not changed using " + testprefix);
}

add_task(async function test_modify_datatransfer_outofevent() {
  await reset();

  // Check if the cached clipboard data can be accessed or modified
  // and whether it modifies the real clipboard
  await checkCachedDataTransfer(cachedCutData, "cut");
  await checkCachedDataTransfer(cachedCopyData, "copy");
  await checkCachedDataTransfer(cachedPasteData, "paste");
});

add_task(async function test_input_cut_disallowed_types_dataTransfer() {
  await reset();

  selectContentInput();
  let oncutExecuted = false;
  contentInput.oncut = function(event) {
    // Setting an arbitrary type should be OK
    try {
      event.clipboardData.setData("apple/cider", "Anything your heart desires");
      ok(true, "We should have successfully executed the setData call");
    } catch(e) {
      ok(false, "We should not have gotten an exception for trying to set that data");
    }

    // Unless that type happens to be application/x-moz-custom-clipdata
    try {
      event.clipboardData.setData("application/x-moz-custom-clipdata", "Anything your heart desires");
      ok(false, "We should not have successfully executed the setData call");
    } catch(e) {
      is(e.name, "NotSupportedError",
         "We should have gotten an NotSupportedError exception for trying to set that data");
    }
    oncutExecuted = true;
  };

  try {
    await putOnClipboard("INPUT TEXT", () => {
      synthesizeKey("x", {accelKey: 1});
    }, "The oncut handler should have been executed data");
    ok(oncutExecuted, "The oncut handler should have been executed");
  } finally {
    contentInput.oncut = null;
  }
});

// Try copying an image to the clipboard and make sure that it looks correct when pasting it.
add_task(async function test_image_dataTransfer() {
  await reset();

  // cmd_copyImageContents errors on Android (bug 1299578).
  if (navigator.userAgent.includes("Android")) {
    return;
  }

  // Copy the image's data to the clipboard
  await putOnClipboard("", () => {
    SpecialPowers.setCommandNode(window, document.getElementById("image"));
    SpecialPowers.doCommand(window, "cmd_copyImageContents");
  }, "copy changed clipboard when preference is disabled");

  let onpasteCalled = false;
  document.onpaste = function(event) {
    ok(event instanceof ClipboardEvent, "paste event is an ClipboardEvent");
    ok(event.clipboardData instanceof DataTransfer, "paste event dataTransfer is a DataTransfer");
    let items = event.clipboardData.items;
    let foundData = false;
    for (let i = 0; i < items.length; ++i)  {
      if (items[i].kind == "file") {
        foundData = true;
        is(items[i].type, "image/png", "The type of the data must be image/png");
        is(items[i].getAsFile().type, "image/png", "The attached file must be image/png");
      }
    }
    ok(foundData, "Should have found a file entry in the DataTransferItemList");
    let files = event.clipboardData.files;
    is(files.length, 1, "There should only be one file on the DataTransfer");
    is(files[0].type, "image/png", "The only file should be an image/png");
    onpasteCalled = true;
  }

  try {
    synthesizeKey("v", {accelKey: 1});
    ok(onpasteCalled, "The paste event listener must have been called");
  } finally {
    document.onpaste = null;
  }
});

</script>
</pre>
</body>
</html>