581 lines
32 KiB
HTML
581 lines
32 KiB
HTML
<!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>
|