--- a/toolkit/components/satchel/test/test_bug_511615.html
+++ b/toolkit/components/satchel/test/test_bug_511615.html
@@ -1,389 +1,194 @@
<!DOCTYPE HTML>
<html>
<head>
- <title>Test for Form History Autocomplete</title>
+ <title>Test for Form History Autocomplete Untrusted Events: Bug 511615</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" 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" />
</head>
<body>
-Form History test: form field autocomplete
+Test for Form History Autocomplete Untrusted Events: Bug 511615
<p id="display"></p>
<!-- we presumably can't hide the content for this test. -->
-<div id="content" style="direction: rtl;">
- <!-- unused -->
- <form id="unused" onsubmit="return false;">
- <input type="text" name="field1" value="unused">
- </form>
-
+<div id="content">
<!-- normal, basic form -->
<form id="form1" onsubmit="return false;">
<input type="text" name="field1">
<button type="submit">Submit</button>
</form>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
-/** Test for Form History autocomplete **/
-var autocompletePopup = NonE10SgetAutocompletePopup();
-autocompletePopup.style.direction = "ltr";
+var resolvePopupShownListener;
+registerPopupShownListener(() => resolvePopupShownListener());
+
+function waitForNextPopup() {
+ return new Promise(resolve => { resolvePopupShownListener = resolve; });
+}
+
+/**
+ * Indicates the time to wait before checking that the state of the autocomplete
+ * popup, including whether it is open, has not changed in response to events.
+ *
+ * Manual testing on a fast machine revealed that 80ms was still unreliable,
+ * while 100ms detected a simulated failure reliably. Unfortunately, this means
+ * that to take into account slower machines we should use a value like 300ms.
+ *
+ * Note that if a machine takes more than this time to show the popup, this
+ * would not cause a failure, conversely the machine would not be able to detect
+ * whether the test should have failed. In other words, this use of timeouts is
+ * never expected to cause intermittent failures with test automation.
+ */
+const POPUP_RESPONSE_WAIT_TIME_MS = 200;
+
+SimpleTest.requestFlakyTimeout("Must ensure that an event does not happen.");
+
+/**
+ * Checks that the popup does not open in response to the given function.
+ */
+function expectPopupDoesNotOpen(triggerFn) {
+ let popupShown = waitForNextPopup();
+ triggerFn();
+ return Promise.race([
+ popupShown.then(() => Promise.reject("Popup opened unexpectedly.")),
+ new Promise(resolve => setTimeout(resolve, POPUP_RESPONSE_WAIT_TIME_MS)),
+ ]);
+}
+
+/**
+ * Checks that the selected index in the popup still matches the given value.
+ */
+function checkSelectedIndexAfterResponseTime(expectedIndex) {
+ return new Promise(resolve => {
+ setTimeout(() => getPopupState(resolve), POPUP_RESPONSE_WAIT_TIME_MS);
+ }).then(popupState => {
+ is(popupState.open, true, "Popup should still be open.");
+ is(popupState.selectedIndex, expectedIndex, "Selected index should match.");
+ });
+}
+
+function doKeyUnprivileged(key) {
+ var keyName = "DOM_VK_" + key.toUpperCase();
+ var keycode, charcode, alwaysVal;
+
+ if (key.length == 1) {
+ keycode = 0;
+ charcode = key.charCodeAt(0);
+ alwaysval = charcode;
+ } else {
+ keycode = KeyEvent[keyName];
+ if (!keycode)
+ throw "invalid keyname in test";
+ charcode = 0;
+ alwaysval = keycode;
+ }
+
+ var dnEvent = document.createEvent('KeyboardEvent');
+ var prEvent = document.createEvent('KeyboardEvent');
+ var upEvent = document.createEvent('KeyboardEvent');
+
+ dnEvent.initKeyEvent("keydown", true, true, null, false, false, false, false, alwaysval, 0);
+ prEvent.initKeyEvent("keypress", true, true, null, false, false, false, false, keycode, charcode);
+ upEvent.initKeyEvent("keyup", true, true, null, false, false, false, false, alwaysval, 0);
+
+ input.dispatchEvent(dnEvent);
+ input.dispatchEvent(prEvent);
+ input.dispatchEvent(upEvent);
+}
+
+function doClickWithMouseEventUnprivileged() {
+ var dnEvent = document.createEvent('MouseEvent');
+ var upEvent = document.createEvent('MouseEvent');
+ var ckEvent = document.createEvent('MouseEvent');
+
+ dnEvent.initMouseEvent("mousedown", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
+ upEvent.initMouseEvent("mouseup", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
+ ckEvent.initMouseEvent("mouseclick", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
+
+ input.dispatchEvent(dnEvent);
+ input.dispatchEvent(upEvent);
+ input.dispatchEvent(ckEvent);
+}
var input = $_(1, "field1");
-// Get the form history service
-function setupFormHistory(aCallback) {
- updateFormHistory([
+add_task(function* test_initialize() {
+ yield new Promise(resolve => 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 : "field1", value : "value5" },
{ op : "add", fieldname : "field1", value : "value6" },
{ op : "add", fieldname : "field1", value : "value7" },
{ op : "add", fieldname : "field1", value : "value8" },
{ op : "add", fieldname : "field1", value : "value9" },
- ], aCallback);
-}
-
-function checkForm(expectedValue) {
- var formID = input.parentNode.id;
- is(input.value, expectedValue, "Checking " + formID + " input");
-}
-
-function checkPopupOpen(isOpen, expectedIndex) {
- var actuallyOpen = autocompletePopup.popupOpen;
- var actualIndex = autocompletePopup.selectedIndex;
- is(actuallyOpen, isOpen, "popup should be " + (isOpen ? "open" : "closed"));
- if (isOpen)
- is(actualIndex, expectedIndex, "checking selected index");
-
- // Correct state if needed, so following tests run as expected.
- if (actuallyOpen != isOpen) {
- if (isOpen)
- autocompletePopup.openPopup();
- else
- autocompletePopup.closePopup();
- }
- if (isOpen && actualIndex != expectedIndex)
- autocompletePopup.selectedIndex = expectedIndex;
-}
-
-function doKeyUnprivileged(key) {
- var keyName = "DOM_VK_" + key.toUpperCase();
- var keycode, charcode, alwaysVal;
-
- if (key.length == 1) {
- keycode = 0;
- charcode = key.charCodeAt(0);
- alwaysval = charcode;
- } else {
- keycode = KeyEvent[keyName];
- if (!keycode)
- throw "invalid keyname in test";
- charcode = 0;
- alwaysval = keycode;
- }
-
- var dnEvent = document.createEvent('KeyboardEvent');
- var prEvent = document.createEvent('KeyboardEvent');
- var upEvent = document.createEvent('KeyboardEvent');
-
- dnEvent.initKeyEvent("keydown", true, true, null, false, false, false, false, alwaysval, 0);
- prEvent.initKeyEvent("keypress", true, true, null, false, false, false, false, keycode, charcode);
- upEvent.initKeyEvent("keyup", true, true, null, false, false, false, false, alwaysval, 0);
-
- input.dispatchEvent(dnEvent);
- input.dispatchEvent(prEvent);
- input.dispatchEvent(upEvent);
-}
-
-function doClickUnprivileged() {
- var dnEvent = document.createEvent('MouseEvent');
- var upEvent = document.createEvent('MouseEvent');
- var ckEvent = document.createEvent('MouseEvent');
-
- dnEvent.initMouseEvent("mousedown", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
- upEvent.initMouseEvent("mouseup", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
- ckEvent.initMouseEvent("mouseclick", true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
-
- input.dispatchEvent(dnEvent);
- input.dispatchEvent(upEvent);
- input.dispatchEvent(ckEvent);
-}
-
-var testNum = 0;
-var expectingPopup = false;
-
-function expectPopup()
-{
- info("expecting popup for test " + testNum);
- expectingPopup = true;
-}
-
-function popupShownListener()
-{
- info("popup shown for test " + testNum);
- if (expectingPopup) {
- expectingPopup = false;
- SimpleTest.executeSoon(runTest);
- }
- else {
- ok(false, "Autocomplete popup not expected" + testNum);
- }
-}
-
-SpecialPowers.addAutoCompletePopupEventListener(window, "popupshown", popupShownListener);
-
-/*
- * Main section of test...
- *
- * This is a bit hacky, because the events are either being sent or
- * processes asynchronously, so we need to interrupt our flow with lots of
- * setTimeout() calls. The case statements are executed in order, one per
- * timeout.
- */
-function runTest() {
- testNum++;
-
- ok(true, "Starting test #" + testNum);
-
- switch(testNum) {
- //
- // Check initial state
- //
- case 1:
- input.value = "";
- checkForm("");
- is(autocompletePopup.popupOpen, false, "popup should be initially closed");
- break;
+ ], resolve));
+});
- //
- // Try to open the autocomplete popup from untrusted events.
- //
- // try a focus()
- case 2:
- input.focus();
- break;
- case 3:
- checkPopupOpen(false);
- checkForm("");
- break;
- // try a click()
- case 4:
- input.click();
- break;
- case 5:
- checkPopupOpen(false);
- checkForm("");
- break;
- // try a mouseclick event
- case 6:
- doClickUnprivileged();
- break;
- case 7:
- checkPopupOpen(false);
- checkForm("");
- break;
- // try a down-arrow
- case 8:
- doKeyUnprivileged("down");
- break;
- case 9:
- checkPopupOpen(false);
- checkForm("");
- break;
- // try a page-down
- case 10:
- doKeyUnprivileged("page_down");
- break;
- case 11:
- checkPopupOpen(false);
- checkForm("");
- break;
- // try a return
- case 12:
-// XXX this causes later tests to fail for some reason.
-// doKeyUnprivileged("return"); // not "enter"!
- break;
- case 13:
- checkPopupOpen(false);
- checkForm("");
- break;
- // try a keypress
- case 14:
- doKeyUnprivileged('v');
- break;
- case 15:
- checkPopupOpen(false);
- checkForm("");
- break;
- // try a space
- case 16:
- doKeyUnprivileged(" ");
- break;
- case 17:
- checkPopupOpen(false);
- checkForm("");
- break;
- // backspace
- case 18:
- doKeyUnprivileged("back_space");
- break;
- case 19:
- checkPopupOpen(false);
- checkForm("");
- break;
- case 20:
- // We're privileged for this test, so open the popup.
- checkPopupOpen(false);
- checkForm("");
- expectPopup();
- doKey("down");
- return;
- break;
- case 21:
- checkPopupOpen(true, -1);
- checkForm("");
- testNum = 99;
- break;
-
- //
- // Try to change the selected autocomplete item from untrusted events
- //
-
- // try a down-arrow
- case 100:
- doKeyUnprivileged("down");
- break;
- case 101:
- checkPopupOpen(true, -1);
- checkForm("");
- break;
- // try a page-down
- case 102:
- doKeyUnprivileged("page_down");
- break;
- case 103:
- checkPopupOpen(true, -1);
- checkForm("");
- break;
- // really adjust the index
- case 104:
- // (We're privileged for this test.) Try a privileged down-arrow.
- doKey("down");
- break;
- case 105:
- checkPopupOpen(true, 0);
- checkForm("");
- break;
- // try a down-arrow
- case 106:
- doKeyUnprivileged("down");
- break;
- case 107:
- checkPopupOpen(true, 0);
- checkForm("");
- break;
- // try a page-down
- case 108:
- doKeyUnprivileged("page_down");
- break;
- case 109:
- checkPopupOpen(true, 0);
- checkForm("");
- // try a keypress
- case 110:
- // XXX this causes the popup to close, and makes the value "vaa" (sic)
- //doKeyUnprivileged('a');
- break;
- case 111:
- checkPopupOpen(true, 0);
- checkForm("");
- testNum = 199;
- break;
-
- //
- // Try to use the selected autocomplete item from untrusted events
- //
- // try a right-arrow
- case 200:
- doKeyUnprivileged("right");
- break;
- case 201:
- checkPopupOpen(true, 0);
- checkForm("");
- break;
- // try a space
- case 202:
- doKeyUnprivileged(" ");
- break;
- case 203:
- // XXX we should ignore this input while popup is open?
- checkPopupOpen(true, 0);
- checkForm("");
- break;
- // backspace
- case 204:
- doKeyUnprivileged("back_space");
- break;
- case 205:
- // XXX we should ignore this input while popup is open?
- checkPopupOpen(true, 0);
- checkForm("");
- break;
- case 206:
- // (this space intentionally left blank)
- break;
- case 207:
- checkPopupOpen(true, 0);
- checkForm("");
- break;
- // try a return
- case 208:
-// XXX this seems to cause problems with reliably closing the popup
-// doKeyUnprivileged("return"); // not "enter"!
- break;
- case 209:
- checkPopupOpen(true, 0);
- checkForm("");
- break;
- // Send a real Escape to ensure popup closed at end of test.
- case 210:
- // Need to use doKey(), even though this test is not privileged.
- doKey("escape");
- break;
- case 211:
- checkPopupOpen(false);
- checkForm("");
- is(autocompletePopup.style.direction, "rtl", "direction should have been changed from ltr to rtl");
-
- SpecialPowers.removeAutoCompletePopupEventListener(window, "popupshown", popupShownListener);
- SimpleTest.finish();
- return;
-
- default:
- ok(false, "Unexpected invocation of test #" + testNum);
- SpecialPowers.removeAutoCompletePopupEventListener(window, "popupshown", popupShownListener);
- SimpleTest.finish();
- return;
+add_task(function* test_untrusted_events_ignored() {
+ // The autocomplete popup should not open from untrusted events.
+ for (let triggerFn of [
+ () => input.focus(),
+ () => input.click(),
+ () => doClickWithMouseEventUnprivileged(),
+ () => doKeyUnprivileged("down"),
+ () => doKeyUnprivileged("page_down"),
+ () => doKeyUnprivileged("return"),
+ () => doKeyUnprivileged("v"),
+ () => doKeyUnprivileged(" "),
+ () => doKeyUnprivileged("back_space"),
+ ]) {
+ // We must wait for the entire timeout for each individual test, because the
+ // next event in the list might prevent the popup from opening.
+ yield expectPopupDoesNotOpen(triggerFn);
}
- SimpleTest.executeSoon(runTest);
-}
+ // A privileged key press will actually open the popup.
+ let popupShown = waitForNextPopup();
+ doKey("down");
+ yield popupShown;
+
+ // The selected autocomplete item should not change from untrusted events.
+ for (let triggerFn of [
+ () => doKeyUnprivileged("down"),
+ () => doKeyUnprivileged("page_down"),
+ ]) {
+ triggerFn();
+ yield checkSelectedIndexAfterResponseTime(-1);
+ }
+
+ // A privileged key press will actually change the selected index.
+ let indexChanged = new Promise(resolve => notifySelectedIndex(0, resolve));
+ doKey("down");
+ yield indexChanged;
-function startTest() {
- setupFormHistory(runTest);
-}
+ // The selected autocomplete item should not change and it should not be
+ // possible to use it from untrusted events.
+ for (let triggerFn of [
+ () => doKeyUnprivileged("down"),
+ () => doKeyUnprivileged("page_down"),
+ () => doKeyUnprivileged("right"),
+ () => doKeyUnprivileged(" "),
+ () => doKeyUnprivileged("back_space"),
+ () => doKeyUnprivileged("back_space"),
+ () => doKeyUnprivileged("return"),
+ ]) {
+ triggerFn();
+ yield checkSelectedIndexAfterResponseTime(0);
+ is(input.value, "", "The selected item should not have been used.");
+ }
-SimpleTest.waitForFocus(startTest);
-
-SimpleTest.waitForExplicitFinish();
+ // Close the popup.
+ doKey("escape");
+});
</script>
</pre>
</body>
</html>