summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/mochitest
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /toolkit/components/passwordmgr/test/mochitest
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-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')
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/auth2/authenticate.sjs220
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/mochitest.ini69
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html218
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_autofill_https_upgrade.html117
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html143
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_autofocus_js.html115
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form.html44
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_0pw.html72
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw.html167
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_1pw_2.html109
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_1.html187
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_2.html105
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_3pw_1.html177
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html859
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_html5.html164
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwevent.html55
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_basic_form_pwonly.html213
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_bug_627616.html145
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_bug_776171.html56
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_case_differences.html147
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_form_action_1.html137
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_form_action_2.html170
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_form_action_javascript.html52
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_autofill.html147
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html183
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html191
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html121
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_input_events.html96
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_input_events_for_identical_values.html51
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html861
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_no_saved_login.html103
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_maxlength.html137
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_password_field_autocomplete.html291
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_passwords_in_type_password.html122
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_prompt.html705
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_prompt_http.html362
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_prompt_noWindow.html81
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth.html406
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_prompt_promptAuth_proxy.html264
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_recipe_login_fields.html145
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_username_focus.html263
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_xhr_2.html55
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>