tor-browser

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

test_MozEditableElement_setUserInput.html (32224B)


      1 <!DOCTYPE>
      2 <html>
      3 <head>
      4  <title>Test for MozEditableElement.setUserInput()</title>
      5  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      6  <script src="/tests/SimpleTest/EventUtils.js"></script>
      7  <link rel="stylesheet" href="/tests/SimpleTest/test.css">
      8 </head>
      9 <body>
     10 <div id="display">
     11 </div>
     12 <div id="content"></div>
     13 <pre id="test">
     14 </pre>
     15 
     16 <script class="testbody" type="application/javascript">
     17 SimpleTest.waitForExplicitFinish();
     18 // eslint-disable-next-line complexity
     19 SimpleTest.waitForFocus(async () => {
     20  const kSetUserInputCancelable = SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input");
     21 
     22  let content = document.getElementById("content");
     23  /**
     24   * Test structure:
     25   *   element: the tag name to create.
     26   *   type: the type attribute value for the element.  If unnecessary omit it.
     27   *   input: the values calling setUserInput() with.
     28   *     before: used when calling setUserInput() before the element gets focus.
     29   *     after: used when calling setUserInput() after the element gets focus.
     30   *   result: the results of calling setUserInput().
     31   *     before: the element's expected value of calling setUserInput() before the element gets focus.
     32   *     after: the element's expected value of calling setUserInput() after the element gets focus.
     33   *     fireBeforeInputEvent: true if "beforeinput" event should be fired.  Otherwise, false.
     34   *     fireInputEvent: true if "input" event should be fired.  Otherwise, false.
     35   */
     36  for (let test of [{element: "input", type: "hidden",
     37                     input: {before: "3", after: "6"},
     38                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
     39                    {element: "input", type: "text",
     40                     input: {before: "3", after: "6"},
     41                     result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
     42                    {element: "input", type: "search",
     43                     input: {before: "3", after: "6"},
     44                     result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
     45                    {element: "input", type: "tel",
     46                     input: {before: "3", after: "6"},
     47                     result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
     48                    {element: "input", type: "url",
     49                     input: {before: "3", after: "6"},
     50                     result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
     51                    {element: "input", type: "email",
     52                     input: {before: "3", after: "6"},
     53                     result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
     54                    {element: "input", type: "password",
     55                     input: {before: "3", after: "6"},
     56                     result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
     57                    // "date" does not support setUserInput, but dispatches "input" event...
     58                    {element: "input", type: "date",
     59                     input: {before: "3", after: "6"},
     60                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
     61                    // "month" does not support setUserInput, but dispatches "input" event...
     62                    {element: "input", type: "month",
     63                     input: {before: "3", after: "6"},
     64                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
     65                    // "week" does not support setUserInput, but dispatches "input" event...
     66                    {element: "input", type: "week",
     67                     input: {before: "3", after: "6"},
     68                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
     69                    // "time" does not support setUserInput, but dispatches "input" event...
     70                    {element: "input", type: "time",
     71                     input: {before: "3", after: "6"},
     72                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
     73                    // "datetime-local" does not support setUserInput, but dispatches "input" event...
     74                    {element: "input", type: "datetime-local",
     75                     input: {before: "3", after: "6"},
     76                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
     77                    {element: "input", type: "number",
     78                     input: {before: "3", after: "6"},
     79                     result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
     80                    {element: "input", type: "range",
     81                     input: {before: "3", after: "6"},
     82                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
     83                    // "color" does not support setUserInput, but dispatches "input" event...
     84                    {element: "input", type: "color",
     85                     input: {before: "#5C5C5C", after: "#FFFFFF"},
     86                     result: {before: "#5c5c5c", after:"#ffffff", fireBeforeInputEvent: false, fireInputEvent: true}},
     87                    {element: "input", type: "checkbox",
     88                     input: {before: "3", after: "6"},
     89                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
     90                    {element: "input", type: "radio",
     91                     input: {before: "3", after: "6"},
     92                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
     93                    // "file" is not supported by setUserInput? But there is a path...
     94                    {element: "input", type: "file",
     95                     input: {before: "3", after: "6"},
     96                     result: {before: "", after:"", fireBeforeInputEvent: false, fireInputEvent: true}},
     97                    {element: "input", type: "submit",
     98                     input: {before: "3", after: "6"},
     99                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
    100                    {element: "input", type: "image",
    101                     input: {before: "3", after: "6"},
    102                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
    103                    {element: "input", type: "reset",
    104                     input: {before: "3", after: "6"},
    105                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
    106                    {element: "input", type: "button",
    107                     input: {before: "3", after: "6"},
    108                     result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
    109                    {element: "textarea",
    110                     input: {before: "3", after: "6"},
    111                     result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}]) {
    112    let tag =
    113      test.type !== undefined ? `<${test.element} type="${test.type}">` :
    114                                `<${test.element}>`;
    115    content.innerHTML =
    116      test.element !== "input" ? tag : `${tag}</${test.element}>`;
    117    content.scrollTop; // Flush pending layout.
    118    let target = content.firstChild;
    119 
    120    let inputEvents = [], beforeInputEvents = [];
    121    function onBeforeInput(aEvent) {
    122      beforeInputEvents.push(aEvent);
    123    }
    124    function onInput(aEvent) {
    125      inputEvents.push(aEvent);
    126    }
    127    target.addEventListener("beforeinput", onBeforeInput);
    128    target.addEventListener("input", onInput);
    129 
    130    // Before setting focus, editor of the element may have not been created yet.
    131    let previousValue = target.value;
    132    SpecialPowers.wrap(target).setUserInput(test.input.before);
    133    if (target.value == previousValue && test.result.before != previousValue) {
    134      todo_is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`);
    135    } else {
    136      is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`);
    137    }
    138    if (target.value == previousValue) {
    139      if (test.type === "date" || test.type === "time" || test.type === "datetime-local") {
    140        todo_is(inputEvents.length, 0,
    141                `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    142      } else {
    143        is(inputEvents.length, 0,
    144           `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    145      }
    146    } else {
    147      if (!test.result.fireBeforeInputEvent) {
    148        is(beforeInputEvents.length, 0,
    149           `No "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    150      } else {
    151        is(beforeInputEvents.length, 1,
    152           `Only one "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    153      }
    154      if (!test.result.fireInputEvent) {
    155        // HTML spec defines that "input" elements whose type are "hidden",
    156        // "submit", "image", "reset" and "button" shouldn't fire input event
    157        // when its value is changed.
    158        // XXX Perhaps, we shouldn't support setUserInput() with such types.
    159        if (test.type === "hidden" ||
    160            test.type === "submit" ||
    161            test.type === "image" ||
    162            test.type === "reset" ||
    163            test.type === "button") {
    164          todo_is(inputEvents.length, 0,
    165                  `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    166        } else {
    167          is(inputEvents.length, 0,
    168             `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    169        }
    170      } else {
    171        is(inputEvents.length, 1,
    172           `Only one "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    173      }
    174    }
    175    if (inputEvents.length) {
    176      if (SpecialPowers.wrap(target).isInputEventTarget) {
    177        if (test.type === "time") {
    178          todo(inputEvents[0] instanceof InputEvent,
    179               `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    180        } else {
    181          if (beforeInputEvents.length && test.result.fireBeforeInputEvent) {
    182            is(beforeInputEvents[0].cancelable, kSetUserInputCancelable,
    183               `"beforeinput" event for "insertReplacementText" should be cancelable when setUserInput("${test.input.before}") is called before ${tag} gets focus unless it's suppressed by the pref`);
    184            is(beforeInputEvents[0].inputType, "insertReplacementText",
    185               `inputType of "beforeinput"event should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    186            is(beforeInputEvents[0].data, test.input.before,
    187               `data of "beforeinput" event should be "${test.input.before}" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    188            is(beforeInputEvents[0].dataTransfer, null,
    189               `dataTransfer of "beforeinput" event should be null when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    190            is(beforeInputEvents[0].getTargetRanges().length, 0,
    191               `getTargetRanges() of "beforeinput" event should return empty array when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    192          }
    193          ok(inputEvents[0] instanceof InputEvent,
    194             `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    195          is(inputEvents[0].inputType, "insertReplacementText",
    196             `inputType of "input" event should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    197          is(inputEvents[0].data, test.input.before,
    198             `data of "input" event should be "${test.input.before}" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    199          is(inputEvents[0].dataTransfer, null,
    200             `dataTransfer of "input" event should be null when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    201          is(inputEvents[0].getTargetRanges().length, 0,
    202             `getTargetRanges() of "input" event should return empty array when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    203        }
    204      } else {
    205        ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
    206           `"input" event should be dispatched with Event interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
    207      }
    208      is(inputEvents[0].cancelable, false,
    209         `"input" event should be never cancelable (${tag}, before getting focus)`);
    210      is(inputEvents[0].bubbles, true,
    211         `"input" event should always bubble (${tag}, before getting focus)`);
    212    }
    213 
    214    beforeInputEvents = [];
    215    inputEvents = [];
    216    target.focus();
    217    previousValue = target.value;
    218    SpecialPowers.wrap(target).setUserInput(test.input.after);
    219    if (target.value == previousValue && test.result.after != previousValue) {
    220      todo_is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`);
    221    } else {
    222      is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`);
    223    }
    224    if (target.value == previousValue) {
    225      if (test.type === "date" || test.type === "time" || test.type === "datetime-local") {
    226        todo_is(inputEvents.length, 0,
    227                `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    228      } else {
    229        is(inputEvents.length, 0,
    230           `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    231      }
    232    } else {
    233      if (!test.result.fireBeforeInputEvent) {
    234        is(beforeInputEvents.length, 0,
    235           `No "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    236      } else {
    237        is(beforeInputEvents.length, 1,
    238           `Only one "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    239      }
    240      if (!test.result.fireInputEvent) {
    241        // HTML spec defines that "input" elements whose type are "hidden",
    242        // "submit", "image", "reset" and "button" shouldn't fire input event
    243        // when its value is changed.
    244        // XXX Perhaps, we shouldn't support setUserInput() with such types.
    245        if (test.type === "hidden" ||
    246            test.type === "submit" ||
    247            test.type === "image" ||
    248            test.type === "reset" ||
    249            test.type === "button") {
    250          todo_is(inputEvents.length, 0,
    251                  `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    252        } else {
    253          is(inputEvents.length, 0,
    254             `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    255        }
    256      } else {
    257        is(inputEvents.length, 1,
    258           `Only one "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    259      }
    260    }
    261    if (inputEvents.length) {
    262      if (SpecialPowers.wrap(target).isInputEventTarget) {
    263        if (test.type === "time") {
    264          todo(inputEvents[0] instanceof InputEvent,
    265               `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    266        } else {
    267          if (beforeInputEvents.length && test.result.fireBeforeInputEvent) {
    268            is(beforeInputEvents[0].cancelable, kSetUserInputCancelable,
    269               `"beforeinput" event should be cancelable when setUserInput("${test.input.after}") is called after ${tag} gets focus unless it's suppressed by the pref`);
    270            is(beforeInputEvents[0].inputType, "insertReplacementText",
    271               `inputType of "beforeinput" event should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    272            is(beforeInputEvents[0].data, test.input.after,
    273               `data of "beforeinput" should be "${test.input.after}" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    274            is(beforeInputEvents[0].dataTransfer, null,
    275               `dataTransfer of "beforeinput" should be null when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    276            is(beforeInputEvents[0].getTargetRanges().length, 0,
    277               `getTargetRanges() of "beforeinput" should return empty array when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    278          }
    279          ok(inputEvents[0] instanceof InputEvent,
    280             `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    281          is(inputEvents[0].inputType, "insertReplacementText",
    282             `inputType of "input" event should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    283          is(inputEvents[0].data, test.input.after,
    284             `data of "input" event should be "${test.input.after}" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    285          is(inputEvents[0].dataTransfer, null,
    286             `dataTransfer of "input" event should be null when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    287          is(inputEvents[0].getTargetRanges().length, 0,
    288             `getTargetRanges() of "input" event should return empty array when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    289        }
    290      } else {
    291        ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
    292           `"input" event should be dispatched with Event interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
    293      }
    294      is(inputEvents[0].cancelable, false,
    295         `"input" event should be never cancelable (${tag}, after getting focus)`);
    296      is(inputEvents[0].bubbles, true,
    297         `"input" event should always bubble (${tag}, after getting focus)`);
    298    }
    299 
    300    target.removeEventListener("input", onInput);
    301  }
    302 
    303  function testValidationMessage(aType, aInvalidValue, aValidValue) {
    304    let tag = `<input type="${aType}">`
    305    content.innerHTML = tag;
    306    content.scrollTop; // Flush pending layout.
    307    let target = content.firstChild;
    308 
    309    let inputEvents = [];
    310    let validationMessage = "";
    311 
    312    function reset() {
    313      inputEvents = [];
    314      validationMessage = "";
    315    }
    316 
    317    function onInput(aEvent) {
    318      inputEvents.push(aEvent);
    319      validationMessage = aEvent.target.validationMessage;
    320    }
    321    target.addEventListener("input", onInput);
    322 
    323    reset();
    324    SpecialPowers.wrap(target).setUserInput(aInvalidValue);
    325    is(inputEvents.length, 1,
    326       `Only one "input" event should be dispatched when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
    327    isnot(validationMessage, "",
    328          `${tag}.validationMessage should not be empty when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
    329    ok(target.matches(":invalid"),
    330       `The target should have invalid pseudo class when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
    331 
    332    reset();
    333    SpecialPowers.wrap(target).setUserInput(aValidValue);
    334    is(inputEvents.length, 1,
    335       `Only one "input" event should be dispatched when setUserInput("${aValidValue}") is called before ${tag} gets focus`);
    336    is(validationMessage, "",
    337       `${tag}.validationMessage should be empty when setUserInput("${aValidValue}") is called before ${tag} gets focus`);
    338    ok(!target.matches(":invalid"),
    339       `The target shouldn't have invalid pseudo class when setUserInput("${aValidValue}") is called before ${tag} gets focus`);
    340 
    341    reset();
    342    SpecialPowers.wrap(target).setUserInput(aInvalidValue);
    343    is(inputEvents.length, 1,
    344       `Only one "input" event should be dispatched again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
    345    isnot(validationMessage, "",
    346          `${tag}.validationMessage should not be empty again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
    347    ok(target.matches(":invalid"),
    348       `The target should have invalid pseudo class again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
    349 
    350    target.value = "";
    351    target.focus();
    352 
    353    reset();
    354    SpecialPowers.wrap(target).setUserInput(aInvalidValue);
    355    is(inputEvents.length, 1,
    356       `Only one "input" event should be dispatched when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
    357    isnot(validationMessage, "",
    358          `${tag}.validationMessage should not be empty when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
    359    ok(target.matches(":invalid"),
    360       `The target should have invalid pseudo class when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
    361 
    362    reset();
    363    SpecialPowers.wrap(target).setUserInput(aValidValue);
    364    is(inputEvents.length, 1,
    365       `Only one "input" event should be dispatched when setUserInput("${aValidValue}") is called after ${tag} gets focus`);
    366    is(validationMessage, "",
    367       `${tag}.validationMessage should be empty when setUserInput("${aValidValue}") is called after ${tag} gets focus`);
    368    ok(!target.matches(":invalid"),
    369       `The target shouldn't have invalid pseudo class when setUserInput("${aValidValue}") is called after ${tag} gets focus`);
    370 
    371    reset();
    372    SpecialPowers.wrap(target).setUserInput(aInvalidValue);
    373    is(inputEvents.length, 1,
    374       `Only one "input" event should be dispatched again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
    375    isnot(validationMessage, "",
    376          `${tag}.validationMessage should not be empty again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
    377    ok(target.matches(":invalid"),
    378       `The target should have invalid pseudo class again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
    379 
    380    target.removeEventListener("input", onInput);
    381  }
    382  testValidationMessage("email", "f", "foo@example.com");
    383 
    384  function testValueMissing(aType, aValidValue) {
    385    let tag = aType === "textarea" ? "<textarea required>" : `<input type="${aType}" required>`;
    386    content.innerHTML = `${tag}${aType === "textarea" ? "</textarea>" : ""}`;
    387    content.scrollTop; // Flush pending layout.
    388    let target = content.firstChild;
    389 
    390    let inputEvents = [], beforeInputEvents = [];
    391    function reset() {
    392      beforeInputEvents = [];
    393      inputEvents = [];
    394    }
    395 
    396    function onBeforeInput(aEvent) {
    397      aEvent.validity = aEvent.target.checkValidity();
    398      beforeInputEvents.push(aEvent);
    399    }
    400    function onInput(aEvent) {
    401      aEvent.validity = aEvent.target.checkValidity();
    402      inputEvents.push(aEvent);
    403    }
    404    target.addEventListener("beforeinput", onBeforeInput);
    405    target.addEventListener("input", onInput);
    406 
    407    reset();
    408    SpecialPowers.wrap(target).setUserInput(aValidValue);
    409    is(beforeInputEvents.length, 1, `Calling  ${tag}.setUserInput(${aValidValue}) should cause a "beforeinput" event (before gets focus)`);
    410    if (beforeInputEvents.length) {
    411      is(beforeInputEvents[0].validity, false,
    412         `The ${tag} should be invalid at "beforeinput" event (before gets focus)`);
    413    }
    414    is(inputEvents.length, 1, `Calling  ${tag}.setUserInput(${aValidValue}) should cause a "input" event (before gets focus)`);
    415    if (inputEvents.length) {
    416      is(inputEvents[0].validity, true,
    417         `The ${tag} should be valid at "input" event (before gets focus)`);
    418    }
    419 
    420    target.removeEventListener("beforeinput", onBeforeInput);
    421    target.removeEventListener("input", onInput);
    422 
    423    content.innerHTML = "";
    424    content.scrollTop; // Flush pending layout.
    425    content.innerHTML = `${tag}${aType === "textarea" ? "</textarea>" : ""}`;
    426    content.scrollTop; // Flush pending layout.
    427    target = content.firstChild;
    428 
    429    target.focus();
    430    target.addEventListener("beforeinput", onBeforeInput);
    431    target.addEventListener("input", onInput);
    432 
    433    reset();
    434    SpecialPowers.wrap(target).setUserInput(aValidValue);
    435    is(beforeInputEvents.length, 1, `Calling  ${tag}.setUserInput(${aValidValue}) should cause a "beforeinput" event (after gets focus)`);
    436    if (beforeInputEvents.length) {
    437      is(beforeInputEvents[0].validity, false,
    438         `The ${tag} should be invalid at "beforeinput" event (after gets focus)`);
    439    }
    440    is(inputEvents.length, 1, `Calling  ${tag}.setUserInput(${aValidValue}) should cause a "input" event (after gets focus)`);
    441    if (inputEvents.length) {
    442      is(inputEvents[0].validity, true,
    443         `The ${tag} should be valid at "input" event (after gets focus)`);
    444    }
    445 
    446    target.removeEventListener("beforeinput", onBeforeInput);
    447    target.removeEventListener("input", onInput);
    448  }
    449  testValueMissing("text", "abc");
    450  testValueMissing("password", "abc");
    451  testValueMissing("textarea", "abc");
    452  testValueMissing("email", "foo@example.com");
    453  testValueMissing("url", "https://example.com/");
    454 
    455  function testEditorValueAtEachEvent(aType) {
    456    let tag = aType === "textarea" ? "<textarea>" : `<input type="${aType}">`
    457    let closeTag = aType === "textarea" ? "</textarea>" : "";
    458    content.innerHTML = `${tag}${closeTag}`;
    459    content.scrollTop; // Flush pending layout.
    460    let target = content.firstChild;
    461    target.value = "Old Value";
    462    let description = `Setting new value of ${tag} before setting focus: `;
    463    let onBeforeInput = (aEvent) => {
    464      is(target.value, "Old Value",
    465         `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`);
    466    };
    467    let onInput = (aEvent) => {
    468      is(target.value, "New Value",
    469         `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`);
    470    };
    471    target.addEventListener("beforeinput", onBeforeInput);
    472    target.addEventListener("input", onInput);
    473    SpecialPowers.wrap(target).setUserInput("New Value");
    474 
    475    description = `Setting new value of ${tag} after setting focus: `;
    476    target.value = "Old Value";
    477    target.focus();
    478    SpecialPowers.wrap(target).setUserInput("New Value");
    479 
    480    target.removeEventListener("beforeinput", onBeforeInput);
    481    target.removeEventListener("input", onInput);
    482 
    483    // FYI: This is not realistic situation because we should do nothing
    484    //      while user composing IME.
    485    // TODO: TextControlState should stop returning setting value as the value
    486    //       while committing composition.
    487    description = `Setting new value of ${tag} during composition: `;
    488    target.value = "";
    489    target.focus();
    490    synthesizeCompositionChange({
    491      composition: {
    492        string: "composition string",
    493        clauses: [{length: 18, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
    494      },
    495      caret: {start: 18, length: 0},
    496    });
    497    let onCompositionUpdate = (aEvent) => {
    498      todo_is(target.value, "composition string",
    499         `${description}The value should not have been modified at "compositionupdate" event yet (data: "${aEvent.data}")`);
    500    };
    501    let onCompositionEnd = (aEvent) => {
    502      todo_is(target.value, "composition string",
    503         `${description}The value should not have been modified at "compositionupdate" event yet (data: "${aEvent.data}")`);
    504    };
    505    onBeforeInput = (aEvent) => {
    506      if (aEvent.inputType === "insertCompositionText") {
    507        todo_is(target.value, "composition string",
    508           `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`);
    509      } else {
    510        is(target.value, "composition string",
    511           `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`);
    512      }
    513    };
    514    onInput = (aEvent) => {
    515      if (aEvent.inputType === "insertCompositionText") {
    516        todo_is(target.value, "composition string",
    517           `${description}The value should not have been modified at "input" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`);
    518      } else {
    519        is(target.value, "New Value",
    520           `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`);
    521      }
    522    };
    523    target.addEventListener("compositionupdate", onCompositionUpdate);
    524    target.addEventListener("compositionend", onCompositionEnd);
    525    target.addEventListener("beforeinput", onBeforeInput);
    526    target.addEventListener("input", onInput);
    527    SpecialPowers.wrap(target).setUserInput("New Value");
    528    target.removeEventListener("compositionupdate", onCompositionUpdate);
    529    target.removeEventListener("compositionend", onCompositionEnd);
    530    target.removeEventListener("beforeinput", onBeforeInput);
    531    target.removeEventListener("input", onInput);
    532  }
    533  testEditorValueAtEachEvent("text");
    534  testEditorValueAtEachEvent("textarea");
    535 
    536  async function testBeforeInputCancelable(aType) {
    537    let tag = aType === "textarea" ? "<textarea>" : `<input type="${aType}">`
    538    let closeTag = aType === "textarea" ? "</textarea>" : "";
    539    for (const kShouldBeCancelable of [true, false]) {
    540      await SpecialPowers.pushPrefEnv({
    541        set: [["dom.input_event.allow_to_cancel_set_user_input", kShouldBeCancelable]],
    542      });
    543 
    544      content.innerHTML = `${tag}${closeTag}`;
    545      content.scrollTop; // Flush pending layout.
    546      let target = content.firstChild;
    547      target.value = "Old Value";
    548      let description = `Setting new value of ${tag} before setting focus (the pref ${kShouldBeCancelable ? "allows" : "disallows"} to cancel beforeinput): `;
    549      let onBeforeInput = (aEvent) => {
    550        is(aEvent.cancelable, kShouldBeCancelable,
    551          `${description}The "beforeinput" event should be ${kShouldBeCancelable ? "cancelable" : "not be cancelable due to suppressed by the pref"}`);
    552      };
    553      let onInput = (aEvent) => {
    554        is(aEvent.cancelable, false,
    555          `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`);
    556      };
    557      target.addEventListener("beforeinput", onBeforeInput);
    558      target.addEventListener("input", onInput);
    559      SpecialPowers.wrap(target).setUserInput("New Value");
    560 
    561      description = `Setting new value of ${tag} after setting focus (the pref ${kShouldBeCancelable ? "allows" : "disallows"} to cancel beforeinput): `;
    562      target.value = "Old Value";
    563      target.focus();
    564      SpecialPowers.wrap(target).setUserInput("New Value");
    565 
    566      target.removeEventListener("beforeinput", onBeforeInput);
    567      target.removeEventListener("input", onInput);
    568    }
    569 
    570    await SpecialPowers.clearUserPref({
    571      clear: [["dom.input_event.allow_to_cancel_set_user_input"]],
    572    });
    573  }
    574  await testBeforeInputCancelable("text");
    575  await testBeforeInputCancelable("textarea");
    576 
    577  SimpleTest.finish();
    578 });
    579 </script>
    580 </body>
    581 </html>