<!DOCTYPE>
<html>
<head>
  <title>Test for setting input value</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/EventUtils.js"></script>
  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
</head>
<body>
<div id="display">
</div>
<div id="content"><input type="text"></div>
<pre id="test">
</pre>

<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
SimpleTest.waitForFocus(() => {
  const kSetUserInputCancelable = SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input");

  let input = document.querySelector("input[type=text]");

  // Setting value during composition causes committing composition before setting the value.
  input.focus();
  let description = 'Setting input value at first "compositionupdate" event: ';
  input.addEventListener("compositionupdate", (aEvent) => {
    is(input.value, "", `${description}input value should not have been modified at first "compositionupdate" event yet`);
    input.value = "def";
    is(input.value, "def", `${description}input value should be the specified value at "compositionupdate" event (after setting the value)`);
  }, {once: true});
  input.addEventListener("compositionend", (aEvent) => {
    todo_is(input.value, "def", `${description}input value should be the specified value at "compositionend" event`);
  }, {once: true});
  input.addEventListener("input", (aEvent) => {
    todo_is(input.value, "def", `${description}input value should be the specified value at "input" event`);
  }, {once: true});
  synthesizeCompositionChange(
    { "composition":
      { "string": "abc",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
    });
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  todo_is(input.value, "def", `${description}input value should be set to specified value after the last "input" event`);

  input.value = "";
  description = 'Setting input value at second "compositionupdate" event: ';
  synthesizeCompositionChange(
    { "composition":
      { "string": "ab",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
    });
  input.addEventListener("compositionupdate", (aEvent) => {
    is(input.value, "ab", `${description}input value should not have been modified at second "compositionupdate" event yet`);
    input.value = "def";
  }, {once: true});
  input.addEventListener("compositionend", (aEvent) => {
    is(input.value, "def", `${description}input value should be specified value at "compositionend" event`);
  }, {once: true});
  input.addEventListener("input", (aEvent) => {
    is(input.value, "def", `${description}input value should be specified value at "input" event`);
  }, {once: true});
  synthesizeCompositionChange(
    { "composition":
      { "string": "abc",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
    });
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should be set to specified value after the last "input" event`);

  input.value = "";
  description = 'Setting input value at "input" event for first composition update: ';
  input.addEventListener("compositionupdate", (aEvent) => {
    is(input.value, "", `${description}input value should not have been modified at first "compositionupdate" event yet`);
  }, {once: true});
  input.addEventListener("compositionend", (aEvent) => {
    todo_is(input.value, "abc", `${description}input value should be the composition string at "compositionend" event`);
  }, {once: true});
  input.addEventListener("input", (aEvent) => {
    is(input.value, "abc", `${description}input value should be the composition string at "input" event`);
    input.value = "def";
    is(input.value, "def", `${description}input value should be the specified value at "input" event (after setting the value)`);
  }, {once: true});
  synthesizeCompositionChange(
    { "composition":
      { "string": "abc",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
    });
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should be set to specified value after the last "input" event`);

  input.value = "";
  description = 'Setting input value at "input" event for second composition update: ';
  synthesizeCompositionChange(
    { "composition":
      { "string": "ab",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
    });
  input.addEventListener("compositionupdate", (aEvent) => {
    is(input.value, "ab", `${description}input value should not have been modified at second "compositionupdate" event yet`);
  }, {once: true});
  input.addEventListener("compositionend", (aEvent) => {
    todo_is(input.value, "abc", `${description}input value should be the composition string at "compositionend" event`);
  }, {once: true});
  input.addEventListener("input", (aEvent) => {
    is(input.value, "abc", `${description}input value should be the composition string at "input" event`);
    input.value = "def";
    is(input.value, "def", `${description}input value should be the specified value at "input" event (after setting the value)`);
  }, {once: true});
  synthesizeCompositionChange(
    { "composition":
      { "string": "abc",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
    });
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should be set to specified value after the last "input" event`);

  input.value = "";
  description = 'Setting input value and reframing at "input" event for first composition update: ';
  input.addEventListener("compositionupdate", (aEvent) => {
    is(input.value, "", `${description}input value should not have been modified at first "compositionupdate" event yet`);
  }, {once: true});
  input.addEventListener("compositionend", (aEvent) => {
    todo_is(input.value, "abc", `${description}input value should be the composition string at "compositionend" event`);
  }, {once: true});
  input.addEventListener("input", (aEvent) => {
    is(input.value, "abc", `${description}input value should be the composition string at "input" event`);
    input.value = "def";
    input.style.width = "1000px";
    is(input.value, "def", `${description}input value should be the specified value at "input" event (after setting the value)`);
  }, {once: true});
  synthesizeCompositionChange(
    { "composition":
      { "string": "abc",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
    });
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should be set to specified value after the last "input" event`);
  input.style.width = "";

  input.value = "";
  description = 'Setting input value and reframing at "input" event for second composition update: ';
  synthesizeCompositionChange(
    { "composition":
      { "string": "ab",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
    });
  input.addEventListener("compositionupdate", (aEvent) => {
    is(input.value, "ab", `${description}input value should not have been modified at second "compositionupdate" event yet`);
  }, {once: true});
  input.addEventListener("compositionend", (aEvent) => {
    todo_is(input.value, "abc", `${description}input value should be the composition string at "compositionend" event`);
  }, {once: true});
  input.addEventListener("input", (aEvent) => {
    is(input.value, "abc", `${description}input value should be the composition string at "input" event`);
    input.value = "def";
    input.style.width = "1000px";
    is(input.value, "def", `${description}input value should be the specified value at "input" event (after setting the value)`);
  }, {once: true});
  synthesizeCompositionChange(
    { "composition":
      { "string": "abc",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
    });
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should be set to specified value after the last "input" event`);
  input.style.width = "";

  input.value = "";
  description = 'Setting input value and reframing with flushing layout at "input" event for first composition update: ';
  input.addEventListener("compositionupdate", (aEvent) => {
    is(input.value, "", `${description}input value should not have been modified at first "compositionupdate" event yet`);
  }, {once: true});
  input.addEventListener("compositionend", (aEvent) => {
    todo_is(input.value, "abc", `${description}input value should be the composition string at "compositionend" event`);
  }, {once: true});
  input.addEventListener("input", (aEvent) => {
    is(input.value, "abc", `${description}input value should be the composition string at "input" event`);
    input.value = "def";
    input.style.width = "1000px";
    document.documentElement.scrollTop;
    is(input.value, "def", `${description}input value should be the specified value at "input" event (after setting the value)`);
  }, {once: true});
  synthesizeCompositionChange(
    { "composition":
      { "string": "abc",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
    });
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should be set to specified value after the last "input" event`);
  input.style.width = "";

  input.value = "";
  description = 'Setting input value and reframing with flushing layout at "input" event for second composition update: ';
  synthesizeCompositionChange(
    { "composition":
      { "string": "ab",
        "clauses":
        [
          { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 2, "length": 0 },
    });
  input.addEventListener("compositionupdate", (aEvent) => {
    is(input.value, "ab", `${description}input value should not have been modified at second "compositionupdate" event yet`);
  }, {once: true});
  input.addEventListener("compositionend", (aEvent) => {
    todo_is(input.value, "abc", `${description}input value should be the composition string at "compositionend" event`);
  }, {once: true});
  input.addEventListener("input", (aEvent) => {
    is(input.value, "abc", `${description}input value should be the composition string at "input" event`);
    input.value = "def";
    input.style.width = "1000px";
    document.documentElement.scrollTop;
    is(input.value, "def", `${description}input value should be the specified value at "input" event (after setting the value)`);
  }, {once: true});
  synthesizeCompositionChange(
    { "composition":
      { "string": "abc",
        "clauses":
        [
          { "length": 3, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
        ]
      },
      "caret": { "start": 3, "length": 0 },
    });
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should be set to specified value after the last "input" event`);
  input.style.width = "";

  // autocomplete and correcting misspelled word by spellchecker cause an "input" event with same path as setting input value.
  input.value = "";
  description = 'Setting input value at "input" event whose inputType is "insertReplacementText';
  let inputEventFired = false;
  input.addEventListener("input", (aEvent) => {
    is(aEvent.inputType, "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
    inputEventFired = true;
    is(input.value, "abc", `${description}input value should be inserted value at "input" event (before setting value)`);
    input.value = "def";
    is(input.value, "def", `${description}input value should be specified value at "input" event (after setting value)`);
  }, {once: true});
  SpecialPowers.wrap(input).setUserInput("abc");
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should keep the specified value after the last "input" event`);
  ok(inputEventFired, `${description}"input" event should've been fired for setUserInput("abc")`);

  input.value = "";
  description = 'Setting input value and reframing at "input" event whose inputType is "insertReplacementText';
  inputEventFired = false;
  input.addEventListener("input", (aEvent) => {
    is(aEvent.inputType, "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
    inputEventFired = true;
    is(input.value, "abc", `${description}input value should be inserted value at "input" event (before setting value)`);
    input.value = "def";
    input.style.width = "1000px";
    is(input.value, "def", `${description}input value should be specified value at "input" event (after setting value)`);
  }, {once: true});
  SpecialPowers.wrap(input).setUserInput("abc");
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should keep the specified value after the last "input" event`);
  ok(inputEventFired, `${description}"input" event should've been fired for setUserInput("abc")`);
  input.style.width = "";

  input.value = "";
  description = 'Setting input value and reframing with flushing layout at "input" event whose inputType is "insertReplacementText';
  inputEventFired = false;
  input.addEventListener("input", (aEvent) => {
    is(aEvent.inputType, "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
    inputEventFired = true;
    is(input.value, "abc", `${description}input value should be inserted value at "input" event (before setting value)`);
    input.value = "def";
    input.style.width = "1000px";
    document.documentElement.scrollTop;
    is(input.value, "def", `${description}input value should be specified value at "input" event (after setting value)`);
  }, {once: true});
  SpecialPowers.wrap(input).setUserInput("abc");
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should keep the specified value after the last "input" event`);
  ok(inputEventFired, `${description}"input" event should've been fired for setUserInput("abc")`);
  input.style.width = "";

  input.value = "";
  description = 'Setting input value and destroying the frame at "input" event whose inputType is "insertReplacementText';
  inputEventFired = false;
  input.addEventListener("input", (aEvent) => {
    is(aEvent.inputType, "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
    inputEventFired = true;
    is(input.value, "abc", `${description}input value should be inserted value at "input" event (before setting value)`);
    input.value = "def";
    input.style.display = "none";
    is(input.value, "def", `${description}input value should be specified value at "input" event (after setting value)`);
  }, {once: true});
  SpecialPowers.wrap(input).setUserInput("abc");
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "def", `${description}input value should keep the specified value after the last "input" event`);
  ok(inputEventFired, `${description}"input" event should've been fired for setUserInput("abc")`);
  input.style.display = "inline";

  input.value = "";
  description = 'Changing input type at "input" event whose inputType is "insertReplacementText';
  inputEventFired = false;
  input.addEventListener("input", (aEvent) => {
    is(aEvent.inputType, "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
    inputEventFired = true;
    is(input.value, "abc", `${description}input value should be inserted value at "input" event (before changing type)`);
    input.type = "button";
    is(input.value, "abc", `${description}input value should keep inserted value at "input" event (after changing type)`);
  }, {once: true});
  SpecialPowers.wrap(input).setUserInput("abc");
  is(input.value, "abc", `${description}input value should keep inserted value after the last "input" event`);
  is(input.type, "button", `${description}input type should be changed correctly`);
  ok(inputEventFired, `${description}"input" event should've been fired for setUserInput("abc")`);
  input.type = "text";
  is(input.value, "abc", `${description}input value should keep inserted value immediately after restoring the type`);
  todo(SpecialPowers.wrap(input).hasEditor, `${description}restoring input type should create editor if it's focused element`);
  input.blur();
  input.focus();
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "abc", `${description}input value should keep inserted value after creating editor`);

  input.value = "";
  description = 'Changing input type and flush layout at "input" event whose inputType is "insertReplacementText';
  inputEventFired = false;
  input.addEventListener("input", (aEvent) => {
    is(aEvent.inputType, "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
    inputEventFired = true;
    is(input.value, "abc", `${description}input value should be inserted value at "input" event (before changing type)`);
    input.type = "button";
    input.getBoundingClientRect().height;
    is(input.value, "abc", `${description}input value should keep inserted value at "input" event (after changing type)`);
  }, {once: true});
  SpecialPowers.wrap(input).setUserInput("abc");
  is(input.value, "abc", `${description}input value should keep inserted value after the last "input" event`);
  is(input.type, "button", `${description}input type should be changed correctly`);
  ok(inputEventFired, `${description}"input" event should've been fired for setUserInput("abc")`);
  input.type = "text";
  is(input.value, "abc", `${description}input value should keep inserted value immediately after restoring the type`);
  todo(SpecialPowers.wrap(input).hasEditor, `${description}restoring input type should create editor if it's focused element`);
  input.blur();
  input.focus();
  is(SpecialPowers.wrap(input).editor.rootElement.firstChild.wholeText, input.value,
     `${description}native anonymous text node should have exactly same value as value of <input> element`);
  is(input.value, "abc", `${description}input value should keep inserted value after creating editor`);

  function testSettingValueFromBeforeInput(aWithEditor, aPreventDefaultOfBeforeInput) {
    let beforeInputEvents = [];
    let inputEvents = [];
    function recordEvent(aEvent) {
      if (aEvent.type === "beforeinput") {
        beforeInputEvents.push(aEvent);
      } else {
        inputEvents.push(aEvent);
      }
    }
    let condition = `(${aWithEditor ? "with editor" : "without editor"}${aPreventDefaultOfBeforeInput ? ' and canceling "beforeinput" event' : ""}, the pref ${kSetUserInputCancelable ? "allows" : "disallows"} to cancel "beforeinput" event})`;
    function Reset() {
      beforeInputEvents = [];
      inputEvents = [];
      if (SpecialPowers.wrap(input).hasEditor != aWithEditor) {
        if (aWithEditor) {
          input.blur();
          input.focus(); // Associate `TextEditor` with input
          if (!SpecialPowers.wrap(input).hasEditor) {
            ok(false, `${description}Failed to associate TextEditor with the input ${condition}`);
            return false;
          }
        } else {
          input.blur();
          input.type = "button";
          input.type = "text";
          if (SpecialPowers.wrap(input).hasEditor) {
            ok(false, `${description}Failed to disassociate TextEditor from the input ${condition}`);
            return false;
          }
        }
      }
      return true;
    }

    description = `Setting value from "beforeinput" event listener whose inputType is "insertReplacementText" ${condition}: `;
    input.value = "abc";
    if (!Reset()) {
      return;
    }
    input.addEventListener("beforeinput", (aEvent) => {
      is(aEvent.inputType, "insertReplacementText", `${description}inputType of "beforeinput" event should be "insertReplacementText"`);
      is(aEvent.cancelable, kSetUserInputCancelable, `${description}"beforeinput" event should be cancelable unless it's suppressed by the pref`);
      is(input.value, "abc", `${description}The value shouldn't have been modified yet at "beforeinput" event listener`);
      input.addEventListener("beforeinput", recordEvent);
      input.addEventListener("input", recordEvent);
      input.value = "hig";
      if (aPreventDefaultOfBeforeInput) {
        aEvent.preventDefault();
      }
    }, {once: true});
    SpecialPowers.wrap(input).setUserInput("def");
    is(beforeInputEvents.length, 0, `${description}"beforeinput" event shouldn't be fired again`);
    if (aPreventDefaultOfBeforeInput && kSetUserInputCancelable) {
      is(input.value, "hig",
         `${description}The value should be set to the specified value in "beforeinput" event listener since "beforeinput" was canceled`);
      is(inputEvents.length, 0,
         `${description}"input" event shouldn't be fired since "beforeinput" was canceled`);
    } else {
      // XXX This result is different from Chrome (verified with spellchecker).
      //     Chrome inserts the new text to current value and selected range.
      //     It might be reasonable, but we don't touch this for now since it
      //     requires a lot of changes.
      is(input.value, "hig",
         `${description}The value should be set to the specified value in "beforeinput" event listener since the event target was already modified`);
      is(inputEvents.length, 1, `${description}"input" event should be fired`);
      if (inputEvents.length) {
        is(inputEvents[0].inputType,
           "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
        is(inputEvents[0].data, "def",
           `${description}data of "input" event should be the value specified by setUserInput()`);
      }
    }
    input.removeEventListener("beforeinput", recordEvent);
    input.removeEventListener("input", recordEvent);

    description = `Setting value from "beforeinput" event listener whose inputType is "insertReplacementText" and changing the type to "button" ${condition}: `;
    input.value = "abc";
    if (!Reset()) {
      return;
    }
    input.addEventListener("beforeinput", (aEvent) => {
      is(aEvent.inputType, "insertReplacementText", `${description}inputType of "beforeinput" event should be "insertReplacementText"`);
      is(aEvent.cancelable, kSetUserInputCancelable, `${description}"beforeinput" event should be cancelable unless it's suppressed by the pref`);
      is(input.value, "abc", `${description}The value shouldn't have been modified yet at "beforeinput" event listener`);
      input.addEventListener("beforeinput", recordEvent);
      input.addEventListener("input", recordEvent);
      input.value = "hig";
      input.type = "button";
      if (aPreventDefaultOfBeforeInput) {
        aEvent.preventDefault();
      }
    }, {once: true});
    SpecialPowers.wrap(input).setUserInput("def");
    is(beforeInputEvents.length, 0, `${description}"beforeinput" event shouldn't be fired again`);
    if (aPreventDefaultOfBeforeInput && kSetUserInputCancelable) {
      is(input.value, "hig",
         `${description}The value should be set to the specified value in "beforeinput" event listener since "beforeinput" was canceled`);
      is(inputEvents.length, 0,
         `${description}"input" event shouldn't be fired since "beforeinput" was canceled`);
    } else {
      // XXX This result is same as Chrome (verified with spellchecker).
      //     But this behavior is not consistent with just setting the value on Chrome.
      is(input.value, "hig",
         `${description}The value should be set to the specified value in "beforeinput" event listener since the event target was already modified`);
      // Same as Chrome
      is(inputEvents.length, 0,
         `${description}"input" event shouldn't be fired since the input element's type is changed`);
    }
    input.type = "text";
    input.removeEventListener("beforeinput", recordEvent);
    input.removeEventListener("input", recordEvent);

    description = `Setting value from "beforeinput" event listener whose inputType is "insertReplacementText" and destroying the frame ${condition}: `;
    input.value = "abc";
    if (!Reset()) {
      return;
    }
    input.addEventListener("beforeinput", (aEvent) => {
      is(aEvent.inputType, "insertReplacementText", `${description}inputType of "beforeinput" event should be "insertReplacementText"`);
      is(aEvent.cancelable, kSetUserInputCancelable, `${description}"beforeinput" event should be cancelable unless it's suppressed by the pref`);
      is(input.value, "abc", `${description}The value shouldn't have been modified yet at "beforeinput" event listener`);
      input.addEventListener("beforeinput", recordEvent);
      input.addEventListener("input", recordEvent);
      input.value = "hig";
      input.style.display = "none";
      if (aPreventDefaultOfBeforeInput) {
        aEvent.preventDefault();
      }
    }, {once: true});
    SpecialPowers.wrap(input).setUserInput("def");
    is(beforeInputEvents.length, 0, `${description}"beforeinput" event shouldn't be fired again`);
    if (aPreventDefaultOfBeforeInput && kSetUserInputCancelable) {
      is(input.value, "hig",
         `${description}The value should be set to the specified value in "beforeinput" event listener since "beforeinput" was canceled`);
      is(inputEvents.length, 0,
         `${description}"input" event shouldn't be fired since "beforeinput" was canceled`);
    } else {
      // XXX This result is same as Chrome (verified with spellchecker).
      //     But this behavior is not consistent with just setting the value on Chrome.
      is(input.value, "hig",
         `${description}The value should be set to the specified value in "beforeinput" event listener since the event target was already modified`);
      // Different from Chrome
      is(inputEvents.length, 1,
         `${description}"input" event should be fired even if the frame of target is destroyed`);
      if (inputEvents.length) {
        is(inputEvents[0].inputType,
           "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
        is(inputEvents[0].data, "def",
           `${description}data of "input" event should be the value specified by setUserInput()`);
      }
    }
    input.style.display = "inline";
    input.removeEventListener("beforeinput", recordEvent);
    input.removeEventListener("input", recordEvent);

    if (aWithEditor) {
      return;
    }

    description = `Setting value from "beforeinput" event listener whose inputType is "insertReplacementText and create editor" ${condition}: `;
    input.value = "abc";
    if (!Reset()) {
      return;
    }
    input.addEventListener("beforeinput", (aEvent) => {
      is(aEvent.inputType, "insertReplacementText", `${description}inputType of "beforeinput" event should be "insertReplacementText"`);
      is(aEvent.cancelable, kSetUserInputCancelable, `${description}"beforeinput" event should be cancelable unless it's suppressed by the pref`);
      is(input.value, "abc", `${description}The value shouldn't have been modified yet at "beforeinput" event listener`);
      input.addEventListener("beforeinput", recordEvent);
      input.addEventListener("input", recordEvent);
      input.value = "hig";
      input.focus();
      if (aPreventDefaultOfBeforeInput) {
        aEvent.preventDefault();
      }
    }, {once: true});
    SpecialPowers.wrap(input).setUserInput("def");
    is(beforeInputEvents.length, 0, `${description}"beforeinput" event shouldn't be fired again`);
    if (aPreventDefaultOfBeforeInput && kSetUserInputCancelable) {
      is(input.value, "hig",
         `${description}The value should be set to the specified value in "beforeinput" event listener since "beforeinput" was canceled`);
      is(inputEvents.length, 0,
         `${description}"input" event shouldn't be fired since "beforeinput" was canceled`);
    } else {
      // XXX This result is different from Chrome (verified with spellchecker).
      //     Chrome inserts the new text to current value and selected range.
      //     It might be reasonable, but we don't touch this for now since it
      //     requires a lot of changes.
      is(input.value, "hig",
         `${description}The value should be set to the specified value in "beforeinput" event listener since the event target was already modified`);
      is(inputEvents.length, 1, `${description}"input" event should be fired`);
      if (inputEvents.length) {
        is(inputEvents[0].inputType,
           "insertReplacementText", `${description}inputType of "input" event should be "insertReplacementText"`);
        is(inputEvents[0].data, "def",
           `${description}data of "input" event should be the value specified by setUserInput()`);
      }
    }
    input.removeEventListener("beforeinput", recordEvent);
    input.removeEventListener("input", recordEvent);
  }
  // testSettingValueFromBeforeInput(true, true);
  // testSettingValueFromBeforeInput(true, false);
  testSettingValueFromBeforeInput(false, true);
  testSettingValueFromBeforeInput(false, false);

  SimpleTest.finish();
});
</script>
</body>
</html>
