summaryrefslogtreecommitdiffstats
path: root/dom/html/test/forms/test_MozEditableElement_setUserInput.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/test/forms/test_MozEditableElement_setUserInput.html')
-rw-r--r--dom/html/test/forms/test_MozEditableElement_setUserInput.html581
1 files changed, 581 insertions, 0 deletions
diff --git a/dom/html/test/forms/test_MozEditableElement_setUserInput.html b/dom/html/test/forms/test_MozEditableElement_setUserInput.html
new file mode 100644
index 0000000000..06380776f6
--- /dev/null
+++ b/dom/html/test/forms/test_MozEditableElement_setUserInput.html
@@ -0,0 +1,581 @@
+<!DOCTYPE>
+<html>
+<head>
+ <title>Test for MozEditableElement.setUserInput()</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"></div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+// eslint-disable-next-line complexity
+SimpleTest.waitForFocus(async () => {
+ const kSetUserInputCancelable = SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input");
+
+ let content = document.getElementById("content");
+ /**
+ * Test structure:
+ * element: the tag name to create.
+ * type: the type attribute value for the element. If unnecessary omit it.
+ * input: the values calling setUserInput() with.
+ * before: used when calling setUserInput() before the element gets focus.
+ * after: used when calling setUserInput() after the element gets focus.
+ * result: the results of calling setUserInput().
+ * before: the element's expected value of calling setUserInput() before the element gets focus.
+ * after: the element's expected value of calling setUserInput() after the element gets focus.
+ * fireBeforeInputEvent: true if "beforeinput" event should be fired. Otherwise, false.
+ * fireInputEvent: true if "input" event should be fired. Otherwise, false.
+ */
+ for (let test of [{element: "input", type: "hidden",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
+ {element: "input", type: "text",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
+ {element: "input", type: "search",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
+ {element: "input", type: "tel",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
+ {element: "input", type: "url",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
+ {element: "input", type: "email",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
+ {element: "input", type: "password",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
+ // "date" does not support setUserInput, but dispatches "input" event...
+ {element: "input", type: "date",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
+ // "month" does not support setUserInput, but dispatches "input" event...
+ {element: "input", type: "month",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
+ // "week" does not support setUserInput, but dispatches "input" event...
+ {element: "input", type: "week",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
+ // "time" does not support setUserInput, but dispatches "input" event...
+ {element: "input", type: "time",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
+ // "datetime-local" does not support setUserInput, but dispatches "input" event...
+ {element: "input", type: "datetime-local",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
+ {element: "input", type: "number",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}},
+ {element: "input", type: "range",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
+ // "color" does not support setUserInput, but dispatches "input" event...
+ {element: "input", type: "color",
+ input: {before: "#5C5C5C", after: "#FFFFFF"},
+ result: {before: "#5c5c5c", after:"#ffffff", fireBeforeInputEvent: false, fireInputEvent: true}},
+ {element: "input", type: "checkbox",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
+ {element: "input", type: "radio",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: true}},
+ // "file" is not supported by setUserInput? But there is a path...
+ {element: "input", type: "file",
+ input: {before: "3", after: "6"},
+ result: {before: "", after:"", fireBeforeInputEvent: false, fireInputEvent: true}},
+ {element: "input", type: "submit",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
+ {element: "input", type: "image",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
+ {element: "input", type: "reset",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
+ {element: "input", type: "button",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: false, fireInputEvent: false}},
+ {element: "textarea",
+ input: {before: "3", after: "6"},
+ result: {before: "3", after:"6", fireBeforeInputEvent: true, fireInputEvent: true}}]) {
+ let tag =
+ test.type !== undefined ? `<${test.element} type="${test.type}">` :
+ `<${test.element}>`;
+ content.innerHTML =
+ test.element !== "input" ? tag : `${tag}</${test.element}>`;
+ content.scrollTop; // Flush pending layout.
+ let target = content.firstChild;
+
+ let inputEvents = [], beforeInputEvents = [];
+ function onBeforeInput(aEvent) {
+ beforeInputEvents.push(aEvent);
+ }
+ function onInput(aEvent) {
+ inputEvents.push(aEvent);
+ }
+ target.addEventListener("beforeinput", onBeforeInput);
+ target.addEventListener("input", onInput);
+
+ // Before setting focus, editor of the element may have not been created yet.
+ let previousValue = target.value;
+ SpecialPowers.wrap(target).setUserInput(test.input.before);
+ if (target.value == previousValue && test.result.before != previousValue) {
+ todo_is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`);
+ } else {
+ is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`);
+ }
+ if (target.value == previousValue) {
+ if (test.type === "date" || test.type === "time" || test.type === "datetime-local") {
+ todo_is(inputEvents.length, 0,
+ `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ } else {
+ is(inputEvents.length, 0,
+ `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ }
+ } else {
+ if (!test.result.fireBeforeInputEvent) {
+ is(beforeInputEvents.length, 0,
+ `No "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ } else {
+ is(beforeInputEvents.length, 1,
+ `Only one "beforeinput" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ }
+ if (!test.result.fireInputEvent) {
+ // HTML spec defines that "input" elements whose type are "hidden",
+ // "submit", "image", "reset" and "button" shouldn't fire input event
+ // when its value is changed.
+ // XXX Perhaps, we shouldn't support setUserInput() with such types.
+ if (test.type === "hidden" ||
+ test.type === "submit" ||
+ test.type === "image" ||
+ test.type === "reset" ||
+ test.type === "button") {
+ todo_is(inputEvents.length, 0,
+ `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ } else {
+ is(inputEvents.length, 0,
+ `No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ }
+ } else {
+ is(inputEvents.length, 1,
+ `Only one "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ }
+ }
+ if (inputEvents.length) {
+ if (SpecialPowers.wrap(target).isInputEventTarget) {
+ if (test.type === "time") {
+ todo(inputEvents[0] instanceof InputEvent,
+ `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ } else {
+ if (beforeInputEvents.length && test.result.fireBeforeInputEvent) {
+ is(beforeInputEvents[0].cancelable, kSetUserInputCancelable,
+ `"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`);
+ is(beforeInputEvents[0].inputType, "insertReplacementText",
+ `inputType of "beforeinput"event should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ is(beforeInputEvents[0].data, test.input.before,
+ `data of "beforeinput" event should be "${test.input.before}" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ is(beforeInputEvents[0].dataTransfer, null,
+ `dataTransfer of "beforeinput" event should be null when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ is(beforeInputEvents[0].getTargetRanges().length, 0,
+ `getTargetRanges() of "beforeinput" event should return empty array when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ }
+ ok(inputEvents[0] instanceof InputEvent,
+ `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ is(inputEvents[0].inputType, "insertReplacementText",
+ `inputType of "input" event should be "insertReplacementText" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ is(inputEvents[0].data, test.input.before,
+ `data of "input" event should be "${test.input.before}" when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ is(inputEvents[0].dataTransfer, null,
+ `dataTransfer of "input" event should be null when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ is(inputEvents[0].getTargetRanges().length, 0,
+ `getTargetRanges() of "input" event should return empty array when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ }
+ } else {
+ ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
+ `"input" event should be dispatched with Event interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
+ }
+ is(inputEvents[0].cancelable, false,
+ `"input" event should be never cancelable (${tag}, before getting focus)`);
+ is(inputEvents[0].bubbles, true,
+ `"input" event should always bubble (${tag}, before getting focus)`);
+ }
+
+ beforeInputEvents = [];
+ inputEvents = [];
+ target.focus();
+ previousValue = target.value;
+ SpecialPowers.wrap(target).setUserInput(test.input.after);
+ if (target.value == previousValue && test.result.after != previousValue) {
+ todo_is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`);
+ } else {
+ is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`);
+ }
+ if (target.value == previousValue) {
+ if (test.type === "date" || test.type === "time" || test.type === "datetime-local") {
+ todo_is(inputEvents.length, 0,
+ `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ } else {
+ is(inputEvents.length, 0,
+ `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ }
+ } else {
+ if (!test.result.fireBeforeInputEvent) {
+ is(beforeInputEvents.length, 0,
+ `No "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ } else {
+ is(beforeInputEvents.length, 1,
+ `Only one "beforeinput" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ }
+ if (!test.result.fireInputEvent) {
+ // HTML spec defines that "input" elements whose type are "hidden",
+ // "submit", "image", "reset" and "button" shouldn't fire input event
+ // when its value is changed.
+ // XXX Perhaps, we shouldn't support setUserInput() with such types.
+ if (test.type === "hidden" ||
+ test.type === "submit" ||
+ test.type === "image" ||
+ test.type === "reset" ||
+ test.type === "button") {
+ todo_is(inputEvents.length, 0,
+ `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ } else {
+ is(inputEvents.length, 0,
+ `No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ }
+ } else {
+ is(inputEvents.length, 1,
+ `Only one "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ }
+ }
+ if (inputEvents.length) {
+ if (SpecialPowers.wrap(target).isInputEventTarget) {
+ if (test.type === "time") {
+ todo(inputEvents[0] instanceof InputEvent,
+ `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ } else {
+ if (beforeInputEvents.length && test.result.fireBeforeInputEvent) {
+ is(beforeInputEvents[0].cancelable, kSetUserInputCancelable,
+ `"beforeinput" event should be cancelable when setUserInput("${test.input.after}") is called after ${tag} gets focus unless it's suppressed by the pref`);
+ is(beforeInputEvents[0].inputType, "insertReplacementText",
+ `inputType of "beforeinput" event should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ is(beforeInputEvents[0].data, test.input.after,
+ `data of "beforeinput" should be "${test.input.after}" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ is(beforeInputEvents[0].dataTransfer, null,
+ `dataTransfer of "beforeinput" should be null when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ is(beforeInputEvents[0].getTargetRanges().length, 0,
+ `getTargetRanges() of "beforeinput" should return empty array when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ }
+ ok(inputEvents[0] instanceof InputEvent,
+ `"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ is(inputEvents[0].inputType, "insertReplacementText",
+ `inputType of "input" event should be "insertReplacementText" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ is(inputEvents[0].data, test.input.after,
+ `data of "input" event should be "${test.input.after}" when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ is(inputEvents[0].dataTransfer, null,
+ `dataTransfer of "input" event should be null when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ is(inputEvents[0].getTargetRanges().length, 0,
+ `getTargetRanges() of "input" event should return empty array when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ }
+ } else {
+ ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
+ `"input" event should be dispatched with Event interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
+ }
+ is(inputEvents[0].cancelable, false,
+ `"input" event should be never cancelable (${tag}, after getting focus)`);
+ is(inputEvents[0].bubbles, true,
+ `"input" event should always bubble (${tag}, after getting focus)`);
+ }
+
+ target.removeEventListener("input", onInput);
+ }
+
+ function testValidationMessage(aType, aInvalidValue, aValidValue) {
+ let tag = `<input type="${aType}">`
+ content.innerHTML = tag;
+ content.scrollTop; // Flush pending layout.
+ let target = content.firstChild;
+
+ let inputEvents = [];
+ let validationMessage = "";
+
+ function reset() {
+ inputEvents = [];
+ validationMessage = "";
+ }
+
+ function onInput(aEvent) {
+ inputEvents.push(aEvent);
+ validationMessage = aEvent.target.validationMessage;
+ }
+ target.addEventListener("input", onInput);
+
+ reset();
+ SpecialPowers.wrap(target).setUserInput(aInvalidValue);
+ is(inputEvents.length, 1,
+ `Only one "input" event should be dispatched when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
+ isnot(validationMessage, "",
+ `${tag}.validationMessage should not be empty when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
+ ok(target.matches(":invalid"),
+ `The target should have invalid pseudo class when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
+
+ reset();
+ SpecialPowers.wrap(target).setUserInput(aValidValue);
+ is(inputEvents.length, 1,
+ `Only one "input" event should be dispatched when setUserInput("${aValidValue}") is called before ${tag} gets focus`);
+ is(validationMessage, "",
+ `${tag}.validationMessage should be empty when setUserInput("${aValidValue}") is called before ${tag} gets focus`);
+ ok(!target.matches(":invalid"),
+ `The target shouldn't have invalid pseudo class when setUserInput("${aValidValue}") is called before ${tag} gets focus`);
+
+ reset();
+ SpecialPowers.wrap(target).setUserInput(aInvalidValue);
+ is(inputEvents.length, 1,
+ `Only one "input" event should be dispatched again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
+ isnot(validationMessage, "",
+ `${tag}.validationMessage should not be empty again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
+ ok(target.matches(":invalid"),
+ `The target should have invalid pseudo class again when setUserInput("${aInvalidValue}") is called before ${tag} gets focus`);
+
+ target.value = "";
+ target.focus();
+
+ reset();
+ SpecialPowers.wrap(target).setUserInput(aInvalidValue);
+ is(inputEvents.length, 1,
+ `Only one "input" event should be dispatched when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
+ isnot(validationMessage, "",
+ `${tag}.validationMessage should not be empty when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
+ ok(target.matches(":invalid"),
+ `The target should have invalid pseudo class when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
+
+ reset();
+ SpecialPowers.wrap(target).setUserInput(aValidValue);
+ is(inputEvents.length, 1,
+ `Only one "input" event should be dispatched when setUserInput("${aValidValue}") is called after ${tag} gets focus`);
+ is(validationMessage, "",
+ `${tag}.validationMessage should be empty when setUserInput("${aValidValue}") is called after ${tag} gets focus`);
+ ok(!target.matches(":invalid"),
+ `The target shouldn't have invalid pseudo class when setUserInput("${aValidValue}") is called after ${tag} gets focus`);
+
+ reset();
+ SpecialPowers.wrap(target).setUserInput(aInvalidValue);
+ is(inputEvents.length, 1,
+ `Only one "input" event should be dispatched again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
+ isnot(validationMessage, "",
+ `${tag}.validationMessage should not be empty again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
+ ok(target.matches(":invalid"),
+ `The target should have invalid pseudo class again when setUserInput("${aInvalidValue}") is called after ${tag} gets focus`);
+
+ target.removeEventListener("input", onInput);
+ }
+ testValidationMessage("email", "f", "foo@example.com");
+
+ function testValueMissing(aType, aValidValue) {
+ let tag = aType === "textarea" ? "<textarea required>" : `<input type="${aType}" required>`;
+ content.innerHTML = `${tag}${aType === "textarea" ? "</textarea>" : ""}`;
+ content.scrollTop; // Flush pending layout.
+ let target = content.firstChild;
+
+ let inputEvents = [], beforeInputEvents = [];
+ function reset() {
+ beforeInputEvents = [];
+ inputEvents = [];
+ }
+
+ function onBeforeInput(aEvent) {
+ aEvent.validity = aEvent.target.checkValidity();
+ beforeInputEvents.push(aEvent);
+ }
+ function onInput(aEvent) {
+ aEvent.validity = aEvent.target.checkValidity();
+ inputEvents.push(aEvent);
+ }
+ target.addEventListener("beforeinput", onBeforeInput);
+ target.addEventListener("input", onInput);
+
+ reset();
+ SpecialPowers.wrap(target).setUserInput(aValidValue);
+ is(beforeInputEvents.length, 1, `Calling ${tag}.setUserInput(${aValidValue}) should cause a "beforeinput" event (before gets focus)`);
+ if (beforeInputEvents.length) {
+ is(beforeInputEvents[0].validity, false,
+ `The ${tag} should be invalid at "beforeinput" event (before gets focus)`);
+ }
+ is(inputEvents.length, 1, `Calling ${tag}.setUserInput(${aValidValue}) should cause a "input" event (before gets focus)`);
+ if (inputEvents.length) {
+ is(inputEvents[0].validity, true,
+ `The ${tag} should be valid at "input" event (before gets focus)`);
+ }
+
+ target.removeEventListener("beforeinput", onBeforeInput);
+ target.removeEventListener("input", onInput);
+
+ content.innerHTML = "";
+ content.scrollTop; // Flush pending layout.
+ content.innerHTML = `${tag}${aType === "textarea" ? "</textarea>" : ""}`;
+ content.scrollTop; // Flush pending layout.
+ target = content.firstChild;
+
+ target.focus();
+ target.addEventListener("beforeinput", onBeforeInput);
+ target.addEventListener("input", onInput);
+
+ reset();
+ SpecialPowers.wrap(target).setUserInput(aValidValue);
+ is(beforeInputEvents.length, 1, `Calling ${tag}.setUserInput(${aValidValue}) should cause a "beforeinput" event (after gets focus)`);
+ if (beforeInputEvents.length) {
+ is(beforeInputEvents[0].validity, false,
+ `The ${tag} should be invalid at "beforeinput" event (after gets focus)`);
+ }
+ is(inputEvents.length, 1, `Calling ${tag}.setUserInput(${aValidValue}) should cause a "input" event (after gets focus)`);
+ if (inputEvents.length) {
+ is(inputEvents[0].validity, true,
+ `The ${tag} should be valid at "input" event (after gets focus)`);
+ }
+
+ target.removeEventListener("beforeinput", onBeforeInput);
+ target.removeEventListener("input", onInput);
+ }
+ testValueMissing("text", "abc");
+ testValueMissing("password", "abc");
+ testValueMissing("textarea", "abc");
+ testValueMissing("email", "foo@example.com");
+ testValueMissing("url", "https://example.com/");
+
+ function testEditorValueAtEachEvent(aType) {
+ let tag = aType === "textarea" ? "<textarea>" : `<input type="${aType}">`
+ let closeTag = aType === "textarea" ? "</textarea>" : "";
+ content.innerHTML = `${tag}${closeTag}`;
+ content.scrollTop; // Flush pending layout.
+ let target = content.firstChild;
+ target.value = "Old Value";
+ let description = `Setting new value of ${tag} before setting focus: `;
+ let onBeforeInput = (aEvent) => {
+ is(target.value, "Old Value",
+ `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`);
+ };
+ let onInput = (aEvent) => {
+ is(target.value, "New Value",
+ `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`);
+ };
+ target.addEventListener("beforeinput", onBeforeInput);
+ target.addEventListener("input", onInput);
+ SpecialPowers.wrap(target).setUserInput("New Value");
+
+ description = `Setting new value of ${tag} after setting focus: `;
+ target.value = "Old Value";
+ target.focus();
+ SpecialPowers.wrap(target).setUserInput("New Value");
+
+ target.removeEventListener("beforeinput", onBeforeInput);
+ target.removeEventListener("input", onInput);
+
+ // FYI: This is not realistic situation because we should do nothing
+ // while user composing IME.
+ // TODO: TextControlState should stop returning setting value as the value
+ // while committing composition.
+ description = `Setting new value of ${tag} during composition: `;
+ target.value = "";
+ target.focus();
+ synthesizeCompositionChange({
+ composition: {
+ string: "composition string",
+ clauses: [{length: 18, attr: COMPOSITION_ATTR_RAW_CLAUSE}],
+ },
+ caret: {start: 18, length: 0},
+ });
+ let onCompositionUpdate = (aEvent) => {
+ todo_is(target.value, "composition string",
+ `${description}The value should not have been modified at "compositionupdate" event yet (data: "${aEvent.data}")`);
+ };
+ let onCompositionEnd = (aEvent) => {
+ todo_is(target.value, "composition string",
+ `${description}The value should not have been modified at "compositionupdate" event yet (data: "${aEvent.data}")`);
+ };
+ onBeforeInput = (aEvent) => {
+ if (aEvent.inputType === "insertCompositionText") {
+ todo_is(target.value, "composition string",
+ `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`);
+ } else {
+ is(target.value, "composition string",
+ `${description}The value should not have been modified at "beforeinput" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`);
+ }
+ };
+ onInput = (aEvent) => {
+ if (aEvent.inputType === "insertCompositionText") {
+ todo_is(target.value, "composition string",
+ `${description}The value should not have been modified at "input" event yet (inputType: "${aEvent.inputType}", data: "${aEvent.data}")`);
+ } else {
+ is(target.value, "New Value",
+ `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`);
+ }
+ };
+ target.addEventListener("compositionupdate", onCompositionUpdate);
+ target.addEventListener("compositionend", onCompositionEnd);
+ target.addEventListener("beforeinput", onBeforeInput);
+ target.addEventListener("input", onInput);
+ SpecialPowers.wrap(target).setUserInput("New Value");
+ target.removeEventListener("compositionupdate", onCompositionUpdate);
+ target.removeEventListener("compositionend", onCompositionEnd);
+ target.removeEventListener("beforeinput", onBeforeInput);
+ target.removeEventListener("input", onInput);
+ }
+ testEditorValueAtEachEvent("text");
+ testEditorValueAtEachEvent("textarea");
+
+ async function testBeforeInputCancelable(aType) {
+ let tag = aType === "textarea" ? "<textarea>" : `<input type="${aType}">`
+ let closeTag = aType === "textarea" ? "</textarea>" : "";
+ for (const kShouldBeCancelable of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.input_event.allow_to_cancel_set_user_input", kShouldBeCancelable]],
+ });
+
+ content.innerHTML = `${tag}${closeTag}`;
+ content.scrollTop; // Flush pending layout.
+ let target = content.firstChild;
+ target.value = "Old Value";
+ let description = `Setting new value of ${tag} before setting focus (the pref ${kShouldBeCancelable ? "allows" : "disallows"} to cancel beforeinput): `;
+ let onBeforeInput = (aEvent) => {
+ is(aEvent.cancelable, kShouldBeCancelable,
+ `${description}The "beforeinput" event should be ${kShouldBeCancelable ? "cancelable" : "not be cancelable due to suppressed by the pref"}`);
+ };
+ let onInput = (aEvent) => {
+ is(aEvent.cancelable, false,
+ `${description}The value should have been modified at "input" event (inputType: "${aEvent.inputType}", data: "${aEvent.data}"`);
+ };
+ target.addEventListener("beforeinput", onBeforeInput);
+ target.addEventListener("input", onInput);
+ SpecialPowers.wrap(target).setUserInput("New Value");
+
+ description = `Setting new value of ${tag} after setting focus (the pref ${kShouldBeCancelable ? "allows" : "disallows"} to cancel beforeinput): `;
+ target.value = "Old Value";
+ target.focus();
+ SpecialPowers.wrap(target).setUserInput("New Value");
+
+ target.removeEventListener("beforeinput", onBeforeInput);
+ target.removeEventListener("input", onInput);
+ }
+
+ await SpecialPowers.clearUserPref({
+ clear: [["dom.input_event.allow_to_cancel_set_user_input"]],
+ });
+ }
+ await testBeforeInputCancelable("text");
+ await testBeforeInputCancelable("textarea");
+
+ SimpleTest.finish();
+});
+</script>
+</body>
+</html>