diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/components/passwordmgr/test/mochitest | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/components/passwordmgr/test/mochitest')
42 files changed, 8325 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/mochitest/auth2/authenticate.sjs b/toolkit/components/passwordmgr/test/mochitest/auth2/authenticate.sjs new file mode 100644 index 000000000..d2f650013 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/auth2/authenticate.sjs @@ -0,0 +1,220 @@ +function handleRequest(request, response) +{ + try { + reallyHandleRequest(request, response); + } catch (e) { + response.setStatusLine("1.0", 200, "AlmostOK"); + response.write("Error handling request: " + e); + } +} + + +function reallyHandleRequest(request, response) { + var match; + var requestAuth = true, requestProxyAuth = true; + + // Allow the caller to drive how authentication is processed via the query. + // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar + // The extra ? allows the user/pass/realm checks to succeed if the name is + // at the beginning of the query string. + var query = "?" + request.queryString; + + var expected_user = "", expected_pass = "", realm = "mochitest"; + var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy"; + var huge = false, plugin = false, anonymous = false; + var authHeaderCount = 1; + // user=xxx + match = /[^_]user=([^&]*)/.exec(query); + if (match) + expected_user = match[1]; + + // pass=xxx + match = /[^_]pass=([^&]*)/.exec(query); + if (match) + expected_pass = match[1]; + + // realm=xxx + match = /[^_]realm=([^&]*)/.exec(query); + if (match) + realm = match[1]; + + // proxy_user=xxx + match = /proxy_user=([^&]*)/.exec(query); + if (match) + proxy_expected_user = match[1]; + + // proxy_pass=xxx + match = /proxy_pass=([^&]*)/.exec(query); + if (match) + proxy_expected_pass = match[1]; + + // proxy_realm=xxx + match = /proxy_realm=([^&]*)/.exec(query); + if (match) + proxy_realm = match[1]; + + // huge=1 + match = /huge=1/.exec(query); + if (match) + huge = true; + + // plugin=1 + match = /plugin=1/.exec(query); + if (match) + plugin = true; + + // multiple=1 + match = /multiple=([^&]*)/.exec(query); + if (match) + authHeaderCount = match[1]+0; + + // anonymous=1 + match = /anonymous=1/.exec(query); + if (match) + anonymous = true; + + // Look for an authentication header, if any, in the request. + // + // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + // + // This test only supports Basic auth. The value sent by the client is + // "username:password", obscured with base64 encoding. + + var actual_user = "", actual_pass = "", authHeader, authPresent = false; + if (request.hasHeader("Authorization")) { + authPresent = true; + authHeader = request.getHeader("Authorization"); + match = /Basic (.+)/.exec(authHeader); + if (match.length != 2) + throw new Error("Couldn't parse auth header: " + authHeader); + + var userpass = base64ToString(match[1]); // no atob() :-( + match = /(.*):(.*)/.exec(userpass); + if (match.length != 3) + throw new Error("Couldn't decode auth header: " + userpass); + actual_user = match[1]; + actual_pass = match[2]; + } + + var proxy_actual_user = "", proxy_actual_pass = ""; + if (request.hasHeader("Proxy-Authorization")) { + authHeader = request.getHeader("Proxy-Authorization"); + match = /Basic (.+)/.exec(authHeader); + if (match.length != 2) + throw new Error("Couldn't parse auth header: " + authHeader); + + var userpass = base64ToString(match[1]); // no atob() :-( + match = /(.*):(.*)/.exec(userpass); + if (match.length != 3) + throw new Error("Couldn't decode auth header: " + userpass); + proxy_actual_user = match[1]; + proxy_actual_pass = match[2]; + } + + // Don't request authentication if the credentials we got were what we + // expected. + if (expected_user == actual_user && + expected_pass == actual_pass) { + requestAuth = false; + } + if (proxy_expected_user == proxy_actual_user && + proxy_expected_pass == proxy_actual_pass) { + requestProxyAuth = false; + } + + if (anonymous) { + if (authPresent) { + response.setStatusLine("1.0", 400, "Unexpected authorization header found"); + } else { + response.setStatusLine("1.0", 200, "Authorization header not found"); + } + } else { + if (requestProxyAuth) { + response.setStatusLine("1.0", 407, "Proxy authentication required"); + for (i = 0; i < authHeaderCount; ++i) + response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true); + } else if (requestAuth) { + response.setStatusLine("1.0", 401, "Authentication required"); + for (i = 0; i < authHeaderCount; ++i) + response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true); + } else { + response.setStatusLine("1.0", 200, "OK"); + } + } + + response.setHeader("Content-Type", "application/xhtml+xml", false); + response.write("<html xmlns='http://www.w3.org/1999/xhtml'>"); + response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n"); + response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n"); + response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n"); + response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n"); + response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n"); + + if (huge) { + response.write("<div style='display: none'>"); + for (i = 0; i < 100000; i++) { + response.write("123456789\n"); + } + response.write("</div>"); + response.write("<span id='footnote'>This is a footnote after the huge content fill</span>"); + } + + if (plugin) { + response.write("<embed id='embedtest' style='width: 400px; height: 100px;' " + + "type='application/x-test'></embed>\n"); + } + + response.write("</html>"); +} + + +// base64 decoder +// +// Yoinked from extensions/xml-rpc/src/nsXmlRpcClient.js because btoa() +// doesn't seem to exist. :-( +/* Convert Base64 data to a string */ +const toBinaryTable = [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +]; +const base64Pad = '='; + +function base64ToString(data) { + + var result = ''; + var leftbits = 0; // number of bits decoded, but yet to be appended + var leftdata = 0; // bits decoded, but yet to be appended + + // Convert one by one. + for (var i = 0; i < data.length; i++) { + var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; + var padding = (data[i] == base64Pad); + // Skip illegal characters and whitespace + if (c == -1) continue; + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) + result += String.fromCharCode((leftdata >> leftbits) & 0xff); + leftdata &= (1 << leftbits) - 1; + } + } + + // If there are any bits left, the base64 string was corrupted + if (leftbits) + throw Components.Exception('Corrupted base64 string'); + + return result; +} diff --git a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini new file mode 100644 index 000000000..a4170d7e0 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini @@ -0,0 +1,69 @@ +[DEFAULT] +support-files = + ../../../prompts/test/chromeScript.js + ../../../prompts/test/prompt_common.js + ../../../satchel/test/parent_utils.js + ../../../satchel/test/satchel_common.js + ../authenticate.sjs + ../blank.html + ../browser/form_autofocus_js.html + ../browser/form_basic.html + ../browser/form_cross_origin_secure_action.html + ../pwmgr_common.js + auth2/authenticate.sjs + +[test_autocomplete_https_upgrade.html] +skip-if = toolkit == 'android' # autocomplete +[test_autofill_https_upgrade.html] +skip-if = toolkit == 'android' # Bug 1259768 +[test_autofill_password-only.html] +[test_autofocus_js.html] +skip-if = toolkit == 'android' # autocomplete +[test_basic_form.html] +[test_basic_form_0pw.html] +[test_basic_form_1pw.html] +[test_basic_form_1pw_2.html] +[test_basic_form_2pw_1.html] +[test_basic_form_2pw_2.html] +[test_basic_form_3pw_1.html] +[test_basic_form_autocomplete.html] +skip-if = toolkit == 'android' # android:autocomplete. +[test_insecure_form_field_autocomplete.html] +skip-if = toolkit == 'android' # android:autocomplete. +[test_password_field_autocomplete.html] +skip-if = toolkit == 'android' # android:autocomplete. +[test_insecure_form_field_no_saved_login.html] +skip-if = toolkit == 'android' || os == 'linux' # android:autocomplete., linux: bug 1325778 +[test_basic_form_html5.html] +[test_basic_form_pwevent.html] +[test_basic_form_pwonly.html] +[test_bug_627616.html] +skip-if = toolkit == 'android' # Tests desktop prompts +[test_bug_776171.html] +[test_case_differences.html] +skip-if = toolkit == 'android' # autocomplete +[test_form_action_1.html] +[test_form_action_2.html] +[test_form_action_javascript.html] +[test_formless_autofill.html] +[test_formless_submit.html] +[test_formless_submit_navigation.html] +[test_formless_submit_navigation_negative.html] +[test_input_events.html] +[test_input_events_for_identical_values.html] +[test_maxlength.html] +[test_passwords_in_type_password.html] +[test_prompt.html] +skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts +[test_prompt_http.html] +skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts +[test_prompt_noWindow.html] +skip-if = e10s || toolkit == 'android' # Tests desktop prompts. e10s: bug 1217876 +[test_prompt_promptAuth.html] +skip-if = os == "linux" || toolkit == 'android' # Tests desktop prompts +[test_prompt_promptAuth_proxy.html] +skip-if = e10s || os == "linux" || toolkit == 'android' # Tests desktop prompts +[test_recipe_login_fields.html] +[test_username_focus.html] +skip-if = toolkit == 'android' # android:autocomplete. +[test_xhr_2.html] diff --git a/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html new file mode 100644 index 000000000..7d5725322 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html @@ -0,0 +1,218 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autocomplete on an HTTPS page using upgraded HTTP logins</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +const chromeScript = runChecksAfterCommonInit(false); + +runInParent(function addLogins() { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + // Create some logins just for this form, since we'll be deleting them. + let nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, "init"); + + // We have two actual HTTPS to avoid autofill before the schemeUpgrades pref flips to true. + let login0 = new nsLoginInfo("https://example.org", "https://example.org", null, + "name", "pass", "uname", "pword"); + + let login1 = new nsLoginInfo("https://example.org", "https://example.org", null, + "name1", "pass1", "uname", "pword"); + + // Same as above but HTTP instead of HTTPS (to test de-duping) + let login2 = new nsLoginInfo("http://example.org", "http://example.org", null, + "name1", "passHTTP", "uname", "pword"); + + // Different HTTP login to upgrade with secure formSubmitURL + let login3 = new nsLoginInfo("http://example.org", "https://example.org", null, + "name2", "passHTTPtoHTTPS", "uname", "pword"); + + try { + Services.logins.addLogin(login0); + Services.logins.addLogin(login1); + Services.logins.addLogin(login2); + Services.logins.addLogin(login3); + } catch (e) { + assert.ok(false, "addLogin threw: " + e); + } +}); +</script> +<p id="display"></p> + +<!-- we presumably can't hide the content for this test. --> +<div id="content"> + <iframe src="https://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_basic.html"></iframe> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +const shiftModifier = SpecialPowers.Ci.nsIDOMEvent.SHIFT_MASK; + +let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]); +let iframeDoc; +let uname; +let pword; + +// Restore the form to the default state. +function restoreForm() { + pword.focus(); + uname.value = ""; + pword.value = ""; + uname.focus(); +} + +// Check for expected username/password in form. +function checkACForm(expectedUsername, expectedPassword) { + let formID = uname.parentNode.id; + is(uname.value, expectedUsername, "Checking " + formID + " username"); + is(pword.value, expectedPassword, "Checking " + formID + " password"); +} + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({"set": [["signon.schemeUpgrades", true]]}); + + yield new Promise(resolve => { + iframe.addEventListener("load", function onLoad() { + iframe.removeEventListener("load", onLoad); + resolve(); + }); + }); + + iframeDoc = iframe.contentDocument; + uname = iframeDoc.getElementById("form-basic-username"); + pword = iframeDoc.getElementById("form-basic-password"); +}); + +add_task(function* test_empty_first_entry() { + // Make sure initial form is empty. + checkACForm("", ""); + // Trigger autocomplete popup + restoreForm(); + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is initially closed"); + let shownPromise = promiseACShown(); + doKey("down"); + let results = yield shownPromise; + popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected"); + checkArrayValues(results, ["name", "name1", "name2"], "initial"); + + // Check first entry + let index0Promise = notifySelectedIndex(0); + doKey("down"); + yield index0Promise; + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("name", "pass"); +}); + +add_task(function* test_empty_second_entry() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + doKey("down"); // first + doKey("down"); // second + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("name1", "pass1"); +}); + +add_task(function* test_search() { + restoreForm(); + let shownPromise = promiseACShown(); + // We need to blur for the autocomplete controller to notice the forced value below. + uname.blur(); + uname.value = "name"; + uname.focus(); + sendChar("1"); + doKey("down"); // open + let results = yield shownPromise; + checkArrayValues(results, ["name1"], "check result deduping for 'name1'"); + doKey("down"); // first + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("name1", "pass1"); + + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is now closed"); +}); + +add_task(function* test_delete_first_entry() { + restoreForm(); + uname.focus(); + let shownPromise = promiseACShown(); + doKey("down"); + yield shownPromise; + + let index0Promise = notifySelectedIndex(0); + doKey("down"); + yield index0Promise; + + let deletionPromise = promiseStorageChanged(["removeLogin"]); + // 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. + doKey("delete", shiftModifier); + yield deletionPromise; + checkACForm("", ""); + + let results = yield notifyMenuChanged(2, "name1"); + + checkArrayValues(results, ["name1", "name2"], "two should remain after deleting the first"); + let popupState = yield getPopupState(); + is(popupState.open, true, "Check popup stays open after deleting"); + doKey("escape"); + popupState = yield getPopupState(); + is(popupState.open, false, "Check popup closed upon ESC"); +}); + +add_task(function* test_delete_duplicate_entry() { + restoreForm(); + uname.focus(); + let shownPromise = promiseACShown(); + doKey("down"); + yield shownPromise; + + let index0Promise = notifySelectedIndex(0); + doKey("down"); + yield index0Promise; + + let deletionPromise = promiseStorageChanged(["removeLogin"]); + // 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. + doKey("delete", shiftModifier); + yield deletionPromise; + checkACForm("", ""); + + is(LoginManager.countLogins("http://example.org", "http://example.org", null), 1, + "Check that the HTTP login remains"); + is(LoginManager.countLogins("https://example.org", "https://example.org", null), 0, + "Check that the HTTPS login was deleted"); + + // Two menu items should remain as the HTTPS login should have been deleted but + // the HTTP would remain. + let results = yield notifyMenuChanged(1, "name2"); + + checkArrayValues(results, ["name2"], "one should remain after deleting the HTTPS name1"); + let popupState = yield getPopupState(); + is(popupState.open, true, "Check popup stays open after deleting"); + doKey("escape"); + popupState = yield getPopupState(); + is(popupState.open, false, "Check popup closed upon ESC"); +}); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_upgrade.html b/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_upgrade.html new file mode 100644 index 000000000..ee1424002 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_upgrade.html @@ -0,0 +1,117 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autocomplete on an HTTPS page using upgraded HTTP logins</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +const MISSING_ACTION_PATH = TESTS_DIR + "mochitest/form_basic.html"; +const CROSS_ORIGIN_SECURE_PATH = TESTS_DIR + "mochitest/form_cross_origin_secure_action.html"; + +const chromeScript = runChecksAfterCommonInit(false); + +let nsLoginInfo = SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1", +SpecialPowers.Ci.nsILoginInfo, +"init"); +</script> +<p id="display"></p> + +<!-- we presumably can't hide the content for this test. --> +<div id="content"> + <iframe></iframe> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]); + +// Check for expected username/password in form. +function checkACForm(expectedUsername, expectedPassword) { + let iframeDoc = iframe.contentDocument; + let uname = iframeDoc.getElementById("form-basic-username"); + let pword = iframeDoc.getElementById("form-basic-password"); + let formID = uname.parentNode.id; + is(uname.value, expectedUsername, "Checking " + formID + " username"); + is(pword.value, expectedPassword, "Checking " + formID + " password"); +} +function* prepareLoginsAndProcessForm(url, logins = []) { + LoginManager.removeAllLogins(); + + let dates = Date.now(); + for (let login of logins) { + SpecialPowers.do_QueryInterface(login, SpecialPowers.Ci.nsILoginMetaInfo); + // Force all dates to be the same so they don't affect things like deduping. + login.timeCreated = login.timePasswordChanged = login.timeLastUsed = dates; + LoginManager.addLogin(login); + } + + iframe.src = url; + yield promiseFormsProcessed(); +} + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({"set": [["signon.schemeUpgrades", true]]}); +}); + +add_task(function* test_simpleNoDupesNoAction() { + yield prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [ + new nsLoginInfo("http://example.com", "http://example.com", null, + "name2", "pass2", "uname", "pword"), + ]); + + checkACForm("name2", "pass2"); +}); + +add_task(function* test_simpleNoDupesUpgradeOriginAndAction() { + yield prepareLoginsAndProcessForm("https://example.com" + CROSS_ORIGIN_SECURE_PATH, [ + new nsLoginInfo("http://example.com", "http://another.domain", null, + "name2", "pass2", "uname", "pword"), + ]); + + checkACForm("name2", "pass2"); +}); + +add_task(function* test_simpleNoDupesUpgradeOriginOnly() { + yield prepareLoginsAndProcessForm("https://example.com" + CROSS_ORIGIN_SECURE_PATH, [ + new nsLoginInfo("http://example.com", "https://another.domain", null, + "name2", "pass2", "uname", "pword"), + ]); + + checkACForm("name2", "pass2"); +}); + +add_task(function* test_simpleNoDupesUpgradeActionOnly() { + yield prepareLoginsAndProcessForm("https://example.com" + CROSS_ORIGIN_SECURE_PATH, [ + new nsLoginInfo("https://example.com", "http://another.domain", null, + "name2", "pass2", "uname", "pword"), + ]); + + checkACForm("name2", "pass2"); +}); + +add_task(function* test_dedupe() { + yield prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [ + new nsLoginInfo("https://example.com", "https://example.com", null, + "name1", "passHTTPStoHTTPS", "uname", "pword"), + new nsLoginInfo("http://example.com", "http://example.com", null, + "name1", "passHTTPtoHTTP", "uname", "pword"), + new nsLoginInfo("http://example.com", "https://example.com", null, + "name1", "passHTTPtoHTTPS", "uname", "pword"), + new nsLoginInfo("https://example.com", "http://example.com", null, + "name1", "passHTTPStoHTTP", "uname", "pword"), + ]); + + checkACForm("name1", "passHTTPStoHTTPS"); +}); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html b/toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html new file mode 100644 index 000000000..983356371 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html @@ -0,0 +1,143 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test password-only forms should prefer a password-only login when present</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: Bug 444968 +<script> +let pwmgrCommonScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); +pwmgrCommonScript.sendSyncMessage("setupParent", { selfFilling: true }); + +SimpleTest.waitForExplicitFinish(); + +let chromeScript = runInParent(function chromeSetup() { + const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; + let pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); + + let login1A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let login1B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let login2A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let login2B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let login2C = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + + login1A.init("http://mochi.test:8888", "http://bug444968-1", null, + "testuser1A", "testpass1A", "", ""); + login1B.init("http://mochi.test:8888", "http://bug444968-1", null, + "", "testpass1B", "", ""); + + login2A.init("http://mochi.test:8888", "http://bug444968-2", null, + "testuser2A", "testpass2A", "", ""); + login2B.init("http://mochi.test:8888", "http://bug444968-2", null, + "", "testpass2B", "", ""); + login2C.init("http://mochi.test:8888", "http://bug444968-2", null, + "testuser2C", "testpass2C", "", ""); + + pwmgr.addLogin(login1A); + pwmgr.addLogin(login1B); + pwmgr.addLogin(login2A); + pwmgr.addLogin(login2B); + pwmgr.addLogin(login2C); + + addMessageListener("removeLogins", function removeLogins() { + pwmgr.removeLogin(login1A); + pwmgr.removeLogin(login1B); + pwmgr.removeLogin(login2A); + pwmgr.removeLogin(login2B); + pwmgr.removeLogin(login2C); + }); +}); + +SimpleTest.registerCleanupFunction(() => chromeScript.sendSyncMessage("removeLogins")); + +registerRunTests(); +</script> + +<p id="display"></p> +<div id="content" style="display: none"> + <!-- first 3 forms have matching user+pass and pass-only logins --> + + <!-- user+pass form. --> + <form id="form1" action="http://bug444968-1"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- password-only form. --> + <form id="form2" action="http://bug444968-1"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- user+pass form, username prefilled --> + <form id="form3" action="http://bug444968-1"> + <input type="text" name="uname" value="testuser1A"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + + <!-- next 4 forms have matching user+pass (2x) and pass-only (1x) logins --> + + <!-- user+pass form. --> + <form id="form4" action="http://bug444968-2"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- password-only form. --> + <form id="form5" action="http://bug444968-2"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- user+pass form, username prefilled --> + <form id="form6" action="http://bug444968-2"> + <input type="text" name="uname" value="testuser2A"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- user+pass form, username prefilled --> + <form id="form7" action="http://bug444968-2"> + <input type="text" name="uname" value="testuser2C"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/* Test for Login Manager: 444968 (password-only forms should prefer a + * password-only login when present ) + */ +function startTest() { + checkForm(1, "testuser1A", "testpass1A"); + checkForm(2, "testpass1B"); + checkForm(3, "testuser1A", "testpass1A"); + + checkUnmodifiedForm(4); // 2 logins match + checkForm(5, "testpass2B"); + checkForm(6, "testuser2A", "testpass2A"); + checkForm(7, "testuser2C", "testpass2C"); + + SimpleTest.finish(); +} + +window.addEventListener("runTests", startTest); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html b/toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html new file mode 100644 index 000000000..2ce3293dd --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html @@ -0,0 +1,115 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test login autocomplete is activated when focused by js on load</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +const chromeScript = runChecksAfterCommonInit(false); + +runInParent(function addLogins() { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + // Create some logins just for this form, since we'll be deleting them. + let nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, "init"); + + let login0 = new nsLoginInfo("https://example.org", "https://example.org", null, + "name", "pass", "uname", "pword"); + + let login1 = new nsLoginInfo("https://example.org", "https://example.org", null, + "name1", "pass1", "uname", "pword"); + + try { + Services.logins.addLogin(login0); + Services.logins.addLogin(login1); + } catch (e) { + assert.ok(false, "addLogin threw: " + e); + } +}); +</script> +<p id="display"></p> + +<div id="content"> + <iframe src="https://example.org/tests/toolkit/components/passwordmgr/test/mochitest/form_autofocus_js.html"></iframe> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]); +let iframeDoc; + +add_task(function* setup() { + yield new Promise(resolve => { + iframe.addEventListener("load", function onLoad() { + iframe.removeEventListener("load", onLoad); + resolve(); + }); + }); + + iframeDoc = iframe.contentDocument; + + SimpleTest.requestFlakyTimeout("Giving a chance for the unexpected popupshown to occur"); +}); + +add_task(function* test_initial_focus() { + let results = yield notifyMenuChanged(2, "name"); + checkArrayValues(results, ["name", "name1"], "Two results"); + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + is(iframeDoc.getElementById("form-basic-password").value, "pass", "Check first password filled"); + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is now closed"); +}); + +// This depends on the filling from the previous test. +add_task(function* test_not_reopened_if_filled() { + listenForUnexpectedPopupShown(); + let usernameField = iframeDoc.getElementById("form-basic-username"); + usernameField.focus(); + info("Waiting to see if a popupshown occurs"); + yield new Promise(resolve => setTimeout(resolve, 1000)); + + // cleanup + gPopupShownExpected = true; + iframeDoc.getElementById("form-basic-submit").focus(); +}); + +add_task(function* test_reopened_after_edit_not_matching_saved() { + let usernameField = iframeDoc.getElementById("form-basic-username"); + usernameField.value = "nam"; + let shownPromise = promiseACShown(); + usernameField.focus(); + yield shownPromise; + iframeDoc.getElementById("form-basic-submit").focus(); +}); + +add_task(function* test_not_reopened_after_selecting() { + let formFillController = SpecialPowers.Cc["@mozilla.org/satchel/form-fill-controller;1"]. + getService(SpecialPowers.Ci.nsIFormFillController); + let usernameField = iframeDoc.getElementById("form-basic-username"); + usernameField.value = ""; + iframeDoc.getElementById("form-basic-password").value = ""; + listenForUnexpectedPopupShown(); + formFillController.markAsLoginManagerField(usernameField); + info("Waiting to see if a popupshown occurs"); + yield new Promise(resolve => setTimeout(resolve, 1000)); + + // Cleanup + gPopupShownExpected = true; +}); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form.html new file mode 100644 index 000000000..3c38343a5 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic autofill</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: simple form fill + +<script> +runChecksAfterCommonInit(startTest); + +/** Test for Login Manager: form fill, multiple forms. **/ + +function startTest() { + is($_(1, "uname").value, "testuser", "Checking for filled username"); + is($_(1, "pword").value, "testpass", "Checking for filled password"); + + SimpleTest.finish(); +} +</script> + +<p id="display"></p> + +<div id="content" style="display: none"> + + <form id="form1" action="formtest.js"> + <p>This is form 1.</p> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + +</div> + +<pre id="test"></pre> +</body> +</html> + diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_0pw.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_0pw.html new file mode 100644 index 000000000..0b416673b --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_0pw.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test forms with no password fields</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: forms with no password fields +<p id="display"></p> + +<div id="content" style="display: none"> + + <!-- Form with no user field or password field --> + <form id="form1" action="formtest.js"> + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Form with no user field or password field, but one other field --> + <form id="form2" action="formtest.js"> + <input type="checkbox"> + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Form with no user field or password field, but one other field --> + <form id="form3" action="formtest.js"> + <input type="checkbox" name="uname" value=""> + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Form with a text field, but no password field --> + <form id="form4" action="formtest.js"> + <input type="text" name="yyyyy"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Form with a user field, but no password field --> + <form id="form5" action="formtest.js"> + <input type="text" name="uname"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: form fill, no password fields. **/ + +function startTest() { + is($_(3, "uname").value, "", "Checking for unfilled checkbox (form 3)"); + is($_(4, "yyyyy").value, "", "Checking for unfilled text field (form 4)"); + is($_(5, "uname").value, "", "Checking for unfilled text field (form 5)"); + + SimpleTest.finish(); +} + +runChecksAfterCommonInit(startTest); +</script> +</pre> +</body> +</html> + diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw.html new file mode 100644 index 000000000..3937fad4b --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw.html @@ -0,0 +1,167 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autofill for forms with 1 password field</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: forms with 1 password field +<script> +runChecksAfterCommonInit(() => startTest()); +</script> +<p id="display"></p> + +<div id="content" style="display: none"> + +<!-- no username fields --> + +<form id='form1' action='formtest.js'> 1 + <!-- Blank, so fill in the password --> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form2' action='formtest.js'> 2 + <!-- Already contains the password, so nothing to do. --> + <input type='password' name='pname' value='testpass'> + <button type='submit'>Submit</button> +</form> + +<form id='form3' action='formtest.js'> 3 + <!-- Contains unknown password, so don't change it --> + <input type='password' name='pname' value='xxxxxxxx'> + <button type='submit'>Submit</button> +</form> + + +<!-- username fields --> + +<form id='form4' action='formtest.js'> 4 + <!-- Blanks, so fill in login --> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form5' action='formtest.js'> 5 + <!-- Username already set, so fill in password --> + <input type='text' name='uname' value='testuser'> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form6' action='formtest.js'> 6 + <!-- Unknown username, so don't fill in password --> + <input type='text' name='uname' value='xxxxxxxx'> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form7' action='formtest.js'> 7 + <!-- Password already set, could fill in username but that's weird so we don't --> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value='testpass'> + <button type='submit'>Submit</button> +</form> + +<form id='form8' action='formtest.js'> 8 + <!-- Unknown password, so don't fill in a username --> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value='xxxxxxxx'> + <button type='submit'>Submit</button> +</form> + + + +<!-- extra text fields --> + +<form id='form9' action='formtest.js'> 9 + <!-- text field _after_ password should never be treated as a username field --> + <input type='password' name='pname' value=''> + <input type='text' name='uname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form10' action='formtest.js'> 10 + <!-- only the first text field before the password should be for username --> + <input type='text' name='other' value=''> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form11' action='formtest.js'> 11 + <!-- variation just to make sure extra text field is still ignored --> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value=''> + <input type='text' name='other' value=''> + <button type='submit'>Submit</button> +</form> + + + +<!-- same as last bunch, but with xxxx in the extra field. --> + +<form id='form12' action='formtest.js'> 12 + <!-- text field _after_ password should never be treated as a username field --> + <input type='password' name='pname' value=''> + <input type='text' name='uname' value='xxxxxxxx'> + <button type='submit'>Submit</button> +</form> + +<form id='form13' action='formtest.js'> 13 + <!-- only the first text field before the password should be for username --> + <input type='text' name='other' value='xxxxxxxx'> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form14' action='formtest.js'> 14 + <!-- variation just to make sure extra text field is still ignored --> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value=''> + <input type='text' name='other' value='xxxxxxxx'> + <button type='submit'>Submit</button> +</form> + + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: simple form fill **/ + +function startTest() { + var f = 1; + + // 1-3 + checkForm(f++, "testpass"); + checkForm(f++, "testpass"); + checkForm(f++, "xxxxxxxx"); + + // 4-8 + checkForm(f++, "testuser", "testpass"); + checkForm(f++, "testuser", "testpass"); + checkForm(f++, "xxxxxxxx", ""); + checkForm(f++, "", "testpass"); + checkForm(f++, "", "xxxxxxxx"); + + // 9-14 + checkForm(f++, "testpass", ""); + checkForm(f++, "", "testuser", "testpass"); + checkForm(f++, "testuser", "testpass", ""); + checkForm(f++, "testpass", "xxxxxxxx"); + checkForm(f++, "xxxxxxxx", "testuser", "testpass"); + checkForm(f++, "testuser", "testpass", "xxxxxxxx"); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw_2.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw_2.html new file mode 100644 index 000000000..0f6566b9c --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw_2.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test forms with 1 password field, part 2</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: forms with 1 password field, part 2 +<script> +runChecksAfterCommonInit(() => startTest()); +</script> +<p id="display"></p> + +<div id="content" style="display: none"> + +<form id='form1' action='formtest.js'> 1 + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form2' action='formtest.js'> 2 + <input type='password' name='pname' value='' disabled> + <button type='submit'>Submit</button> +</form> + +<form id='form3' action='formtest.js'> 3 + <input type='password' name='pname' value='' readonly> + <button type='submit'>Submit</button> +</form> + +<form id='form4' action='formtest.js'> 4 + <input type='text' name='uname' value=''> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form5' action='formtest.js'> 5 + <input type='text' name='uname' value='' disabled> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form6' action='formtest.js'> 6 + <input type='text' name='uname' value='' readonly> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form7' action='formtest.js'> 7 + <input type='text' name='uname' value=''> + <input type='password' name='pname' value='' disabled> + <button type='submit'>Submit</button> +</form> + +<form id='form8' action='formtest.js'> 8 + <input type='text' name='uname' value=''> + <input type='password' name='pname' value='' readonly> + <button type='submit'>Submit</button> +</form> + +<form id='form9' action='formtest.js'> 9 + <input type='text' name='uname' value='TESTUSER'> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form10' action='formtest.js'> 10 + <input type='text' name='uname' value='TESTUSER' readonly> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form11' action='formtest.js'> 11 + <input type='text' name='uname' value='TESTUSER' disabled> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: simple form fill, part 2 **/ + +function startTest() { + var f; + + // Test various combinations of disabled/readonly inputs + checkForm(1, "testpass"); // control + checkUnmodifiedForm(2); + checkUnmodifiedForm(3); + checkForm(4, "testuser", "testpass"); // control + for (f = 5; f <= 8; f++) { checkUnmodifiedForm(f); } + // Test case-insensitive comparison of username field + checkForm(9, "testuser", "testpass"); + checkForm(10, "TESTUSER", "testpass"); + checkForm(11, "TESTUSER", "testpass"); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> + diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_1.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_1.html new file mode 100644 index 000000000..128ffca7c --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_1.html @@ -0,0 +1,187 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autofill for forms with 2 password fields</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: forms with 2 password fields +<script> +runChecksAfterCommonInit(() => startTest()); +</script> +<p id="display"></p> + +<div id="content" style="display: none"> + + +<!-- no username fields --> + +<form id='form1' action='formtest.js'> 1 + <!-- simple form, fill in first pw --> + <input type='password' name='pname' value=''> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form2' action='formtest.js'> 2 + <!-- same but reverse pname and qname, field names are ignored. --> + <input type='password' name='qname' value=''> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form3' action='formtest.js'> 3 + <!-- text field after password fields should be ignored, no username. --> + <input type='password' name='pname' value=''> + <input type='password' name='qname' value=''> + <input type='text' name='uname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form4' action='formtest.js'> 4 + <!-- nothing to do, password already present --> + <input type='password' name='pname' value='testpass'> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form5' action='formtest.js'> 5 + <!-- don't clobber an existing unrecognized password --> + <input type='password' name='pname' value='xxxxxxxx'> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form6' action='formtest.js'> 6 + <!-- fill in first field, 2nd field shouldn't be touched anyway. --> + <input type='password' name='pname' value=''> + <input type='password' name='qname' value='xxxxxxxx'> + <button type='submit'>Submit</button> +</form> + + + +<!-- with username fields --> + + + +<form id='form7' action='formtest.js'> 7 + <!-- simple form, should fill in username and first pw --> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value=''> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form8' action='formtest.js'> 8 + <!-- reverse pname and qname, field names are ignored. --> + <input type='text' name='uname' value=''> + <input type='password' name='qname' value=''> + <input type='password' name='pname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form9' action='formtest.js'> 9 + <!-- username already filled, so just fill first password --> + <input type='text' name='uname' value='testuser'> + <input type='password' name='pname' value=''> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form10' action='formtest.js'> 10 + <!-- unknown username, don't fill in a password --> + <input type='text' name='uname' value='xxxxxxxx'> + <input type='password' name='pname' value=''> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form11' action='formtest.js'> 11 + <!-- don't clobber unknown password --> + <input type='text' name='uname' value='testuser'> + <input type='password' name='pname' value='xxxxxxxx'> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form12' action='formtest.js'> 12 + <!-- fill in 1st pass, don't clobber 2nd pass --> + <input type='text' name='uname' value='testuser'> + <input type='password' name='pname' value=''> + <input type='password' name='qname' value='xxxxxxxx'> + <button type='submit'>Submit</button> +</form> + +<form id='form13' action='formtest.js'> 13 + <!-- nothing to do, user and pass prefilled. life is easy. --> + <input type='text' name='uname' value='testuser'> + <input type='password' name='pname' value='testpass'> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form14' action='formtest.js'> 14 + <!-- shouldn't fill in username because 1st pw field is unknown. --> + <input type='text' name='uname' value=''> + <input type='password' name='pname' value='xxxxxxxx'> + <input type='password' name='qname' value='testpass'> + <button type='submit'>Submit</button> +</form> + +<form id='form15' action='formtest.js'> 15 + <!-- textfield in the middle of pw fields should be ignored --> + <input type='password' name='pname' value=''> + <input type='text' name='uname' value=''> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + +<form id='form16' action='formtest.js'> 16 + <!-- same, and don't clobber existing unknown password --> + <input type='password' name='pname' value='xxxxxxxx'> + <input type='text' name='uname' value=''> + <input type='password' name='qname' value=''> + <button type='submit'>Submit</button> +</form> + + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: simple form fill **/ + +function startTest() { + var f = 1; + + // 1-6 no username + checkForm(f++, "testpass", ""); + checkForm(f++, "testpass", ""); + checkForm(f++, "testpass", "", ""); + checkForm(f++, "testpass", ""); + checkForm(f++, "xxxxxxxx", ""); + checkForm(f++, "testpass", "xxxxxxxx"); + + // 7-15 with username + checkForm(f++, "testuser", "testpass", ""); + checkForm(f++, "testuser", "testpass", ""); + checkForm(f++, "testuser", "testpass", ""); + checkForm(f++, "xxxxxxxx", "", ""); + checkForm(f++, "testuser", "xxxxxxxx", ""); + checkForm(f++, "testuser", "testpass", "xxxxxxxx"); + checkForm(f++, "testuser", "testpass", ""); + checkForm(f++, "", "xxxxxxxx", "testpass"); + checkForm(f++, "testpass", "", ""); + checkForm(f++, "xxxxxxxx", "", ""); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_2.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_2.html new file mode 100644 index 000000000..eba811cf9 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_2.html @@ -0,0 +1,105 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for form fill with 2 password fields</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: form fill, 2 password fields +<p id="display"></p> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: form fill, 2 password fields **/ + +/* + * If a form has two password fields, other things may be going on.... + * + * 1 - The user might be creating a new login (2nd field for typo checking) + * 2 - The user is changing a password (old and new password each have field) + * + * This test is for case #1. + */ + +var numSubmittedForms = 0; +var numStartingLogins = 0; + +function startTest() { + // Check for unfilled forms + is($_(1, "uname").value, "", "Checking username 1"); + is($_(1, "pword").value, "", "Checking password 1A"); + is($_(1, "qword").value, "", "Checking password 1B"); + + // Fill in the username and password fields, for account creation. + // Form 1 + $_(1, "uname").value = "newuser1"; + $_(1, "pword").value = "newpass1"; + $_(1, "qword").value = "newpass1"; + + var button = getFormSubmitButton(1); + + todo(false, "form submission disabled, can't auto-accept dialog yet"); + SimpleTest.finish(); +} + + +// Called by each form's onsubmit handler. +function checkSubmit(formNum) { + numSubmittedForms++; + + // End the test at the last form. + if (formNum == 999) { + is(numSubmittedForms, 999, "Ensuring all forms submitted for testing."); + + var numEndingLogins = LoginManager.countLogins("", "", ""); + + ok(numEndingLogins > 0, "counting logins at end"); + is(numStartingLogins, numEndingLogins + 222, "counting logins at end"); + + SimpleTest.finish(); + return false; // return false to cancel current form submission + } + + // submit the next form. + var button = getFormSubmitButton(formNum + 1); + button.click(); + + return false; // return false to cancel current form submission +} + + +function getFormSubmitButton(formNum) { + var form = $("form" + formNum); // by id, not name + ok(form != null, "getting form " + formNum); + + // we can't just call form.submit(), because that doesn't seem to + // invoke the form onsubmit handler. + var button = form.firstChild; + while (button && button.type != "submit") { button = button.nextSibling; } + ok(button != null, "getting form submit button"); + + return button; +} + +runChecksAfterCommonInit(startTest); + +</script> +</pre> +<div id="content" style="display: none"> + <form id="form1" onsubmit="return checkSubmit(1)" action="http://newuser.com"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <input type="password" name="qword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + +</div> + +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_3pw_1.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_3pw_1.html new file mode 100644 index 000000000..30b5a319f --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_3pw_1.html @@ -0,0 +1,177 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autofill for forms with 3 password fields</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: forms with 3 password fields (form filling) +<script> +runChecksAfterCommonInit(() => startTest()); +</script> +<p id="display"></p> + +<div id="content" style="display: none"> + <p>The next three forms are <b>user/pass/passB/passC</b>, as all-empty, preuser(only), and preuser/pass</p> + <form id="form1" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <input type="password" name="qword"> + <input type="password" name="rword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <form id="form2" action="formtest.js"> + <input type="text" name="uname" value="testuser"> + <input type="password" name="pword"> + <input type="password" name="qword"> + <input type="password" name="rword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <form id="form3" action="formtest.js"> + <input type="text" name="uname" value="testuser"> + <input type="password" name="pword" value="testpass"> + <input type="password" name="qword"> + <input type="password" name="rword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + + <p>The next three forms are <b>user/passB/pass/passC</b>, as all-empty, preuser(only), and preuser/pass</p> + <form id="form4" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="qword"> + <input type="password" name="pword"> + <input type="password" name="rword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <form id="form5" action="formtest.js"> + <input type="text" name="uname" value="testuser"> + <input type="password" name="qword"> + <input type="password" name="pword"> + <input type="password" name="rword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <form id="form6" action="formtest.js"> + <input type="text" name="uname" value="testuser"> + <input type="password" name="qword"> + <input type="password" name="pword" value="testpass"> + <input type="password" name="rword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <p>The next three forms are <b>user/passB/passC/pass</b>, as all-empty, preuser(only), and preuser/pass</p> + <form id="form7" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="qword"> + <input type="password" name="rword"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <form id="form8" action="formtest.js"> + <input type="text" name="uname" value="testuser"> + <input type="password" name="qword"> + <input type="password" name="rword"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <form id="form9" action="formtest.js"> + <input type="text" name="uname" value="testuser"> + <input type="password" name="qword"> + <input type="password" name="rword"> + <input type="password" name="pword" value="testpass"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: form fill, 3 password fields **/ + +// Test to make sure 3-password forms are filled properly. + +function startTest() { + // Check form 1 + is($_(1, "uname").value, "testuser", "Checking username 1"); + is($_(1, "pword").value, "testpass", "Checking password 1"); + is($_(1, "qword").value, "", "Checking password 1 (q)"); + is($_(1, "rword").value, "", "Checking password 1 (r)"); + // Check form 2 + is($_(2, "uname").value, "testuser", "Checking username 2"); + is($_(2, "pword").value, "testpass", "Checking password 2"); + is($_(2, "qword").value, "", "Checking password 2 (q)"); + is($_(2, "rword").value, "", "Checking password 2 (r)"); + // Check form 3 + is($_(3, "uname").value, "testuser", "Checking username 3"); + is($_(3, "pword").value, "testpass", "Checking password 3"); + is($_(3, "qword").value, "", "Checking password 3 (q)"); + is($_(3, "rword").value, "", "Checking password 3 (r)"); + + // Check form 4 + is($_(4, "uname").value, "testuser", "Checking username 4"); + todo_is($_(4, "qword").value, "", "Checking password 4 (q)"); + todo_is($_(4, "pword").value, "testpass", "Checking password 4"); + is($_(4, "rword").value, "", "Checking password 4 (r)"); + // Check form 5 + is($_(5, "uname").value, "testuser", "Checking username 5"); + todo_is($_(5, "qword").value, "", "Checking password 5 (q)"); + todo_is($_(5, "pword").value, "testpass", "Checking password 5"); + is($_(5, "rword").value, "", "Checking password 5 (r)"); + // Check form 6 + is($_(6, "uname").value, "testuser", "Checking username 6"); + todo_is($_(6, "qword").value, "", "Checking password 6 (q)"); + is($_(6, "pword").value, "testpass", "Checking password 6"); + is($_(6, "rword").value, "", "Checking password 6 (r)"); + + // Check form 7 + is($_(7, "uname").value, "testuser", "Checking username 7"); + todo_is($_(7, "qword").value, "", "Checking password 7 (q)"); + is($_(7, "rword").value, "", "Checking password 7 (r)"); + todo_is($_(7, "pword").value, "testpass", "Checking password 7"); + // Check form 8 + is($_(8, "uname").value, "testuser", "Checking username 8"); + todo_is($_(8, "qword").value, "", "Checking password 8 (q)"); + is($_(8, "rword").value, "", "Checking password 8 (r)"); + todo_is($_(8, "pword").value, "testpass", "Checking password 8"); + // Check form 9 + is($_(9, "uname").value, "testuser", "Checking username 9"); + todo_is($_(9, "qword").value, "", "Checking password 9 (q)"); + is($_(9, "rword").value, "", "Checking password 9 (r)"); + is($_(9, "pword").value, "testpass", "Checking password 9"); + + // TODO: as with the 2-password cases, add tests to check for creating new + // logins and changing passwords. + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> + diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html new file mode 100644 index 000000000..0eee8e696 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html @@ -0,0 +1,859 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic login autocomplete</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: multiple login autocomplete + +<script> +var chromeScript = runChecksAfterCommonInit(); + +var setupScript = runInParent(function setup() { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + // Create some logins just for this form, since we'll be deleting them. + var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, "init"); + assert.ok(nsLoginInfo != null, "nsLoginInfo constructor"); + + // login0 has no username, so should be filtered out from the autocomplete list. + var login0 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "", "user0pass", "", "pword"); + + var login1 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "tempuser1", "temppass1", "uname", "pword"); + + var login2 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "testuser2", "testpass2", "uname", "pword"); + + var login3 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "testuser3", "testpass3", "uname", "pword"); + + var login4 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "zzzuser4", "zzzpass4", "uname", "pword"); + + // login 5 only used in the single-user forms + var login5 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete2", null, + "singleuser5", "singlepass5", "uname", "pword"); + + var login6A = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete3", null, + "form7user1", "form7pass1", "uname", "pword"); + var login6B = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete3", null, + "form7user2", "form7pass2", "uname", "pword"); + + var login7 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete4", null, + "form8user", "form8pass", "uname", "pword"); + + var login8A = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null, + "form9userAB", "form9pass", "uname", "pword"); + var login8B = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null, + "form9userAAB", "form9pass", "uname", "pword"); + var login8C = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null, + "form9userAABzz", "form9pass", "uname", "pword"); + + var login10 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete7", null, + "testuser10", "testpass10", "uname", "pword"); + + + // try/catch in case someone runs the tests manually, twice. + try { + Services.logins.addLogin(login0); + Services.logins.addLogin(login1); + Services.logins.addLogin(login2); + Services.logins.addLogin(login3); + Services.logins.addLogin(login4); + Services.logins.addLogin(login5); + Services.logins.addLogin(login6A); + Services.logins.addLogin(login6B); + Services.logins.addLogin(login7); + Services.logins.addLogin(login8A); + Services.logins.addLogin(login8B); + // login8C is added later + Services.logins.addLogin(login10); + } catch (e) { + assert.ok(false, "addLogin threw: " + e); + } + + addMessageListener("addLogin", loginVariableName => { + let login = eval(loginVariableName); + assert.ok(!!login, "Login to add is defined: " + loginVariableName); + Services.logins.addLogin(login); + }); + addMessageListener("removeLogin", loginVariableName => { + let login = eval(loginVariableName); + assert.ok(!!login, "Login to delete is defined: " + loginVariableName); + Services.logins.removeLogin(login); + }); +}); +</script> +<p id="display"></p> + +<!-- we presumably can't hide the content for this test. --> +<div id="content"> + + <!-- form1 tests multiple matching logins --> + <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- other forms test single logins, with autocomplete=off set --> + <form id="form2" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword" autocomplete="off"> + <button type="submit">Submit</button> + </form> + + <form id="form3" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname" autocomplete="off"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form4" action="http://autocomplete2" onsubmit="return false;" autocomplete="off"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form5" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname" autocomplete="off"> + <input type="password" name="pword" autocomplete="off"> + <button type="submit">Submit</button> + </form> + + <!-- control --> + <form id="form6" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- This form will be manipulated to insert a different username field. --> + <form id="form7" action="http://autocomplete3" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- test for no autofill after onblur with blank username --> + <form id="form8" action="http://autocomplete4" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- test autocomplete dropdown --> + <form id="form9" action="http://autocomplete5" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- test for onUsernameInput recipe testing --> + <form id="form11" action="http://autocomplete7" onsubmit="return false;"> + <input type="text" name="1"> + <input type="text" name="2"> + <button type="submit">Submit</button> + </form> + + <!-- tests <form>-less autocomplete --> + <div id="form12"> + <input type="text" name="uname" id="uname"> + <input type="password" name="pword" id="pword"> + <button type="submit">Submit</button> + </div> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: multiple login autocomplete. **/ + +var uname = $_(1, "uname"); +var pword = $_(1, "pword"); +const shiftModifier = SpecialPowers.Ci.nsIDOMEvent.SHIFT_MASK; + +// Restore the form to the default state. +function restoreForm() { + uname.value = ""; + pword.value = ""; + uname.focus(); +} + +// Check for expected username/password in form. +function checkACForm(expectedUsername, expectedPassword) { + var formID = uname.parentNode.id; + is(uname.value, expectedUsername, "Checking " + formID + " username is: " + expectedUsername); + is(pword.value, expectedPassword, "Checking " + formID + " password is: " + expectedPassword); +} + +function sendFakeAutocompleteEvent(element) { + var acEvent = document.createEvent("HTMLEvents"); + acEvent.initEvent("DOMAutoComplete", true, false); + element.dispatchEvent(acEvent); +} + +function spinEventLoop() { + return Promise.resolve(); +} + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({"set": [["security.insecure_field_warning.contextual.enabled", false], + ["signon.autofillForms.http", true]]}); + listenForUnexpectedPopupShown(); +}); + +add_task(function* test_form1_initial_empty() { + yield SimpleTest.promiseFocus(window); + + // Make sure initial form is empty. + checkACForm("", ""); + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is initially closed"); +}); + +add_task(function* test_form1_menuitems() { + yield SimpleTest.promiseFocus(window); + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + let results = yield shownPromise; + + let popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected upon opening"); + + let expectedMenuItems = ["tempuser1", + "testuser2", + "testuser3", + "zzzuser4"]; + checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly."); + + checkACForm("", ""); // value shouldn't update just by selecting + doKey("return"); // not "enter"! + yield spinEventLoop(); // let focus happen + checkACForm("", ""); +}); + +add_task(function* test_form1_first_entry() { + yield SimpleTest.promiseFocus(window); + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + let popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected upon opening"); + + doKey("down"); // first + checkACForm("", ""); // value shouldn't update just by selecting + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("tempuser1", "temppass1"); +}); + +add_task(function* test_form1_second_entry() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // first + doKey("down"); // second + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_third_entry() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // first + doKey("down"); // second + doKey("down"); // third + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser3", "testpass3"); +}); + +add_task(function* test_form1_fourth_entry() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // first + doKey("down"); // second + doKey("down"); // third + doKey("down"); // fourth + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_wraparound_first_entry() { + // Trigger autocomplete popup + restoreForm(); + yield spinEventLoop(); // let focus happen + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // first + doKey("down"); // second + doKey("down"); // third + doKey("down"); // fourth + doKey("down"); // deselects + doKey("down"); // first + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("tempuser1", "temppass1"); +}); + +add_task(function* test_form1_wraparound_up_last_entry() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("up"); // last (fourth) + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_wraparound_down_up_up() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // select first entry + doKey("up"); // selects nothing! + doKey("up"); // select last entry + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_wraparound_up_last() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); + doKey("up"); // deselects + doKey("up"); // last entry + doKey("up"); + doKey("up"); + doKey("up"); // first entry + doKey("up"); // deselects + doKey("up"); // last entry + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_fill_username_without_autofill_right() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Set first entry w/o triggering autocomplete + doKey("down"); // first + doKey("right"); + yield spinEventLoop(); + checkACForm("tempuser1", ""); // empty password +}); + +add_task(function* test_form1_fill_username_without_autofill_left() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Set first entry w/o triggering autocomplete + doKey("down"); // first + doKey("left"); + checkACForm("tempuser1", ""); // empty password +}); + +add_task(function* test_form1_pageup_first() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check first entry (page up) + doKey("down"); // first + doKey("down"); // second + doKey("page_up"); // first + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("tempuser1", "temppass1"); +}); + +add_task(function* test_form1_pagedown_last() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + /* test 13 */ + // Check last entry (page down) + doKey("down"); // first + doKey("page_down"); // last + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_untrusted_event() { + restoreForm(); + yield spinEventLoop(); + + // Send a fake (untrusted) event. + checkACForm("", ""); + uname.value = "zzzuser4"; + sendFakeAutocompleteEvent(uname); + yield spinEventLoop(); + checkACForm("zzzuser4", ""); +}); + +add_task(function* test_form1_delete() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // XXX tried sending character "t" before/during dropdown to test + // filtering, but had no luck. Seemed like the character was getting lost. + // Setting uname.value didn't seem to work either. This works with a human + // driver, so I'm not sure what's up. + + // Delete the first entry (of 4), "tempuser1" + doKey("down"); + var numLogins; + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 5, "Correct number of logins before deleting one"); + + let countChangedPromise = notifyMenuChanged(3); + var deletionPromise = promiseStorageChanged(["removeLogin"]); + // 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. + doKey("delete", shiftModifier); + yield deletionPromise; + + checkACForm("", ""); + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 4, "Correct number of logins after deleting one"); + yield countChangedPromise; + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_first_after_deletion() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check the new first entry (of 3) + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_delete_second() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Delete the second entry (of 3), "testuser3" + doKey("down"); + doKey("down"); + doKey("delete", shiftModifier); + checkACForm("", ""); + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 3, "Correct number of logins after deleting one"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_first_after_deletion2() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check the new first entry (of 2) + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_delete_last() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + /* test 54 */ + // Delete the last entry (of 2), "zzzuser4" + doKey("down"); + doKey("down"); + doKey("delete", shiftModifier); + checkACForm("", ""); + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 2, "Correct number of logins after deleting one"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_first_after_3_deletions() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check the only remaining entry + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_check_only_entry_remaining() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + /* test 56 */ + // Delete the only remaining entry, "testuser2" + doKey("down"); + doKey("delete", shiftModifier); + checkACForm("", ""); + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 1, "Correct number of logins after deleting one"); + + // remove the login that's not shown in the list. + setupScript.sendSyncMessage("removeLogin", "login0"); +}); + +/* Tests for single-user forms for ignoring autocomplete=off */ +add_task(function* test_form2() { + // Turn our attention to form2 + uname = $_(2, "uname"); + pword = $_(2, "pword"); + checkACForm("singleuser5", "singlepass5"); + + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form3() { + uname = $_(3, "uname"); + pword = $_(3, "pword"); + checkACForm("singleuser5", "singlepass5"); + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form4() { + uname = $_(4, "uname"); + pword = $_(4, "pword"); + checkACForm("singleuser5", "singlepass5"); + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form5() { + uname = $_(5, "uname"); + pword = $_(5, "pword"); + checkACForm("singleuser5", "singlepass5"); + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form6() { + // (this is a control, w/o autocomplete=off, to ensure the login + // that was being suppressed would have been filled in otherwise) + uname = $_(6, "uname"); + pword = $_(6, "pword"); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form6_changeUsername() { + // Test that the password field remains filled in after changing + // the username. + uname.focus(); + doKey("right"); + sendChar("X"); + // Trigger the 'blur' event on uname + pword.focus(); + yield spinEventLoop(); + checkACForm("singleuser5X", "singlepass5"); + + setupScript.sendSyncMessage("removeLogin", "login5"); +}); + +add_task(function* test_form7() { + uname = $_(7, "uname"); + pword = $_(7, "pword"); + checkACForm("", ""); + + // Insert a new username field into the form. We'll then make sure + // that invoking the autocomplete doesn't try to fill the form. + var newField = document.createElement("input"); + newField.setAttribute("type", "text"); + newField.setAttribute("name", "uname2"); + pword.parentNode.insertBefore(newField, pword); + is($_(7, "uname2").value, "", "Verifying empty uname2"); + + // Delete login6B. It was created just to prevent filling in a login + // automatically, removing it makes it more likely that we'll catch a + // future regression with form filling here. + setupScript.sendSyncMessage("removeLogin", "login6B"); +}); + +add_task(function* test_form7_2() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + // The form changes, so we expect the old username field to get the + // selected autocomplete value, but neither the new username field nor + // the password field should have any values filled in. + yield spinEventLoop(); + checkACForm("form7user1", ""); + is($_(7, "uname2").value, "", "Verifying empty uname2"); + restoreForm(); // clear field, so reloading test doesn't fail + + setupScript.sendSyncMessage("removeLogin", "login6A"); +}); + +add_task(function* test_form8() { + uname = $_(8, "uname"); + pword = $_(8, "pword"); + checkACForm("form8user", "form8pass"); + restoreForm(); +}); + +add_task(function* test_form8_blur() { + checkACForm("", ""); + // Focus the previous form to trigger a blur. + $_(7, "uname").focus(); +}); + +add_task(function* test_form8_2() { + checkACForm("", ""); + restoreForm(); +}); + +add_task(function* test_form8_3() { + checkACForm("", ""); + setupScript.sendSyncMessage("removeLogin", "login7"); +}); + +add_task(function* test_form9_filtering() { + // Turn our attention to form9 to test the dropdown - bug 497541 + uname = $_(9, "uname"); + pword = $_(9, "pword"); + uname.focus(); + let shownPromise = promiseACShown(); + sendString("form9userAB"); + yield shownPromise; + + checkACForm("form9userAB", ""); + uname.focus(); + doKey("left"); + shownPromise = promiseACShown(); + sendChar("A"); + let results = yield shownPromise; + + checkACForm("form9userAAB", ""); + checkArrayValues(results, ["form9userAAB"], "Check dropdown is updated after inserting 'A'"); + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("form9userAAB", "form9pass"); +}); + +add_task(function* test_form9_autocomplete_cache() { + // Note that this addLogin call will only be seen by the autocomplete + // attempt for the sendChar if we do not successfully cache the + // autocomplete results. + setupScript.sendSyncMessage("addLogin", "login8C"); + uname.focus(); + let promise0 = notifyMenuChanged(0); + sendChar("z"); + yield promise0; + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup shouldn't open"); + + // check that empty results are cached - bug 496466 + promise0 = notifyMenuChanged(0); + sendChar("z"); + yield promise0; + popupState = yield getPopupState(); + is(popupState.open, false, "Check popup stays closed due to cached empty result"); +}); + +add_task(function* test_form11_recipes() { + yield loadRecipes({ + siteRecipes: [{ + "hosts": ["mochi.test:8888"], + "usernameSelector": "input[name='1']", + "passwordSelector": "input[name='2']" + }], + }); + uname = $_(11, "1"); + pword = $_(11, "2"); + + // First test DOMAutocomplete + // Switch the password field to type=password so _fillForm marks the username + // field for autocomplete. + pword.type = "password"; + yield promiseFormsProcessed(); + restoreForm(); + checkACForm("", ""); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("testuser10", "testpass10"); + + // Now test recipes with blur on the username field. + restoreForm(); + checkACForm("", ""); + uname.value = "testuser10"; + checkACForm("testuser10", ""); + doKey("tab"); + yield promiseFormsProcessed(); + checkACForm("testuser10", "testpass10"); + yield resetRecipes(); +}); + +add_task(function* test_form12_formless() { + // Test form-less autocomplete + uname = $_(12, "uname"); + pword = $_(12, "pword"); + restoreForm(); + checkACForm("", ""); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Trigger autocomplete + doKey("down"); + checkACForm("", ""); // value shouldn't update + let processedPromise = promiseFormsProcessed(); + doKey("return"); // not "enter"! + yield processedPromise; + checkACForm("testuser", "testpass"); +}); + +add_task(function* test_form12_open_on_trusted_focus() { + uname = $_(12, "uname"); + pword = $_(12, "pword"); + uname.value = ""; + pword.value = ""; + + // Move focus to the password field so we can test the first click on the + // username field. + pword.focus(); + checkACForm("", ""); + const firePrivEventPromise = new Promise((resolve) => { + uname.addEventListener("click", (e) => { + ok(e.isTrusted, "Ensure event is trusted"); + resolve(); + }); + }); + const shownPromise = promiseACShown(); + synthesizeMouseAtCenter(uname, {}); + yield firePrivEventPromise; + yield shownPromise; + doKey("down"); + const processedPromise = promiseFormsProcessed(); + doKey("return"); // not "enter"! + yield processedPromise; + checkACForm("testuser", "testpass"); +}); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_html5.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_html5.html new file mode 100644 index 000000000..40e322afd --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_html5.html @@ -0,0 +1,164 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for html5 input types (email, tel, url, etc.)</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: html5 input types (email, tel, url, etc.) +<script> +runChecksAfterCommonInit(() => startTest()); + +runInParent(function setup() { + const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; + let pwmgr = Cc["@mozilla.org/login-manager;1"]. + getService(Ci.nsILoginManager); + + login1 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login3 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login4 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + + login1.init("http://mochi.test:8888", "http://bug600551-1", null, + "testuser@example.com", "testpass1", "", ""); + login2.init("http://mochi.test:8888", "http://bug600551-2", null, + "555-555-5555", "testpass2", "", ""); + login3.init("http://mochi.test:8888", "http://bug600551-3", null, + "http://mozilla.org", "testpass3", "", ""); + login4.init("http://mochi.test:8888", "http://bug600551-4", null, + "123456789", "testpass4", "", ""); + + pwmgr.addLogin(login1); + pwmgr.addLogin(login2); + pwmgr.addLogin(login3); + pwmgr.addLogin(login4); +}); +</script> + +<p id="display"></p> +<div id="content" style="display: none"> + + <form id="form1" action="http://bug600551-1"> + <input type="email" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form2" action="http://bug600551-2"> + <input type="tel" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form3" action="http://bug600551-3"> + <input type="url" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form4" action="http://bug600551-4"> + <input type="number" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- The following forms should not be filled with usernames --> + <form id="form5" action="formtest.js"> + <input type="search" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form6" action="formtest.js"> + <input type="datetime" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form7" action="formtest.js"> + <input type="date" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form8" action="formtest.js"> + <input type="month" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form9" action="formtest.js"> + <input type="week" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form10" action="formtest.js"> + <input type="time" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form11" action="formtest.js"> + <input type="datetime-local" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form12" action="formtest.js"> + <input type="range" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form13" action="formtest.js"> + <input type="color" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/* Test for Login Manager: 600551 + (Password manager not working with input type=email) + */ +function startTest() { + checkForm(1, "testuser@example.com", "testpass1"); + checkForm(2, "555-555-5555", "testpass2"); + checkForm(3, "http://mozilla.org", "testpass3"); + checkForm(4, "123456789", "testpass4"); + + is($_(5, "uname").value, "", "type=search should not be considered a username"); + + is($_(6, "uname").value, "", "type=datetime should not be considered a username"); + + is($_(7, "uname").value, "", "type=date should not be considered a username"); + + is($_(8, "uname").value, "", "type=month should not be considered a username"); + + is($_(9, "uname").value, "", "type=week should not be considered a username"); + + is($_(10, "uname").value, "", "type=time should not be considered a username"); + + is($_(11, "uname").value, "", "type=datetime-local should not be considered a username"); + + is($_(12, "uname").value, "50", "type=range should not be considered a username"); + + is($_(13, "uname").value, "#000000", "type=color should not be considered a username"); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwevent.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwevent.html new file mode 100644 index 000000000..e0a2883c8 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwevent.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=355063 +--> +<head> + <meta charset="utf-8"/> + <title>Test for Bug 355063</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="pwmgr_common.js"></script> + <script type="application/javascript"> + /** Test for Bug 355063 **/ + + runChecksAfterCommonInit(function startTest() { + info("startTest"); + // Password Manager's own listener should always have been added first, so + // the test's listener should be called after the pwmgr's listener fills in + // a login. + // + SpecialPowers.addChromeEventListener("DOMFormHasPassword", function eventFired() { + SpecialPowers.removeChromeEventListener("DOMFormHasPassword", eventFired); + var passField = $("p1"); + passField.addEventListener("input", checkForm); + }); + addForm(); + }); + + function addForm() { + info("addForm"); + var c = document.getElementById("content"); + c.innerHTML = "<form id=form1>form1: <input id=u1><input type=password id=p1></form><br>"; + } + + function checkForm() { + info("checkForm"); + var userField = document.getElementById("u1"); + var passField = document.getElementById("p1"); + is(userField.value, "testuser", "checking filled username"); + is(passField.value, "testpass", "checking filled password"); + + SimpleTest.finish(); + } +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=355063">Mozilla Bug 355063</a> +<p id="display"></p> +<div id="content"> +forms go here! +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwonly.html b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwonly.html new file mode 100644 index 000000000..40fec8c46 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwonly.html @@ -0,0 +1,213 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test forms and logins without a username</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: forms and logins without a username. +<script> +runChecksAfterCommonInit(() => startTest()); +runInParent(() => { + const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; + var pwmgr = Cc["@mozilla.org/login-manager;1"] + .getService(Ci.nsILoginManager); + + var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo); + + // pwlogin1 uses a unique formSubmitURL, to check forms where no other logins + // will apply. pwlogin2 uses the normal formSubmitURL, so that we can test + // forms with a mix of username and non-username logins that might apply. + // + // Note: pwlogin2 is deleted at the end of the test. + + pwlogin1 = new nsLoginInfo(); + pwlogin2 = new nsLoginInfo(); + + pwlogin1.init("http://mochi.test:8888", "http://mochi.test:1111", null, + "", "1234", "uname", "pword"); + + pwlogin2.init("http://mochi.test:8888", "http://mochi.test:8888", null, + "", "1234", "uname", "pword"); + + + pwmgr.addLogin(pwlogin1); + pwmgr.addLogin(pwlogin2); +}); +</script> +<p id="display"></p> + +<div id="content" style="display: none"> + + +<!-- simple form: no username field, 1 password field --> +<form id='form1' action='http://mochi.test:1111/formtest.js'> 1 + <input type='password' name='pname' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +<!-- simple form: no username field, 2 password fields --> +<form id='form2' action='http://mochi.test:1111/formtest.js'> 2 + <input type='password' name='pname1' value=''> + <input type='password' name='pname2' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +<!-- simple form: no username field, 3 password fields --> +<form id='form3' action='http://mochi.test:1111/formtest.js'> 3 + <input type='password' name='pname1' value=''> + <input type='password' name='pname2' value=''> + <input type='password' name='pname3' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +<!-- 4 password fields, should be ignored. --> +<form id='form4' action='http://mochi.test:1111/formtest.js'> 4 + <input type='password' name='pname1' value=''> + <input type='password' name='pname2' value=''> + <input type='password' name='pname3' value=''> + <input type='password' name='pname4' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + + + +<!-- 1 username field --> +<form id='form5' action='http://mochi.test:1111/formtest.js'> 5 + <input type='text' name='uname' value=''> + <input type='password' name='pname' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + + +<!-- 1 username field, with a value set --> +<form id='form6' action='http://mochi.test:1111/formtest.js'> 6 + <input type='text' name='uname' value='someuser'> + <input type='password' name='pname' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + + + +<!-- +(The following forms have 2 potentially-matching logins, on is +password-only, the other is username+password) +--> + + + +<!-- 1 username field, with value set. Fill in the matching U+P login --> +<form id='form7' action='formtest.js'> 7 + <input type='text' name='uname' value='testuser'> + <input type='password' name='pname' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +<!-- 1 username field, with value set. Don't fill in U+P login--> +<form id='form8' action='formtest.js'> 8 + <input type='text' name='uname' value='someuser'> + <input type='password' name='pname' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + + + +<!-- 1 username field, too small for U+P login --> +<form id='form9' action='formtest.js'> 9 + <input type='text' name='uname' value='' maxlength="4"> + <input type='password' name='pname' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +<!-- 1 username field, too small for U+P login --> +<form id='form10' action='formtest.js'> 10 + <input type='text' name='uname' value='' maxlength="0"> + <input type='password' name='pname' value=''> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +<!-- 1 username field, too small for U+P login --> +<form id='form11' action='formtest.js'> 11 + <input type='text' name='uname' value=''> + <input type='password' name='pname' value='' maxlength="4"> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +<!-- 1 username field, too small for either login --> +<form id='form12' action='formtest.js'> 12 + <input type='text' name='uname' value=''> + <input type='password' name='pname' value='' maxlength="1"> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +<!-- 1 username field, too small for either login --> +<form id='form13' action='formtest.js'> 13 + <input type='text' name='uname' value=''> + <input type='password' name='pname' value='' maxlength="0"> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + + + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: password-only logins **/ +function startTest() { + + checkForm(1, "1234"); + checkForm(2, "1234", ""); + checkForm(3, "1234", "", ""); + checkUnmodifiedForm(4); + + checkForm(5, "", "1234"); + checkForm(6, "someuser", ""); + + checkForm(7, "testuser", "testpass"); + checkForm(8, "someuser", ""); + + checkForm(9, "", "1234"); + checkForm(10, "", "1234"); + checkForm(11, "", "1234"); + + checkUnmodifiedForm(12); + checkUnmodifiedForm(13); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> + diff --git a/toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html b/toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html new file mode 100644 index 000000000..ad4a41cdb --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html @@ -0,0 +1,145 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test bug 627616 related to proxy authentication</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + var Ci = SpecialPowers.Ci; + + function makeXHR(expectedStatus, expectedText, extra) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "authenticate.sjs?" + + "proxy_user=proxy_user&" + + "proxy_pass=proxy_pass&" + + "proxy_realm=proxy_realm&" + + "user=user1name&" + + "pass=user1pass&" + + "realm=mochirealm&" + + extra || ""); + xhr.onloadend = function() { + is(xhr.status, expectedStatus, "xhr.status"); + is(xhr.statusText, expectedText, "xhr.statusText"); + runNextTest(); + }; + return xhr; + } + + function testNonAnonymousCredentials() { + var xhr = makeXHR(200, "OK"); + xhr.send(); + } + + function testAnonymousCredentials() { + // Test that an anonymous request correctly performs proxy authentication + var xhr = makeXHR(401, "Authentication required"); + SpecialPowers.wrap(xhr).channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS; + xhr.send(); + } + + function testAnonymousNoAuth() { + // Next, test that an anonymous request still does not include any non-proxy + // authentication headers. + var xhr = makeXHR(200, "Authorization header not found", "anonymous=1"); + SpecialPowers.wrap(xhr).channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS; + xhr.send(); + } + + var gExpectedDialogs = 0; + var gCurrentTest; + function runNextTest() { + is(gExpectedDialogs, 0, "received expected number of auth dialogs"); + mm.sendAsyncMessage("prepareForNextTest"); + mm.addMessageListener("prepareForNextTestDone", function prepared(msg) { + mm.removeMessageListener("prepareForNextTestDone", prepared); + if (pendingTests.length > 0) { + ({expectedDialogs: gExpectedDialogs, + test: gCurrentTest} = pendingTests.shift()); + gCurrentTest.call(this); + } else { + mm.sendAsyncMessage("cleanup"); + mm.addMessageListener("cleanupDone", () => { + // mm.destroy() is called as a cleanup function by runInParent(), no + // need to do it here. + SimpleTest.finish(); + }); + } + }); + } + + var pendingTests = [{expectedDialogs: 2, test: testNonAnonymousCredentials}, + {expectedDialogs: 1, test: testAnonymousCredentials}, + {expectedDialogs: 0, test: testAnonymousNoAuth}]; + + let mm = runInParent(() => { + const { classes: parentCc, interfaces: parentCi, utils: parentCu } = Components; + + parentCu.import("resource://gre/modules/Services.jsm"); + parentCu.import("resource://gre/modules/NetUtil.jsm"); + parentCu.import("resource://gre/modules/Timer.jsm"); + parentCu.import("resource://gre/modules/XPCOMUtils.jsm"); + + let channel = NetUtil.newChannel({ + uri: "http://example.com", + loadUsingSystemPrincipal: true + }); + + let pps = parentCc["@mozilla.org/network/protocol-proxy-service;1"]. + getService(parentCi.nsIProtocolProxyService); + pps.asyncResolve(channel, 0, { + onProxyAvailable(req, uri, pi, status) { + let mozproxy = "moz-proxy://" + pi.host + ":" + pi.port; + let login = parentCc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(parentCi.nsILoginInfo); + login.init(mozproxy, null, "proxy_realm", "proxy_user", "proxy_pass", + "", ""); + Services.logins.addLogin(login); + + let login2 = parentCc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(parentCi.nsILoginInfo); + login2.init("http://mochi.test:8888", null, "mochirealm", "user1name", + "user1pass", "", ""); + Services.logins.addLogin(login2); + + sendAsyncMessage("setupDone"); + }, + QueryInterface: XPCOMUtils.generateQI([parentCi.nsIProtocolProxyCallback]), + }); + + addMessageListener("prepareForNextTest", message => { + parentCc["@mozilla.org/network/http-auth-manager;1"]. + getService(parentCi.nsIHttpAuthManager). + clearAll(); + sendAsyncMessage("prepareForNextTestDone"); + }); + + let dialogObserverTopic = "common-dialog-loaded"; + + function dialogObserver(subj, topic, data) { + subj.Dialog.ui.prompt.document.documentElement.acceptDialog(); + sendAsyncMessage("promptAccepted"); + } + + Services.obs.addObserver(dialogObserver, dialogObserverTopic, false); + + addMessageListener("cleanup", message => { + Services.obs.removeObserver(dialogObserver, dialogObserverTopic); + sendAsyncMessage("cleanupDone"); + }); + }); + + mm.addMessageListener("promptAccepted", msg => { + gExpectedDialogs--; + }); + mm.addMessageListener("setupDone", msg => { + runNextTest(); + }); +</script> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_bug_776171.html b/toolkit/components/passwordmgr/test/mochitest/test_bug_776171.html new file mode 100644 index 000000000..4ad08bee2 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_bug_776171.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=776171 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 776171 related to HTTP auth</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="startTest()"> +<script class="testbody" type="text/javascript"> + +/** + * This test checks we correctly ignore authentication entry + * for a subpath and use creds from the URL when provided when XHR + * is used with filled user name and password. + * + * 1. connect auth2/authenticate.sjs that expects user1:pass1 password + * 2. connect a dummy URL at the same path + * 3. connect authenticate.sjs that again expects user1:pass1 password + * in this case, however, we have an entry without an identity + * for this path (that is a parent for auth2 path in the first step) + */ + +SimpleTest.waitForExplicitFinish(); + +function doxhr(URL, user, pass, next) { + var xhr = new XMLHttpRequest(); + if (user && pass) + xhr.open("POST", URL, true, user, pass); + else + xhr.open("POST", URL, true); + xhr.onload = function() { + is(xhr.status, 200, "Got status 200"); + next(); + }; + xhr.onerror = function() { + ok(false, "request passed"); + finishTest(); + }; + xhr.send(); +} + +function startTest() { + doxhr("auth2/authenticate.sjs?user=user1&pass=pass1&realm=realm1", "user1", "pass1", function() { + doxhr("auth2", null, null, function() { + doxhr("authenticate.sjs?user=user1&pass=pass1&realm=realm1", "user1", "pass1", SimpleTest.finish); + }); + }); +} +</script> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_case_differences.html b/toolkit/components/passwordmgr/test/mochitest/test_case_differences.html new file mode 100644 index 000000000..316f59da7 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_case_differences.html @@ -0,0 +1,147 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test autocomplete due to multiple matching logins</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: autocomplete due to multiple matching logins + +<script> +runChecksAfterCommonInit(false); + +SpecialPowers.loadChromeScript(function addLogins() { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + // Create some logins just for this form, since we'll be deleting them. + var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, "init"); + + var login0 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "name", "pass", "uname", "pword"); + + var login1 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "Name", "Pass", "uname", "pword"); + + var login2 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "USER", "PASS", "uname", "pword"); + + try { + Services.logins.addLogin(login0); + Services.logins.addLogin(login1); + Services.logins.addLogin(login2); + } catch (e) { + assert.ok(false, "addLogin threw: " + e); + } +}); +</script> +<p id="display"></p> + +<!-- we presumably can't hide the content for this test. --> +<div id="content"> + + <!-- form1 tests multiple matching logins --> + <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: autocomplete due to multiple matching logins **/ + +var uname = $_(1, "uname"); +var pword = $_(1, "pword"); + +// Restore the form to the default state. +function restoreForm() { + uname.value = ""; + pword.value = ""; + uname.focus(); +} + +// Check for expected username/password in form. +function checkACForm(expectedUsername, expectedPassword) { + var formID = uname.parentNode.id; + is(uname.value, expectedUsername, "Checking " + formID + " username"); + is(pword.value, expectedPassword, "Checking " + formID + " password"); +} + +add_task(function* test_empty_first_entry() { + /* test 1 */ + // Make sure initial form is empty. + checkACForm("", ""); + // Trigger autocomplete popup + restoreForm(); + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is initially closed"); + let shownPromise = promiseACShown(); + doKey("down"); + let results = yield shownPromise; + popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected"); + checkArrayValues(results, ["name", "Name", "USER"], "initial"); + + // Check first entry + let index0Promise = notifySelectedIndex(0); + doKey("down"); + yield index0Promise; + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("name", "pass"); +}); + +add_task(function* test_empty_second_entry() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + doKey("down"); // first + doKey("down"); // second + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("Name", "Pass"); +}); + +add_task(function* test_empty_third_entry() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + doKey("down"); // first + doKey("down"); // second + doKey("down"); // third + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("USER", "PASS"); +}); + +add_task(function* test_preserve_matching_username_case() { + restoreForm(); + uname.value = "user"; + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check that we don't clobber user-entered text when tabbing away + // (even with no autocomplete entry selected) + doKey("tab"); + yield promiseFormsProcessed(); + checkACForm("user", "PASS"); +}); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_form_action_1.html b/toolkit/components/passwordmgr/test/mochitest/test_form_action_1.html new file mode 100644 index 000000000..430081b3a --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_form_action_1.html @@ -0,0 +1,137 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for considering form action</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: Bug 360493 +<script> +runChecksAfterCommonInit(() => startTest()); +</script> +<p id="display"></p> +<div id="content" style="display: none"> + + <!-- normal form with normal relative action. --> + <form id="form1" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- fully specify the action URL --> + <form id="form2" action="http://mochi.test:8888/tests/toolkit/components/passwordmgr/test/formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- fully specify the action URL, and change the path --> + <form id="form3" action="http://mochi.test:8888/zomg/wtf/bbq/passwordmgr/test/formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- fully specify the action URL, and change the path and filename --> + <form id="form4" action="http://mochi.test:8888/zomg/wtf/bbq/passwordmgr/test/not_a_test.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- specify the action URL relative to the current document--> + <form id="form5" action="./formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- specify the action URL relative to the current server --> + <form id="form6" action="/tests/toolkit/components/passwordmgr/test/formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Change the method from get to post --> + <form id="form7" action="formtest.js" method="POST"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Blank action URL specified --> + <form id="form8" action=""> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- |action| attribute entirely missing --> + <form id="form9" > + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- action url as javascript --> + <form id="form10" action="javascript:alert('this form is not submitted so this alert should not be invoked');"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- TODO: action=IP.ADDRESS instead of HOSTNAME? --> + <!-- TODO: test with |base href="http://othersite//"| ? --> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: 360493 (Cross-Site Forms + Password + Manager = Security Failure) **/ + +// This test is designed to make sure variations on the form's |action| +// and |method| continue to work with the fix for 360493. + +function startTest() { + for (var i = 1; i <= 9; i++) { + // Check form i + is($_(i, "uname").value, "testuser", "Checking for filled username " + i); + is($_(i, "pword").value, "testpass", "Checking for filled password " + i); + } + + // The login's formSubmitURL isn't "javascript:", so don't fill it in. + isnot($_(10, "uname"), "testuser", "Checking username w/ JS action URL"); + isnot($_(10, "pword"), "testpass", "Checking password w/ JS action URL"); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> + diff --git a/toolkit/components/passwordmgr/test/mochitest/test_form_action_2.html b/toolkit/components/passwordmgr/test/mochitest/test_form_action_2.html new file mode 100644 index 000000000..0f0056de0 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_form_action_2.html @@ -0,0 +1,170 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for considering form action</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: Bug 360493 +<script> +runChecksAfterCommonInit(() => startTest()); +</script> +<p id="display"></p> +<div id="content" style="display: none"> + + <!-- The tests in this page exercise things that shouldn't work. --> + + <!-- Change port # of action URL from 8888 to 7777 --> + <form id="form1" action="http://localhost:7777/tests/toolkit/components/passwordmgr/test/formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- No port # in action URL --> + <form id="form2" action="http://localhost/tests/toolkit/components/passwordmgr/test/formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Change protocol from http:// to ftp://, include the expected 8888 port # --> + <form id="form3" action="ftp://localhost:8888/tests/toolkit/components/passwordmgr/test/formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Change protocol from http:// to ftp://, no port # specified --> + <form id="form4" action="ftp://localhost/tests/toolkit/components/passwordmgr/test/formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Try a weird URL. --> + <form id="form5" action="about:blank"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Try a weird URL. (If the normal embedded action URL doesn't work, that should mean other URLs won't either) --> + <form id="form6" action="view-source:http://localhost:8888/tests/toolkit/components/passwordmgr/test/formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Try a weird URL. --> + <form id="form7" action="view-source:formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Action URL points to a different host (this is the archetypical exploit) --> + <form id="form8" action="http://www.cnn.com/"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Action URL points to a different host, user field prefilled --> + <form id="form9" action="http://www.cnn.com/"> + <input type="text" name="uname" value="testuser"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- Try wrapping a evil form around a good form, to see if we can confuse the parser. --> + <form id="form10-A" action="http://www.cnn.com/"> + <form id="form10-B" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit (inner)</button> + <button type="reset"> Reset (inner)</button> + </form> + <button type="submit" id="neutered_submit10">Submit (outer)</button> + <button type="reset">Reset (outer)</button> + </form> + + <!-- Try wrapping a good form around an evil form, to see if we can confuse the parser. --> + <form id="form11-A" action="formtest.js"> + <form id="form11-B" action="http://www.cnn.com/"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit (inner)</button> + <button type="reset"> Reset (inner)</button> + </form> + <button type="submit" id="neutered_submit11">Submit (outer)</button> + <button type="reset">Reset (outer)</button> + </form> + +<!-- TODO: probably should have some accounts which have no port # in the action url. JS too. And different host/proto. --> +<!-- TODO: www.site.com vs. site.com? --> +<!-- TODO: foo.site.com vs. bar.site.com? --> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: 360493 (Cross-Site Forms + Password Manager = Security Failure) **/ + +function startTest() { + for (var i = 1; i <= 8; i++) { + // Check form i + is($_(i, "uname").value, "", "Checking for unfilled username " + i); + is($_(i, "pword").value, "", "Checking for unfilled password " + i); + } + + is($_(9, "uname").value, "testuser", "Checking for unmodified username 9"); + is($_(9, "pword").value, "", "Checking for unfilled password 9"); + + is($_("10-A", "uname").value, "", "Checking for unfilled username 10A"); + is($_("10-A", "pword").value, "", "Checking for unfilled password 10A"); + + // The DOM indicates this form could be filled, as the evil inner form + // is discarded. And yet pwmgr seems not to fill it. Not sure why. + todo(false, "Mangled form combo not being filled when maybe it could be?"); + is($_("11-A", "uname").value, "testuser", "Checking filled username 11A"); + is($_("11-A", "pword").value, "testpass", "Checking filled password 11A"); + + // Verify this by making sure there are no extra forms in the document, and + // that the submit button for the neutered forms don't do anything. + // If the test finds extra forms the submit() causes the test to timeout, then + // there may be a security issue. + is(document.forms.length, 11, "Checking for unexpected forms"); + $("neutered_submit10").click(); + $("neutered_submit11").click(); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> + diff --git a/toolkit/components/passwordmgr/test/mochitest/test_form_action_javascript.html b/toolkit/components/passwordmgr/test/mochitest/test_form_action_javascript.html new file mode 100644 index 000000000..d37e92c40 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_form_action_javascript.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test forms with a JS submit action</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: form with JS submit action +<script> +runChecksAfterCommonInit(() => startTest()); + +runInParent(function setup() { + const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + let jslogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo); + jslogin.init("http://mochi.test:8888", "javascript:", null, + "jsuser", "jspass123", "uname", "pword"); + Services.logins.addLogin(jslogin); +}); + +/** Test for Login Manager: JS action URL **/ + +function startTest() { + checkForm(1, "jsuser", "jspass123"); + + SimpleTest.finish(); +} +</script> + +<p id="display"></p> + +<div id="content" style="display: none"> + + +<form id='form1' action='javascript:alert("never shows")'> 1 + <input name="uname"> + <input name="pword" type="password"> + + <button type='submit'>Submit</button> + <button type='reset'> Reset </button> +</form> + +</div> + +<pre id="test"></pre> +</body> +</html> + diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html new file mode 100644 index 000000000..6263c818d --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html @@ -0,0 +1,147 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Test autofilling of fields outside of a form</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="application/javascript;version=1.8"> +let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); + +document.addEventListener("DOMContentLoaded", () => { + document.getElementById("loginFrame").addEventListener("load", (evt) => { + // Tell the parent to setup test logins. + chromeScript.sendAsyncMessage("setupParent", { selfFilling: true }); + }); +}); + +let doneSetupPromise = new Promise(resolve => { + // When the setup is done, load a recipe for this test. + chromeScript.addMessageListener("doneSetup", function doneSetup() { + resolve(); + }); +}); + +add_task(function* setup() { + info("Waiting for loads and setup"); + yield doneSetupPromise; + + yield loadRecipes({ + siteRecipes: [{ + hosts: ["mochi.test:8888"], + usernameSelector: "input[name='recipeuname']", + passwordSelector: "input[name='recipepword']", + }], + }); +}); + + +const DEFAULT_ORIGIN = "http://mochi.test:8888"; +const TESTCASES = [ + { + // Inputs + document: `<input type=password>`, + + // Expected outputs + expectedInputValues: ["testpass"], + }, + { + document: `<input> + <input type=password>`, + expectedInputValues: ["testuser", "testpass"], + }, + { + document: `<input> + <input type=password> + <input type=password>`, + expectedInputValues: ["testuser", "testpass", ""], + }, + { + document: `<input> + <input type=password> + <input type=password> + <input type=password>`, + expectedInputValues: ["testuser", "testpass", "", ""], + }, + { + document: `<input> + <input type=password form="form1"> + <input type=password> + <form id="form1"> + <input> + <input type=password> + </form>`, + expectedFormCount: 2, + expectedInputValues: ["testuser", "testpass", "testpass", "", ""], + }, + { + document: `<!-- formless password field selector recipe test --> + <input> + <input type=password> + <input> + <input type=password name="recipepword">`, + expectedInputValues: ["", "", "testuser", "testpass"], + }, + { + document: `<!-- formless username and password field selector recipe test --> + <input name="recipeuname"> + <input> + <input type=password> + <input type=password name="recipepword">`, + expectedInputValues: ["testuser", "", "", "testpass"], + }, + { + document: `<!-- form and formless recipe field selector test --> + <input name="recipeuname"> + <input> + <input type=password form="form1"> <!-- not filled since recipe affects both FormLikes --> + <input type=password> + <input type=password name="recipepword"> + <form id="form1"> + <input> + <input type=password> + </form>`, + expectedFormCount: 2, + expectedInputValues: ["testuser", "", "", "", "testpass", "", ""], + }, +]; + +add_task(function* test() { + let loginFrame = document.getElementById("loginFrame"); + let frameDoc = loginFrame.contentWindow.document; + + for (let tc of TESTCASES) { + info("Starting testcase: " + JSON.stringify(tc)); + + let numFormLikesExpected = tc.expectedFormCount || 1; + + let processedFormPromise = promiseFormsProcessed(numFormLikesExpected); + + frameDoc.documentElement.innerHTML = tc.document; + info("waiting for " + numFormLikesExpected + " processed form(s)"); + yield processedFormPromise; + + let testInputs = frameDoc.documentElement.querySelectorAll("input"); + is(testInputs.length, tc.expectedInputValues.length, "Check number of inputs"); + for (let i = 0; i < tc.expectedInputValues.length; i++) { + let expectedValue = tc.expectedInputValues[i]; + is(testInputs[i].value, expectedValue, + "Check expected input value " + i + ": " + expectedValue); + } + } +}); + +</script> + +<p id="display"></p> + +<div id="content"> + <iframe id="loginFrame" src="http://mochi.test:8888/tests/toolkit/components/passwordmgr/test/blank.html"></iframe> +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html new file mode 100644 index 000000000..468da1e7f --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html @@ -0,0 +1,183 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Test capturing of fields outside of a form</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="pwmgr_common.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="application/javascript;version=1.8"> +const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerContent.jsm"); +const { LoginManagerContent, LoginFormFactory } = LMCBackstagePass; + +let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); + +let loadPromise = new Promise(resolve => { + document.addEventListener("DOMContentLoaded", () => { + document.getElementById("loginFrame").addEventListener("load", (evt) => { + resolve(); + }); + }); +}); + +add_task(function* setup() { + info("Waiting for page and frame loads"); + yield loadPromise; + + yield loadRecipes({ + siteRecipes: [{ + hosts: ["mochi.test:8888"], + usernameSelector: "input[name='recipeuname']", + passwordSelector: "input[name='recipepword']", + }], + }); +}); + +const DEFAULT_ORIGIN = "http://mochi.test:8888"; +const TESTCASES = [ + { + // Inputs + document: `<input type=password value="pass1">`, + inputIndexForFormLike: 0, + + // Expected outputs similar to RemoteLogins:onFormSubmit + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: null, + newPasswordFieldValue: "pass1", + oldPasswordFieldValue: null, + }, + { + document: `<input value="user1"> + <input type=password value="pass1">`, + inputIndexForFormLike: 0, + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass1", + oldPasswordFieldValue: null, + }, + { + document: `<input value="user1"> + <input type=password value="pass1">`, + inputIndexForFormLike: 1, + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass1", + oldPasswordFieldValue: null, + }, + { + document: `<input value="user1"> + <input type=password value="pass1"> + <input type=password value="pass2">`, + inputIndexForFormLike: 2, + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass2", + oldPasswordFieldValue: "pass1", + }, + { + document: `<input value="user1"> + <input type=password value="pass1"> + <input type=password value="pass2"> + <input type=password value="pass2">`, + inputIndexForFormLike: 3, + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass2", + oldPasswordFieldValue: "pass1", + }, + { + document: `<input value="user1"> + <input type=password value="user2" form="form1"> + <input type=password value="pass1"> + <form id="form1"> + <input value="user3"> + <input type=password value="pass2"> + </form>`, + inputIndexForFormLike: 2, + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass1", + oldPasswordFieldValue: null, + }, + { + document: `<!-- recipe field override --> + <input name="recipeuname" value="username from recipe"> + <input value="default field username"> + <input type=password value="pass1"> + <input name="recipepword" type=password value="pass2">`, + inputIndexForFormLike: 2, + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "username from recipe", + newPasswordFieldValue: "pass2", + oldPasswordFieldValue: null, + }, +]; + +function getSubmitMessage() { + info("getSubmitMessage"); + return new Promise((resolve, reject) => { + chromeScript.addMessageListener("formSubmissionProcessed", function processed(...args) { + info("got formSubmissionProcessed"); + chromeScript.removeMessageListener("formSubmissionProcessed", processed); + resolve(...args); + }); + }); +} + +add_task(function* test() { + let loginFrame = document.getElementById("loginFrame"); + let frameDoc = loginFrame.contentWindow.document; + + for (let tc of TESTCASES) { + info("Starting testcase: " + JSON.stringify(tc)); + frameDoc.documentElement.innerHTML = tc.document; + let inputForFormLike = frameDoc.querySelectorAll("input")[tc.inputIndexForFormLike]; + + let formLike = LoginFormFactory.createFromField(inputForFormLike); + + info("Calling _onFormSubmit with FormLike"); + let processedPromise = getSubmitMessage(); + LoginManagerContent._onFormSubmit(formLike); + + let submittedResult = yield processedPromise; + + // Check data sent via RemoteLogins:onFormSubmit + is(submittedResult.hostname, tc.hostname, "Check hostname"); + is(submittedResult.formSubmitURL, tc.formSubmitURL, "Check formSubmitURL"); + + if (tc.usernameFieldValue === null) { + is(submittedResult.usernameField, tc.usernameFieldValue, "Check usernameField"); + } else { + is(submittedResult.usernameField.value, tc.usernameFieldValue, "Check usernameField"); + } + + is(submittedResult.newPasswordField.value, tc.newPasswordFieldValue, "Check newPasswordFieldValue"); + + if (tc.oldPasswordFieldValue === null) { + is(submittedResult.oldPasswordField, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue"); + } else { + is(submittedResult.oldPasswordField.value, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue"); + } + } +}); + +</script> + +<p id="display"></p> + +<div id="content"> + <iframe id="loginFrame" src="http://mochi.test:8888/tests/toolkit/components/passwordmgr/test/blank.html"></iframe> +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html new file mode 100644 index 000000000..b07d0886c --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html @@ -0,0 +1,191 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Test capturing of fields outside of a form due to navigation</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="pwmgr_common.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="application/javascript;version=1.8"> +const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerContent.jsm"); +const { LoginManagerContent, LoginFormFactory } = LMCBackstagePass; + +let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); + +let loadPromise = new Promise(resolve => { + document.addEventListener("DOMContentLoaded", () => { + document.getElementById("loginFrame").addEventListener("load", (evt) => { + resolve(); + }); + }); +}); + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ + set: [ + ["signon.formlessCapture.enabled", true], + ], + }); + + info("Waiting for page and frame loads"); + yield loadPromise; + + yield loadRecipes({ + siteRecipes: [{ + hosts: ["test1.mochi.test:8888"], + usernameSelector: "input[name='recipeuname']", + passwordSelector: "input[name='recipepword']", + }], + }); +}); + +const DEFAULT_ORIGIN = "http://test1.mochi.test:8888"; +const SCRIPTS = { + PUSHSTATE: `history.pushState({}, "Pushed state", "?pushed");`, + WINDOW_LOCATION: `window.location = "data:text/html;charset=utf-8,window.location";`, +}; +const TESTCASES = [ + { + // Inputs + document: `<input type=password value="pass1">`, + + // Expected outputs similar to RemoteLogins:onFormSubmit + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: null, + newPasswordFieldValue: "pass1", + oldPasswordFieldValue: null, + }, + { + document: `<input value="user1"> + <input type=password value="pass1">`, + + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass1", + oldPasswordFieldValue: null, + }, + { + document: `<input value="user1"> + <input type=password value="pass1"> + <input type=password value="pass2">`, + + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass2", + oldPasswordFieldValue: "pass1", + }, + { + document: `<input value="user1"> + <input type=password value="pass1"> + <input type=password value="pass2"> + <input type=password value="pass2">`, + + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass2", + oldPasswordFieldValue: "pass1", + }, + { + document: `<input value="user1"> + <input type=password value="user2" form="form1"> + <input type=password value="pass1"> + <form id="form1"> + <input value="user3"> + <input type=password value="pass2"> + </form>`, + + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "user1", + newPasswordFieldValue: "pass1", + oldPasswordFieldValue: null, + }, + { + document: `<!-- recipe field override --> + <input name="recipeuname" value="username from recipe"> + <input value="default field username"> + <input type=password value="pass1"> + <input name="recipepword" type=password value="pass2">`, + + hostname: DEFAULT_ORIGIN, + formSubmitURL: DEFAULT_ORIGIN, + usernameFieldValue: "username from recipe", + newPasswordFieldValue: "pass2", + oldPasswordFieldValue: null, + }, +]; + +function getSubmitMessage() { + info("getSubmitMessage"); + return new Promise((resolve, reject) => { + chromeScript.addMessageListener("formSubmissionProcessed", function processed(...args) { + info("got formSubmissionProcessed"); + chromeScript.removeMessageListener("formSubmissionProcessed", processed); + resolve(...args); + }); + }); +} + +add_task(function* test() { + let loginFrame = document.getElementById("loginFrame"); + + for (let tc of TESTCASES) { + for (let scriptName of Object.keys(SCRIPTS)) { + info("Starting testcase with script " + scriptName + ": " + JSON.stringify(tc)); + let loadedPromise = new Promise((resolve) => { + loginFrame.addEventListener("load", function frameLoaded() { + loginFrame.removeEventListener("load", frameLoaded); + resolve(); + }); + }); + loginFrame.src = DEFAULT_ORIGIN + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html"; + yield loadedPromise; + + let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document; + frameDoc.documentElement.innerHTML = tc.document; + // Wait for the form to be processed before trying to submit. + yield promiseFormsProcessed(); + let processedPromise = getSubmitMessage(); + info("Running " + scriptName + " script to cause a submission"); + frameDoc.defaultView.eval(SCRIPTS[scriptName]); + + let submittedResult = yield processedPromise; + + // Check data sent via RemoteLogins:onFormSubmit + is(submittedResult.hostname, tc.hostname, "Check hostname"); + is(submittedResult.formSubmitURL, tc.formSubmitURL, "Check formSubmitURL"); + + if (tc.usernameFieldValue === null) { + is(submittedResult.usernameField, tc.usernameFieldValue, "Check usernameField"); + } else { + is(submittedResult.usernameField.value, tc.usernameFieldValue, "Check usernameField"); + } + + is(submittedResult.newPasswordField.value, tc.newPasswordFieldValue, "Check newPasswordFieldValue"); + + if (tc.oldPasswordFieldValue === null) { + is(submittedResult.oldPasswordField, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue"); + } else { + is(submittedResult.oldPasswordField.value, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue"); + } + } + } +}); + +</script> + +<p id="display"></p> + +<div id="content"> + <iframe id="loginFrame" src="http://test1.mochi.test:8888/tests/toolkit/components/passwordmgr/test/mochitest/blank.html"></iframe> +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html new file mode 100644 index 000000000..4283f128c --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Test no capturing of fields outside of a form due to navigation</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="pwmgr_common.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script type="application/javascript;version=1.8"> +const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerContent.jsm"); +const { LoginManagerContent, LoginFormFactory } = LMCBackstagePass; + +SimpleTest.requestFlakyTimeout("Testing that a message doesn't arrive"); + +let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); + +let loadPromise = new Promise(resolve => { + document.addEventListener("DOMContentLoaded", () => { + document.getElementById("loginFrame").addEventListener("load", (evt) => { + resolve(); + }); + }); +}); + +function submissionProcessed(...args) { + ok(false, "No formSubmissionProcessed should occur in this test"); + info("got: " + JSON.stringify(args)); +} + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({ + set: [ + ["signon.formlessCapture.enabled", true], + ], + }); + + info("Waiting for page and frame loads"); + yield loadPromise; + + chromeScript.addMessageListener("formSubmissionProcessed", submissionProcessed); + + SimpleTest.registerCleanupFunction(() => { + chromeScript.removeMessageListener("formSubmissionProcessed", submissionProcessed); + }); +}); + +const DEFAULT_ORIGIN = "http://test1.mochi.test:8888"; +const SCRIPTS = { + PUSHSTATE: `history.pushState({}, "Pushed state", "?pushed");`, + WINDOW_LOCATION: `window.location = "data:text/html;charset=utf-8,window.location";`, + WINDOW_LOCATION_RELOAD: `window.location.reload();`, + HISTORY_BACK: `history.back();`, + HISTORY_GO_MINUS1: `history.go(-1);`, +}; +const TESTCASES = [ + // Begin test cases that shouldn't trigger capture. + { + // For now we don't trigger upon navigation if <form> is used. + document: `<form><input type=password value="pass1"></form>`, + }, + { + // Empty password field + document: `<input type=password value="">`, + }, + { + // Test with an input that would normally be captured but with SCRIPTS that + // shouldn't trigger capture. + document: `<input type=password value="pass2">`, + wouldCapture: true, + }, +]; + +add_task(function* test() { + let loginFrame = document.getElementById("loginFrame"); + + for (let tc of TESTCASES) { + for (let scriptName of Object.keys(SCRIPTS)) { + if (tc.wouldCapture && ["PUSHSTATE", "WINDOW_LOCATION"].includes(scriptName)) { + // Don't run scripts that should actually capture for this testcase. + continue; + } + + info("Starting testcase with script " + scriptName + ": " + JSON.stringify(tc)); + let loadedPromise = new Promise((resolve) => { + loginFrame.addEventListener("load", function frameLoaded() { + loginFrame.removeEventListener("load", frameLoaded); + resolve(); + }); + }); + loginFrame.src = DEFAULT_ORIGIN + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html"; + yield loadedPromise; + + let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document; + frameDoc.documentElement.innerHTML = tc.document; + + // Wait for the form to be processed before trying to submit. + yield promiseFormsProcessed(); + + info("Running " + scriptName + " script to check for a submission"); + frameDoc.defaultView.eval(SCRIPTS[scriptName]); + + // Wait for 5000ms to see if the promise above resolves. + yield new Promise(resolve => setTimeout(resolve, 5000)); + ok(true, "Done waiting for captures"); + } + } +}); + +</script> + +<p id="display"></p> + +<div id="content"> + <iframe id="loginFrame" src="http://test1.mochi.test:8888/tests/toolkit/components/passwordmgr/test/mochitest/blank.html"></iframe> +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_input_events.html b/toolkit/components/passwordmgr/test/mochitest/test_input_events.html new file mode 100644 index 000000000..0e77956d8 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_input_events.html @@ -0,0 +1,96 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for input events in Login Manager</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="onNewEvent(event)"> +Login Manager test: input events should fire. + +<script> +runChecksAfterCommonInit(); + +SimpleTest.requestFlakyTimeout("untriaged"); + +/** Test for Login Manager: form fill, should get input events. **/ + +var usernameInputFired = false; +var passwordInputFired = false; +var usernameChangeFired = false; +var passwordChangeFired = false; +var onloadFired = false; + +function onNewEvent(e) { + info("Got " + e.type + " event."); + if (e.type == "load") { + onloadFired = true; + } else if (e.type == "input") { + if (e.target.name == "uname") { + is(e.target.value, "testuser", "Should get 'testuser' as username"); + ok(!usernameInputFired, "Should not have gotten an input event for the username field yet."); + usernameInputFired = true; + } else if (e.target.name == "pword") { + is(e.target.value, "testpass", "Should get 'testpass' as password"); + ok(!passwordInputFired, "Should not have gotten an input event for the password field yet."); + passwordInputFired = true; + } + } else if (e.type == "change") { + if (e.target.name == "uname") { + is(e.target.value, "testuser", "Should get 'testuser' as username"); + ok(usernameInputFired, "Should get input event before change event for username field."); + ok(!usernameChangeFired, "Should not have gotten a change event for the username field yet."); + usernameChangeFired = true; + } else if (e.target.name == "pword") { + is(e.target.value, "testpass", "Should get 'testpass' as password"); + ok(passwordInputFired, "Should get input event before change event for password field."); + ok(!passwordChangeFired, "Should not have gotten a change event for the password field yet."); + passwordChangeFired = true; + } + } + if (onloadFired && usernameInputFired && passwordInputFired && usernameChangeFired && passwordChangeFired) { + ok(true, "All events fired as expected, we're done."); + SimpleTest.finish(); + } +} + +SimpleTest.registerCleanupFunction(function cleanup() { + clearTimeout(timeout); + $_(1, "uname").removeAttribute("oninput"); + $_(1, "pword").removeAttribute("oninput"); + $_(1, "uname").removeAttribute("onchange"); + $_(1, "pword").removeAttribute("onchange"); + document.body.removeAttribute("onload"); +}); + +var timeout = setTimeout(function() { + ok(usernameInputFired, "Username input event should have fired by now."); + ok(passwordInputFired, "Password input event should have fired by now."); + ok(usernameChangeFired, "Username change event should have fired by now."); + ok(passwordChangeFired, "Password change event should have fired by now."); + ok(onloadFired, "Window load event should have fired by now."); + ok(false, "Not all events fired yet."); + SimpleTest.finish(); +}, 10000); + +</script> + +<p id="display"></p> + +<div id="content" style="display: none"> + + <form id="form1" action="formtest.js"> + <p>This is form 1.</p> + <input type="text" name="uname" oninput="onNewEvent(event)" onchange="onNewEvent(event)"> + <input type="password" name="pword" oninput="onNewEvent(event)" onchange="onNewEvent(event)"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_input_events_for_identical_values.html b/toolkit/components/passwordmgr/test/mochitest/test_input_events_for_identical_values.html new file mode 100644 index 000000000..d058a87f9 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_input_events_for_identical_values.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for input events in Login Manager when username/password are filled in already</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="onNewEvent(event)"> +Login Manager test: input events should fire. + +<script> +runChecksAfterCommonInit(); + +SimpleTest.requestFlakyTimeout("untriaged"); + +/** Test for Login Manager: form fill when form is already filled, should not get input events. **/ + +var onloadFired = false; + +function onNewEvent(e) { + console.error("Got " + e.type + " event."); + if (e.type == "load") { + onloadFired = true; + $_(1, "uname").focus(); + sendKey("Tab"); + } else { + ok(false, "Got an input event for " + e.target.name + " field, which shouldn't happen."); + } +} +</script> + +<p id="display"></p> + +<div id="content"> + + <form id="form1" action="formtest.js"> + <p>This is form 1.</p> + <input type="text" name="uname" oninput="onNewEvent(event)" value="testuser"> + <input type="password" name="pword" oninput="onNewEvent(event)" onfocus="setTimeout(function() { SimpleTest.finish() }, 1000);" value="testpass"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html b/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html new file mode 100644 index 000000000..c5d0a44fa --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html @@ -0,0 +1,861 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test insecure form field autocomplete</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<script> +var chromeScript = runChecksAfterCommonInit(); + +var setupScript = runInParent(function setup() { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + // Create some logins just for this form, since we'll be deleting them. + var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, "init"); + assert.ok(nsLoginInfo != null, "nsLoginInfo constructor"); + + // login0 has no username, so should be filtered out from the autocomplete list. + var login0 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "", "user0pass", "", "pword"); + + var login1 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "tempuser1", "temppass1", "uname", "pword"); + + var login2 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "testuser2", "testpass2", "uname", "pword"); + + var login3 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "testuser3", "testpass3", "uname", "pword"); + + var login4 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "zzzuser4", "zzzpass4", "uname", "pword"); + + // login 5 only used in the single-user forms + var login5 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete2", null, + "singleuser5", "singlepass5", "uname", "pword"); + + var login6A = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete3", null, + "form7user1", "form7pass1", "uname", "pword"); + var login6B = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete3", null, + "form7user2", "form7pass2", "uname", "pword"); + + var login7 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete4", null, + "form8user", "form8pass", "uname", "pword"); + + var login8A = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null, + "form9userAB", "form9pass", "uname", "pword"); + var login8B = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null, + "form9userAAB", "form9pass", "uname", "pword"); + var login8C = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete5", null, + "form9userAABzz", "form9pass", "uname", "pword"); + + var login10 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete7", null, + "testuser10", "testpass10", "uname", "pword"); + + + // try/catch in case someone runs the tests manually, twice. + try { + Services.logins.addLogin(login0); + Services.logins.addLogin(login1); + Services.logins.addLogin(login2); + Services.logins.addLogin(login3); + Services.logins.addLogin(login4); + Services.logins.addLogin(login5); + Services.logins.addLogin(login6A); + Services.logins.addLogin(login6B); + Services.logins.addLogin(login7); + Services.logins.addLogin(login8A); + Services.logins.addLogin(login8B); + // login8C is added later + Services.logins.addLogin(login10); + } catch (e) { + assert.ok(false, "addLogin threw: " + e); + } + + addMessageListener("addLogin", loginVariableName => { + let login = eval(loginVariableName); + assert.ok(!!login, "Login to add is defined: " + loginVariableName); + Services.logins.addLogin(login); + }); + addMessageListener("removeLogin", loginVariableName => { + let login = eval(loginVariableName); + assert.ok(!!login, "Login to delete is defined: " + loginVariableName); + Services.logins.removeLogin(login); + }); +}); +</script> +<p id="display"></p> + +<!-- we presumably can't hide the content for this test. --> +<div id="content"> + + <!-- form1 tests multiple matching logins --> + <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- other forms test single logins, with autocomplete=off set --> + <form id="form2" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword" autocomplete="off"> + <button type="submit">Submit</button> + </form> + + <form id="form3" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname" autocomplete="off"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form4" action="http://autocomplete2" onsubmit="return false;" autocomplete="off"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form5" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname" autocomplete="off"> + <input type="password" name="pword" autocomplete="off"> + <button type="submit">Submit</button> + </form> + + <!-- control --> + <form id="form6" action="http://autocomplete2" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- This form will be manipulated to insert a different username field. --> + <form id="form7" action="http://autocomplete3" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- test for no autofill after onblur with blank username --> + <form id="form8" action="http://autocomplete4" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- test autocomplete dropdown --> + <form id="form9" action="http://autocomplete5" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- test for onUsernameInput recipe testing --> + <form id="form11" action="http://autocomplete7" onsubmit="return false;"> + <input type="text" name="1"> + <input type="text" name="2"> + <button type="submit">Submit</button> + </form> + + <!-- tests <form>-less autocomplete --> + <div id="form12"> + <input type="text" name="uname" id="uname"> + <input type="password" name="pword" id="pword"> + <button type="submit">Submit</button> + </div> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: multiple login autocomplete. **/ + +var uname = $_(1, "uname"); +var pword = $_(1, "pword"); +const shiftModifier = SpecialPowers.Ci.nsIDOMEvent.SHIFT_MASK; + +// Restore the form to the default state. +function restoreForm() { + uname.value = ""; + pword.value = ""; + uname.focus(); +} + +// Check for expected username/password in form. +function checkACForm(expectedUsername, expectedPassword) { + var formID = uname.parentNode.id; + is(uname.value, expectedUsername, "Checking " + formID + " username is: " + expectedUsername); + is(pword.value, expectedPassword, "Checking " + formID + " password is: " + expectedPassword); +} + +function sendFakeAutocompleteEvent(element) { + var acEvent = document.createEvent("HTMLEvents"); + acEvent.initEvent("DOMAutoComplete", true, false); + element.dispatchEvent(acEvent); +} + +function spinEventLoop() { + return Promise.resolve(); +} + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({"set": [["security.insecure_field_warning.contextual.enabled", true], + ["signon.autofillForms.http", true]]}); + listenForUnexpectedPopupShown(); +}); + +add_task(function* test_form1_initial_empty() { + yield SimpleTest.promiseFocus(window); + + // Make sure initial form is empty. + checkACForm("", ""); + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is initially closed"); +}); + +add_task(function* test_form1_warning_entry() { + yield SimpleTest.promiseFocus(window); + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + let results = yield shownPromise; + + let popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected upon opening"); + + let expectedMenuItems = ["This connection is not secure. Logins entered here could be compromised. Learn More", + "tempuser1", + "testuser2", + "testuser3", + "zzzuser4"]; + checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly."); + + doKey("down"); // select insecure warning + checkACForm("", ""); // value shouldn't update just by selecting + doKey("return"); // not "enter"! + yield spinEventLoop(); // let focus happen + checkACForm("", ""); +}); + +add_task(function* test_form1_first_entry() { + yield SimpleTest.promiseFocus(window); + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + let popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected upon opening"); + + doKey("down"); // skip insecure warning + doKey("down"); // first + checkACForm("", ""); // value shouldn't update just by selecting + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("tempuser1", "temppass1"); +}); + +add_task(function* test_form1_second_entry() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + doKey("down"); // first + doKey("down"); // second + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_third_entry() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + doKey("down"); // first + doKey("down"); // second + doKey("down"); // third + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser3", "testpass3"); +}); + +add_task(function* test_form1_fourth_entry() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + doKey("down"); // first + doKey("down"); // second + doKey("down"); // third + doKey("down"); // fourth + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_wraparound_first_entry() { + // Trigger autocomplete popup + restoreForm(); + yield spinEventLoop(); // let focus happen + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + doKey("down"); // first + doKey("down"); // second + doKey("down"); // third + doKey("down"); // fourth + doKey("down"); // deselects + doKey("down"); // skip insecure warning + doKey("down"); // first + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("tempuser1", "temppass1"); +}); + +add_task(function* test_form1_wraparound_up_last_entry() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("up"); // last (fourth) + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_wraparound_down_up_up() { + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // select first entry + doKey("up"); // selects nothing! + doKey("up"); // select last entry + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_wraparound_up_last() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); + doKey("up"); // deselects + doKey("up"); // last entry + doKey("up"); + doKey("up"); + doKey("up"); // skip insecure warning + doKey("up"); // first entry + doKey("up"); // deselects + doKey("up"); // last entry + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_fill_username_without_autofill_right() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Set first entry w/o triggering autocomplete + doKey("down"); // skip insecure warning + doKey("down"); // first + doKey("right"); + yield spinEventLoop(); + checkACForm("tempuser1", ""); // empty password +}); + +add_task(function* test_form1_fill_username_without_autofill_left() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Set first entry w/o triggering autocomplete + doKey("down"); // skip insecure warning + doKey("down"); // first + doKey("left"); + checkACForm("tempuser1", ""); // empty password +}); + +add_task(function* test_form1_pageup_first() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // Check first entry (page up) + doKey("down"); // first + doKey("down"); // second + doKey("page_up"); // first + doKey("down"); // skip insecure warning + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("tempuser1", "temppass1"); +}); + +add_task(function* test_form1_pagedown_last() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // test 13 + // Check last entry (page down) + doKey("down"); // first + doKey("page_down"); // last + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_untrusted_event() { + restoreForm(); + yield spinEventLoop(); + + // Send a fake (untrusted) event. + checkACForm("", ""); + uname.value = "zzzuser4"; + sendFakeAutocompleteEvent(uname); + yield spinEventLoop(); + checkACForm("zzzuser4", ""); +}); + +add_task(function* test_form1_delete() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + // XXX tried sending character "t" before/during dropdown to test + // filtering, but had no luck. Seemed like the character was getting lost. + // Setting uname.value didn't seem to work either. This works with a human + // driver, so I'm not sure what's up. + + doKey("down"); // skip insecure warning + // Delete the first entry (of 4), "tempuser1" + doKey("down"); + var numLogins; + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 5, "Correct number of logins before deleting one"); + + let countChangedPromise = notifyMenuChanged(4); + var deletionPromise = promiseStorageChanged(["removeLogin"]); + // 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. + doKey("delete", shiftModifier); + yield deletionPromise; + + checkACForm("", ""); + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 4, "Correct number of logins after deleting one"); + yield countChangedPromise; + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_first_after_deletion() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Check the new first entry (of 3) + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_delete_second() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Delete the second entry (of 3), "testuser3" + doKey("down"); + doKey("down"); + doKey("delete", shiftModifier); + checkACForm("", ""); + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 3, "Correct number of logins after deleting one"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("zzzuser4", "zzzpass4"); +}); + +add_task(function* test_form1_first_after_deletion2() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Check the new first entry (of 2) + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_delete_last() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // test 54 + // Delete the last entry (of 2), "zzzuser4" + doKey("down"); + doKey("down"); + doKey("delete", shiftModifier); + checkACForm("", ""); + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 2, "Correct number of logins after deleting one"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_first_after_3_deletions() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Check the only remaining entry + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("testuser2", "testpass2"); +}); + +add_task(function* test_form1_check_only_entry_remaining() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // test 56 + // Delete the only remaining entry, "testuser2" + doKey("down"); + doKey("delete", shiftModifier); + checkACForm("", ""); + numLogins = LoginManager.countLogins("http://mochi.test:8888", "http://autocomplete:8888", null); + is(numLogins, 1, "Correct number of logins after deleting one"); + + // remove the login that's not shown in the list. + setupScript.sendSyncMessage("removeLogin", "login0"); +}); + +// Tests for single-user forms for ignoring autocomplete=off +add_task(function* test_form2() { + // Turn our attention to form2 + uname = $_(2, "uname"); + pword = $_(2, "pword"); + checkACForm("singleuser5", "singlepass5"); + + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form3() { + uname = $_(3, "uname"); + pword = $_(3, "pword"); + checkACForm("singleuser5", "singlepass5"); + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form4() { + uname = $_(4, "uname"); + pword = $_(4, "pword"); + checkACForm("singleuser5", "singlepass5"); + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form5() { + uname = $_(5, "uname"); + pword = $_(5, "pword"); + checkACForm("singleuser5", "singlepass5"); + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form6() { + // (this is a control, w/o autocomplete=off, to ensure the login + // that was being suppressed would have been filled in otherwise) + uname = $_(6, "uname"); + pword = $_(6, "pword"); + checkACForm("singleuser5", "singlepass5"); +}); + +add_task(function* test_form6_changeUsername() { + // Test that the password field remains filled in after changing + // the username. + uname.focus(); + doKey("right"); + sendChar("X"); + // Trigger the 'blur' event on uname + pword.focus(); + yield spinEventLoop(); + checkACForm("singleuser5X", "singlepass5"); + + setupScript.sendSyncMessage("removeLogin", "login5"); +}); + +add_task(function* test_form7() { + uname = $_(7, "uname"); + pword = $_(7, "pword"); + checkACForm("", ""); + + // Insert a new username field into the form. We'll then make sure + // that invoking the autocomplete doesn't try to fill the form. + var newField = document.createElement("input"); + newField.setAttribute("type", "text"); + newField.setAttribute("name", "uname2"); + pword.parentNode.insertBefore(newField, pword); + is($_(7, "uname2").value, "", "Verifying empty uname2"); + + // Delete login6B. It was created just to prevent filling in a login + // automatically, removing it makes it more likely that we'll catch a + // future regression with form filling here. + setupScript.sendSyncMessage("removeLogin", "login6B"); +}); + +add_task(function* test_form7_2() { + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Check first entry + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + // The form changes, so we expect the old username field to get the + // selected autocomplete value, but neither the new username field nor + // the password field should have any values filled in. + yield spinEventLoop(); + checkACForm("form7user1", ""); + is($_(7, "uname2").value, "", "Verifying empty uname2"); + restoreForm(); // clear field, so reloading test doesn't fail + + setupScript.sendSyncMessage("removeLogin", "login6A"); +}); + +add_task(function* test_form8() { + uname = $_(8, "uname"); + pword = $_(8, "pword"); + checkACForm("form8user", "form8pass"); + restoreForm(); +}); + +add_task(function* test_form8_blur() { + checkACForm("", ""); + // Focus the previous form to trigger a blur. + $_(7, "uname").focus(); +}); + +add_task(function* test_form8_2() { + checkACForm("", ""); + restoreForm(); +}); + +add_task(function* test_form8_3() { + checkACForm("", ""); + setupScript.sendSyncMessage("removeLogin", "login7"); +}); + +add_task(function* test_form9_filtering() { + // Turn our attention to form9 to test the dropdown - bug 497541 + uname = $_(9, "uname"); + pword = $_(9, "pword"); + uname.focus(); + let shownPromise = promiseACShown(); + sendString("form9userAB"); + yield shownPromise; + + checkACForm("form9userAB", ""); + uname.focus(); + doKey("left"); + shownPromise = promiseACShown(); + sendChar("A"); + let results = yield shownPromise; + + checkACForm("form9userAAB", ""); + checkArrayValues(results, ["This connection is not secure. Logins entered here could be compromised. Learn More", "form9userAAB"], + "Check dropdown is updated after inserting 'A'"); + doKey("down"); // skip insecure warning + doKey("down"); + doKey("return"); + yield promiseFormsProcessed(); + checkACForm("form9userAAB", "form9pass"); +}); + +add_task(function* test_form9_autocomplete_cache() { + // Note that this addLogin call will only be seen by the autocomplete + // attempt for the sendChar if we do not successfully cache the + // autocomplete results. + setupScript.sendSyncMessage("addLogin", "login8C"); + uname.focus(); + let promise0 = notifyMenuChanged(1); + let shownPromise = promiseACShown(); + sendChar("z"); + yield promise0; + yield shownPromise; + let popupState = yield getPopupState(); + is(popupState.open, true, "Check popup should open"); + + // check that empty results are cached - bug 496466 + promise0 = notifyMenuChanged(1); + sendChar("z"); + yield promise0; + popupState = yield getPopupState(); + is(popupState.open, true, "Check popup stays opened due to cached empty result"); +}); + +add_task(function* test_form11_recipes() { + yield loadRecipes({ + siteRecipes: [{ + "hosts": ["mochi.test:8888"], + "usernameSelector": "input[name='1']", + "passwordSelector": "input[name='2']" + }], + }); + uname = $_(11, "1"); + pword = $_(11, "2"); + + // First test DOMAutocomplete + // Switch the password field to type=password so _fillForm marks the username + // field for autocomplete. + pword.type = "password"; + yield promiseFormsProcessed(); + restoreForm(); + checkACForm("", ""); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + doKey("down"); + checkACForm("", ""); // value shouldn't update + doKey("return"); // not "enter"! + yield promiseFormsProcessed(); + checkACForm("testuser10", "testpass10"); + + // Now test recipes with blur on the username field. + restoreForm(); + checkACForm("", ""); + uname.value = "testuser10"; + checkACForm("testuser10", ""); + doKey("tab"); + yield promiseFormsProcessed(); + checkACForm("testuser10", "testpass10"); + yield resetRecipes(); +}); + +add_task(function* test_form12_formless() { + // Test form-less autocomplete + uname = $_(12, "uname"); + pword = $_(12, "pword"); + restoreForm(); + checkACForm("", ""); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + doKey("down"); // skip insecure warning + // Trigger autocomplete + doKey("down"); + checkACForm("", ""); // value shouldn't update + let processedPromise = promiseFormsProcessed(); + doKey("return"); // not "enter"! + yield processedPromise; + checkACForm("testuser", "testpass"); +}); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_no_saved_login.html b/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_no_saved_login.html new file mode 100644 index 000000000..c3a894958 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_no_saved_login.html @@ -0,0 +1,103 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic login, contextual inscure password warning without saved logins</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: contextual inscure password warning without saved logins + +<script> +let chromeScript = runChecksAfterCommonInit(); +</script> +<p id="display"></p> + +<!-- we presumably can't hide the content for this test. --> +<div id="content"> + + <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: contextual inscure password warning without saved logins. **/ + +// Set to pref before the document loads. +SpecialPowers.setBoolPref( + "security.insecure_field_warning.contextual.enabled", true); + +SimpleTest.registerCleanupFunction(() => { + SpecialPowers.clearUserPref( + "security.insecure_field_warning.contextual.enabled"); +}); + +let uname = $_(1, "uname"); +let pword = $_(1, "pword"); +const shiftModifier = SpecialPowers.Ci.nsIDOMEvent.SHIFT_MASK; + +// Restore the form to the default state. +function restoreForm() { + uname.value = ""; + pword.value = ""; + uname.focus(); +} + +// Check for expected username/password in form. +function checkACForm(expectedUsername, expectedPassword) { + let formID = uname.parentNode.id; + is(uname.value, expectedUsername, "Checking " + formID + " username is: " + expectedUsername); + is(pword.value, expectedPassword, "Checking " + formID + " password is: " + expectedPassword); +} + +function spinEventLoop() { + return Promise.resolve(); +} + +add_task(function* setup() { + listenForUnexpectedPopupShown(); +}); + +add_task(function* test_form1_initial_empty() { + yield SimpleTest.promiseFocus(window); + + // Make sure initial form is empty. + checkACForm("", ""); + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is initially closed"); +}); + +add_task(function* test_form1_warning_entry() { + yield SimpleTest.promiseFocus(window); + // Trigger autocomplete popup + restoreForm(); + let shownPromise = promiseACShown(); + doKey("down"); // open + yield shownPromise; + + let popupState = yield getPopupState(); + is(popupState.open, true, "Check popup is opened"); + is(popupState.selectedIndex, -1, "Check no entries are selected upon opening"); + + doKey("down"); // select insecure warning + checkACForm("", ""); // value shouldn't update just by selecting + doKey("return"); // not "enter"! + yield spinEventLoop(); // let focus happen + checkACForm("", ""); +}); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_maxlength.html b/toolkit/components/passwordmgr/test/mochitest/test_maxlength.html new file mode 100644 index 000000000..2b6da33ec --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_maxlength.html @@ -0,0 +1,137 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test for maxlength attributes</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: Bug 391514 +<script> +runChecksAfterCommonInit(() => startTest()); +</script> +<p id="display"></p> +<div id="content" style="display: none"> + <!-- normal form. --> + <form id="form1" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- limited username --> + <form id="form2" action="formtest.js"> + <input type="text" name="uname" maxlength="4"> + <input type="password" name="pword"> + </form> + + <!-- limited password --> + <form id="form3" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword" maxlength="4"> + </form> + + <!-- limited username and password --> + <form id="form4" action="formtest.js"> + <input type="text" name="uname" maxlength="4"> + <input type="password" name="pword" maxlength="4"> + </form> + + + <!-- limited username --> + <form id="form5" action="formtest.js"> + <input type="text" name="uname" maxlength="0"> + <input type="password" name="pword"> + </form> + + <!-- limited password --> + <form id="form6" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword" maxlength="0"> + </form> + + <!-- limited username and password --> + <form id="form7" action="formtest.js"> + <input type="text" name="uname" maxlength="0"> + <input type="password" name="pword" maxlength="0"> + </form> + + + <!-- limited, but ok, username --> + <form id="form8" action="formtest.js"> + <input type="text" name="uname" maxlength="999"> + <input type="password" name="pword"> + </form> + + <!-- limited, but ok, password --> + <form id="form9" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword" maxlength="999"> + </form> + + <!-- limited, but ok, username and password --> + <form id="form10" action="formtest.js"> + <input type="text" name="uname" maxlength="999"> + <input type="password" name="pword" maxlength="999"> + </form> + + + <!-- limited, but ok, username --> + <!-- (note that filled values are exactly 8 characters) --> + <form id="form11" action="formtest.js"> + <input type="text" name="uname" maxlength="8"> + <input type="password" name="pword"> + </form> + + <!-- limited, but ok, password --> + <!-- (note that filled values are exactly 8 characters) --> + <form id="form12" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword" maxlength="8"> + </form> + + <!-- limited, but ok, username and password --> + <!-- (note that filled values are exactly 8 characters) --> + <form id="form13" action="formtest.js"> + <input type="text" name="uname" maxlength="8"> + <input type="password" name="pword" maxlength="8"> + </form> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/* Test for Login Manager: 391514 (Login Manager gets confused with + * password/PIN on usaa.com) + */ + +function startTest() { + var i; + + is($_(1, "uname").value, "testuser", "Checking for filled username 1"); + is($_(1, "pword").value, "testpass", "Checking for filled password 1"); + + for (i = 2; i < 8; i++) { + is($_(i, "uname").value, "", "Checking for unfilled username " + i); + is($_(i, "pword").value, "", "Checking for unfilled password " + i); + } + + for (i = 8; i < 14; i++) { + is($_(i, "uname").value, "testuser", "Checking for filled username " + i); + is($_(i, "pword").value, "testpass", "Checking for filled password " + i); + } + + // Note that tests 11-13 are limited to exactly the expected value. + // Assert this lest someone change the login we're testing with. + is($_(11, "uname").value.length, 8, "asserting test assumption is valid."); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_password_field_autocomplete.html b/toolkit/components/passwordmgr/test/mochitest/test_password_field_autocomplete.html new file mode 100644 index 000000000..443c8a5e9 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_password_field_autocomplete.html @@ -0,0 +1,291 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test basic login autocomplete</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: multiple login autocomplete + +<script> +var chromeScript = runChecksAfterCommonInit(); + +var setupScript = runInParent(function setup() { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + // Create some logins just for this form, since we'll be deleting them. + var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1", + Ci.nsILoginInfo, "init"); + assert.ok(nsLoginInfo != null, "nsLoginInfo constructor"); + + // login0 has no username, so should be filtered out from the autocomplete list. + var login0 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "", "user0pass", "", "pword"); + + var login1 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "tempuser1", "temppass1", "uname", "pword"); + + var login2 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "testuser2", "testpass2", "uname", "pword"); + + var login3 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "testuser3", "testpass3", "uname", "pword"); + + var login4 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null, + "zzzuser4", "zzzpass4", "uname", "pword"); + + + // try/catch in case someone runs the tests manually, twice. + try { + Services.logins.addLogin(login0); + Services.logins.addLogin(login1); + Services.logins.addLogin(login2); + Services.logins.addLogin(login3); + Services.logins.addLogin(login4); + } catch (e) { + assert.ok(false, "addLogin threw: " + e); + } + + addMessageListener("addLogin", loginVariableName => { + let login = eval(loginVariableName); + assert.ok(!!login, "Login to add is defined: " + loginVariableName); + Services.logins.addLogin(login); + }); + addMessageListener("removeLogin", loginVariableName => { + let login = eval(loginVariableName); + assert.ok(!!login, "Login to delete is defined: " + loginVariableName); + Services.logins.removeLogin(login); + }); +}); +</script> +<p id="display"></p> + +<!-- we presumably can't hide the content for this test. --> +<div id="content"> + + <!-- form1 tests multiple matching logins --> + <form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <form id="form2" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword" readonly="true"> + <button type="submit">Submit</button> + </form> + + <form id="form3" action="http://autocomplete:8888/formtest.js" onsubmit="return false;"> + <input type="text" name="uname"> + <input type="password" name="pword" disabled="true"> + <button type="submit">Submit</button> + </form> + +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: multiple login autocomplete. **/ + +var uname = $_(1, "uname"); +var pword = $_(1, "pword"); +const shiftModifier = SpecialPowers.Ci.nsIDOMEvent.SHIFT_MASK; + +// Restore the form to the default state. +function* reinitializeForm(index) { + // Using innerHTML is for creating the autocomplete popup again, so the + // preference value will be applied to the constructor of + // UserAutoCompleteResult. + let form = document.getElementById("form" + index); + let temp = form.innerHTML; + form.innerHTML = ""; + form.innerHTML = temp; + + yield new Promise(resolve => { + let observer = SpecialPowers.wrapCallback(() => { + SpecialPowers.removeObserver(observer, "passwordmgr-processed-form"); + resolve(); + }); + SpecialPowers.addObserver(observer, "passwordmgr-processed-form", false); + }); + + yield SimpleTest.promiseFocus(window); + + uname = $_(index, "uname"); + pword = $_(index, "pword"); + uname.value = ""; + pword.value = ""; + pword.focus(); +} + +function generateDateString(date) { + let dateAndTimeFormatter = new Intl.DateTimeFormat(undefined, + { day: "numeric", month: "short", year: "numeric" }); + return dateAndTimeFormatter.format(date); +} + +const DATE_NOW_STRING = generateDateString(new Date()); + +// Check for expected username/password in form. +function checkACFormPasswordField(expectedPassword) { + var formID = uname.parentNode.id; + is(pword.value, expectedPassword, "Checking " + formID + " password is: " + expectedPassword); +} + +function spinEventLoop() { + return Promise.resolve(); +} + +add_task(function* setup() { + listenForUnexpectedPopupShown(); +}); + +add_task(function* test_form1_initial_empty() { + yield SimpleTest.promiseFocus(window); + + // Make sure initial form is empty. + checkACFormPasswordField(""); + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is initially closed"); +}); + +add_task(function* test_form2_password_readonly() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["security.insecure_field_warning.contextual.enabled", true], + ["signon.autofillForms.http", true] + ]}); + yield reinitializeForm(2); + + // Trigger autocomplete popup + doKey("down"); // open + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is closed for a readonly field."); +}); + +add_task(function* test_form3_password_disabled() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["security.insecure_field_warning.contextual.enabled", true], + ["signon.autofillForms.http", true] + ]}); + yield reinitializeForm(3); + + // Trigger autocomplete popup + doKey("down"); // open + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is closed for a disabled field."); +}); + +add_task(function* test_form1_enabledInsecureFieldWarning_enabledInsecureAutoFillForm() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["security.insecure_field_warning.contextual.enabled", true], + ["signon.autofillForms.http", true] + ]}); + yield reinitializeForm(1); + // Trigger autocomplete popup + let shownPromise = promiseACShown(); + doKey("down"); // open + let results = yield shownPromise; + + let popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected upon opening"); + + let expectedMenuItems = ["This connection is not secure. Logins entered here could be compromised. Learn More", + "No username (" + DATE_NOW_STRING + ")", + "tempuser1", + "testuser2", + "testuser3", + "zzzuser4"]; + checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly."); + + doKey("down"); // select insecure warning + checkACFormPasswordField(""); // value shouldn't update just by selecting + doKey("return"); // not "enter"! + yield spinEventLoop(); // let focus happen + checkACFormPasswordField(""); +}); + +add_task(function* test_form1_disabledInsecureFieldWarning_enabledInsecureAutoFillForm() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["security.insecure_field_warning.contextual.enabled", false], + ["signon.autofillForms.http", true] + ]}); + yield reinitializeForm(1); + + // Trigger autocomplete popup + let shownPromise = promiseACShown(); + doKey("down"); // open + let results = yield shownPromise; + + let popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected upon opening"); + + let expectedMenuItems = ["No username (" + DATE_NOW_STRING + ")", + "tempuser1", + "testuser2", + "testuser3", + "zzzuser4"]; + checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly."); + + doKey("down"); // select first item + checkACFormPasswordField(""); // value shouldn't update just by selecting + doKey("return"); // not "enter"! + yield spinEventLoop(); // let focus happen + checkACFormPasswordField("user0pass"); +}); + +add_task(function* test_form1_enabledInsecureFieldWarning_disabledInsecureAutoFillForm() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["security.insecure_field_warning.contextual.enabled", true], + ["signon.autofillForms.http", false] + ]}); + yield reinitializeForm(1); + + // Trigger autocomplete popup + let shownPromise = promiseACShown(); + doKey("down"); // open + let results = yield shownPromise; + + let popupState = yield getPopupState(); + is(popupState.selectedIndex, -1, "Check no entries are selected upon opening"); + + let expectedMenuItems = ["This connection is not secure. Logins entered here could be compromised. Learn More", + "No username (" + DATE_NOW_STRING + ")", + "tempuser1", + "testuser2", + "testuser3", + "zzzuser4"]; + checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly."); + + doKey("down"); // select insecure warning + checkACFormPasswordField(""); // value shouldn't update just by selecting + doKey("return"); // not "enter"! + yield spinEventLoop(); // let focus happen + checkACFormPasswordField(""); +}); + +add_task(function* test_form1_disabledInsecureFieldWarning_disabledInsecureAutoFillForm() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["security.insecure_field_warning.contextual.enabled", false], + ["signon.autofillForms.http", false] + ]}); + yield reinitializeForm(1); + + // Trigger autocomplete popup + doKey("down"); // open + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is closed with no AutoFillForms."); +}); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html b/toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html new file mode 100644 index 000000000..e107cebe6 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html @@ -0,0 +1,122 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test that passwords only get filled in type=password</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +Login Manager test: Bug 242956 +<script> +runChecksAfterCommonInit(() => startTest()); +</script> +<p id="display"></p> +<div id="content" style="display: none"> + <!-- pword is not a type=password input --> + <form id="form1" action="formtest.js"> + <input type="text" name="uname"> + <input type="text" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- uname is not a type=text input --> + <form id="form2" action="formtest.js"> + <input type="password" name="uname"> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- two "pword" inputs, (text + password) --> + <form id="form3" action="formtest.js"> + <input type="text" name="uname"> + <input type="text" name="pword"> + <input type="password" name="qword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- same thing, different order --> + <form id="form4" action="formtest.js"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <input type="text" name="qword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- uname is not a type=text input (try a checkbox just for variety) --> + <form id="form5" action="formtest.js"> + <input type="checkbox" name="uname" value=""> + <input type="password" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- pword is not a type=password input (try a checkbox just for variety) --> + <form id="form6" action="formtest.js"> + <input type="text" name="uname"> + <input type="checkbox" name="pword" value=""> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + <!-- pword is not a type=password input --> + <form id="form7" action="formtest.js"> + <input type="text" name="uname" value="testuser"> + <input type="text" name="pword"> + + <button type="submit">Submit</button> + <button type="reset"> Reset </button> + </form> + + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Login Manager: 242956 (Stored password is inserted into a + readable text input on a second page) **/ + +// Make sure that pwmgr only puts passwords into type=password <input>s. +// Might as well test the converse, too (username in password field). + +function startTest() { + is($_(1, "uname").value, "", "Checking for unfilled username 1"); + is($_(1, "pword").value, "", "Checking for unfilled password 1"); + + is($_(2, "uname").value, "testpass", "Checking for password not username 2"); + is($_(2, "pword").value, "", "Checking for unfilled password 2"); + + is($_(3, "uname").value, "", "Checking for unfilled username 3"); + is($_(3, "pword").value, "testuser", "Checking for unfilled password 3"); + is($_(3, "qword").value, "testpass", "Checking for unfilled qassword 3"); + + is($_(4, "uname").value, "testuser", "Checking for password not username 4"); + is($_(4, "pword").value, "testpass", "Checking for unfilled password 4"); + is($_(4, "qword").value, "", "Checking for unfilled qassword 4"); + + is($_(5, "uname").value, "", "Checking for unfilled username 5"); + is($_(5, "pword").value, "testpass", "Checking for filled password 5"); + + is($_(6, "uname").value, "", "Checking for unfilled username 6"); + is($_(6, "pword").value, "", "Checking for unfilled password 6"); + + is($_(7, "uname").value, "testuser", "Checking for unmodified username 7"); + is($_(7, "pword").value, "", "Checking for unfilled password 7"); + + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt.html new file mode 100644 index 000000000..1050ab66b --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt.html @@ -0,0 +1,705 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test prompter.{prompt,promptPassword,promptUsernameAndPassword}</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="pwmgr_common.js"></script> + <script type="text/javascript" src="prompt_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content" style="display: none"> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +var state, action; +var uname = { value: null }; +var pword = { value: null }; +var result = { value: null }; +var isOk; + +// Force parent to not look for tab-modal prompts, as they're not used for auth prompts. +isTabModal = false; + +let prompterParent = runInParent(() => { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + const promptFac = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"]. + getService(Ci.nsIPromptFactory); + + Cu.import("resource://gre/modules/Services.jsm"); + let chromeWin = Services.wm.getMostRecentWindow("navigator:browser"); + let prompter1 = promptFac.getPrompt(chromeWin, Ci.nsIAuthPrompt); + + addMessageListener("proxyPrompter", function onMessage(msg) { + let rv = prompter1[msg.methodName](...msg.args); + return { + rv, + // Send the args back to content so out/inout args can be checked. + args: msg.args, + }; + }); +}); + +let prompter1 = new PrompterProxy(prompterParent); + +const defaultTitle = "the title"; +const defaultMsg = "the message"; + +function initLogins() { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + var login1, login2A, login2B, login2C, login2D, login2E; + var pwmgr = Cc["@mozilla.org/login-manager;1"]. + getService(Ci.nsILoginManager); + + login1 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2C = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2D = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2E = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + + login1.init("http://example.com", null, "http://example.com", + "", "examplepass", "", ""); + login2A.init("http://example2.com", null, "http://example2.com", + "user1name", "user1pass", "", ""); + login2B.init("http://example2.com", null, "http://example2.com", + "user2name", "user2pass", "", ""); + login2C.init("http://example2.com", null, "http://example2.com", + "user3.name@host", "user3pass", "", ""); + login2D.init("http://example2.com", null, "http://example2.com", + "100@beef", "user3pass", "", ""); + login2E.init("http://example2.com", null, "http://example2.com", + "100%beef", "user3pass", "", ""); + + pwmgr.addLogin(login1); + pwmgr.addLogin(login2A); + pwmgr.addLogin(login2B); + pwmgr.addLogin(login2C); + pwmgr.addLogin(login2D); + pwmgr.addLogin(login2E); +} + +add_task(function* setup() { + runInParent(initLogins); +}); + +add_task(function* test_prompt_accept() { + state = { + msg : "the message", + title : "the title", + textValue : "abc", + passValue : "", + iconClass : "question-icon", + titleHidden : true, + textHidden : false, + passHidden : true, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + textField : "xyz", + }; + promptDone = handlePrompt(state, action); + isOk = prompter1.prompt(defaultTitle, defaultMsg, "http://example.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, "abc", result); + yield promptDone; + + ok(isOk, "Checking dialog return value (accept)"); + is(result.value, "xyz", "Checking prompt() returned value"); +}); + +add_task(function* test_prompt_cancel() { + state = { + msg : "the message", + title : "the title", + textValue : "abc", + passValue : "", + iconClass : "question-icon", + titleHidden : true, + textHidden : false, + passHidden : true, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "cancel", + }; + promptDone = handlePrompt(state, action); + isOk = prompter1.prompt(defaultTitle, defaultMsg, "http://example.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, "abc", result); + yield promptDone; + ok(!isOk, "Checking dialog return value (cancel)"); +}); + +add_task(function* test_promptPassword_defaultAccept() { + // Default password provided, existing logins are ignored. + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "inputpw", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : true, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "passField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "secret", + }; + pword.value = "inputpw"; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://example.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "secret", "Checking returned password"); +}); + +add_task(function* test_promptPassword_defaultCancel() { + // Default password provided, existing logins are ignored. + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "inputpw", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : true, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "passField", + defButton : "button0", + }; + action = { + buttonClick : "cancel", + }; + pword.value = "inputpw"; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://example.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + yield promptDone; + ok(!isOk, "Checking dialog return value (cancel)"); +}); + +add_task(function* test_promptPassword_emptyAccept() { + // No default password provided, realm does not match existing login. + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : true, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "passField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "secret", + }; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://nonexample.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "secret", "Checking returned password"); +}); + +add_task(function* test_promptPassword_saved() { + // No default password provided, matching login is returned w/o prompting. + pword.value = null; + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://example.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "examplepass", "Checking returned password"); +}); + +add_task(function* test_promptPassword_noMatchingPasswordForEmptyUN() { + // No default password provided, none of the logins from this host are + // password-only so the user is prompted. + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : true, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "passField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "secret", + }; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "secret", "Checking returned password"); +}); + +add_task(function* test_promptPassword_matchingPWForUN() { + // No default password provided, matching login is returned w/o prompting. + pword.value = null; + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user1name@example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "user1pass", "Checking returned password"); +}); + +add_task(function* test_promptPassword_matchingPWForUN2() { + // No default password provided, matching login is returned w/o prompting. + pword.value = null; + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user2name@example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "user2pass", "Checking returned password"); +}); + +add_task(function* test_promptPassword_matchingPWForUN3() { + // No default password provided, matching login is returned w/o prompting. + pword.value = null; + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://user3%2Ename%40host@example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "user3pass", "Checking returned password"); +}); + +add_task(function* test_promptPassword_extraAt() { + // No default password provided, matching login is returned w/o prompting. + pword.value = null; + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://100@beef@example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "user3pass", "Checking returned password"); +}); + +add_task(function* test_promptPassword_usernameEncoding() { + // No default password provided, matching login is returned w/o prompting. + pword.value = null; + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "http://100%25beef@example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "user3pass", "Checking returned password"); + + // XXX test saving a password with Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY +}); + +add_task(function* test_promptPassword_realm() { + // We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : true, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "passField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "fill2pass", + }; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "example2.com:80 (somerealm)", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "fill2pass", "Checking returned password"); +}); + +add_task(function* test_promptPassword_realm2() { + // We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : true, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "passField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "fill2pass", + }; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptPassword(defaultTitle, defaultMsg, "example2.com:80 (somerealm)", + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(pword.value, "fill2pass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_accept() { + state = { + msg : "the message", + title : "the title", + textValue : "inuser", + passValue : "inpass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + textField : "outuser", + passField : "outpass", + }; + uname.value = "inuser"; + pword.value = "inpass"; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://nonexample.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(uname.value, "outuser", "Checking returned username"); + is(pword.value, "outpass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_cancel() { + state = { + msg : "the message", + title : "the title", + textValue : "inuser", + passValue : "inpass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "cancel", + }; + uname.value = "inuser"; + pword.value = "inpass"; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://nonexample.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword); + yield promptDone; + ok(!isOk, "Checking dialog return value (cancel)"); +}); + +add_task(function* test_promptUsernameAndPassword_autofill() { + // test filling in existing password-only login + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "examplepass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : false, + checkMsg : "Use Password Manager to remember this password.", + checked : true, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + uname.value = null; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(uname.value, "", "Checking returned username"); + is(pword.value, "examplepass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_multipleExisting() { + // test filling in existing login (undetermined from multiple selection) + // user2name/user2pass would also be valid to fill here. + state = { + msg : "the message", + title : "the title", + textValue : "user1name", + passValue : "user1pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : false, + checkMsg : "Use Password Manager to remember this password.", + checked : true, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + uname.value = null; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + ok(uname.value == "user1name" || uname.value == "user2name", "Checking returned username"); + ok(pword.value == "user1pass" || uname.value == "user2pass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_multipleExisting1() { + // test filling in existing login (user1 from multiple selection) + state = { + msg : "the message", + title : "the title", + textValue : "user1name", + passValue : "user1pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : false, + checkMsg : "Use Password Manager to remember this password.", + checked : true, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + uname.value = "user1name"; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(uname.value, "user1name", "Checking returned username"); + is(pword.value, "user1pass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_multipleExisting2() { + // test filling in existing login (user2 from multiple selection) + state = { + msg : "the message", + title : "the title", + textValue : "user2name", + passValue : "user2pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : false, + checkMsg : "Use Password Manager to remember this password.", + checked : true, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + uname.value = "user2name"; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(uname.value, "user2name", "Checking returned username"); + is(pword.value, "user2pass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_passwordChange() { + // test changing password + state = { + msg : "the message", + title : "the title", + textValue : "user2name", + passValue : "user2pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : false, + checkMsg : "Use Password Manager to remember this password.", + checked : true, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "NEWuser2pass", + }; + uname.value = "user2name"; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(uname.value, "user2name", "Checking returned username"); + is(pword.value, "NEWuser2pass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_changePasswordBack() { + // test changing password (back to original value) + state = { + msg : "the message", + title : "the title", + textValue : "user2name", + passValue : "NEWuser2pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : false, + checkMsg : "Use Password Manager to remember this password.", + checked : true, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "user2pass", + }; + uname.value = "user2name"; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "http://example2.com", + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(uname.value, "user2name", "Checking returned username"); + is(pword.value, "user2pass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_realm() { + // We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + textField : "fill2user", + passField : "fill2pass", + }; + uname.value = null; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "example2.com:80 (somerealm)", + Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(uname.value, "fill2user", "Checking returned username"); + is(pword.value, "fill2pass", "Checking returned password"); +}); + +add_task(function* test_promptUsernameAndPassword_realm2() { + // We don't pre-fill or save for NS_GetAuthKey-generated realms, but we should still prompt + state = { + msg : "the message", + title : "the title", + textValue : "", + passValue : "", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + textField : "fill2user", + passField : "fill2pass", + }; + uname.value = null; + pword.value = null; + promptDone = handlePrompt(state, action); + isOk = prompter1.promptUsernameAndPassword(defaultTitle, defaultMsg, "example2.com:80 (somerealm)", + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY, uname, pword); + yield promptDone; + ok(isOk, "Checking dialog return value (accept)"); + is(uname.value, "fill2user", "Checking returned username"); + is(pword.value, "fill2pass", "Checking returned password"); +}); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_http.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_http.html new file mode 100644 index 000000000..0dc8fdf9c --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_http.html @@ -0,0 +1,362 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test HTTP auth prompts by loading authenticate.sjs</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="pwmgr_common.js"></script> + <script type="text/javascript" src="prompt_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content" style="display: none"> + <iframe id="iframe"></iframe> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +var iframe = document.getElementById("iframe"); + +// Force parent to not look for tab-modal prompts, as they're not used for auth prompts. +isTabModal = false; + +const AUTHENTICATE_PATH = new URL("authenticate.sjs", window.location.href).pathname; + +let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); + +runInParent(() => { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + let pwmgr = Cc["@mozilla.org/login-manager;1"]. + getService(Ci.nsILoginManager); + + let login3A, login3B, login4; + login3A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login3B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login4 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let httpUpgradeLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let httpsDowngradeLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let dedupeHttpUpgradeLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let dedupeHttpsUpgradeLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + + + login3A.init("http://mochi.test:8888", null, "mochitest", + "mochiuser1", "mochipass1", "", ""); + login3B.init("http://mochi.test:8888", null, "mochitest2", + "mochiuser2", "mochipass2", "", ""); + login4.init("http://mochi.test:8888", null, "mochitest3", + "mochiuser3", "mochipass3-old", "", ""); + // Logins to test scheme upgrades (allowed) and downgrades (disallowed) + httpUpgradeLogin.init("http://example.com", null, "schemeUpgrade", + "httpUser", "httpPass", "", ""); + httpsDowngradeLogin.init("https://example.com", null, "schemeDowngrade", + "httpsUser", "httpsPass", "", ""); + // HTTP and HTTPS version of the same domain and realm but with different passwords. + dedupeHttpUpgradeLogin.init("http://example.org", null, "schemeUpgradeDedupe", + "dedupeUser", "httpPass", "", ""); + dedupeHttpsUpgradeLogin.init("https://example.org", null, "schemeUpgradeDedupe", + "dedupeUser", "httpsPass", "", ""); + + + pwmgr.addLogin(login3A); + pwmgr.addLogin(login3B); + pwmgr.addLogin(login4); + pwmgr.addLogin(httpUpgradeLogin); + pwmgr.addLogin(httpsDowngradeLogin); + pwmgr.addLogin(dedupeHttpUpgradeLogin); + pwmgr.addLogin(dedupeHttpsUpgradeLogin); +}); + +add_task(function* test_iframe() { + let state = { + msg : "http://mochi.test:8888 is requesting your username and password. The site says: “mochitest”", + title : "Authentication Required", + textValue : "mochiuser1", + passValue : "mochipass1", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + let action = { + buttonClick : "ok", + }; + promptDone = handlePrompt(state, action); + + // The following tests are driven by iframe loads + + var iframeLoaded = onloadPromiseFor("iframe"); + iframe.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1"; + yield promptDone; + yield iframeLoaded; + checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"}, + iframe.contentDocument); + + state = { + msg : "http://mochi.test:8888 is requesting your username and password. The site says: “mochitest2”", + title : "Authentication Required", + textValue : "mochiuser2", + passValue : "mochipass2", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + promptDone = handlePrompt(state, action); + // We've already authenticated to this host:port. For this next + // request, the existing auth should be sent, we'll get a 401 reply, + // and we should prompt for new auth. + iframeLoaded = onloadPromiseFor("iframe"); + iframe.src = "authenticate.sjs?user=mochiuser2&pass=mochipass2&realm=mochitest2"; + yield promptDone; + yield iframeLoaded; + checkEchoedAuthInfo({user: "mochiuser2", pass: "mochipass2"}, + iframe.contentDocument); + + // Now make a load that requests the realm from test 1000. It was + // already provided there, so auth will *not* be prompted for -- the + // networking layer already knows it! + iframeLoaded = onloadPromiseFor("iframe"); + iframe.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1"; + yield iframeLoaded; + checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1"}, + iframe.contentDocument); + + // Same realm we've already authenticated to, but with a different + // expected password (to trigger an auth prompt, and change-password + // popup notification). + state = { + msg : "http://mochi.test:8888 is requesting your username and password. The site says: “mochitest”", + title : "Authentication Required", + textValue : "mochiuser1", + passValue : "mochipass1", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "mochipass1-new", + }; + promptDone = handlePrompt(state, action); + iframeLoaded = onloadPromiseFor("iframe"); + let promptShownPromise = promisePromptShown("passwordmgr-prompt-change"); + iframe.src = "authenticate.sjs?user=mochiuser1&pass=mochipass1-new"; + yield promptDone; + yield iframeLoaded; + checkEchoedAuthInfo({user: "mochiuser1", pass: "mochipass1-new"}, + iframe.contentDocument); + yield promptShownPromise; + + // Same as last test, but for a realm we haven't already authenticated + // to (but have an existing saved login for, so that we'll trigger + // a change-password popup notification. + state = { + msg : "http://mochi.test:8888 is requesting your username and password. The site says: “mochitest3”", + title : "Authentication Required", + textValue : "mochiuser3", + passValue : "mochipass3-old", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + passField : "mochipass3-new", + }; + promptDone = handlePrompt(state, action); + iframeLoaded = onloadPromiseFor("iframe"); + promptShownPromise = promisePromptShown("passwordmgr-prompt-change"); + iframe.src = "authenticate.sjs?user=mochiuser3&pass=mochipass3-new&realm=mochitest3"; + yield promptDone; + yield iframeLoaded; + checkEchoedAuthInfo({user: "mochiuser3", pass: "mochipass3-new"}, + iframe.contentDocument); + yield promptShownPromise; + + // Housekeeping: Delete login4 to test the save prompt in the next test. + runInParent(() => { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + var tmpLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + tmpLogin.init("http://mochi.test:8888", null, "mochitest3", + "mochiuser3", "mochipass3-old", "", ""); + Services.logins.removeLogin(tmpLogin); + + // Clear cached auth from this subtest, and avoid leaking due to bug 459620. + var authMgr = Cc['@mozilla.org/network/http-auth-manager;1']. + getService(Ci.nsIHttpAuthManager); + authMgr.clearAll(); + }); + + state = { + msg : "http://mochi.test:8888 is requesting your username and password. The site says: “mochitest3”", + title : "Authentication Required", + textValue : "", + passValue : "", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + textField : "mochiuser3", + passField : "mochipass3-old", + }; + // Trigger a new prompt, so we can test adding a new login. + promptDone = handlePrompt(state, action); + + iframeLoaded = onloadPromiseFor("iframe"); + promptShownPromise = promisePromptShown("passwordmgr-prompt-save"); + iframe.src = "authenticate.sjs?user=mochiuser3&pass=mochipass3-old&realm=mochitest3"; + yield promptDone; + yield iframeLoaded; + checkEchoedAuthInfo({user: "mochiuser3", pass: "mochipass3-old"}, + iframe.contentDocument); + yield promptShownPromise; +}); + +add_task(function* test_schemeUpgrade() { + let state = { + msg : "https://example.com is requesting your username and password. " + + "WARNING: Your password will not be sent to the website you are currently visiting!", + title : "Authentication Required", + textValue : "httpUser", + passValue : "httpPass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + let action = { + buttonClick : "ok", + }; + let promptDone = handlePrompt(state, action); + + // The following tests are driven by iframe loads + + let iframeLoaded = onloadPromiseFor("iframe"); + iframe.src = "https://example.com" + AUTHENTICATE_PATH + + "?user=httpUser&pass=httpPass&realm=schemeUpgrade"; + yield promptDone; + yield iframeLoaded; + checkEchoedAuthInfo({user: "httpUser", pass: "httpPass"}, + SpecialPowers.wrap(iframe).contentDocument); +}); + +add_task(function* test_schemeDowngrade() { + let state = { + msg : "http://example.com is requesting your username and password. " + + "WARNING: Your password will not be sent to the website you are currently visiting!", + title : "Authentication Required", + textValue : "", // empty because we shouldn't downgrade + passValue : "", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + let action = { + buttonClick : "cancel", + }; + let promptDone = handlePrompt(state, action); + + // The following tests are driven by iframe loads + + let iframeLoaded = onloadPromiseFor("iframe"); + iframe.src = "http://example.com" + AUTHENTICATE_PATH + + "?user=unused&pass=unused&realm=schemeDowngrade"; + yield promptDone; + yield iframeLoaded; +}); + +add_task(function* test_schemeUpgrade_dedupe() { + let state = { + msg : "https://example.org is requesting your username and password. " + + "WARNING: Your password will not be sent to the website you are currently visiting!", + title : "Authentication Required", + textValue : "dedupeUser", + passValue : "httpsPass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + let action = { + buttonClick : "ok", + }; + let promptDone = handlePrompt(state, action); + + // The following tests are driven by iframe loads + + let iframeLoaded = onloadPromiseFor("iframe"); + iframe.src = "https://example.org" + AUTHENTICATE_PATH + + "?user=dedupeUser&pass=httpsPass&realm=schemeUpgradeDedupe"; + yield promptDone; + yield iframeLoaded; + checkEchoedAuthInfo({user: "dedupeUser", pass: "httpsPass"}, + SpecialPowers.wrap(iframe).contentDocument); +}); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_noWindow.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_noWindow.html new file mode 100644 index 000000000..92af172ca --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_noWindow.html @@ -0,0 +1,81 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test HTTP auth prompts by loading authenticate.sjs with no window</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="pwmgr_common.js"></script> + <script type="text/javascript" src="prompt_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content" style="display: none"> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// Force parent to not look for tab-modal prompts, as they're not used for auth prompts. +isTabModal = false; + +let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); + +runInParent(() => { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/Services.jsm"); + + let login = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login.init("http://mochi.test:8888", null, "mochitest", + "mochiuser1", "mochipass1", "", ""); + Services.logins.addLogin(login); +}); + +add_task(function* test_sandbox_xhr() { + let state = { + msg : "http://mochi.test:8888 is requesting your username and password. The site says: “mochitest”", + title : "Authentication Required", + textValue : "mochiuser1", + passValue : "mochipass1", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + let action = { + buttonClick : "ok", + }; + let promptDone = handlePrompt(state, action); + + let url = new URL("authenticate.sjs?user=mochiuser1&pass=mochipass1", window.location.href); + let sandboxConstructor = SpecialPowers.Cu.Sandbox; + let sandbox = new sandboxConstructor(this, {wantXrays: true}); + function sandboxedRequest(sandboxedUrl) { + let req = SpecialPowers.Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(SpecialPowers.Ci.nsIXMLHttpRequest); + req.open("GET", sandboxedUrl, true); + req.send(null); + } + + let loginModifiedPromise = promiseStorageChanged(["modifyLogin"]); + sandbox.sandboxedRequest = sandboxedRequest(url); + info("send the XHR request in the sandbox"); + SpecialPowers.Cu.evalInSandbox("sandboxedRequest;", sandbox); + + yield promptDone; + info("prompt shown, waiting for metadata updates"); + // Ensure the timeLastUsed and timesUsed metadata are updated. + yield loginModifiedPromise; +}); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth.html new file mode 100644 index 000000000..36f53a54a --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth.html @@ -0,0 +1,406 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test promptAuth prompts</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="pwmgr_common.js"></script> + <script type="text/javascript" src="prompt_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content" style="display: none"> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +var state, action; +var isOk; + +var level = Ci.nsIAuthPrompt2.LEVEL_NONE; +var authinfo = { + username : "", + password : "", + domain : "", + + flags : Ci.nsIAuthInformation.AUTH_HOST, + authenticationScheme : "basic", + realm : "" +}; + +// Force parent to not look for tab-modal prompts, as they're not used for auth prompts. +isTabModal = false; + +let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); + +let prompterParent = runInParent(() => { + const { classes: Cc, interfaces: Ci, utils: Cu } = Components; + const promptFac = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"]. + getService(Ci.nsIPromptFactory); + + Cu.import("resource://gre/modules/Services.jsm"); + let chromeWin = Services.wm.getMostRecentWindow("navigator:browser"); + let prompter2 = promptFac.getPrompt(chromeWin, Ci.nsIAuthPrompt2); + + let ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + let channels = {}; + channels.channel1 = ioService.newChannel2("http://example.com", + null, + null, + null, // aLoadingNode + Services. + scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + + channels.channel2 = ioService.newChannel2("http://example2.com", + null, + null, + null, // aLoadingNode + Services. + scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + + addMessageListener("proxyPrompter", function onMessage(msg) { + let args = [...msg.args]; + let channelName = args.shift(); + // Replace the channel name string (arg. 0) with the channel by that name. + args.unshift(channels[channelName]); + + let rv = prompter2[msg.methodName](...args); + return { + rv, + // Send the args back to content so out/inout args can be checked. + args: msg.args, + }; + }); + + Cu.import("resource://gre/modules/Services.jsm"); + + let pwmgr = Cc["@mozilla.org/login-manager;1"]. + getService(Ci.nsILoginManager); + + let login1, login2A, login2B, login2C, login2D, login2E; + login1 = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2C = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2D = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + login2E = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + + login1.init("http://example.com", null, "http://example.com", + "", "examplepass", "", ""); + login2A.init("http://example2.com", null, "http://example2.com", + "user1name", "user1pass", "", ""); + login2B.init("http://example2.com", null, "http://example2.com", + "user2name", "user2pass", "", ""); + login2C.init("http://example2.com", null, "http://example2.com", + "user3.name@host", "user3pass", "", ""); + login2D.init("http://example2.com", null, "http://example2.com", + "100@beef", "user3pass", "", ""); + login2E.init("http://example2.com", null, "http://example2.com", + "100%beef", "user3pass", "", ""); + + pwmgr.addLogin(login1); + pwmgr.addLogin(login2A); + pwmgr.addLogin(login2B); + pwmgr.addLogin(login2C); + pwmgr.addLogin(login2D); + pwmgr.addLogin(login2E); +}); + +let prompter2 = new PrompterProxy(prompterParent); + +add_task(function* test_accept() { + state = { + msg : "http://example.com is requesting your username and password. The site says: “some realm”", + title : "Authentication Required", + textValue : "inuser", + passValue : "inpass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + textField : "outuser", + passField : "outpass", + }; + authinfo.username = "inuser"; + authinfo.password = "inpass"; + authinfo.realm = "some realm"; + + promptDone = handlePrompt(state, action); + // Since prompter2 is actually a proxy to send a message to a chrome script and + // we can't send a channel in a message, we instead send the channel name that + // already exists in the chromeScript. + isOk = prompter2.promptAuth("channel1", level, authinfo); + yield promptDone; + + ok(isOk, "Checking dialog return value (accept)"); + is(authinfo.username, "outuser", "Checking returned username"); + is(authinfo.password, "outpass", "Checking returned password"); +}); + +add_task(function* test_cancel() { + state = { + msg : "http://example.com is requesting your username and password. The site says: “some realm”", + title : "Authentication Required", + textValue : "outuser", + passValue : "outpass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "cancel", + }; + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth("channel1", level, authinfo); + yield promptDone; + + ok(!isOk, "Checking dialog return value (cancel)"); +}); + +add_task(function* test_pwonly() { + // test filling in password-only login + state = { + msg : "http://example.com is requesting your username and password. The site says: “http://example.com”", + title : "Authentication Required", + textValue : "", + passValue : "examplepass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + authinfo.username = ""; + authinfo.password = ""; + authinfo.realm = "http://example.com"; + + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth("channel1", level, authinfo); + yield promptDone; + + ok(isOk, "Checking dialog return value (accept)"); + is(authinfo.username, "", "Checking returned username"); + is(authinfo.password, "examplepass", "Checking returned password"); +}); + +add_task(function* test_multipleExisting() { + // test filling in existing login (undetermined from multiple selection) + // user2name/user2pass would also be valid to fill here. + state = { + msg : "http://example2.com is requesting your username and password. The site says: “http://example2.com”", + title : "Authentication Required", + textValue : "user1name", + passValue : "user1pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + authinfo.username = ""; + authinfo.password = ""; + authinfo.realm = "http://example2.com"; + + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth("channel2", level, authinfo); + yield promptDone; + + ok(isOk, "Checking dialog return value (accept)"); + ok(authinfo.username == "user1name" || authinfo.username == "user2name", "Checking returned username"); + ok(authinfo.password == "user1pass" || authinfo.password == "user2pass", "Checking returned password"); +}); + +add_task(function* test_multipleExisting2() { + // test filling in existing login (undetermined --> user1) + // user2name/user2pass would also be valid to fill here. + state = { + msg : "http://example2.com is requesting your username and password. The site says: “http://example2.com”", + title : "Authentication Required", + textValue : "user1name", + passValue : "user1pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + // enter one of the known logins, test 504+505 exercise the two possible states. + action = { + buttonClick : "ok", + textField : "user1name", + passField : "user1pass", + }; + authinfo.username = ""; + authinfo.password = ""; + authinfo.realm = "http://example2.com"; + + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth("channel2", level, authinfo); + yield promptDone; + + ok(isOk, "Checking dialog return value (accept)"); + is(authinfo.username, "user1name", "Checking returned username"); + is(authinfo.password, "user1pass", "Checking returned password"); +}); + +add_task(function* test_multipleExisting3() { + // test filling in existing login (undetermined --> user2) + // user2name/user2pass would also be valid to fill here. + state = { + msg : "http://example2.com is requesting your username and password. The site says: “http://example2.com”", + title : "Authentication Required", + textValue : "user1name", + passValue : "user1pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + // enter one of the known logins, test 504+505 exercise the two possible states. + action = { + buttonClick : "ok", + textField : "user2name", + passField : "user2pass", + }; + authinfo.username = ""; + authinfo.password = ""; + authinfo.realm = "http://example2.com"; + + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth("channel2", level, authinfo); + yield promptDone; + + ok(isOk, "Checking dialog return value (accept)"); + is(authinfo.username, "user2name", "Checking returned username"); + is(authinfo.password, "user2pass", "Checking returned password"); +}); + +add_task(function* test_changingMultiple() { + // test changing a password (undetermined --> user2 w/ newpass) + // user2name/user2pass would also be valid to fill here. + state = { + msg : "http://example2.com is requesting your username and password. The site says: “http://example2.com”", + title : "Authentication Required", + textValue : "user1name", + passValue : "user1pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + // force to user2, and change the password + action = { + buttonClick : "ok", + textField : "user2name", + passField : "NEWuser2pass", + }; + authinfo.username = ""; + authinfo.password = ""; + authinfo.realm = "http://example2.com"; + + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth("channel2", level, authinfo); + yield promptDone; + + ok(isOk, "Checking dialog return value (accept)"); + is(authinfo.username, "user2name", "Checking returned username"); + is(authinfo.password, "NEWuser2pass", "Checking returned password"); +}); + +add_task(function* test_changingMultiple2() { + // test changing a password (undetermined --> user2 w/ origpass) + // user2name/user2pass would also be valid to fill here. + state = { + msg : "http://example2.com is requesting your username and password. The site says: “http://example2.com”", + title : "Authentication Required", + textValue : "user1name", + passValue : "user1pass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + // force to user2, and change the password back + action = { + buttonClick : "ok", + textField : "user2name", + passField : "user2pass", + }; + authinfo.username = ""; + authinfo.password = ""; + authinfo.realm = "http://example2.com"; + + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth("channel2", level, authinfo); + yield promptDone; + + ok(isOk, "Checking dialog return value (accept)"); + is(authinfo.username, "user2name", "Checking returned username"); + is(authinfo.password, "user2pass", "Checking returned password"); +}); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html new file mode 100644 index 000000000..95dd4c7bc --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html @@ -0,0 +1,264 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test promptAuth proxy prompts</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="pwmgr_common.js"></script> + <script type="text/javascript" src="prompt_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content" style="display: none"> + <iframe id="iframe"></iframe> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> +var state, action; +var pwmgr; +var proxyLogin; +var isOk; +var mozproxy, proxiedHost = "http://mochi.test:8888"; +var proxyChannel; +var systemPrincipal = SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(); +var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + +var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + +var level = Ci.nsIAuthPrompt2.LEVEL_NONE; + +var proxyAuthinfo = { + username : "", + password : "", + domain : "", + + flags : Ci.nsIAuthInformation.AUTH_PROXY, + authenticationScheme : "basic", + realm : "" +}; + +// Force parent to not look for tab-modal prompts, as they're not used for auth prompts. +isTabModal = false; + +const Cc_promptFac = Cc["@mozilla.org/passwordmanager/authpromptfactory;1"]; +ok(Cc_promptFac != null, "Access Cc[@mozilla.org/passwordmanager/authpromptfactory;1]"); + +const Ci_promptFac = Ci.nsIPromptFactory; +ok(Ci_promptFac != null, "Access Ci.nsIPromptFactory"); + +const promptFac = Cc_promptFac.getService(Ci_promptFac); +ok(promptFac != null, "promptFac getService()"); + +var prompter2 = promptFac.getPrompt(window, Ci.nsIAuthPrompt2); + +function initLogins(pi) { + pwmgr = Cc["@mozilla.org/login-manager;1"]. + getService(Ci.nsILoginManager); + + mozproxy = "moz-proxy://" + SpecialPowers.wrap(pi).host + ":" + + SpecialPowers.wrap(pi).port; + + proxyLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + + proxyLogin.init(mozproxy, null, "Proxy Realm", + "proxuser", "proxpass", "", ""); + + pwmgr.addLogin(proxyLogin); +} + +var startupCompleteResolver; +var startupComplete = new Promise(resolve => startupCompleteResolver = resolve); + +function proxyChannelListener() { } +proxyChannelListener.prototype = { + onStartRequest: function(request, context) { + startupCompleteResolver(); + }, + onStopRequest: function(request, context, status) { } +}; + +var resolveCallback = SpecialPowers.wrapCallbackObject({ + QueryInterface : function (iid) { + const interfaces = [Ci.nsIProtocolProxyCallback, Ci.nsISupports]; + + if (!interfaces.some( function(v) { return iid.equals(v); } )) + throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE; + return this; + }, + + onProxyAvailable : function (req, uri, pi, status) { + initLogins(pi); + + // I'm cheating a bit here... We should probably do some magic foo to get + // something implementing nsIProxiedProtocolHandler and then call + // NewProxiedChannel(), so we have something that's definately a proxied + // channel. But Mochitests use a proxy for a number of hosts, so just + // requesting a normal channel will give us a channel that's proxied. + // The proxyChannel needs to move to at least on-modify-request to + // have valid ProxyInfo, but we use OnStartRequest during startup() + // for simplicity. + proxyChannel = ioService.newChannel2(proxiedHost, + null, + null, + null, // aLoadingNode + systemPrincipal, + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + proxyChannel.asyncOpen2(SpecialPowers.wrapCallbackObject(new proxyChannelListener())); + } +}); + +function startup() { + // Need to allow for arbitrary network servers defined in PAC instead of a hardcoded moz-proxy. + var ios = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]. + getService(SpecialPowers.Ci.nsIIOService); + + var pps = SpecialPowers.Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(); + + var channel = ios.newChannel2("http://example.com", + null, + null, + null, // aLoadingNode + systemPrincipal, + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + Ci.nsIContentPolicy.TYPE_OTHER); + pps.asyncResolve(channel, 0, resolveCallback); +} + +startup(); + +add_task(function* setup() { + info("Waiting for startup to complete..."); + yield startupComplete; +}); + +add_task(function* test_noAutologin() { + // test proxy login (default = no autologin), make sure it prompts. + state = { + msg : "The proxy moz-proxy://127.0.0.1:8888 is requesting a username and password. The site says: “Proxy Realm”", + title : "Authentication Required", + textValue : "proxuser", + passValue : "proxpass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + proxyAuthinfo.username = ""; + proxyAuthinfo.password = ""; + proxyAuthinfo.realm = "Proxy Realm"; + proxyAuthinfo.flags = Ci.nsIAuthInformation.AUTH_PROXY; + + var time1 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed; + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth(proxyChannel, level, proxyAuthinfo); + yield promptDone; + var time2 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed; + + ok(isOk, "Checking dialog return value (accept)"); + isnot(time1, time2, "Checking that timeLastUsed was updated"); + is(proxyAuthinfo.username, "proxuser", "Checking returned username"); + is(proxyAuthinfo.password, "proxpass", "Checking returned password"); +}); + +add_task(function* test_autologin() { + // test proxy login (with autologin) + + // Enable the autologin pref. + prefs.setBoolPref("signon.autologin.proxy", true); + + proxyAuthinfo.username = ""; + proxyAuthinfo.password = ""; + proxyAuthinfo.realm = "Proxy Realm"; + proxyAuthinfo.flags = Ci.nsIAuthInformation.AUTH_PROXY; + + time1 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed; + isOk = prompter2.promptAuth(proxyChannel, level, proxyAuthinfo); + time2 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed; + + ok(isOk, "Checking dialog return value (accept)"); + isnot(time1, time2, "Checking that timeLastUsed was updated"); + is(proxyAuthinfo.username, "proxuser", "Checking returned username"); + is(proxyAuthinfo.password, "proxpass", "Checking returned password"); +}); + +add_task(function* test_autologin_incorrect() { + // test proxy login (with autologin), ensure it prompts after a failed auth. + state = { + msg : "The proxy moz-proxy://127.0.0.1:8888 is requesting a username and password. The site says: “Proxy Realm”", + title : "Authentication Required", + textValue : "proxuser", + passValue : "proxpass", + iconClass : "authentication-icon question-icon", + titleHidden : true, + textHidden : false, + passHidden : false, + checkHidden : true, + checkMsg : "", + checked : false, + focused : "textField", + defButton : "button0", + }; + action = { + buttonClick : "ok", + }; + + proxyAuthinfo.username = ""; + proxyAuthinfo.password = ""; + proxyAuthinfo.realm = "Proxy Realm"; + proxyAuthinfo.flags = (Ci.nsIAuthInformation.AUTH_PROXY | Ci.nsIAuthInformation.PREVIOUS_FAILED); + + time1 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed; + promptDone = handlePrompt(state, action); + isOk = prompter2.promptAuth(proxyChannel, level, proxyAuthinfo); + yield promptDone; + time2 = pwmgr.findLogins({}, mozproxy, null, "Proxy Realm")[0].QueryInterface(Ci.nsILoginMetaInfo).timeLastUsed; + + ok(isOk, "Checking dialog return value (accept)"); + isnot(time1, time2, "Checking that timeLastUsed was updated"); + is(proxyAuthinfo.username, "proxuser", "Checking returned username"); + is(proxyAuthinfo.password, "proxpass", "Checking returned password"); +}); + +add_task(function* test_autologin_private() { + // test proxy login (with autologin), ensure it prompts in Private Browsing mode. + state = { + msg : "the message", + title : "the title", + textValue : "proxuser", + passValue : "proxpass", + }; + action = { + buttonClick : "ok", + }; + + proxyAuthinfo.username = ""; + proxyAuthinfo.password = ""; + proxyAuthinfo.realm = "Proxy Realm"; + proxyAuthinfo.flags = Ci.nsIAuthInformation.AUTH_PROXY; + + prefs.clearUserPref("signon.autologin.proxy"); + + // XXX check for and kill popup notification?? + // XXX check for checkbox / checkstate on old prompts? + // XXX check NTLM domain stuff +}); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_recipe_login_fields.html b/toolkit/components/passwordmgr/test/mochitest/test_recipe_login_fields.html new file mode 100644 index 000000000..943bffc52 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_recipe_login_fields.html @@ -0,0 +1,145 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Test for recipes overriding login fields</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="pwmgr_common.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +var chromeScript = runChecksAfterCommonInit(); + +let fillPromiseResolvers = []; + +function waitForFills(fillCount) { + let promises = []; + while (fillCount--) { + let promise = new Promise(resolve => fillPromiseResolvers.push(resolve)); + promises.push(promise); + } + + return Promise.all(promises); +} + +add_task(function* setup() { + if (document.readyState !== "complete") { + yield new Promise((resolve) => { + document.onreadystatechange = () => { + if (document.readyState !== "complete") { + return; + } + document.onreadystatechange = null; + resolve(); + }; + }); + } + + document.getElementById("content") + .addEventListener("input", function handleInputEvent(evt) { + let resolve = fillPromiseResolvers.shift(); + if (!resolve) { + ok(false, "Too many fills"); + return; + } + + resolve(evt.target); + }); +}); + +add_task(function* loadUsernamePasswordSelectorRecipes() { + yield loadRecipes({ + siteRecipes: [{ + hosts: ["mochi.test:8888"], + usernameSelector: "input[name='uname1']", + passwordSelector: "input[name='pword2']", + }], + }); +}); + +add_task(function* testOverriddingFields() { + // Insert the form dynamically so autofill is triggered after setup above. + document.getElementById("content").innerHTML = ` + <!-- form with recipe for the username and password --> + <form id="form1"> + <input type="text" name="uname1" data-expected="true"> + <input type="text" name="uname2" data-expected="false"> + <input type="password" name="pword1" data-expected="false"> + <input type="password" name="pword2" data-expected="true"> + </form>`; + + let elements = yield waitForFills(2); + for (let element of elements) { + is(element.dataset.expected, "true", `${element.name} was filled`); + } +}); + +add_task(function* testDefaultHeuristics() { + // Insert the form dynamically so autofill is triggered after setup above. + document.getElementById("content").innerHTML = ` + <!-- Fallback to the default heuristics since the selectors don't match --> + <form id="form2"> + <input type="text" name="uname3" data-expected="false"> + <input type="text" name="uname4" data-expected="true"> + <input type="password" name="pword3" data-expected="true"> + <input type="password" name="pword4" data-expected="false"> + </form>`; + + let elements = yield waitForFills(2); + for (let element of elements) { + is(element.dataset.expected, "true", `${element.name} was filled`); + } +}); + +add_task(function* loadNotUsernameSelectorRecipes() { + yield resetRecipes(); + yield loadRecipes({ + siteRecipes: [{ + hosts: ["mochi.test:8888"], + notUsernameSelector: "input[name='not_uname1']" + }], + }); +}); + +add_task(function* testNotUsernameField() { + document.getElementById("content").innerHTML = ` + <!-- The field matching notUsernameSelector should be skipped --> + <form id="form3"> + <input type="text" name="uname5" data-expected="true"> + <input type="text" name="not_uname1" data-expected="false"> + <input type="password" name="pword5" data-expected="true"> + </form>`; + + let elements = yield waitForFills(2); + for (let element of elements) { + is(element.dataset.expected, "true", `${element.name} was filled`); + } +}); + +add_task(function* testNotUsernameFieldNoUsername() { + document.getElementById("content").innerHTML = ` + <!-- The field matching notUsernameSelector should be skipped. + No username field should be found and filled in this case --> + <form id="form4"> + <input type="text" name="not_uname1" data-expected="false"> + <input type="password" name="pword6" data-expected="true"> + </form>`; + + let elements = yield waitForFills(1); + for (let element of elements) { + is(element.dataset.expected, "true", `${element.name} was filled`); + } +}); + +</script> + +<p id="display"></p> + +<div id="content"> + // Forms are inserted dynamically +</div> +<pre id="test"></pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_username_focus.html b/toolkit/components/passwordmgr/test/mochitest/test_username_focus.html new file mode 100644 index 000000000..c93c1e9c9 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_username_focus.html @@ -0,0 +1,263 @@ + +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Test interaction between autocomplete and focus on username fields</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="satchel_common.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script> +let pwmgrCommonScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js")); + +let readyPromise = registerRunTests(); +let chromeScript = runInParent(function chromeSetup() { + const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components; + let pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); + + let login1A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let login1B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let login2A = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let login2B = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + let login2C = Cc["@mozilla.org/login-manager/loginInfo;1"]. + createInstance(Ci.nsILoginInfo); + + login1A.init("http://mochi.test:8888", "http://username-focus-1", null, + "testuser1A", "testpass1A", "", ""); + + login2A.init("http://mochi.test:8888", "http://username-focus-2", null, + "testuser2A", "testpass2A", "", ""); + login2B.init("http://mochi.test:8888", "http://username-focus-2", null, + "testuser2B", "testpass2B", "", ""); + + pwmgr.addLogin(login1A); + pwmgr.addLogin(login2A); + pwmgr.addLogin(login2B); +}); +</script> + +<p id="display"></p> +<div id="content"> + <!-- first 3 forms have a matching user+pass login --> + + <!-- user+pass form. --> + <form id="form-autofilled" action="http://username-focus-1"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit" name="submit">Submit</button> + </form> + + <!-- user+pass form, username prefilled --> + <form id="form-autofilled-prefilled-un" action="http://username-focus-1"> + <input type="text" name="uname" value="testuser1A"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- user+pass form. --> + <form id="form-autofilled-focused-dynamic" action="http://username-focus-1"> + <input type="text" name="uname"> + <input type="not-yet-password" name="pword"> + <button type="submit">Submit</button> + </form> + + + <!-- next 5 forms have matching user+pass (2x) logins --> + + <!-- user+pass form. --> + <form id="form-multiple" action="http://username-focus-2"> + <input type="text" name="uname"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- user+pass form dynamic with existing focus --> + <form id="form-multiple-dynamic" action="http://username-focus-2"> + <input type="text" name="uname"> + <input type="not-yet-password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- user+pass form, username prefilled --> + <form id="form-multiple-prefilled-un1" action="http://username-focus-2"> + <input type="text" name="uname" value="testuser2A"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- user+pass form, different username prefilled --> + <form id="form-multiple-prefilled-un2" action="http://username-focus-2"> + <input type="text" name="uname" value="testuser2B"> + <input type="password" name="pword"> + <button type="submit">Submit</button> + </form> + + <!-- user+pass form, username prefilled with existing focus --> + <form id="form-multiple-prefilled-focused-dynamic" action="http://username-focus-2"> + <input type="text" name="uname" value="testuser2B"> + <input type="not-yet-password" name="pword"> + <button type="submit">Submit</button> + </form> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +function removeFocus() { + $_("-autofilled", "submit").focus(); +} + +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["security.insecure_field_warning.contextual.enabled", false], + ]}); + + ok(readyPromise, "check promise is available"); + yield readyPromise; +}); + +add_task(function* test_autofilled() { + let usernameField = $_("-autofilled", "uname"); + info("Username and password already filled so don't show autocomplete"); + let noPopupPromise = promiseNoUnexpectedPopupShown(); + usernameField.focus(); + yield noPopupPromise; + + removeFocus(); + usernameField.value = "testuser"; + info("Focus when we don't have an exact match"); + shownPromise = promiseACShown(); + usernameField.focus(); + yield shownPromise; +}); + +add_task(function* test_autofilled_prefilled_un() { + let usernameField = $_("-autofilled-prefilled-un", "uname"); + info("Username and password already filled so don't show autocomplete"); + let noPopupPromise = promiseNoUnexpectedPopupShown(); + usernameField.focus(); + yield noPopupPromise; + + removeFocus(); + usernameField.value = "testuser"; + info("Focus when we don't have an exact match"); + shownPromise = promiseACShown(); + usernameField.focus(); + yield shownPromise; +}); + +add_task(function* test_autofilled_focused_dynamic() { + let usernameField = $_("-autofilled-focused-dynamic", "uname"); + let passwordField = $_("-autofilled-focused-dynamic", "pword"); + info("Username and password will be filled while username focused"); + let noPopupPromise = promiseNoUnexpectedPopupShown(); + usernameField.focus(); + yield noPopupPromise; + info("triggering autofill"); + noPopupPromise = promiseNoUnexpectedPopupShown(); + passwordField.type = "password"; + yield noPopupPromise; + + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is closed"); + + removeFocus(); + passwordField.value = "test"; + info("Focus when we don't have an exact match"); + shownPromise = promiseACShown(); + usernameField.focus(); + yield shownPromise; +}); + +// Begin testing forms that have multiple saved logins + +add_task(function* test_multiple() { + let usernameField = $_("-multiple", "uname"); + info("Fields not filled due to multiple so autocomplete upon focus"); + shownPromise = promiseACShown(); + usernameField.focus(); + yield shownPromise; +}); + +add_task(function* test_multiple_dynamic() { + let usernameField = $_("-multiple-dynamic", "uname"); + let passwordField = $_("-multiple-dynamic", "pword"); + info("Fields not filled but username is focused upon marking so open"); + let noPopupPromise = promiseNoUnexpectedPopupShown(); + usernameField.focus(); + yield noPopupPromise; + + info("triggering _fillForm code"); + let shownPromise = promiseACShown(); + passwordField.type = "password"; + yield shownPromise; +}); + +add_task(function* test_multiple_prefilled_un1() { + let usernameField = $_("-multiple-prefilled-un1", "uname"); + info("Username and password already filled so don't show autocomplete"); + let noPopupPromise = promiseNoUnexpectedPopupShown(); + usernameField.focus(); + yield noPopupPromise; + + removeFocus(); + usernameField.value = "testuser"; + info("Focus when we don't have an exact match"); + shownPromise = promiseACShown(); + usernameField.focus(); + yield shownPromise; +}); + +add_task(function* test_multiple_prefilled_un2() { + let usernameField = $_("-multiple-prefilled-un2", "uname"); + info("Username and password already filled so don't show autocomplete"); + let noPopupPromise = promiseNoUnexpectedPopupShown(); + usernameField.focus(); + yield noPopupPromise; + + removeFocus(); + usernameField.value = "testuser"; + info("Focus when we don't have an exact match"); + shownPromise = promiseACShown(); + usernameField.focus(); + yield shownPromise; +}); + +add_task(function* test_multiple_prefilled_focused_dynamic() { + let usernameField = $_("-multiple-prefilled-focused-dynamic", "uname"); + let passwordField = $_("-multiple-prefilled-focused-dynamic", "pword"); + info("Username and password will be filled while username focused"); + let noPopupPromise = promiseNoUnexpectedPopupShown(); + usernameField.focus(); + yield noPopupPromise; + info("triggering autofill"); + noPopupPromise = promiseNoUnexpectedPopupShown(); + passwordField.type = "password"; + yield noPopupPromise; + + let popupState = yield getPopupState(); + is(popupState.open, false, "Check popup is closed"); + + removeFocus(); + passwordField.value = "test"; + info("Focus when we don't have an exact match"); + shownPromise = promiseACShown(); + usernameField.focus(); + yield shownPromise; +}); + +add_task(function* cleanup() { + removeFocus(); +}); +</script> +</pre> +</body> +</html> diff --git a/toolkit/components/passwordmgr/test/mochitest/test_xhr_2.html b/toolkit/components/passwordmgr/test/mochitest/test_xhr_2.html new file mode 100644 index 000000000..fa8357792 --- /dev/null +++ b/toolkit/components/passwordmgr/test/mochitest/test_xhr_2.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=654348 +--> +<head> + <meta charset="utf-8"> + <title>Test XHR auth with user and pass arguments</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="pwmgr_common.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="startTest()"> +<script class="testbody" type="text/javascript"> + +/** + * This test checks we correctly ignore authentication entry + * for a subpath and use creds from the URL when provided when XHR + * is used with filled user name and password. + * + * 1. connect authenticate.sjs that excepts user1:pass1 password + * 2. connect authenticate.sjs that this time expects differentuser2:pass2 password + * we must use the creds that are provided to the xhr witch are different and expected + */ + +function doxhr(URL, user, pass, code, next) { + var xhr = new XMLHttpRequest(); + if (user && pass) + xhr.open("POST", URL, true, user, pass); + else + xhr.open("POST", URL, true); + xhr.onload = function() { + is(xhr.status, code, "expected response code " + code); + next(); + }; + xhr.onerror = function() { + ok(false, "request passed"); + finishTest(); + }; + xhr.send(); +} + +function startTest() { + doxhr("authenticate.sjs?user=dummy&pass=pass1&realm=realm1&formauth=1", "dummy", "dummy", 403, function() { + doxhr("authenticate.sjs?user=dummy&pass=pass1&realm=realm1&formauth=1", "dummy", "pass1", 200, finishTest); + }); +} + +function finishTest() { + SimpleTest.finish(); +} + +</script> +</body> +</html> |