Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android'
- Manifest: toolkit/components/satchel/test/mochitest.toml
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Form History Autocomplete</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="satchel_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style>
.spacer {
height: 50vh;
width: 100%;
}
</style>
</head>
<body>
Form History test: form field autocomplete
<p id="display"></p>
<!-- We presumably can't hide the content for this test. The large top padding is to allow
listening for scrolls to occur. -->
<div id="content" style="padding-top: 20000px;">
<!-- normal, basic form -->
<form id="form1">
<input type="text" name="field1">
<button type="submit">Submit</button>
</form>
<!-- normal, basic form (new fieldname) -->
<form id="form2">
<input type="text" name="field2">
<button type="submit">Submit</button>
</form>
<div class="spacer">
Space to force a scroll on form3 focus
</div>
<!-- form with autocomplete=off on input -->
<form id="form3">
<input type="text" name="field2" autocomplete="off">
<button type="submit">Submit</button>
</form>
<!-- form with autocomplete=off on form -->
<form id="form4" autocomplete="off">
<input type="text" name="field2">
<button type="submit">Submit</button>
</form>
<!-- normal form for testing filtering -->
<form id="form5">
<input type="text" name="field3">
<button type="submit">Submit</button>
</form>
<!-- normal form for testing word boundary filtering -->
<form id="form6">
<input type="text" name="field4">
<button type="submit">Submit</button>
</form>
<!-- form with maxlength attribute on input -->
<form id="form7">
<input type="text" name="field5" maxlength="10">
<button type="submit">Submit</button>
</form>
<!-- form with input type='email' -->
<form id="form8">
<input type="email" name="field6">
<button type="submit">Submit</button>
</form>
<!-- form with input type='tel' -->
<form id="form9">
<input type="tel" name="field7">
<button type="submit">Submit</button>
</form>
<!-- form with input type='url' -->
<form id="form10">
<input type="url" name="field8">
<button type="submit">Submit</button>
</form>
<!-- form with input type='search' -->
<form id="form11">
<input type="search" name="field9">
<button type="submit">Submit</button>
</form>
<!-- form with input type='number' -->
<form id="form12">
<input type="text" name="field10"> <!-- TODO: change back to type=number -->
<button type="submit">Submit</button>
</form>
<!-- normal, basic form (with fieldname='searchbar-history') -->
<form id="form13">
<input type="text" name="searchbar-history">
<button type="submit">Submit</button>
</form>
<!-- form with input type='date' -->
<form id="form14">
<input type="date" name="field11">
<button type="submit">Submit</button>
</form>
<!-- form with input type='time' -->
<form id="form15">
<input type="time" name="field12">
<button type="submit">Submit</button>
</form>
<!-- form with input type='range' -->
<form id="form16">
<input type="range" name="field13" max="64">
<button type="submit">Submit</button>
</form>
<!-- form with input type='color' -->
<form id="form17">
<input type="color" name="field14">
<button type="submit">Submit</button>
</form>
<!-- form with input type='month' -->
<form id="form18">
<input type="month" name="field15">
<button type="submit">Submit</button>
</form>
<!-- form with input type='week' -->
<form id="form19">
<input type="week" name="field16">
<button type="submit">Submit</button>
</form>
<!-- form with input type='datetime-local' -->
<form id="form20">
<input type="datetime-local" name="field17">
<button type="submit">Submit</button>
</form>
</div>
<script>
async function addEntry(fieldname, value) {
await updateFormHistory({ op: "add", fieldname, value });
}
preventSubmitOnForms();
SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", true]]});
add_setup(async () => {
await updateFormHistory([
{ op: "remove" },
{ op: "add", fieldname: "field1", value: "value1" },
{ op: "add", fieldname: "field1", value: "value2" },
{ op: "add", fieldname: "field1", value: "value3" },
{ op: "add", fieldname: "field1", value: "value4" },
{ op: "add", fieldname: "field2", value: "value1" },
{ op: "add", fieldname: "field3", value: "a" },
{ op: "add", fieldname: "field3", value: "aa" },
{ op: "add", fieldname: "field3", value: "aaz" },
{ op: "add", fieldname: "field3", value: "aa\xe6" }, // 0xae == latin ae pair (0xc6 == AE)
{ op: "add", fieldname: "field3", value: "az" },
{ op: "add", fieldname: "field3", value: "z" },
{ op: "add", fieldname: "field4", value: "a\xe6" },
{ op: "add", fieldname: "field4", value: "aa a\xe6" },
{ op: "add", fieldname: "field4", value: "aba\xe6" },
{ op: "add", fieldname: "field4", value: "bc d\xe6" },
{ op: "add", fieldname: "field5", value: "1" },
{ op: "add", fieldname: "field5", value: "12" },
{ op: "add", fieldname: "field5", value: "123" },
{ op: "add", fieldname: "field5", value: "1234" },
{ op: "add", fieldname: "field6", value: "value" },
{ op: "add", fieldname: "field7", value: "value" },
{ op: "add", fieldname: "field8", value: "value" },
{ op: "add", fieldname: "field9", value: "value" },
{ op: "add", fieldname: "field10", value: "42" },
// not used, since type=date doesn't have autocomplete currently
{ op: "add", fieldname: "field11", value: "2010-10-10" },
// not used, since type=time doesn't have autocomplete currently
{ op: "add", fieldname: "field12", value: "21:21" },
// not used, since type=range doesn't have a dropdown menu
{ op: "add", fieldname: "field13", value: "32" },
// not used, since type=color doesn't have autocomplete currently
{ op: "add", fieldname: "field14", value: "#ffffff" },
{ op: "add", fieldname: "field15", value: "2016-08" },
{ op: "add", fieldname: "field16", value: "2016-W32" },
{ op: "add", fieldname: "field17", value: "2016-10-21T10:10" },
{ op: "add", fieldname: "searchbar-history", value: "blacklist test" },
]);
});
add_task(async function use_1st_item() {
const { input } = await openPopupOn("#form1 > input");
assertAutocompleteItems("value1", "value2", "value3", "value4");
assertValueAfterKeys(input, "KEY_ArrowDown", "");
assertValueAfterKeys(input, "KEY_Enter", "value1");
});
add_task(async function use_2nd_item() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
"value2");
});
add_task(async function use_3rd_item() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
"value3");
});
add_task(async function use_4th_item() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
"value4");
});
add_task(async function use_1st_item_wrap_around() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowDown",
"KEY_ArrowDown", "KEY_ArrowDown", "KEY_Enter"],
"value1");
});
add_task(async function use_last_item_via_arrow_up() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowUp", "KEY_Enter"],
"value4");
});
add_task(async function use_last_item_via_arrow_up_from_selected_1st() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowUp", "KEY_ArrowUp", "KEY_Enter"],
"value4");
});
add_task(async function test9() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowUp", "KEY_ArrowUp", "KEY_ArrowUp", "KEY_ArrowUp",
"KEY_ArrowUp", "KEY_ArrowUp", "KEY_ArrowUp", "KEY_Enter"],
"value4");
});
add_task(async function select_1st_item_without_autocomplete() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowRight"],
"value1");
});
add_task(async function set_first_item_without_autocomplete() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowLeft"],
"value1");
});
add_task(async function use_1st_item_with_page_up() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_ArrowDown", "KEY_PageUp", "KEY_Enter"],
"value1");
});
add_task(async function use_last_item_with_page_down() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_PageDown", "KEY_Enter"],
"value4");
});
add_task(async function delete_1st_item() {
assertAutocompleteItems("value1", "value2", "value3", "value4");
const { input } = await openPopupOn("#form1 > input", { inputValue: "value" });
synthesizeKey("KEY_ArrowDown");
// Tests that on OS X shift-backspace didn't delete the last character
// On OS X, shift-backspace and shift-delete work, just delete does not.
// On Win/Linux, shift-backspace does not work, delete and shift-delete do.
synthesizeKey(SpecialPowers.OS == "Darwin" ? "KEY_Backspace" : "KEY_Delete", { shiftKey: true });
assertValueAfterKeys(input, [], "value");
await notifyMenuChanged(3);
is(await countEntries("field1", "value1"), 0, "field1:value1 item deleted");
assertValueAfterKeys(input, ["KEY_Enter"], "value2");
assertAutocompleteItems("value2", "value3", "value4");
});
add_task(async function use_1st_item_of_3() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"value2");
});
add_task(async function delete_2nd_item() {
const { input } = await openPopupOn("#form1 > input");
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_ArrowDown");
deleteSelectedAutocompleteItem();
await notifyMenuChanged(2);
assertValueAfterKeys(input, [], "");
is(await countEntries("field1", "value3"), 0, "field1:value3 item deleted");
assertValueAfterKeys(input, ["KEY_Enter"], "value4");
assertAutocompleteItems("value2", "value4");
});
add_task(async function use_1st_item_of_2() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"value2");
});
add_task(async function delete_last_item_of_2() {
const { input } = await openPopupOn("#form1 > input");
synthesizeKey("KEY_ArrowDown");
synthesizeKey("KEY_ArrowDown");
deleteSelectedAutocompleteItem();
await notifyMenuChanged(1);
assertValueAfterKeys(input, [], "");
is(await countEntries("field1", "value4"), 0, "field1/value4 item deleted");
assertAutocompleteItems("value2");
assertValueAfterKeys(input, "KEY_Enter", "value2");
});
add_task(async function use_1st_item_of_1() {
const { input } = await openPopupOn("#form1 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"value2");
});
add_task(async function delete_only_item() {
const { input } = await openPopupOn("#form1 > input");
synthesizeKey("KEY_ArrowDown");
deleteSelectedAutocompleteItem();
await notifyMenuChanged(0);
is(await countEntries("field1", "value2"), 0, "field1/value2 item deleted");
assertValueAfterKeys(input, [], "");
});
add_task(async function form2_fills() {
const { input } = await openPopupOn("#form2 > input");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"value1");
});
add_task(async function form3_autocomplete_off() {
// Look at form 3, try to trigger autocomplete popup
// Sometimes, this will fail if scrollTo(0, 0) is called, so that doesn't
// happen here. Fortunately, a different input is used from the last test,
// so a scroll should still occur.
const scrollend = new Promise(resolve => {
addEventListener("scrollend", resolve);
});
const { input } = await openPopupOn("#form3 > input", { expectPopup: false });
await scrollend;
// Ensure there's no autocomplete dropdown (autocomplete=off is present)
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"");
});
add_task(async function form4_autocomplete_off() {
const { input } = await openPopupOn("#form4 > input", { expectPopup: false });
await notifyMenuChanged(0);
// Ensure there's no autocomplete dropdown (autocomplete=off is present)
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"");
});
add_task(async function filtering_form5() {
const { input } = await openPopupOn("#form5 > input");
sendChar("a");
await notifyMenuChanged(5);
assertAutocompleteItems("a", "aa", "aaz", "aa\xe6", "az");
sendChar("a");
await notifyMenuChanged(3);
assertAutocompleteItems("aa", "aaz", "aa\xe6");
sendChar("\xc6");
await notifyMenuChanged(1);
assertAutocompleteItems("aa\xe6");
synthesizeKey("KEY_Backspace");
await notifyMenuChanged(3);
assertAutocompleteItems("aa", "aaz", "aa\xe6");
synthesizeKey("KEY_Backspace");
await notifyMenuChanged(5);
assertAutocompleteItems("a", "aa", "aaz", "aa\xe6", "az");
input.focus();
sendChar("z");
await notifyMenuChanged(2);
assertAutocompleteItems("az", "aaz");
synthesizeKey("KEY_ArrowLeft");
// Check case-insensitivity.
sendChar("A");
await notifyMenuChanged(1);
assertAutocompleteItems("aaz");
await addEntry("field3", "aazq");
// check that results were cached
input.focus();
synthesizeKey("KEY_ArrowRight");
sendChar("q");
await notifyMenuChanged(0);
// check that results were cached
assertAutocompleteItems();
await addEntry("field3", "aazqq");
input.focus();
window.scrollTo(0, 0);
sendChar("q");
await notifyMenuChanged(0);
assertAutocompleteItems();
synthesizeKey("KEY_Escape");
});
add_task(async function filtering_form6_part1() {
await openPopupOn("#form6 > input");
sendChar("a");
await notifyMenuChanged(3);
// Test substring matches and word boundary bonuses
// alphabetical results for first character
assertAutocompleteItems("aa a\xe6", "aba\xe6", "a\xe6");
sendChar("\xe6");
await notifyMenuChanged(3, "a\xe6");
// prefix match comes first, then word boundary match
// followed by substring match
assertAutocompleteItems("a\xe6", "aa a\xe6", "aba\xe6");
});
add_task(async function filtering_form6_part2() {
await openPopupOn("#form6 > input");
sendChar("b");
await notifyMenuChanged(1, "bc d\xe6");
assertAutocompleteItems("bc d\xe6");
sendChar(" ");
await notifyMenuChanged(1);
// check that trailing space has no effect after single char.
assertAutocompleteItems("bc d\xe6");
sendChar("\xc6");
await notifyMenuChanged(2);
// check multi-word substring matches
assertAutocompleteItems("bc d\xe6", "aba\xe6");
synthesizeKey("KEY_ArrowLeft");
sendChar("d");
await notifyMenuChanged(1);
// check inserting in multi-word searches
assertAutocompleteItems("bc d\xe6");
sendChar("z");
await notifyMenuChanged(0);
assertAutocompleteItems();
});
add_task(async function input_maxLength() {
let { input } = await openPopupOn("#form7 > input");
await notifyMenuChanged(4);
assertAutocompleteItems("1", "12", "123", "1234");
input.maxLength = 4;
input = (await openPopupOn("#form7 > input")).input;
await notifyMenuChanged(4);
assertAutocompleteItems("1", "12", "123", "1234");
input.maxLength = 3;
input = (await openPopupOn("#form7 > input")).input;
await notifyMenuChanged(3);
assertAutocompleteItems("1", "12", "123");
input.maxLength = 2;
input = (await openPopupOn("#form7 > input")).input;
await notifyMenuChanged(2);
assertAutocompleteItems("1", "12");
input.maxLength = 1;
input = (await openPopupOn("#form7 > input")).input;
await notifyMenuChanged(1);
assertAutocompleteItems("1");
input.maxLength = 0;
synthesizeKey("KEY_Escape");
synthesizeKey("KEY_ArrowDown");
await notifyMenuChanged(0);
assertAutocompleteItems();
input.maxLength = 4;
});
add_task(async function input_maxLength_with_character_typed() {
let { input } = await openPopupOn("#form7 > input");
sendChar("1");
await notifyMenuChanged(4);
assertAutocompleteItems("1", "12", "123", "1234");
input.maxLength = 3;
input = (await openPopupOn("#form7 > input")).input;
assertAutocompleteItems("1", "12", "123");
input.maxLength = 2;
input = (await openPopupOn("#form7 > input")).input;
assertAutocompleteItems("1", "12");
input.maxLength = 1;
input = (await openPopupOn("#form7 > input")).input;
assertAutocompleteItems("1");
input.maxLength = 0;
synthesizeKey("KEY_Escape");
synthesizeKey("KEY_ArrowDown");
await notifyMenuChanged(0);
assertAutocompleteItems();
});
for (const formId of ["form8", "form9", "form10", "form11"]) {
add_named_task(formId, async () => {
const { input } = await openPopupOn(`#${formId} > input`);
assertAutocompleteItems("value");
assertValueAfterKeys(input, ["KEY_ArrowDown", "KEY_Enter"], "value");
});
}
add_task(async function form12() {
const { input } = await openPopupOn("#form12 > input");
assertAutocompleteItems("42");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"42");
});
add_task(async function form14() {
const { input } = await openPopupOn("#form14 > input", { expectPopup: false });
await notifyMenuChanged(0);
// type=date with it's own control frame does not have a dropdown menu
assertAutocompleteItems();
assertValueAfterKeys(input, [], "");
});
add_task(async function form15() {
const { input } = await openPopupOn("#form15 > input", { expectPopup: false });
await notifyMenuChanged(0);
// type=time with it's own control frame does not have a dropdown menu
assertAutocompleteItems();
assertValueAfterKeys(input, [], "");
});
add_task(async function form16() {
const { input } = await openPopupOn("#form16 > input", { expectPopup: false });
await notifyMenuChanged(0);
// type=range does not have a dropdown menu
assertAutocompleteItems();
// default (midway between minimum (0) and maximum (64)) - step
assertValueAfterKeys(input, [], "31");
});
add_task(async function form17() {
const { input } = await openPopupOn("#form17 > input", { expectPopup: false });
await notifyMenuChanged(0);
// type=color does not have a dropdown menu
assertAutocompleteItems();
// default color value
assertValueAfterKeys(input, [], "#000000");
});
add_task(async function form18() {
const { input } = await openPopupOn("#form18 > input");
assertAutocompleteItems("2016-08");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"2016-08");
});
add_task(async function form19() {
const { input } = await openPopupOn("#form19 > input");
assertAutocompleteItems("2016-W32");
assertValueAfterKeys(
input,
["KEY_ArrowDown", "KEY_Enter"],
"2016-W32");
});
add_task(async function form20() {
const { input } = await openPopupOn("#form20 > input", { expectPopup: false });
await notifyMenuChanged(0);
// type=datetime-local with it's own control frame does not have a dropdown menu
assertAutocompleteItems();
assertValueAfterKeys(input, [], "");
});
add_task(async function input_event_fired() {
await addEntry("field1", "value1");
const { input } = await openPopupOn("#form1 > input");
let beforeInputFired = false;
input.addEventListener("beforeinput", (e) => {
beforeInputFired = true;
ok(e instanceof InputEvent, "beforeinput event has InputEvent interface");
ok(e.bubbles, "beforeinput event should bubble");
is(e.cancelable, SpecialPowers.getBoolPref("dom.input_event.allow_to_cancel_set_user_input"),
"beforeinput event for insertReplacementText should be cancelable unless it's suppressed by the pref");
is(e.inputType, "insertReplacementText",
"inputType of beforeinput event should be insertReplacementText");
is(e.data, "value1", "data of beforeinput event should be value1");
is(e.dataTransfer, null, "dataTransfer of beforeinput event should be null");
is(e.getTargetRanges().length, 0,
"getTargetRanges() of beforeinput event should empty array");
is(input.value, "", "input value should've not been modified yet at beforeinput event");
}, { once: true });
let inputFired = false;
input.addEventListener("input", (e) => {
inputFired = true;
ok(e instanceof InputEvent, "input event has InputEvent interface");
ok(e.bubbles, "input event should bubble");
ok(!e.cancelable, "input event shouldn't be cancelable");
is(e.inputType, "insertReplacementText",
"inputType of input event should be insertReplacementText");
is(e.data, "value1","data of input event should be value1");
is(e.dataTransfer, null, "dataTransfer of input event should be null");
is(e.getTargetRanges().length, 0,
"getTargetRanges() of input event should empty array");
is(input.value, "value1", "input value should've already been modified at input event");
}, { once: true });
assertValueAfterKeys(input, "KEY_ArrowDown", "");
assertValueAfterKeys(input, "KEY_Enter", "value1");
ok(beforeInputFired, "beforeinput event should have been fired");
ok(inputFired, "input event should have been fired");
});
add_task(async function cancelling_beforeinput_cancels_autocompletion() {
const { input } = await openPopupOn("#form1 > input");
await SpecialPowers.pushPrefEnv({
set: [["dom.input_event.allow_to_cancel_set_user_input", true]],
});
input.addEventListener("beforeinput", (e) => e.preventDefault(), { once: true });
let inputFired = false;
input.addEventListener("input", () => inputFired = true, { once: true });
assertValueAfterKeys(input, "KEY_ArrowDown", "");
assertValueAfterKeys(input, "KEY_Enter", "");
ok(!inputFired, "no input event when beforeinput is canceled");
await SpecialPowers.pushPrefEnv({
clear: [["dom.input_event.allow_to_cancel_set_user_input"]],
});
});
add_task(async function no_autocomplete_for_searchbar_history() {
await openPopupOn("#form13 > input", { expectPopup: false });
await notifyMenuChanged(0);
assertAutocompleteItems();
});
</script>
</body>
</html>