<!DOCTYPE HTML> <html> <head> <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> 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"> <!-- 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"> 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 larger value. * * 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) { let keyName = "DOM_VK_" + key.toUpperCase(); let keycode, charcode; 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; } let dnEvent = document.createEvent('KeyboardEvent'); let prEvent = document.createEvent('KeyboardEvent'); let 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() { let dnEvent = document.createEvent('MouseEvent'); let upEvent = document.createEvent('MouseEvent'); let 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); } let input = $_(1, "field1"); 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" }, ], resolve)); }); 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); } // 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; // 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."); } // Close the popup. input.blur(); }); </script> </pre> </body> </html>