import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,7 @@
"use strict";
module.exports = {
"extends": [
"../../../../../testing/mochitest/browser.eslintrc.js"
]
};

View file

@ -0,0 +1,110 @@
function handleRequest(request, response)
{
var match;
var requestAuth = 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 = "test", expected_pass = "testpass", realm = "mochitest";
// 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];
}
// 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 (requestAuth) {
response.setStatusLine("1.0", 401, "Authentication required");
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>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");
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;
}

View file

@ -0,0 +1,72 @@
[DEFAULT]
support-files =
../formsubmit.sjs
authenticate.sjs
form_basic.html
form_basic_iframe.html
formless_basic.html
form_same_origin_action.html
form_cross_origin_secure_action.html
head.js
insecure_test.html
insecure_test_subframe.html
multiple_forms.html
streamConverter_content.sjs
[browser_autocomplete_insecure_warning.js]
support-files =
form_cross_origin_insecure_action.html
[browser_capture_doorhanger.js]
support-files =
subtst_notifications_1.html
subtst_notifications_2.html
subtst_notifications_2pw_0un.html
subtst_notifications_2pw_1un_1text.html
subtst_notifications_3.html
subtst_notifications_4.html
subtst_notifications_5.html
subtst_notifications_6.html
subtst_notifications_8.html
subtst_notifications_9.html
subtst_notifications_10.html
subtst_notifications_change_p.html
[browser_capture_doorhanger_httpsUpgrade.js]
support-files =
subtst_notifications_1.html
subtst_notifications_8.html
[browser_capture_doorhanger_window_open.js]
support-files =
subtst_notifications_11.html
subtst_notifications_11_popup.html
skip-if = os == "linux" # Bug 1312981, bug 1313136
[browser_context_menu_autocomplete_interaction.js]
[browser_username_select_dialog.js]
support-files =
subtst_notifications_change_p.html
[browser_DOMFormHasPassword.js]
[browser_DOMInputPasswordAdded.js]
[browser_exceptions_dialog.js]
[browser_formless_submit_chrome.js]
[browser_hasInsecureLoginForms.js]
[browser_hasInsecureLoginForms_streamConverter.js]
[browser_http_autofill.js]
[browser_insecurePasswordConsoleWarning.js]
support-files =
form_cross_origin_insecure_action.html
[browser_master_password_autocomplete.js]
[browser_notifications.js]
[browser_notifications_username.js]
[browser_notifications_password.js]
[browser_notifications_2.js]
skip-if = os == "linux" # Bug 1272849 Main action button disabled state intermittent
[browser_passwordmgr_editing.js]
skip-if = os == "linux"
[browser_context_menu.js]
[browser_context_menu_iframe.js]
[browser_passwordmgr_contextmenu.js]
subsuite = clipboard
[browser_passwordmgr_fields.js]
[browser_passwordmgr_observers.js]
[browser_passwordmgr_sort.js]
[browser_passwordmgr_switchtab.js]
[browser_passwordmgrdlg.js]

View file

@ -0,0 +1,94 @@
const ids = {
INPUT_ID: "input1",
FORM1_ID: "form1",
FORM2_ID: "form2",
CHANGE_INPUT_ID: "input2",
};
function task(contentIds) {
let resolve;
let promise = new Promise(r => { resolve = r; });
function unexpectedContentEvent(evt) {
ok(false, "Received a " + evt.type + " event on content");
}
var gDoc = null;
addEventListener("load", tabLoad, true);
function tabLoad() {
if (content.location.href == "about:blank")
return;
removeEventListener("load", tabLoad, true);
gDoc = content.document;
gDoc.addEventListener("DOMFormHasPassword", unexpectedContentEvent, false);
gDoc.defaultView.setTimeout(test_inputAdd, 0);
}
function test_inputAdd() {
addEventListener("DOMFormHasPassword", test_inputAddHandler, false);
let input = gDoc.createElementNS("http://www.w3.org/1999/xhtml", "input");
input.setAttribute("type", "password");
input.setAttribute("id", contentIds.INPUT_ID);
input.setAttribute("data-test", "unique-attribute");
gDoc.getElementById(contentIds.FORM1_ID).appendChild(input);
}
function test_inputAddHandler(evt) {
removeEventListener(evt.type, test_inputAddHandler, false);
is(evt.target.id, contentIds.FORM1_ID,
evt.type + " event targets correct form element (added password element)");
gDoc.defaultView.setTimeout(test_inputChangeForm, 0);
}
function test_inputChangeForm() {
addEventListener("DOMFormHasPassword", test_inputChangeFormHandler, false);
let input = gDoc.getElementById(contentIds.INPUT_ID);
input.setAttribute("form", contentIds.FORM2_ID);
}
function test_inputChangeFormHandler(evt) {
removeEventListener(evt.type, test_inputChangeFormHandler, false);
is(evt.target.id, contentIds.FORM2_ID,
evt.type + " event targets correct form element (changed form)");
gDoc.defaultView.setTimeout(test_inputChangesType, 0);
}
function test_inputChangesType() {
addEventListener("DOMFormHasPassword", test_inputChangesTypeHandler, false);
let input = gDoc.getElementById(contentIds.CHANGE_INPUT_ID);
input.setAttribute("type", "password");
}
function test_inputChangesTypeHandler(evt) {
removeEventListener(evt.type, test_inputChangesTypeHandler, false);
is(evt.target.id, contentIds.FORM1_ID,
evt.type + " event targets correct form element (changed type)");
gDoc.defaultView.setTimeout(finish, 0);
}
function finish() {
gDoc.removeEventListener("DOMFormHasPassword", unexpectedContentEvent, false);
resolve();
}
return promise;
}
add_task(function* () {
let tab = gBrowser.selectedTab = gBrowser.addTab();
let promise = ContentTask.spawn(tab.linkedBrowser, ids, task);
tab.linkedBrowser.loadURI("data:text/html;charset=utf-8," +
"<html><body>" +
"<form id='" + ids.FORM1_ID + "'>" +
"<input id='" + ids.CHANGE_INPUT_ID + "'></form>" +
"<form id='" + ids.FORM2_ID + "'></form>" +
"</body></html>");
yield promise;
ok(true, "Test completed");
gBrowser.removeCurrentTab();
});

View file

@ -0,0 +1,99 @@
const consts = {
HTML_NS: "http://www.w3.org/1999/xhtml",
INPUT_ID: "input1",
FORM1_ID: "form1",
FORM2_ID: "form2",
CHANGE_INPUT_ID: "input2",
BODY_INPUT_ID: "input3",
};
function task(contentConsts) {
let resolve;
let promise = new Promise(r => { resolve = r; });
function unexpectedContentEvent(evt) {
Assert.ok(false, "Received a " + evt.type + " event on content");
}
var gDoc = null;
addEventListener("load", tabLoad, true);
function tabLoad() {
removeEventListener("load", tabLoad, true);
gDoc = content.document;
// These events shouldn't escape to content.
gDoc.addEventListener("DOMInputPasswordAdded", unexpectedContentEvent, false);
gDoc.defaultView.setTimeout(test_inputAdd, 0);
}
function test_inputAdd() {
addEventListener("DOMInputPasswordAdded", test_inputAddHandler, false);
let input = gDoc.createElementNS(contentConsts.HTML_NS, "input");
input.setAttribute("type", "password");
input.setAttribute("id", contentConsts.INPUT_ID);
input.setAttribute("data-test", "unique-attribute");
gDoc.getElementById(contentConsts.FORM1_ID).appendChild(input);
info("Done appending the input element");
}
function test_inputAddHandler(evt) {
removeEventListener(evt.type, test_inputAddHandler, false);
Assert.equal(evt.target.id, contentConsts.INPUT_ID,
evt.type + " event targets correct input element (added password element)");
gDoc.defaultView.setTimeout(test_inputAddOutsideForm, 0);
}
function test_inputAddOutsideForm() {
addEventListener("DOMInputPasswordAdded", test_inputAddOutsideFormHandler, false);
let input = gDoc.createElementNS(contentConsts.HTML_NS, "input");
input.setAttribute("type", "password");
input.setAttribute("id", contentConsts.BODY_INPUT_ID);
input.setAttribute("data-test", "unique-attribute");
gDoc.body.appendChild(input);
info("Done appending the input element to the body");
}
function test_inputAddOutsideFormHandler(evt) {
removeEventListener(evt.type, test_inputAddOutsideFormHandler, false);
Assert.equal(evt.target.id, contentConsts.BODY_INPUT_ID,
evt.type + " event targets correct input element (added password element outside form)");
gDoc.defaultView.setTimeout(test_inputChangesType, 0);
}
function test_inputChangesType() {
addEventListener("DOMInputPasswordAdded", test_inputChangesTypeHandler, false);
let input = gDoc.getElementById(contentConsts.CHANGE_INPUT_ID);
input.setAttribute("type", "password");
}
function test_inputChangesTypeHandler(evt) {
removeEventListener(evt.type, test_inputChangesTypeHandler, false);
Assert.equal(evt.target.id, contentConsts.CHANGE_INPUT_ID,
evt.type + " event targets correct input element (changed type)");
gDoc.defaultView.setTimeout(completeTest, 0);
}
function completeTest() {
Assert.ok(true, "Test completed");
gDoc.removeEventListener("DOMInputPasswordAdded", unexpectedContentEvent, false);
resolve();
}
return promise;
}
add_task(function* () {
let tab = gBrowser.selectedTab = gBrowser.addTab();
let promise = ContentTask.spawn(tab.linkedBrowser, consts, task);
tab.linkedBrowser.loadURI("data:text/html;charset=utf-8," +
"<html><body>" +
"<form id='" + consts.FORM1_ID + "'>" +
"<input id='" + consts.CHANGE_INPUT_ID + "'></form>" +
"<form id='" + consts.FORM2_ID + "'></form>" +
"</body></html>");
yield promise;
gBrowser.removeCurrentTab();
});

View file

@ -0,0 +1,41 @@
"use strict";
const EXPECTED_SUPPORT_URL = Services.urlFormatter.formatURLPref("app.support.baseURL") +
"insecure-password";
add_task(function* test_clickInsecureFieldWarning() {
let url = "https://example.com" + DIRECTORY_PATH + "form_cross_origin_insecure_action.html";
yield BrowserTestUtils.withNewTab({
gBrowser,
url,
}, function*(browser) {
let popup = document.getElementById("PopupAutoComplete");
ok(popup, "Got popup");
let promiseShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
yield SimpleTest.promiseFocus(browser);
info("content window focused");
// Focus the username field to open the popup.
yield ContentTask.spawn(browser, null, function openAutocomplete() {
content.document.getElementById("form-basic-username").focus();
});
yield promiseShown;
ok(promiseShown, "autocomplete shown");
let warningItem = document.getAnonymousElementByAttribute(popup, "type", "insecureWarning");
ok(warningItem, "Got warning richlistitem");
yield BrowserTestUtils.waitForCondition(() => !warningItem.collapsed, "Wait for warning to show");
info("Clicking on warning");
let supportTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, EXPECTED_SUPPORT_URL);
EventUtils.synthesizeMouseAtCenter(warningItem, {});
let supportTab = yield supportTabPromise;
ok(supportTab, "Support tab opened");
yield BrowserTestUtils.removeTab(supportTab);
});
});

View file

@ -0,0 +1,600 @@
/*
* Test capture popup notifications
*/
const BRAND_BUNDLE = Services.strings.createBundle("chrome://branding/locale/brand.properties");
const BRAND_SHORT_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let login1 = new nsLoginInfo("http://example.com", "http://example.com", null,
"notifyu1", "notifyp1", "user", "pass");
let login2 = new nsLoginInfo("http://example.com", "http://example.com", null,
"", "notifyp1", "", "pass");
let login1B = new nsLoginInfo("http://example.com", "http://example.com", null,
"notifyu1B", "notifyp1B", "user", "pass");
let login2B = new nsLoginInfo("http://example.com", "http://example.com", null,
"", "notifyp1B", "", "pass");
requestLongerTimeout(2);
add_task(function* setup() {
// Load recipes for this test.
let recipeParent = yield LoginManagerParent.recipeParentPromise;
yield recipeParent.load({
siteRecipes: [{
hosts: ["example.org"],
usernameSelector: "#user",
passwordSelector: "#pass",
}],
});
});
add_task(function* test_remember_opens() {
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
notif.remove();
});
});
add_task(function* test_clickNever() {
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
is(true, Services.logins.getLoginSavingEnabled("http://example.com"),
"Checking for login saving enabled");
yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1");
clickDoorhangerButton(notif, NEVER_BUTTON);
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
info("Make sure Never took effect");
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(!notif, "checking for no notification popup");
is(false, Services.logins.getLoginSavingEnabled("http://example.com"),
"Checking for login saving disabled");
Services.logins.setLoginSavingEnabled("http://example.com", true);
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_clickRemember() {
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1");
clickDoorhangerButton(notif, REMEMBER_BUTTON);
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username used on the new entry");
is(login.password, "notifyp1", "Check the password used on the new entry");
is(login.timesUsed, 1, "Check times used on new entry");
info("Make sure Remember took effect and we don't prompt for an existing login");
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(!notif, "checking for no notification popup");
});
logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username used");
is(login.password, "notifyp1", "Check the password used");
is(login.timesUsed, 2, "Check times used incremented");
checkOnlyLoginWasUsedTwice({ justChanged: false });
// remove that login
Services.logins.removeLogin(login1);
});
/* signons.rememberSignons pref tests... */
add_task(function* test_rememberSignonsFalse() {
info("Make sure we don't prompt with rememberSignons=false");
Services.prefs.setBoolPref("signon.rememberSignons", false);
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(!notif, "checking for no notification popup");
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_rememberSignonsTrue() {
info("Make sure we prompt with rememberSignons=true");
Services.prefs.setBoolPref("signon.rememberSignons", true);
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
/* autocomplete=off tests... */
add_task(function* test_autocompleteOffUsername() {
info("Check for notification popup when autocomplete=off present on username");
yield testSubmittingLoginForm("subtst_notifications_2.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "checking for notification popup");
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_autocompleteOffPassword() {
info("Check for notification popup when autocomplete=off present on password");
yield testSubmittingLoginForm("subtst_notifications_3.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "checking for notification popup");
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_autocompleteOffForm() {
info("Check for notification popup when autocomplete=off present on form");
yield testSubmittingLoginForm("subtst_notifications_4.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "checking for notification popup");
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_noPasswordField() {
info("Check for no notification popup when no password field present");
yield testSubmittingLoginForm("subtst_notifications_5.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "null", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(!notif, "checking for no notification popup");
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_pwOnlyLoginMatchesForm() {
info("Check for update popup when existing pw-only login matches form.");
Services.logins.addLogin(login2);
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "checking for notification popup");
notif.remove();
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "", "Check the username");
is(login.password, "notifyp1", "Check the password");
is(login.timesUsed, 1, "Check times used");
Services.logins.removeLogin(login2);
});
add_task(function* test_pwOnlyFormMatchesLogin() {
info("Check for no notification popup when pw-only form matches existing login.");
Services.logins.addLogin(login1);
yield testSubmittingLoginForm("subtst_notifications_6.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(!notif, "checking for no notification popup");
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username");
is(login.password, "notifyp1", "Check the password");
is(login.timesUsed, 2, "Check times used");
Services.logins.removeLogin(login1);
});
add_task(function* test_pwOnlyFormDoesntMatchExisting() {
info("Check for notification popup when pw-only form doesn't match existing login.");
Services.logins.addLogin(login1B);
yield testSubmittingLoginForm("subtst_notifications_6.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
notif.remove();
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1B", "Check the username unchanged");
is(login.password, "notifyp1B", "Check the password unchanged");
is(login.timesUsed, 1, "Check times used");
Services.logins.removeLogin(login1B);
});
add_task(function* test_changeUPLoginOnUPForm_dont() {
info("Check for change-password popup, u+p login on u+p form. (not changed)");
Services.logins.addLogin(login1);
yield testSubmittingLoginForm("subtst_notifications_8.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "pass2", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "got notification popup");
yield* checkDoorhangerUsernamePassword("notifyu1", "pass2");
clickDoorhangerButton(notif, DONT_CHANGE_BUTTON);
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username unchanged");
is(login.password, "notifyp1", "Check the password unchanged");
is(login.timesUsed, 1, "Check times used");
Services.logins.removeLogin(login1);
});
add_task(function* test_changeUPLoginOnUPForm_change() {
info("Check for change-password popup, u+p login on u+p form.");
Services.logins.addLogin(login1);
yield testSubmittingLoginForm("subtst_notifications_8.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "pass2", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "got notification popup");
yield* checkDoorhangerUsernamePassword("notifyu1", "pass2");
clickDoorhangerButton(notif, CHANGE_BUTTON);
ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username unchanged");
is(login.password, "pass2", "Check the password changed");
is(login.timesUsed, 2, "Check times used");
checkOnlyLoginWasUsedTwice({ justChanged: true });
// cleanup
login1.password = "pass2";
Services.logins.removeLogin(login1);
login1.password = "notifyp1";
});
add_task(function* test_changePLoginOnUPForm() {
info("Check for change-password popup, p-only login on u+p form.");
Services.logins.addLogin(login2);
yield testSubmittingLoginForm("subtst_notifications_9.html", function*(fieldValues) {
is(fieldValues.username, "", "Checking submitted username");
is(fieldValues.password, "pass2", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "got notification popup");
yield* checkDoorhangerUsernamePassword("", "pass2");
clickDoorhangerButton(notif, CHANGE_BUTTON);
ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "", "Check the username unchanged");
is(login.password, "pass2", "Check the password changed");
is(login.timesUsed, 2, "Check times used");
// no cleanup -- saved password to be used in the next test.
});
add_task(function* test_changePLoginOnPForm() {
info("Check for change-password popup, p-only login on p-only form.");
yield testSubmittingLoginForm("subtst_notifications_10.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "got notification popup");
yield* checkDoorhangerUsernamePassword("", "notifyp1");
clickDoorhangerButton(notif, CHANGE_BUTTON);
ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "", "Check the username unchanged");
is(login.password, "notifyp1", "Check the password changed");
is(login.timesUsed, 3, "Check times used");
Services.logins.removeLogin(login2);
});
add_task(function* test_checkUPSaveText() {
info("Check text on a user+pass notification popup");
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
// Check the text, which comes from the localized saveLoginText string.
let notificationText = notif.message;
let expectedText = "Would you like " + BRAND_SHORT_NAME + " to remember this login?";
is(expectedText, notificationText, "Checking text: " + notificationText);
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_checkPSaveText() {
info("Check text on a pass-only notification popup");
yield testSubmittingLoginForm("subtst_notifications_6.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
// Check the text, which comes from the localized saveLoginTextNoUser string.
let notificationText = notif.message;
let expectedText = "Would you like " + BRAND_SHORT_NAME + " to remember this password?";
is(expectedText, notificationText, "Checking text: " + notificationText);
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_capture2pw0un() {
info("Check for notification popup when a form with 2 password fields (no username) " +
"is submitted and there are no saved logins.");
yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
notif.remove();
});
is(Services.logins.getAllLogins().length, 0, "Should not have any logins yet");
});
add_task(function* test_change2pw0unExistingDifferentUP() {
info("Check for notification popup when a form with 2 password fields (no username) " +
"is submitted and there is a saved login with a username and different password.");
Services.logins.addLogin(login1B);
yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "got notification popup");
notif.remove();
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1B", "Check the username unchanged");
is(login.password, "notifyp1B", "Check the password unchanged");
is(login.timesUsed, 1, "Check times used");
Services.logins.removeLogin(login1B);
});
add_task(function* test_change2pw0unExistingDifferentP() {
info("Check for notification popup when a form with 2 password fields (no username) " +
"is submitted and there is a saved login with no username and different password.");
Services.logins.addLogin(login2B);
yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "got notification popup");
notif.remove();
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "", "Check the username unchanged");
is(login.password, "notifyp1B", "Check the password unchanged");
is(login.timesUsed, 1, "Check times used");
Services.logins.removeLogin(login2B);
});
add_task(function* test_change2pw0unExistingWithSameP() {
info("Check for no notification popup when a form with 2 password fields (no username) " +
"is submitted and there is a saved login with a username and the same password.");
Services.logins.addLogin(login2);
yield testSubmittingLoginForm("subtst_notifications_2pw_0un.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(!notif, "checking for no notification popup");
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "", "Check the username unchanged");
is(login.password, "notifyp1", "Check the password unchanged");
is(login.timesUsed, 2, "Check times used incremented");
checkOnlyLoginWasUsedTwice({ justChanged: false });
Services.logins.removeLogin(login2);
});
add_task(function* test_changeUPLoginOnPUpdateForm() {
info("Check for change-password popup, u+p login on password update form.");
Services.logins.addLogin(login1);
yield testSubmittingLoginForm("subtst_notifications_change_p.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "pass2", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "got notification popup");
yield* checkDoorhangerUsernamePassword("notifyu1", "pass2");
clickDoorhangerButton(notif, CHANGE_BUTTON);
ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
});
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username unchanged");
is(login.password, "pass2", "Check the password changed");
is(login.timesUsed, 2, "Check times used");
checkOnlyLoginWasUsedTwice({ justChanged: true });
// cleanup
login1.password = "pass2";
Services.logins.removeLogin(login1);
login1.password = "notifyp1";
});
add_task(function* test_recipeCaptureFields_NewLogin() {
info("Check that we capture the proper fields when a field recipe is in use.");
yield testSubmittingLoginForm("subtst_notifications_2pw_1un_1text.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
// Sanity check, no logins should exist yet.
let logins = Services.logins.getAllLogins();
is(logins.length, 0, "Should not have any logins yet");
yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1");
clickDoorhangerButton(notif, REMEMBER_BUTTON);
}, "http://example.org"); // The recipe is for example.org
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username unchanged");
is(login.password, "notifyp1", "Check the password unchanged");
is(login.timesUsed, 1, "Check times used");
});
add_task(function* test_recipeCaptureFields_ExistingLogin() {
info("Check that we capture the proper fields when a field recipe is in use " +
"and there is a matching login");
yield testSubmittingLoginForm("subtst_notifications_2pw_1un_1text.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(!notif, "checking for no notification popup");
}, "http://example.org");
checkOnlyLoginWasUsedTwice({ justChanged: false });
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username unchanged");
is(login.password, "notifyp1", "Check the password unchanged");
is(login.timesUsed, 2, "Check times used incremented");
Services.logins.removeAllLogins();
});
add_task(function* test_noShowPasswordOnDismissal() {
info("Check for no Show Password field when the doorhanger is dismissed");
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
info("Opening popup");
let notif = getCaptureDoorhanger("password-save");
let { panel } = PopupNotifications;
info("Hiding popup.");
let promiseHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
panel.hidePopup();
yield promiseHidden;
info("Clicking on anchor to reshow popup.");
let promiseShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
notif.anchorElement.click();
yield promiseShown;
let passwordVisiblityToggle = panel.querySelector("#password-notification-visibilityToggle");
is(passwordVisiblityToggle.hidden, true, "Check that the Show Password field is Hidden");
});
});
// TODO:
// * existing login test, form has different password --> change password, no save prompt

View file

@ -0,0 +1,123 @@
/*
* Test capture popup notifications with HTTPS upgrades
*/
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let login1 = new nsLoginInfo("http://example.com", "http://example.com", null,
"notifyu1", "notifyp1", "user", "pass");
let login1HTTPS = new nsLoginInfo("https://example.com", "https://example.com", null,
"notifyu1", "notifyp1", "user", "pass");
add_task(function* test_httpsUpgradeCaptureFields_noChange() {
info("Check that we don't prompt to remember when capturing an upgraded login with no change");
Services.logins.addLogin(login1);
// Sanity check the HTTP login exists.
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should have the HTTP login");
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(!notif, "checking for no notification popup");
}, "https://example.com"); // This is HTTPS whereas the saved login is HTTP
logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login still");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.hostname, "http://example.com", "Check the hostname is unchanged");
is(login.username, "notifyu1", "Check the username is unchanged");
is(login.password, "notifyp1", "Check the password is unchanged");
is(login.timesUsed, 2, "Check times used increased");
Services.logins.removeLogin(login1);
});
add_task(function* test_httpsUpgradeCaptureFields_changePW() {
info("Check that we prompt to change when capturing an upgraded login with a new PW");
Services.logins.addLogin(login1);
// Sanity check the HTTP login exists.
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should have the HTTP login");
yield testSubmittingLoginForm("subtst_notifications_8.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "pass2", "Checking submitted password");
let notif = getCaptureDoorhanger("password-change");
ok(notif, "checking for a change popup");
yield* checkDoorhangerUsernamePassword("notifyu1", "pass2");
clickDoorhangerButton(notif, CHANGE_BUTTON);
ok(!getCaptureDoorhanger("password-change"), "popup should be gone");
}, "https://example.com"); // This is HTTPS whereas the saved login is HTTP
checkOnlyLoginWasUsedTwice({ justChanged: true });
logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login still");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.hostname, "https://example.com", "Check the hostname is upgraded");
is(login.formSubmitURL, "https://example.com", "Check the formSubmitURL is upgraded");
is(login.username, "notifyu1", "Check the username is unchanged");
is(login.password, "pass2", "Check the password changed");
is(login.timesUsed, 2, "Check times used increased");
Services.logins.removeAllLogins();
});
add_task(function* test_httpsUpgradeCaptureFields_captureMatchingHTTP() {
info("Capture a new HTTP login which matches a stored HTTPS one.");
Services.logins.addLogin(login1HTTPS);
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
is(Services.logins.getAllLogins().length, 1, "Should only have the HTTPS login");
yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1");
clickDoorhangerButton(notif, REMEMBER_BUTTON);
});
let logins = Services.logins.getAllLogins();
is(logins.length, 2, "Should have both HTTP and HTTPS logins");
for (let login of logins) {
login = login.QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username used on the new entry");
is(login.password, "notifyp1", "Check the password used on the new entry");
is(login.timesUsed, 1, "Check times used on entry");
}
info("Make sure Remember took effect and we don't prompt for an existing HTTP login");
yield testSubmittingLoginForm("subtst_notifications_1.html", function*(fieldValues) {
is(fieldValues.username, "notifyu1", "Checking submitted username");
is(fieldValues.password, "notifyp1", "Checking submitted password");
let notif = getCaptureDoorhanger("password-save");
ok(!notif, "checking for no notification popup");
});
logins = Services.logins.getAllLogins();
is(logins.length, 2, "Should have both HTTP and HTTPS still");
let httpsLogins = LoginHelper.searchLoginsWithObject({
hostname: "https://example.com",
});
is(httpsLogins.length, 1, "Check https logins count");
let httpsLogin = httpsLogins[0].QueryInterface(Ci.nsILoginMetaInfo);
ok(httpsLogin.equals(login1HTTPS), "Check HTTPS login didn't change");
is(httpsLogin.timesUsed, 1, "Check times used");
let httpLogins = LoginHelper.searchLoginsWithObject({
hostname: "http://example.com",
});
is(httpLogins.length, 1, "Check http logins count");
let httpLogin = httpLogins[0].QueryInterface(Ci.nsILoginMetaInfo);
ok(httpLogin.equals(login1), "Check HTTP login is as expected");
is(httpLogin.timesUsed, 2, "Check times used increased");
Services.logins.removeLogin(login1);
Services.logins.removeLogin(login1HTTPS);
});

View file

@ -0,0 +1,144 @@
/*
* Test capture popup notifications in content opened by window.open
*/
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let login1 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
"notifyu1", "notifyp1", "user", "pass");
let login2 = new nsLoginInfo("http://mochi.test:8888", "http://mochi.test:8888", null,
"notifyu2", "notifyp2", "user", "pass");
function withTestTabUntilStorageChange(aPageFile, aTaskFn) {
function storageChangedObserved(subject, data) {
// Watch for actions triggered from a doorhanger (not cleanup tasks with removeLogin)
if (data == "removeLogin") {
return false;
}
return true;
}
let storageChangedPromised = TestUtils.topicObserved("passwordmgr-storage-changed",
storageChangedObserved);
return BrowserTestUtils.withNewTab({
gBrowser,
url: "http://mochi.test:8888" + DIRECTORY_PATH + aPageFile,
}, function*(browser) {
ok(true, "loaded " + aPageFile);
info("running test case task");
yield* aTaskFn();
info("waiting for storage change");
yield storageChangedPromised;
});
}
add_task(function* setup() {
yield SimpleTest.promiseFocus(window);
});
add_task(function* test_saveChromeHiddenAutoClose() {
let notifShownPromise = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
// query arguments are: username, password, features, auto-close (delimited by '|')
let url = "subtst_notifications_11.html?notifyu1|notifyp1|" +
"menubar=no,toolbar=no,location=no|autoclose";
yield withTestTabUntilStorageChange(url, function*() {
info("waiting for popupshown");
yield notifShownPromise;
// the popup closes and the doorhanger should appear in the opener
let popup = getCaptureDoorhanger("password-save");
ok(popup, "got notification popup");
yield* checkDoorhangerUsernamePassword("notifyu1", "notifyp1");
// Sanity check, no logins should exist yet.
let logins = Services.logins.getAllLogins();
is(logins.length, 0, "Should not have any logins yet");
clickDoorhangerButton(popup, REMEMBER_BUTTON);
});
// Check result of clicking Remember
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.timesUsed, 1, "Check times used on new entry");
is(login.username, "notifyu1", "Check the username used on the new entry");
is(login.password, "notifyp1", "Check the password used on the new entry");
});
add_task(function* test_changeChromeHiddenAutoClose() {
let notifShownPromise = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
let url = "subtst_notifications_11.html?notifyu1|pass2|menubar=no,toolbar=no,location=no|autoclose";
yield withTestTabUntilStorageChange(url, function*() {
info("waiting for popupshown");
yield notifShownPromise;
let popup = getCaptureDoorhanger("password-change");
ok(popup, "got notification popup");
yield* checkDoorhangerUsernamePassword("notifyu1", "pass2");
clickDoorhangerButton(popup, CHANGE_BUTTON);
});
// Check to make sure we updated the password, timestamps and use count for
// the login being changed with this form.
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username");
is(login.password, "pass2", "Check password changed");
is(login.timesUsed, 2, "check .timesUsed incremented on change");
ok(login.timeCreated < login.timeLastUsed, "timeLastUsed bumped");
ok(login.timeLastUsed == login.timePasswordChanged, "timeUsed == timeChanged");
login1.password = "pass2";
Services.logins.removeLogin(login1);
login1.password = "notifyp1";
});
add_task(function* test_saveChromeVisibleSameWindow() {
// This test actually opens a new tab in the same window with default browser settings.
let url = "subtst_notifications_11.html?notifyu2|notifyp2||";
let notifShownPromise = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
yield withTestTabUntilStorageChange(url, function*() {
yield notifShownPromise;
let popup = getCaptureDoorhanger("password-save");
ok(popup, "got notification popup");
yield* checkDoorhangerUsernamePassword("notifyu2", "notifyp2");
clickDoorhangerButton(popup, REMEMBER_BUTTON);
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// Check result of clicking Remember
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login now");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu2", "Check the username used on the new entry");
is(login.password, "notifyp2", "Check the password used on the new entry");
is(login.timesUsed, 1, "Check times used on new entry");
});
add_task(function* test_changeChromeVisibleSameWindow() {
let url = "subtst_notifications_11.html?notifyu2|pass2||";
let notifShownPromise = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
yield withTestTabUntilStorageChange(url, function*() {
yield notifShownPromise;
let popup = getCaptureDoorhanger("password-change");
ok(popup, "got notification popup");
yield* checkDoorhangerUsernamePassword("notifyu2", "pass2");
clickDoorhangerButton(popup, CHANGE_BUTTON);
yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
// Check to make sure we updated the password, timestamps and use count for
// the login being changed with this form.
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should have 1 login");
let login = logins[0].QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu2", "Check the username");
is(login.password, "pass2", "Check password changed");
is(login.timesUsed, 2, "check .timesUsed incremented on change");
ok(login.timeCreated < login.timeLastUsed, "timeLastUsed bumped");
ok(login.timeLastUsed == login.timePasswordChanged, "timeUsed == timeChanged");
// cleanup
login2.password = "pass2";
Services.logins.removeLogin(login2);
login2.password = "notifyp2";
});

View file

@ -0,0 +1,432 @@
/*
* Test the password manager context menu.
*/
/* eslint no-shadow:"off" */
"use strict";
// The hostname for the test URIs.
const TEST_HOSTNAME = "https://example.com";
const MULTIPLE_FORMS_PAGE_PATH = "/browser/toolkit/components/passwordmgr/test/browser/multiple_forms.html";
const CONTEXT_MENU = document.getElementById("contentAreaContextMenu");
const POPUP_HEADER = document.getElementById("fill-login");
/**
* Initialize logins needed for the tests and disable autofill
* for login forms for easier testing of manual fill.
*/
add_task(function* test_initialize() {
Services.prefs.setBoolPref("signon.autofillForms", false);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("signon.autofillForms");
Services.prefs.clearUserPref("signon.schemeUpgrades");
});
for (let login of loginList()) {
Services.logins.addLogin(login);
}
});
/**
* Check if the context menu is populated with the right
* menuitems for the target password input field.
*/
add_task(function* test_context_menu_populate_password_noSchemeUpgrades() {
Services.prefs.setBoolPref("signon.schemeUpgrades", false);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
yield openPasswordContextMenu(browser, "#test-password-1");
// Check the content of the password manager popup
let popupMenu = document.getElementById("fill-login-popup");
checkMenu(popupMenu, 2);
CONTEXT_MENU.hidePopup();
});
});
/**
* Check if the context menu is populated with the right
* menuitems for the target password input field.
*/
add_task(function* test_context_menu_populate_password_schemeUpgrades() {
Services.prefs.setBoolPref("signon.schemeUpgrades", true);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
yield openPasswordContextMenu(browser, "#test-password-1");
// Check the content of the password manager popup
let popupMenu = document.getElementById("fill-login-popup");
checkMenu(popupMenu, 3);
CONTEXT_MENU.hidePopup();
});
});
/**
* Check if the context menu is populated with the right menuitems
* for the target username field with a password field present.
*/
add_task(function* test_context_menu_populate_username_with_password_noSchemeUpgrades() {
Services.prefs.setBoolPref("signon.schemeUpgrades", false);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + "/browser/toolkit/components/" +
"passwordmgr/test/browser/multiple_forms.html",
}, function* (browser) {
yield openPasswordContextMenu(browser, "#test-username-2");
// Check the content of the password manager popup
let popupMenu = document.getElementById("fill-login-popup");
checkMenu(popupMenu, 2);
CONTEXT_MENU.hidePopup();
});
});
/**
* Check if the context menu is populated with the right menuitems
* for the target username field with a password field present.
*/
add_task(function* test_context_menu_populate_username_with_password_schemeUpgrades() {
Services.prefs.setBoolPref("signon.schemeUpgrades", true);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + "/browser/toolkit/components/" +
"passwordmgr/test/browser/multiple_forms.html",
}, function* (browser) {
yield openPasswordContextMenu(browser, "#test-username-2");
// Check the content of the password manager popup
let popupMenu = document.getElementById("fill-login-popup");
checkMenu(popupMenu, 3);
CONTEXT_MENU.hidePopup();
});
});
/**
* Check if the password field is correctly filled when one
* login menuitem is clicked.
*/
add_task(function* test_context_menu_password_fill() {
Services.prefs.setBoolPref("signon.schemeUpgrades", true);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
let formDescriptions = yield ContentTask.spawn(browser, {}, function*() {
let forms = Array.from(content.document.getElementsByClassName("test-form"));
return forms.map((f) => f.getAttribute("description"));
});
for (let description of formDescriptions) {
info("Testing form: " + description);
let passwordInputIds = yield ContentTask.spawn(browser, {description}, function*({description}) {
let formElement = content.document.querySelector(`[description="${description}"]`);
let passwords = Array.from(formElement.querySelectorAll("input[type='password']"));
return passwords.map((p) => p.id);
});
for (let inputId of passwordInputIds) {
info("Testing password field: " + inputId);
// Synthesize a right mouse click over the username input element.
yield openPasswordContextMenu(browser, "#" + inputId, function*() {
let inputDisabled = yield ContentTask
.spawn(browser, {inputId}, function*({inputId}) {
let input = content.document.getElementById(inputId);
return input.disabled || input.readOnly;
});
// If the password field is disabled or read-only, we want to see
// the disabled Fill Password popup header.
if (inputDisabled) {
Assert.ok(!POPUP_HEADER.hidden, "Popup menu is not hidden.");
Assert.ok(POPUP_HEADER.disabled, "Popup menu is disabled.");
CONTEXT_MENU.hidePopup();
}
return !inputDisabled;
});
if (CONTEXT_MENU.state != "open") {
continue;
}
// The only field affected by the password fill
// should be the target password field itself.
yield assertContextMenuFill(browser, description, null, inputId, 1);
yield ContentTask.spawn(browser, {inputId}, function*({inputId}) {
let passwordField = content.document.getElementById(inputId);
Assert.equal(passwordField.value, "password1", "Check upgraded login was actually used");
});
CONTEXT_MENU.hidePopup();
}
}
});
});
/**
* Check if the form is correctly filled when one
* username context menu login menuitem is clicked.
*/
add_task(function* test_context_menu_username_login_fill() {
Services.prefs.setBoolPref("signon.schemeUpgrades", true);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
let formDescriptions = yield ContentTask.spawn(browser, {}, function*() {
let forms = Array.from(content.document.getElementsByClassName("test-form"));
return forms.map((f) => f.getAttribute("description"));
});
for (let description of formDescriptions) {
info("Testing form: " + description);
let usernameInputIds = yield ContentTask
.spawn(browser, {description}, function*({description}) {
let formElement = content.document.querySelector(`[description="${description}"]`);
let inputs = Array.from(formElement.querySelectorAll("input[type='text']"));
return inputs.map((p) => p.id);
});
for (let inputId of usernameInputIds) {
info("Testing username field: " + inputId);
// Synthesize a right mouse click over the username input element.
yield openPasswordContextMenu(browser, "#" + inputId, function*() {
let headerHidden = POPUP_HEADER.hidden;
let headerDisabled = POPUP_HEADER.disabled;
let data = {description, inputId, headerHidden, headerDisabled};
let shouldContinue = yield ContentTask.spawn(browser, data, function*(data) {
let {description, inputId, headerHidden, headerDisabled} = data;
let formElement = content.document.querySelector(`[description="${description}"]`);
let usernameField = content.document.getElementById(inputId);
// We always want to check if the first password field is filled,
// since this is the current behavior from the _fillForm function.
let passwordField = formElement.querySelector("input[type='password']");
// If we don't want to see the actual popup menu,
// check if the popup is hidden or disabled.
if (!passwordField || usernameField.disabled || usernameField.readOnly ||
passwordField.disabled || passwordField.readOnly) {
if (!passwordField) {
Assert.ok(headerHidden, "Popup menu is hidden.");
} else {
Assert.ok(!headerHidden, "Popup menu is not hidden.");
Assert.ok(headerDisabled, "Popup menu is disabled.");
}
return false;
}
return true;
});
if (!shouldContinue) {
CONTEXT_MENU.hidePopup();
}
return shouldContinue;
});
if (CONTEXT_MENU.state != "open") {
continue;
}
let passwordFieldId = yield ContentTask
.spawn(browser, {description}, function*({description}) {
let formElement = content.document.querySelector(`[description="${description}"]`);
return formElement.querySelector("input[type='password']").id;
});
// We shouldn't change any field that's not the target username field or the first password field
yield assertContextMenuFill(browser, description, inputId, passwordFieldId, 1);
yield ContentTask.spawn(browser, {passwordFieldId}, function*({passwordFieldId}) {
let passwordField = content.document.getElementById(passwordFieldId);
if (!passwordField.hasAttribute("expectedFail")) {
Assert.equal(passwordField.value, "password1", "Check upgraded login was actually used");
}
});
CONTEXT_MENU.hidePopup();
}
}
});
});
/**
* Synthesize mouse clicks to open the password manager context menu popup
* for a target password input element.
*
* assertCallback should return true if we should continue or else false.
*/
function* openPasswordContextMenu(browser, passwordInput, assertCallback = null) {
// Synthesize a right mouse click over the password input element.
let contextMenuShownPromise = BrowserTestUtils.waitForEvent(CONTEXT_MENU, "popupshown");
let eventDetails = {type: "contextmenu", button: 2};
BrowserTestUtils.synthesizeMouseAtCenter(passwordInput, eventDetails, browser);
yield contextMenuShownPromise;
if (assertCallback) {
let shouldContinue = yield assertCallback();
if (!shouldContinue) {
return;
}
}
// Synthesize a mouse click over the fill login menu header.
let popupShownPromise = BrowserTestUtils.waitForEvent(POPUP_HEADER, "popupshown");
EventUtils.synthesizeMouseAtCenter(POPUP_HEADER, {});
yield popupShownPromise;
}
/**
* Verify that only the expected form fields are filled.
*/
function* assertContextMenuFill(browser, formId, usernameFieldId, passwordFieldId, loginIndex) {
let popupMenu = document.getElementById("fill-login-popup");
let unchangedSelector = `[description="${formId}"] input:not(#${passwordFieldId})`;
if (usernameFieldId) {
unchangedSelector += `:not(#${usernameFieldId})`;
}
yield ContentTask.spawn(browser, {unchangedSelector}, function*({unchangedSelector}) {
let unchangedFields = content.document.querySelectorAll(unchangedSelector);
// Store the value of fields that should remain unchanged.
if (unchangedFields.length) {
for (let field of unchangedFields) {
field.setAttribute("original-value", field.value);
}
}
});
// Execute the default command of the specified login menuitem found in the context menu.
let loginItem = popupMenu.getElementsByClassName("context-login-item")[loginIndex];
// Find the used login by it's username (Use only unique usernames in this test).
let {username, password} = getLoginFromUsername(loginItem.label);
let data = {username, password, usernameFieldId, passwordFieldId, formId, unchangedSelector};
let continuePromise = ContentTask.spawn(browser, data, function*(data) {
let {username, password, usernameFieldId, passwordFieldId, formId, unchangedSelector} = data;
let form = content.document.querySelector(`[description="${formId}"]`);
yield ContentTaskUtils.waitForEvent(form, "input", "Username input value changed");
if (usernameFieldId) {
let usernameField = content.document.getElementById(usernameFieldId);
// If we have an username field, check if it's correctly filled
if (usernameField.getAttribute("expectedFail") == null) {
Assert.equal(username, usernameField.value, "Username filled and correct.");
}
}
if (passwordFieldId) {
let passwordField = content.document.getElementById(passwordFieldId);
// If we have a password field, check if it's correctly filled
if (passwordField && passwordField.getAttribute("expectedFail") == null) {
Assert.equal(password, passwordField.value, "Password filled and correct.");
}
}
let unchangedFields = content.document.querySelectorAll(unchangedSelector);
// Check that all fields that should not change have the same value as before.
if (unchangedFields.length) {
Assert.ok(() => {
for (let field of unchangedFields) {
if (field.value != field.getAttribute("original-value")) {
return false;
}
}
return true;
}, "Other fields were not changed.");
}
});
loginItem.doCommand();
return continuePromise;
}
/**
* Check if every login that matches the page hostname are available at the context menu.
* @param {Element} contextMenu
* @param {Number} expectedCount - Number of logins expected in the context menu. Used to ensure
* we continue testing something useful.
*/
function checkMenu(contextMenu, expectedCount) {
let logins = loginList().filter(login => {
return LoginHelper.isOriginMatching(login.hostname, TEST_HOSTNAME, {
schemeUpgrades: Services.prefs.getBoolPref("signon.schemeUpgrades"),
});
});
// Make an array of menuitems for easier comparison.
let menuitems = [...CONTEXT_MENU.getElementsByClassName("context-login-item")];
Assert.equal(menuitems.length, expectedCount, "Expected number of menu items");
Assert.ok(logins.every(l => menuitems.some(m => l.username == m.label)), "Every login have an item at the menu.");
}
/**
* Search for a login by it's username.
*
* Only unique login/hostname combinations should be used at this test.
*/
function getLoginFromUsername(username) {
return loginList().find(login => login.username == username);
}
/**
* List of logins used for the test.
*
* We should only use unique usernames in this test,
* because we need to search logins by username. There is one duplicate u+p combo
* in order to test de-duping in the menu.
*/
function loginList() {
return [
LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username",
password: "password",
}),
// Same as above but HTTP in order to test de-duping.
LoginTestUtils.testData.formLogin({
hostname: "http://example.com",
formSubmitURL: "http://example.com",
username: "username",
password: "password",
}),
LoginTestUtils.testData.formLogin({
hostname: "http://example.com",
formSubmitURL: "http://example.com",
username: "username1",
password: "password1",
}),
LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username2",
password: "password2",
}),
LoginTestUtils.testData.formLogin({
hostname: "http://example.org",
formSubmitURL: "http://example.org",
username: "username-cross-origin",
password: "password-cross-origin",
}),
];
}

View file

@ -0,0 +1,99 @@
/*
* Test the password manager context menu interaction with autocomplete.
*/
"use strict";
const TEST_HOSTNAME = "https://example.com";
const BASIC_FORM_PAGE_PATH = DIRECTORY_PATH + "form_basic.html";
var gUnexpectedIsTODO = false;
/**
* Initialize logins needed for the tests and disable autofill
* for login forms for easier testing of manual fill.
*/
add_task(function* test_initialize() {
let autocompletePopup = document.getElementById("PopupAutoComplete");
Services.prefs.setBoolPref("signon.autofillForms", false);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("signon.autofillForms");
autocompletePopup.removeEventListener("popupshowing", autocompleteUnexpectedPopupShowing);
});
for (let login of loginList()) {
Services.logins.addLogin(login);
}
autocompletePopup.addEventListener("popupshowing", autocompleteUnexpectedPopupShowing);
});
add_task(function* test_context_menu_username() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + BASIC_FORM_PAGE_PATH,
}, function* (browser) {
yield openContextMenu(browser, "#form-basic-username");
let contextMenu = document.getElementById("contentAreaContextMenu");
Assert.equal(contextMenu.state, "open", "Context menu opened");
contextMenu.hidePopup();
});
});
add_task(function* test_context_menu_password() {
gUnexpectedIsTODO = true;
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + BASIC_FORM_PAGE_PATH,
}, function* (browser) {
yield openContextMenu(browser, "#form-basic-password");
let contextMenu = document.getElementById("contentAreaContextMenu");
Assert.equal(contextMenu.state, "open", "Context menu opened");
contextMenu.hidePopup();
});
});
function autocompleteUnexpectedPopupShowing(event) {
if (gUnexpectedIsTODO) {
todo(false, "Autocomplete shouldn't appear");
} else {
Assert.ok(false, "Autocomplete shouldn't appear");
}
event.target.hidePopup();
}
/**
* Synthesize mouse clicks to open the context menu popup
* for a target login input element.
*/
function* openContextMenu(browser, loginInput) {
// First synthesize a mousedown. We need this to get the focus event with the "contextmenu" event.
let eventDetails1 = {type: "mousedown", button: 2};
BrowserTestUtils.synthesizeMouseAtCenter(loginInput, eventDetails1, browser);
// Then synthesize the contextmenu click over the input element.
let contextMenuShownPromise = BrowserTestUtils.waitForEvent(window, "popupshown");
let eventDetails = {type: "contextmenu", button: 2};
BrowserTestUtils.synthesizeMouseAtCenter(loginInput, eventDetails, browser);
yield contextMenuShownPromise;
// Wait to see which popups are shown.
yield new Promise(resolve => setTimeout(resolve, 1000));
}
function loginList() {
return [
LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username",
password: "password",
}),
LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username2",
password: "password2",
}),
];
}

View file

@ -0,0 +1,144 @@
/*
* Test the password manager context menu.
*/
"use strict";
const TEST_HOSTNAME = "https://example.com";
// Test with a page that only has a form within an iframe, not in the top-level document
const IFRAME_PAGE_PATH = "/browser/toolkit/components/passwordmgr/test/browser/form_basic_iframe.html";
/**
* Initialize logins needed for the tests and disable autofill
* for login forms for easier testing of manual fill.
*/
add_task(function* test_initialize() {
Services.prefs.setBoolPref("signon.autofillForms", false);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("signon.autofillForms");
Services.prefs.clearUserPref("signon.schemeUpgrades");
});
for (let login of loginList()) {
Services.logins.addLogin(login);
}
});
/**
* Check if the password field is correctly filled when it's in an iframe.
*/
add_task(function* test_context_menu_iframe_fill() {
Services.prefs.setBoolPref("signon.schemeUpgrades", true);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + IFRAME_PAGE_PATH
}, function* (browser) {
function getPasswordInput() {
let frame = content.document.getElementById("test-iframe");
return frame.contentDocument.getElementById("form-basic-password");
}
let contextMenuShownPromise = BrowserTestUtils.waitForEvent(window, "popupshown");
let eventDetails = {type: "contextmenu", button: 2};
// To click at the right point we have to take into account the iframe offset.
// Synthesize a right mouse click over the password input element.
BrowserTestUtils.synthesizeMouseAtCenter(getPasswordInput, eventDetails, browser);
yield contextMenuShownPromise;
// Synthesize a mouse click over the fill login menu header.
let popupHeader = document.getElementById("fill-login");
let popupShownPromise = BrowserTestUtils.waitForEvent(popupHeader, "popupshown");
EventUtils.synthesizeMouseAtCenter(popupHeader, {});
yield popupShownPromise;
let popupMenu = document.getElementById("fill-login-popup");
// Stores the original value of username
function promiseFrameInputValue(name) {
return ContentTask.spawn(browser, name, function(inputname) {
let iframe = content.document.getElementById("test-iframe");
let input = iframe.contentDocument.getElementById(inputname);
return input.value;
});
}
let usernameOriginalValue = yield promiseFrameInputValue("form-basic-username");
// Execute the command of the first login menuitem found at the context menu.
let passwordChangedPromise = ContentTask.spawn(browser, null, function* () {
let frame = content.document.getElementById("test-iframe");
let passwordInput = frame.contentDocument.getElementById("form-basic-password");
yield ContentTaskUtils.waitForEvent(passwordInput, "input");
});
let firstLoginItem = popupMenu.getElementsByClassName("context-login-item")[0];
firstLoginItem.doCommand();
yield passwordChangedPromise;
// Find the used login by it's username.
let login = getLoginFromUsername(firstLoginItem.label);
let passwordValue = yield promiseFrameInputValue("form-basic-password");
is(login.password, passwordValue, "Password filled and correct.");
let usernameNewValue = yield promiseFrameInputValue("form-basic-username");
is(usernameOriginalValue,
usernameNewValue,
"Username value was not changed.");
let contextMenu = document.getElementById("contentAreaContextMenu");
contextMenu.hidePopup();
});
});
/**
* Search for a login by it's username.
*
* Only unique login/hostname combinations should be used at this test.
*/
function getLoginFromUsername(username) {
return loginList().find(login => login.username == username);
}
/**
* List of logins used for the test.
*
* We should only use unique usernames in this test,
* because we need to search logins by username. There is one duplicate u+p combo
* in order to test de-duping in the menu.
*/
function loginList() {
return [
LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username",
password: "password",
}),
// Same as above but HTTP in order to test de-duping.
LoginTestUtils.testData.formLogin({
hostname: "http://example.com",
formSubmitURL: "http://example.com",
username: "username",
password: "password",
}),
LoginTestUtils.testData.formLogin({
hostname: "http://example.com",
formSubmitURL: "http://example.com",
username: "username1",
password: "password1",
}),
LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username2",
password: "password2",
}),
LoginTestUtils.testData.formLogin({
hostname: "http://example.org",
formSubmitURL: "http://example.org",
username: "username-cross-origin",
password: "password-cross-origin",
}),
];
}

View file

@ -0,0 +1,56 @@
"use strict";
const LOGIN_HOST = "http://example.com";
function openExceptionsDialog() {
return window.openDialog(
"chrome://browser/content/preferences/permissions.xul",
"Toolkit:PasswordManagerExceptions", "",
{
blockVisible: true,
sessionVisible: false,
allowVisible: false,
hideStatusColumn: true,
prefilledHost: "",
permissionType: "login-saving"
}
);
}
function countDisabledHosts(dialog) {
let doc = dialog.document;
let rejectsTree = doc.getElementById("permissionsTree");
return rejectsTree.view.rowCount;
}
function promiseStorageChanged(expectedData) {
function observer(subject, data) {
return data == expectedData && subject.QueryInterface(Ci.nsISupportsString).data == LOGIN_HOST;
}
return TestUtils.topicObserved("passwordmgr-storage-changed", observer);
}
add_task(function* test_disable() {
let dialog = openExceptionsDialog();
let promiseChanged = promiseStorageChanged("hostSavingDisabled");
yield BrowserTestUtils.waitForEvent(dialog, "load");
Services.logins.setLoginSavingEnabled(LOGIN_HOST, false);
yield promiseChanged;
is(countDisabledHosts(dialog), 1, "Verify disabled host added");
yield BrowserTestUtils.closeWindow(dialog);
});
add_task(function* test_enable() {
let dialog = openExceptionsDialog();
let promiseChanged = promiseStorageChanged("hostSavingEnabled");
yield BrowserTestUtils.waitForEvent(dialog, "load");
Services.logins.setLoginSavingEnabled(LOGIN_HOST, true);
yield promiseChanged;
is(countDisabledHosts(dialog), 0, "Verify disabled host removed");
yield BrowserTestUtils.closeWindow(dialog);
});

View file

@ -0,0 +1,126 @@
/*
* Test that browser chrome UI interactions don't trigger a capture doorhanger.
*/
"use strict";
function* fillTestPage(aBrowser) {
yield ContentTask.spawn(aBrowser, null, function*() {
content.document.getElementById("form-basic-username").value = "my_username";
content.document.getElementById("form-basic-password").value = "my_password";
});
info("fields filled");
}
function* withTestPage(aTaskFn) {
return BrowserTestUtils.withNewTab({
gBrowser,
url: "https://example.com" + DIRECTORY_PATH + "formless_basic.html",
}, function*(aBrowser) {
info("tab opened");
yield fillTestPage(aBrowser);
yield* aTaskFn(aBrowser);
// Give a chance for the doorhanger to appear
yield new Promise(resolve => SimpleTest.executeSoon(resolve));
ok(!getCaptureDoorhanger("any"), "No doorhanger should be present");
});
}
add_task(function* setup() {
yield SimpleTest.promiseFocus(window);
});
add_task(function* test_urlbar_new_URL() {
yield withTestPage(function*(aBrowser) {
gURLBar.value = "";
let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus");
gURLBar.focus();
yield focusPromise;
info("focused");
EventUtils.sendString("http://mochi.test:8888/");
EventUtils.synthesizeKey("VK_RETURN", {});
yield BrowserTestUtils.browserLoaded(aBrowser, false, "http://mochi.test:8888/");
});
});
add_task(function* test_urlbar_fragment_enter() {
yield withTestPage(function*(aBrowser) {
gURLBar.focus();
EventUtils.synthesizeKey("VK_RIGHT", {});
EventUtils.sendString("#fragment");
EventUtils.synthesizeKey("VK_RETURN", {});
});
});
add_task(function* test_backButton_forwardButton() {
yield withTestPage(function*(aBrowser) {
// Load a new page in the tab so we can test going back
aBrowser.loadURI("https://example.com" + DIRECTORY_PATH + "formless_basic.html?second");
yield BrowserTestUtils.browserLoaded(aBrowser, false,
"https://example.com" + DIRECTORY_PATH +
"formless_basic.html?second");
yield fillTestPage(aBrowser);
let forwardButton = document.getElementById("forward-button");
// We need to wait for the forward button transition to complete before we
// can click it, so we hook up a listener to wait for it to be ready.
let forwardTransitionPromise = BrowserTestUtils.waitForEvent(forwardButton, "transitionend");
let backPromise = BrowserTestUtils.browserStopped(aBrowser);
EventUtils.synthesizeMouseAtCenter(document.getElementById("back-button"), {});
yield backPromise;
// Give a chance for the doorhanger to appear
yield new Promise(resolve => SimpleTest.executeSoon(resolve));
ok(!getCaptureDoorhanger("any"), "No doorhanger should be present");
// Now go forward again after filling
yield fillTestPage(aBrowser);
yield forwardTransitionPromise;
info("transition done");
yield BrowserTestUtils.waitForCondition(() => {
return forwardButton.disabled == false;
});
let forwardPromise = BrowserTestUtils.browserStopped(aBrowser);
info("click the forward button");
EventUtils.synthesizeMouseAtCenter(forwardButton, {});
yield forwardPromise;
});
});
add_task(function* test_reloadButton() {
yield withTestPage(function*(aBrowser) {
let reloadButton = document.getElementById("urlbar-reload-button");
let loadPromise = BrowserTestUtils.browserLoaded(aBrowser, false,
"https://example.com" + DIRECTORY_PATH +
"formless_basic.html");
yield BrowserTestUtils.waitForCondition(() => {
return reloadButton.disabled == false;
});
EventUtils.synthesizeMouseAtCenter(reloadButton, {});
yield loadPromise;
});
});
add_task(function* test_back_keyboard_shortcut() {
if (Services.prefs.getIntPref("browser.backspace_action") != 0) {
ok(true, "Skipped testing backspace to go back since it's disabled");
return;
}
yield withTestPage(function*(aBrowser) {
// Load a new page in the tab so we can test going back
aBrowser.loadURI("https://example.com" + DIRECTORY_PATH + "formless_basic.html?second");
yield BrowserTestUtils.browserLoaded(aBrowser, false,
"https://example.com" + DIRECTORY_PATH +
"formless_basic.html?second");
yield fillTestPage(aBrowser);
let backPromise = BrowserTestUtils.browserStopped(aBrowser);
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
yield backPromise;
});
});

View file

@ -0,0 +1,93 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/LoginManagerParent.jsm", this);
const testUrlPath =
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
}
/**
* Checks that hasInsecureLoginForms is true for a simple HTTP page and false
* for a simple HTTPS page.
*/
add_task(function* test_simple() {
for (let scheme of ["http", "https"]) {
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
Assert.equal(LoginManagerParent.hasInsecureLoginForms(browser),
scheme == "http");
gBrowser.removeTab(tab);
}
});
/**
* Checks that hasInsecureLoginForms is true if a password field is present in
* an HTTP page loaded as a subframe of a top-level HTTPS page, when mixed
* active content blocking is disabled.
*
* When the subframe is navigated to an HTTPS page, hasInsecureLoginForms should
* be set to false.
*
* Moving back in history should set hasInsecureLoginForms to true again.
*/
add_task(function* test_subframe_navigation() {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [["security.mixed_content.block_active_content", false]],
}, resolve));
// Load the page with the subframe in a new tab.
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
// Navigate the subframe to a secure page.
let promiseSubframeReady = Promise.all([
BrowserTestUtils.browserLoaded(browser, true),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("test-iframe")
.contentDocument.getElementById("test-link").click();
});
yield promiseSubframeReady;
Assert.ok(!LoginManagerParent.hasInsecureLoginForms(browser));
// Navigate back to the insecure page. We only have to wait for the
// InsecureLoginFormsStateChange event that is triggered by pageshow.
let promise = waitForInsecureLoginFormsStateChange(browser, 1);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("test-iframe")
.contentWindow.history.back();
});
yield promise;
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
gBrowser.removeTab(tab);
});

View file

@ -0,0 +1,102 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/LoginManagerParent.jsm", this);
function* registerConverter() {
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/NetUtil.jsm", this);
/**
* Converts the "test/content" MIME type, served by the test over HTTP, to an
* HTML viewer page containing the "form_basic.html" code. The viewer is
* served from a "resource:" URI while keeping the "resource:" principal.
*/
function TestStreamConverter() {}
TestStreamConverter.prototype = {
classID: Components.ID("{5f01d6ef-c090-45a4-b3e5-940d64713eb7}"),
contractID: "@mozilla.org/streamconv;1?from=test/content&to=*/*",
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIRequestObserver,
Ci.nsIStreamListener,
Ci.nsIStreamConverter,
]),
// nsIStreamConverter
convert() {},
// nsIStreamConverter
asyncConvertData(aFromType, aToType, aListener, aCtxt) {
this.listener = aListener;
},
// nsIRequestObserver
onStartRequest(aRequest, aContext) {
let channel = NetUtil.newChannel({
uri: "resource://testing-common/form_basic.html",
loadUsingSystemPrincipal: true,
});
channel.originalURI = aRequest.QueryInterface(Ci.nsIChannel).URI;
channel.loadGroup = aRequest.loadGroup;
channel.owner = Services.scriptSecurityManager
.createCodebasePrincipal(channel.URI, {});
// In this test, we pass the new channel to the listener but don't fire a
// redirect notification, even if it would be required. This keeps the
// test code simpler and doesn't impact the principal check we're testing.
channel.asyncOpen2(this.listener);
},
// nsIRequestObserver
onStopRequest() {},
// nsIStreamListener
onDataAvailable() {},
};
let factory = XPCOMUtils._getFactory(TestStreamConverter);
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(TestStreamConverter.prototype.classID, "",
TestStreamConverter.prototype.contractID, factory);
this.cleanupFunction = function () {
registrar.unregisterFactory(TestStreamConverter.prototype.classID, factory);
};
}
/**
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
* on the given browser element.
*/
function waitForInsecureLoginFormsStateChange(browser, count) {
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
false, () => --count == 0);
}
/**
* Checks that hasInsecureLoginForms is false for a viewer served internally
* using a "resource:" URI.
*/
add_task(function* test_streamConverter() {
let originalBrowser = gBrowser.selectedTab.linkedBrowser;
yield ContentTask.spawn(originalBrowser, null, registerConverter);
let tab = gBrowser.addTab("http://example.com/browser/toolkit/components/" +
"passwordmgr/test/browser/streamConverter_content.sjs",
{ relatedBrowser: originalBrowser.linkedBrowser });
let browser = tab.linkedBrowser;
yield Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
Assert.ok(!LoginManagerParent.hasInsecureLoginForms(browser));
yield BrowserTestUtils.removeTab(tab);
yield ContentTask.spawn(originalBrowser, null, function* () {
this.cleanupFunction();
});
});

View file

@ -0,0 +1,78 @@
const TEST_URL_PATH = "://example.org/browser/toolkit/components/passwordmgr/test/browser/";
add_task(function* setup() {
let login = LoginTestUtils.testData.formLogin({
hostname: "http://example.org",
formSubmitURL: "http://example.org",
username: "username",
password: "password",
});
Services.logins.addLogin(login);
login = LoginTestUtils.testData.formLogin({
hostname: "http://example.org",
formSubmitURL: "http://another.domain",
username: "username",
password: "password",
});
Services.logins.addLogin(login);
yield SpecialPowers.pushPrefEnv({ "set": [["signon.autofillForms.http", false]] });
});
add_task(function* test_http_autofill() {
for (let scheme of ["http", "https"]) {
let tab = yield BrowserTestUtils
.openNewForegroundTab(gBrowser, `${scheme}${TEST_URL_PATH}form_basic.html`);
let [username, password] = yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
let doc = content.document;
let contentUsername = doc.getElementById("form-basic-username").value;
let contentPassword = doc.getElementById("form-basic-password").value;
return [contentUsername, contentPassword];
});
is(username, scheme == "http" ? "" : "username", "Username filled correctly");
is(password, scheme == "http" ? "" : "password", "Password filled correctly");
gBrowser.removeTab(tab);
}
});
add_task(function* test_iframe_in_http_autofill() {
for (let scheme of ["http", "https"]) {
let tab = yield BrowserTestUtils
.openNewForegroundTab(gBrowser, `${scheme}${TEST_URL_PATH}form_basic_iframe.html`);
let [username, password] = yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
let doc = content.document;
let iframe = doc.getElementById("test-iframe");
let contentUsername = iframe.contentWindow.document.getElementById("form-basic-username").value;
let contentPassword = iframe.contentWindow.document.getElementById("form-basic-password").value;
return [contentUsername, contentPassword];
});
is(username, scheme == "http" ? "" : "username", "Username filled correctly");
is(password, scheme == "http" ? "" : "password", "Password filled correctly");
gBrowser.removeTab(tab);
}
});
add_task(function* test_http_action_autofill() {
for (let type of ["insecure", "secure"]) {
let tab = yield BrowserTestUtils
.openNewForegroundTab(gBrowser, `https${TEST_URL_PATH}form_cross_origin_${type}_action.html`);
let [username, password] = yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
let doc = content.document;
let contentUsername = doc.getElementById("form-basic-username").value;
let contentPassword = doc.getElementById("form-basic-password").value;
return [contentUsername, contentPassword];
});
is(username, type == "insecure" ? "" : "username", "Username filled correctly");
is(password, type == "insecure" ? "" : "password", "Password filled correctly");
gBrowser.removeTab(tab);
}
});

View file

@ -0,0 +1,94 @@
"use strict";
const WARNING_PATTERN = [{
key: "INSECURE_FORM_ACTION",
msg: 'JavaScript Warning: "Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen."'
}, {
key: "INSECURE_PAGE",
msg: 'JavaScript Warning: "Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen."'
}];
add_task(function* testInsecurePasswordWarning() {
let warningPatternHandler;
function messageHandler(msgObj) {
function findWarningPattern(msg) {
return WARNING_PATTERN.find(patternPair => {
return msg.indexOf(patternPair.msg) !== -1;
});
}
let warning = findWarningPattern(msgObj.message);
// Only handle the insecure password related warning messages.
if (warning) {
// Prevent any unexpected or redundant matched warning message coming after
// the test case is ended.
ok(warningPatternHandler, "Invoke a valid warning message handler");
warningPatternHandler(warning, msgObj.message);
}
}
Services.console.registerListener(messageHandler);
registerCleanupFunction(function() {
Services.console.unregisterListener(messageHandler);
});
for (let [origin, testFile, expectWarnings] of [
["http://127.0.0.1", "form_basic.html", []],
["http://127.0.0.1", "formless_basic.html", []],
["http://example.com", "form_basic.html", ["INSECURE_PAGE"]],
["http://example.com", "formless_basic.html", ["INSECURE_PAGE"]],
["https://example.com", "form_basic.html", []],
["https://example.com", "formless_basic.html", []],
// For a form with customized action link in the same origin.
["http://127.0.0.1", "form_same_origin_action.html", []],
["http://example.com", "form_same_origin_action.html", ["INSECURE_PAGE"]],
["https://example.com", "form_same_origin_action.html", []],
// For a form with an insecure (http) customized action link.
["http://127.0.0.1", "form_cross_origin_insecure_action.html", ["INSECURE_FORM_ACTION"]],
["http://example.com", "form_cross_origin_insecure_action.html", ["INSECURE_PAGE"]],
["https://example.com", "form_cross_origin_insecure_action.html", ["INSECURE_FORM_ACTION"]],
// For a form with a secure (https) customized action link.
["http://127.0.0.1", "form_cross_origin_secure_action.html", []],
["http://example.com", "form_cross_origin_secure_action.html", ["INSECURE_PAGE"]],
["https://example.com", "form_cross_origin_secure_action.html", []],
]) {
let testURL = origin + DIRECTORY_PATH + testFile;
let promiseConsoleMessages = new Promise(resolve => {
warningPatternHandler = function (warning, originMessage) {
ok(warning, "Handling a warning pattern");
let fullMessage = `[${warning.msg} {file: "${testURL}" line: 0 column: 0 source: "0"}]`;
is(originMessage, fullMessage, "Message full matched:" + originMessage);
let index = expectWarnings.indexOf(warning.key);
isnot(index, -1, "Found warning: " + warning.key + " for URL:" + testURL);
if (index !== -1) {
// Remove the shown message.
expectWarnings.splice(index, 1);
}
if (expectWarnings.length === 0) {
info("All warnings are shown for URL:" + testURL);
resolve();
}
};
});
yield BrowserTestUtils.withNewTab({
gBrowser,
url: testURL
}, function*() {
if (expectWarnings.length === 0) {
info("All warnings are shown for URL:" + testURL);
return Promise.resolve();
}
return promiseConsoleMessages;
});
// Remove warningPatternHandler to stop handling the matched warning pattern
// and the task should not get any warning anymore.
warningPatternHandler = null;
}
});

View file

@ -0,0 +1,59 @@
const HOST = "https://example.com";
const URL = HOST + "/browser/toolkit/components/passwordmgr/test/browser/form_basic.html";
const TIMEOUT_PREF = "signon.masterPasswordReprompt.timeout_ms";
// Waits for the master password prompt and cancels it.
function waitForDialog() {
let dialogShown = TestUtils.topicObserved("common-dialog-loaded");
return dialogShown.then(function([subject]) {
let dialog = subject.Dialog;
is(dialog.args.title, "Password Required");
dialog.ui.button1.click();
});
}
// Test that autocomplete does not trigger a master password prompt
// for a certain time after it was cancelled.
add_task(function* test_mpAutocompleteTimeout() {
let login = LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username",
password: "password",
});
Services.logins.addLogin(login);
LoginTestUtils.masterPassword.enable();
registerCleanupFunction(function() {
LoginTestUtils.masterPassword.disable();
Services.logins.removeAllLogins();
});
// Set master password prompt timeout to 3s.
// If this test goes intermittent, you likely have to increase this value.
yield SpecialPowers.pushPrefEnv({set: [[TIMEOUT_PREF, 3000]]});
// Wait for initial master password dialog after opening the tab.
let dialogShown = waitForDialog();
yield BrowserTestUtils.withNewTab(URL, function*(browser) {
yield dialogShown;
yield ContentTask.spawn(browser, null, function*() {
// Focus the password field to trigger autocompletion.
content.document.getElementById("form-basic-password").focus();
});
// Wait 4s, dialog should not have been shown
// (otherwise the code below will not work).
yield new Promise((c) => setTimeout(c, 4000));
dialogShown = waitForDialog();
yield ContentTask.spawn(browser, null, function*() {
// Re-focus the password field to trigger autocompletion.
content.document.getElementById("form-basic-username").focus();
content.document.getElementById("form-basic-password").focus();
});
yield dialogShown;
});
});

View file

@ -0,0 +1,81 @@
/**
* Test that the doorhanger notification for password saving is populated with
* the correct values in various password capture cases.
*/
add_task(function* test_save_change() {
let testCases = [{
username: "username",
password: "password",
}, {
username: "",
password: "password",
}, {
username: "username",
oldPassword: "password",
password: "newPassword",
}, {
username: "",
oldPassword: "password",
password: "newPassword",
}];
for (let { username, oldPassword, password } of testCases) {
// Add a login for the origin of the form if testing a change notification.
if (oldPassword) {
Services.logins.addLogin(LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username,
password: oldPassword,
}));
}
yield BrowserTestUtils.withNewTab({
gBrowser,
url: "https://example.com/browser/toolkit/components/" +
"passwordmgr/test/browser/form_basic.html",
}, function* (browser) {
// Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"popupshown",
(event) => event.target == PopupNotifications.panel);
yield ContentTask.spawn(browser, [username, password],
function* ([contentUsername, contentPassword]) {
let doc = content.document;
doc.getElementById("form-basic-username").value = contentUsername;
doc.getElementById("form-basic-password").value = contentPassword;
doc.getElementById("form-basic").submit();
});
yield promiseShown;
let notificationElement = PopupNotifications.panel.childNodes[0];
// Style flush to make sure binding is attached
notificationElement.querySelector("#password-notification-password").clientTop;
// Check the actual content of the popup notification.
Assert.equal(notificationElement.querySelector("#password-notification-username")
.value, username);
Assert.equal(notificationElement.querySelector("#password-notification-password")
.value, password);
// Simulate the action on the notification to request the login to be
// saved, and wait for the data to be updated or saved based on the type
// of operation we expect.
let expectedNotification = oldPassword ? "modifyLogin" : "addLogin";
let promiseLogin = TestUtils.topicObserved("passwordmgr-storage-changed",
(_, data) => data == expectedNotification);
notificationElement.button.doCommand();
let [result] = yield promiseLogin;
// Check that the values in the database match the expected values.
let login = oldPassword ? result.QueryInterface(Ci.nsIArray)
.queryElementAt(1, Ci.nsILoginInfo)
: result.QueryInterface(Ci.nsILoginInfo);
Assert.equal(login.username, username);
Assert.equal(login.password, password);
});
// Clean up the database before the next test case is executed.
Services.logins.removeAllLogins();
}
});

View file

@ -0,0 +1,125 @@
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({"set": [
["signon.rememberSignons.visibilityToggle", true]
]});
});
/**
* Test that the doorhanger main action button is disabled
* when the password field is empty.
*
* Also checks that submiting an empty password throws an error.
*/
add_task(function* test_empty_password() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: "https://example.com/browser/toolkit/components/" +
"passwordmgr/test/browser/form_basic.html",
}, function* (browser) {
// Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"popupshown",
(event) => event.target == PopupNotifications.panel);
yield ContentTask.spawn(browser, null,
function* () {
let doc = content.document;
doc.getElementById("form-basic-username").value = "username";
doc.getElementById("form-basic-password").value = "p";
doc.getElementById("form-basic").submit();
});
yield promiseShown;
let notificationElement = PopupNotifications.panel.childNodes[0];
let passwordTextbox = notificationElement.querySelector("#password-notification-password");
let toggleCheckbox = notificationElement.querySelector("#password-notification-visibilityToggle");
// Synthesize input to empty the field
passwordTextbox.focus();
yield EventUtils.synthesizeKey("VK_RIGHT", {});
yield EventUtils.synthesizeKey("VK_BACK_SPACE", {});
let mainActionButton = document.getAnonymousElementByAttribute(notificationElement.button, "anonid", "button");
Assert.ok(mainActionButton.disabled, "Main action button is disabled");
// Makes sure submiting an empty password throws an error
Assert.throws(notificationElement.button.doCommand(),
"Can't add a login with a null or empty password.",
"Should fail for an empty password");
});
});
/**
* Test that the doorhanger password field shows plain or * text
* when the checkbox is checked.
*/
add_task(function* test_toggle_password() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: "https://example.com/browser/toolkit/components/" +
"passwordmgr/test/browser/form_basic.html",
}, function* (browser) {
// Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"popupshown",
(event) => event.target == PopupNotifications.panel);
yield ContentTask.spawn(browser, null,
function* () {
let doc = content.document;
doc.getElementById("form-basic-username").value = "username";
doc.getElementById("form-basic-password").value = "p";
doc.getElementById("form-basic").submit();
});
yield promiseShown;
let notificationElement = PopupNotifications.panel.childNodes[0];
let passwordTextbox = notificationElement.querySelector("#password-notification-password");
let toggleCheckbox = notificationElement.querySelector("#password-notification-visibilityToggle");
yield EventUtils.synthesizeMouseAtCenter(toggleCheckbox, {});
Assert.ok(toggleCheckbox.checked);
Assert.equal(passwordTextbox.type, "", "Password textbox changed to plain text");
yield EventUtils.synthesizeMouseAtCenter(toggleCheckbox, {});
Assert.ok(!toggleCheckbox.checked);
Assert.equal(passwordTextbox.type, "password", "Password textbox changed to * text");
});
});
/**
* Test that the doorhanger password toggle checkbox is disabled
* when the master password is set.
*/
add_task(function* test_checkbox_disabled_if_has_master_password() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: "https://example.com/browser/toolkit/components/" +
"passwordmgr/test/browser/form_basic.html",
}, function* (browser) {
// Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"popupshown",
(event) => event.target == PopupNotifications.panel);
LoginTestUtils.masterPassword.enable();
yield ContentTask.spawn(browser, null, function* () {
let doc = content.document;
doc.getElementById("form-basic-username").value = "username";
doc.getElementById("form-basic-password").value = "p";
doc.getElementById("form-basic").submit();
});
yield promiseShown;
let notificationElement = PopupNotifications.panel.childNodes[0];
let passwordTextbox = notificationElement.querySelector("#password-notification-password");
let toggleCheckbox = notificationElement.querySelector("#password-notification-visibilityToggle");
Assert.equal(passwordTextbox.type, "password", "Password textbox should show * text");
Assert.ok(toggleCheckbox.getAttribute("hidden"), "checkbox is hidden when master password is set");
});
LoginTestUtils.masterPassword.disable();
});

View file

@ -0,0 +1,145 @@
/**
* Test changing the password inside the doorhanger notification for passwords.
*
* We check the following cases:
* - Editing the password of a new login.
* - Editing the password of an existing login.
* - Changing both username and password to an existing login.
* - Changing the username to an existing login.
* - Editing username to an empty one and a new password.
*
* If both the username and password matches an already existing login, we should not
* update it's password, but only it's usage timestamp and count.
*/
add_task(function* test_edit_password() {
let testCases = [{
usernameInPage: "username",
passwordInPage: "password",
passwordChangedTo: "newPassword",
timesUsed: 1,
}, {
usernameInPage: "username",
usernameInPageExists: true,
passwordInPage: "password",
passwordInStorage: "oldPassword",
passwordChangedTo: "newPassword",
timesUsed: 2,
}, {
usernameInPage: "username",
usernameChangedTo: "newUsername",
usernameChangedToExists: true,
passwordInPage: "password",
passwordChangedTo: "newPassword",
timesUsed: 2,
}, {
usernameInPage: "username",
usernameChangedTo: "newUsername",
usernameChangedToExists: true,
passwordInPage: "password",
passwordChangedTo: "password",
timesUsed: 2,
checkPasswordNotUpdated: true,
}, {
usernameInPage: "newUsername",
usernameChangedTo: "",
usernameChangedToExists: true,
passwordInPage: "password",
passwordChangedTo: "newPassword",
timesUsed: 2,
}];
for (let testCase of testCases) {
info("Test case: " + JSON.stringify(testCase));
// Create the pre-existing logins when needed.
if (testCase.usernameInPageExists) {
Services.logins.addLogin(LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: testCase.usernameInPage,
password: testCase.passwordInStorage,
}));
}
if (testCase.usernameChangedToExists) {
Services.logins.addLogin(LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: testCase.usernameChangedTo,
password: testCase.passwordChangedTo,
}));
}
yield BrowserTestUtils.withNewTab({
gBrowser,
url: "https://example.com/browser/toolkit/components/" +
"passwordmgr/test/browser/form_basic.html",
}, function* (browser) {
// Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"popupshown",
(event) => event.target == PopupNotifications.panel);
yield ContentTask.spawn(browser, testCase,
function* (contentTestCase) {
let doc = content.document;
doc.getElementById("form-basic-username").value = contentTestCase.usernameInPage;
doc.getElementById("form-basic-password").value = contentTestCase.passwordInPage;
doc.getElementById("form-basic").submit();
});
yield promiseShown;
let notificationElement = PopupNotifications.panel.childNodes[0];
// Style flush to make sure binding is attached
notificationElement.querySelector("#password-notification-password").clientTop;
// Modify the username in the dialog if requested.
if (testCase.usernameChangedTo) {
notificationElement.querySelector("#password-notification-username")
.value = testCase.usernameChangedTo;
}
// Modify the password in the dialog if requested.
if (testCase.passwordChangedTo) {
notificationElement.querySelector("#password-notification-password")
.value = testCase.passwordChangedTo;
}
// We expect a modifyLogin notification if the final username used by the
// dialog exists in the logins database, otherwise an addLogin one.
let expectModifyLogin = typeof testCase.usernameChangedTo !== "undefined"
? testCase.usernameChangedToExists
: testCase.usernameInPageExists;
// Simulate the action on the notification to request the login to be
// saved, and wait for the data to be updated or saved based on the type
// of operation we expect.
let expectedNotification = expectModifyLogin ? "modifyLogin" : "addLogin";
let promiseLogin = TestUtils.topicObserved("passwordmgr-storage-changed",
(_, data) => data == expectedNotification);
notificationElement.button.doCommand();
let [result] = yield promiseLogin;
// Check that the values in the database match the expected values.
let login = expectModifyLogin ? result.QueryInterface(Ci.nsIArray)
.queryElementAt(1, Ci.nsILoginInfo)
: result.QueryInterface(Ci.nsILoginInfo);
Assert.equal(login.username, testCase.usernameChangedTo ||
testCase.usernameInPage);
Assert.equal(login.password, testCase.passwordChangedTo ||
testCase.passwordInPage);
let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
Assert.equal(meta.timesUsed, testCase.timesUsed);
// Check that the password was not updated if the user is empty
if (testCase.checkPasswordNotUpdated) {
Assert.ok(meta.timeLastUsed > meta.timeCreated);
Assert.ok(meta.timeCreated == meta.timePasswordChanged);
}
});
// Clean up the database before the next test case is executed.
Services.logins.removeAllLogins();
}
});

View file

@ -0,0 +1,119 @@
/**
* Test changing the username inside the doorhanger notification for passwords.
*
* We have to test combination of existing and non-existing logins both for
* the original one from the webpage and the final one used by the dialog.
*
* We also check switching to and from empty usernames.
*/
add_task(function* test_edit_username() {
let testCases = [{
usernameInPage: "username",
usernameChangedTo: "newUsername",
}, {
usernameInPage: "username",
usernameInPageExists: true,
usernameChangedTo: "newUsername",
}, {
usernameInPage: "username",
usernameChangedTo: "newUsername",
usernameChangedToExists: true,
}, {
usernameInPage: "username",
usernameInPageExists: true,
usernameChangedTo: "newUsername",
usernameChangedToExists: true,
}, {
usernameInPage: "",
usernameChangedTo: "newUsername",
}, {
usernameInPage: "newUsername",
usernameChangedTo: "",
}, {
usernameInPage: "",
usernameChangedTo: "newUsername",
usernameChangedToExists: true,
}, {
usernameInPage: "newUsername",
usernameChangedTo: "",
usernameChangedToExists: true,
}];
for (let testCase of testCases) {
info("Test case: " + JSON.stringify(testCase));
// Create the pre-existing logins when needed.
if (testCase.usernameInPageExists) {
Services.logins.addLogin(LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: testCase.usernameInPage,
password: "old password",
}));
}
if (testCase.usernameChangedToExists) {
Services.logins.addLogin(LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: testCase.usernameChangedTo,
password: "old password",
}));
}
yield BrowserTestUtils.withNewTab({
gBrowser,
url: "https://example.com/browser/toolkit/components/" +
"passwordmgr/test/browser/form_basic.html",
}, function* (browser) {
// Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
"popupshown",
(event) => event.target == PopupNotifications.panel);
yield ContentTask.spawn(browser, testCase.usernameInPage,
function* (usernameInPage) {
let doc = content.document;
doc.getElementById("form-basic-username").value = usernameInPage;
doc.getElementById("form-basic-password").value = "password";
doc.getElementById("form-basic").submit();
});
yield promiseShown;
let notificationElement = PopupNotifications.panel.childNodes[0];
// Style flush to make sure binding is attached
notificationElement.querySelector("#password-notification-password").clientTop;
// Modify the username in the dialog if requested.
if (testCase.usernameChangedTo) {
notificationElement.querySelector("#password-notification-username")
.value = testCase.usernameChangedTo;
}
// We expect a modifyLogin notification if the final username used by the
// dialog exists in the logins database, otherwise an addLogin one.
let expectModifyLogin = testCase.usernameChangedTo
? testCase.usernameChangedToExists
: testCase.usernameInPageExists;
// Simulate the action on the notification to request the login to be
// saved, and wait for the data to be updated or saved based on the type
// of operation we expect.
let expectedNotification = expectModifyLogin ? "modifyLogin" : "addLogin";
let promiseLogin = TestUtils.topicObserved("passwordmgr-storage-changed",
(_, data) => data == expectedNotification);
notificationElement.button.doCommand();
let [result] = yield promiseLogin;
// Check that the values in the database match the expected values.
let login = expectModifyLogin ? result.QueryInterface(Ci.nsIArray)
.queryElementAt(1, Ci.nsILoginInfo)
: result.QueryInterface(Ci.nsILoginInfo);
Assert.equal(login.username, testCase.usernameChangedTo ||
testCase.usernameInPage);
Assert.equal(login.password, "password");
});
// Clean up the database before the next test case is executed.
Services.logins.removeAllLogins();
}
});

View file

@ -0,0 +1,100 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function test() {
waitForExplicitFinish();
Services.logins.removeAllLogins();
// Add some initial logins
let urls = [
"http://example.com/",
"http://mozilla.org/",
"http://spreadfirefox.com/",
"https://support.mozilla.org/",
"http://hg.mozilla.org/"
];
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let logins = [
new nsLoginInfo(urls[0], urls[0], null, "", "o hai", "u1", "p1"),
new nsLoginInfo(urls[1], urls[1], null, "ehsan", "coded", "u2", "p2"),
new nsLoginInfo(urls[2], urls[2], null, "this", "awesome", "u3", "p3"),
new nsLoginInfo(urls[3], urls[3], null, "array of", "logins", "u4", "p4"),
new nsLoginInfo(urls[4], urls[4], null, "then", "i wrote the test", "u5", "p5")
];
logins.forEach(login => Services.logins.addLogin(login));
// Open the password manager dialog
const PWMGR_DLG = "chrome://passwordmgr/content/passwordManager.xul";
let pwmgrdlg = window.openDialog(PWMGR_DLG, "Toolkit:PasswordManager", "");
SimpleTest.waitForFocus(doTest, pwmgrdlg);
// Test if "Copy Username" and "Copy Password" works
function doTest() {
let doc = pwmgrdlg.document;
let selection = doc.getElementById("signonsTree").view.selection;
let menuitem = doc.getElementById("context-copyusername");
function copyField() {
info("Select all");
selection.selectAll();
assertMenuitemEnabled("copyusername", false);
assertMenuitemEnabled("editusername", false);
assertMenuitemEnabled("copypassword", false);
assertMenuitemEnabled("editpassword", false);
info("Select the first row (with an empty username)");
selection.select(0);
assertMenuitemEnabled("copyusername", false, "empty username");
assertMenuitemEnabled("editusername", true);
assertMenuitemEnabled("copypassword", true);
assertMenuitemEnabled("editpassword", false, "password column hidden");
info("Clear the selection");
selection.clearSelection();
assertMenuitemEnabled("copyusername", false);
assertMenuitemEnabled("editusername", false);
assertMenuitemEnabled("copypassword", false);
assertMenuitemEnabled("editpassword", false);
info("Select the third row and making the password column visible");
selection.select(2);
doc.getElementById("passwordCol").hidden = false;
assertMenuitemEnabled("copyusername", true);
assertMenuitemEnabled("editusername", true);
assertMenuitemEnabled("copypassword", true);
assertMenuitemEnabled("editpassword", true, "password column visible");
menuitem.doCommand();
}
function assertMenuitemEnabled(idSuffix, expected, reason = "") {
doc.defaultView.UpdateContextMenu();
let actual = !doc.getElementById("context-" + idSuffix).getAttribute("disabled");
is(actual, expected, idSuffix + " should be " + (expected ? "enabled" : "disabled") +
(reason ? ": " + reason : ""));
}
function cleanUp() {
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
Services.ww.unregisterNotification(arguments.callee);
Services.logins.removeAllLogins();
doc.getElementById("passwordCol").hidden = true;
finish();
});
pwmgrdlg.close();
}
function testPassword() {
info("Testing Copy Password");
waitForClipboard("coded", function copyPassword() {
menuitem = doc.getElementById("context-copypassword");
menuitem.doCommand();
}, cleanUp, cleanUp);
}
info("Testing Copy Username");
waitForClipboard("ehsan", copyField, testPassword, testPassword);
}
}

View file

@ -0,0 +1,126 @@
const { ContentTaskUtils } = Cu.import("resource://testing-common/ContentTaskUtils.jsm", {});
const PWMGR_DLG = "chrome://passwordmgr/content/passwordManager.xul";
var doc;
var pwmgr;
var pwmgrdlg;
var signonsTree;
function addLogin(site, username, password) {
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let login = new nsLoginInfo(site, site, null, username, password, "u", "p");
Services.logins.addLogin(login);
}
function getUsername(row) {
return signonsTree.view.getCellText(row, signonsTree.columns.getNamedColumn("userCol"));
}
function getPassword(row) {
return signonsTree.view.getCellText(row, signonsTree.columns.getNamedColumn("passwordCol"));
}
function synthesizeDblClickOnCell(aTree, column, row) {
let tbo = aTree.treeBoxObject;
let rect = tbo.getCoordsForCellItem(row, aTree.columns[column], "text");
let x = rect.x + rect.width / 2;
let y = rect.y + rect.height / 2;
// Simulate the double click.
EventUtils.synthesizeMouse(aTree.body, x, y, { clickCount: 2 },
aTree.ownerDocument.defaultView);
}
function* togglePasswords() {
pwmgrdlg.document.querySelector("#togglePasswords").doCommand();
yield new Promise(resolve => waitForFocus(resolve, pwmgrdlg));
}
function* editUsernamePromises(site, oldUsername, newUsername) {
is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login found");
let login = Services.logins.findLogins({}, site, "", "")[0];
is(login.username, oldUsername, "Correct username saved");
is(getUsername(0), oldUsername, "Correct username shown");
synthesizeDblClickOnCell(signonsTree, 1, 0);
yield ContentTaskUtils.waitForCondition(() => signonsTree.getAttribute("editing"),
"Waiting for editing");
EventUtils.sendString(newUsername, pwmgrdlg);
let signonsIntro = doc.querySelector("#signonsIntro");
EventUtils.sendMouseEvent({type: "click"}, signonsIntro, pwmgrdlg);
yield ContentTaskUtils.waitForCondition(() => !signonsTree.getAttribute("editing"),
"Waiting for editing to stop");
is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login replaced");
login = Services.logins.findLogins({}, site, "", "")[0];
is(login.username, newUsername, "Correct username updated");
is(getUsername(0), newUsername, "Correct username shown after the update");
}
function* editPasswordPromises(site, oldPassword, newPassword) {
is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login found");
let login = Services.logins.findLogins({}, site, "", "")[0];
is(login.password, oldPassword, "Correct password saved");
is(getPassword(0), oldPassword, "Correct password shown");
synthesizeDblClickOnCell(signonsTree, 2, 0);
yield ContentTaskUtils.waitForCondition(() => signonsTree.getAttribute("editing"),
"Waiting for editing");
EventUtils.sendString(newPassword, pwmgrdlg);
let signonsIntro = doc.querySelector("#signonsIntro");
EventUtils.sendMouseEvent({type: "click"}, signonsIntro, pwmgrdlg);
yield ContentTaskUtils.waitForCondition(() => !signonsTree.getAttribute("editing"),
"Waiting for editing to stop");
is(Services.logins.findLogins({}, site, "", "").length, 1, "Correct login replaced");
login = Services.logins.findLogins({}, site, "", "")[0];
is(login.password, newPassword, "Correct password updated");
is(getPassword(0), newPassword, "Correct password shown after the update");
}
add_task(function* test_setup() {
registerCleanupFunction(function() {
Services.logins.removeAllLogins();
});
Services.logins.removeAllLogins();
// Open the password manager dialog.
pwmgrdlg = window.openDialog(PWMGR_DLG, "Toolkit:PasswordManager", "");
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
if (aTopic == "domwindowopened") {
let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
SimpleTest.waitForFocus(function() {
EventUtils.sendKey("RETURN", win);
}, win);
} else if (aSubject.location == pwmgrdlg.location && aTopic == "domwindowclosed") {
// Unregister ourself.
Services.ww.unregisterNotification(arguments.callee);
}
});
yield new Promise((resolve) => {
SimpleTest.waitForFocus(() => {
doc = pwmgrdlg.document;
signonsTree = doc.querySelector("#signonsTree");
resolve();
}, pwmgrdlg);
});
});
add_task(function* test_edit_multiple_logins() {
function* testLoginChange(site, oldUsername, oldPassword, newUsername, newPassword) {
addLogin(site, oldUsername, oldPassword);
yield* editUsernamePromises(site, oldUsername, newUsername);
yield* togglePasswords();
yield* editPasswordPromises(site, oldPassword, newPassword);
yield* togglePasswords();
}
yield* testLoginChange("http://c.tn/", "userC", "passC", "usernameC", "passwordC");
yield* testLoginChange("http://b.tn/", "userB", "passB", "usernameB", "passwordB");
yield* testLoginChange("http://a.tn/", "userA", "passA", "usernameA", "passwordA");
pwmgrdlg.close();
});

View file

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function test() {
waitForExplicitFinish();
let pwmgr = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
pwmgr.removeAllLogins();
// add login data
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let login = new nsLoginInfo("http://example.com/", "http://example.com/", null,
"user", "password", "u1", "p1");
pwmgr.addLogin(login);
// Open the password manager dialog
const PWMGR_DLG = "chrome://passwordmgr/content/passwordManager.xul";
let pwmgrdlg = window.openDialog(PWMGR_DLG, "Toolkit:PasswordManager", "");
SimpleTest.waitForFocus(doTest, pwmgrdlg);
function doTest() {
let doc = pwmgrdlg.document;
let signonsTree = doc.querySelector("#signonsTree");
is(signonsTree.view.rowCount, 1, "One entry in the passwords list");
is(signonsTree.view.getCellText(0, signonsTree.columns.getNamedColumn("siteCol")),
"http://example.com/",
"Correct website saved");
is(signonsTree.view.getCellText(0, signonsTree.columns.getNamedColumn("userCol")),
"user",
"Correct user saved");
let timeCreatedCol = doc.getElementById("timeCreatedCol");
is(timeCreatedCol.getAttribute("hidden"), "true",
"Time created column is not displayed");
let timeLastUsedCol = doc.getElementById("timeLastUsedCol");
is(timeLastUsedCol.getAttribute("hidden"), "true",
"Last Used column is not displayed");
let timePasswordChangedCol = doc.getElementById("timePasswordChangedCol");
is(timePasswordChangedCol.getAttribute("hidden"), "",
"Last Changed column is displayed");
// cleanup
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
if (aSubject.location == pwmgrdlg.location && aTopic == "domwindowclosed") {
// unregister ourself
Services.ww.unregisterNotification(arguments.callee);
pwmgr.removeAllLogins();
finish();
}
});
pwmgrdlg.close();
}
}

View file

@ -0,0 +1,129 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
waitForExplicitFinish();
const LOGIN_HOST = "http://example.com";
const LOGIN_COUNT = 5;
let nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
let pmDialog = window.openDialog(
"chrome://passwordmgr/content/passwordManager.xul",
"Toolkit:PasswordManager", "");
let logins = [];
let loginCounter = 0;
let loginOrder = null;
let modifiedLogin;
let testNumber = 0;
let testObserver = {
observe: function (subject, topic, data) {
if (topic == "passwordmgr-dialog-updated") {
switch (testNumber) {
case 1:
case 2:
case 3:
case 4:
case 5:
is(countLogins(), loginCounter, "Verify login added");
ok(getLoginOrder().startsWith(loginOrder), "Verify login order");
runNextTest();
break;
case 6:
is(countLogins(), loginCounter, "Verify login count");
is(getLoginOrder(), loginOrder, "Verify login order");
is(getLoginPassword(), "newpassword0", "Verify login modified");
runNextTest();
break;
case 7:
is(countLogins(), loginCounter, "Verify login removed");
ok(loginOrder.endsWith(getLoginOrder()), "Verify login order");
runNextTest();
break;
case 8:
is(countLogins(), 0, "Verify all logins removed");
runNextTest();
break;
}
}
}
};
SimpleTest.waitForFocus(startTest, pmDialog);
function createLogins() {
let login;
for (let i = 0; i < LOGIN_COUNT; i++) {
login = new nsLoginInfo(LOGIN_HOST + "?n=" + i, LOGIN_HOST + "?n=" + i,
null, "user" + i, "password" + i, "u" + i, "p" + i);
logins.push(login);
}
modifiedLogin = new nsLoginInfo(LOGIN_HOST + "?n=0", LOGIN_HOST + "?n=0",
null, "user0", "newpassword0", "u0", "p0");
is(logins.length, LOGIN_COUNT, "Verify logins created");
}
function countLogins() {
let doc = pmDialog.document;
let signonsTree = doc.getElementById("signonsTree");
return signonsTree.view.rowCount;
}
function getLoginOrder() {
let doc = pmDialog.document;
let signonsTree = doc.getElementById("signonsTree");
let column = signonsTree.columns[0]; // host column
let order = [];
for (let i = 0; i < signonsTree.view.rowCount; i++) {
order.push(signonsTree.view.getCellText(i, column));
}
return order.join(',');
}
function getLoginPassword() {
let doc = pmDialog.document;
let loginsTree = doc.getElementById("signonsTree");
let column = loginsTree.columns[2]; // password column
return loginsTree.view.getCellText(0, column);
}
function startTest() {
Services.obs.addObserver(
testObserver, "passwordmgr-dialog-updated", false);
is(countLogins(), 0, "Verify starts with 0 logins");
createLogins();
runNextTest();
}
function runNextTest() {
switch (++testNumber) {
case 1: // add the logins
for (let i = 0; i < logins.length; i++) {
loginCounter++;
loginOrder = getLoginOrder();
Services.logins.addLogin(logins[i]);
}
break;
case 6: // modify a login
loginOrder = getLoginOrder();
Services.logins.modifyLogin(logins[0], modifiedLogin);
break;
case 7: // remove a login
loginCounter--;
loginOrder = getLoginOrder();
Services.logins.removeLogin(modifiedLogin);
break;
case 8: // remove all logins
Services.logins.removeAllLogins();
break;
case 9: // finish
Services.obs.removeObserver(
testObserver, "passwordmgr-dialog-updated", false);
pmDialog.close();
finish();
break;
}
}
}

View file

@ -0,0 +1,208 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function test() {
waitForExplicitFinish();
let pwmgr = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
pwmgr.removeAllLogins();
// Add some initial logins
let urls = [
"http://example.com/",
"http://example.org/",
"http://mozilla.com/",
"http://mozilla.org/",
"http://spreadfirefox.com/",
"http://planet.mozilla.org/",
"https://developer.mozilla.org/",
"http://hg.mozilla.org/",
"http://dxr.mozilla.org/",
"http://feeds.mozilla.org/",
];
let users = [
"user",
"username",
"ehsan",
"ehsan",
"john",
"what?",
"really?",
"you sure?",
"my user name",
"my username",
];
let pwds = [
"password",
"password",
"mypass",
"mypass",
"smith",
"very secret",
"super secret",
"absolutely",
"mozilla",
"mozilla.com",
];
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
for (let i = 0; i < 10; i++)
pwmgr.addLogin(new nsLoginInfo(urls[i], urls[i], null, users[i], pwds[i],
"u" + (i + 1), "p" + (i + 1)));
// Open the password manager dialog
const PWMGR_DLG = "chrome://passwordmgr/content/passwordManager.xul";
let pwmgrdlg = window.openDialog(PWMGR_DLG, "Toolkit:PasswordManager", "");
SimpleTest.waitForFocus(doTest, pwmgrdlg);
// the meat of the test
function doTest() {
let doc = pwmgrdlg.document;
let win = doc.defaultView;
let sTree = doc.getElementById("signonsTree");
let filter = doc.getElementById("filter");
let siteCol = doc.getElementById("siteCol");
let userCol = doc.getElementById("userCol");
let passwordCol = doc.getElementById("passwordCol");
let toggleCalls = 0;
function toggleShowPasswords(func) {
let toggleButton = doc.getElementById("togglePasswords");
let showMode = (toggleCalls++ % 2) == 0;
// only watch for a confirmation dialog every other time being called
if (showMode) {
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
if (aTopic == "domwindowclosed")
Services.ww.unregisterNotification(arguments.callee);
else if (aTopic == "domwindowopened") {
let targetWin = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
SimpleTest.waitForFocus(function() {
EventUtils.sendKey("RETURN", targetWin);
}, targetWin);
}
});
}
Services.obs.addObserver(function (aSubject, aTopic, aData) {
if (aTopic == "passwordmgr-password-toggle-complete") {
Services.obs.removeObserver(arguments.callee, aTopic);
func();
}
}, "passwordmgr-password-toggle-complete", false);
EventUtils.synthesizeMouse(toggleButton, 1, 1, {}, win);
}
function clickCol(col) {
EventUtils.synthesizeMouse(col, 20, 1, {}, win);
setTimeout(runNextTest, 0);
}
function setFilter(string) {
filter.value = string;
filter.doCommand();
setTimeout(runNextTest, 0);
}
function checkSortMarkers(activeCol) {
let isOk = true;
let col = null;
let hasAttr = false;
let treecols = activeCol.parentNode;
for (let i = 0; i < treecols.childNodes.length; i++) {
col = treecols.childNodes[i];
if (col.nodeName != "treecol")
continue;
hasAttr = col.hasAttribute("sortDirection");
isOk &= col == activeCol ? hasAttr : !hasAttr;
}
ok(isOk, "Only " + activeCol.id + " has a sort marker");
}
function checkSortDirection(col, ascending) {
checkSortMarkers(col);
let direction = ascending ? "ascending" : "descending";
is(col.getAttribute("sortDirection"), direction,
col.id + ": sort direction is " + direction);
}
function checkColumnEntries(aCol, expectedValues) {
let actualValues = getColumnEntries(aCol);
is(actualValues.length, expectedValues.length, "Checking length of expected column");
for (let i = 0; i < expectedValues.length; i++)
is(actualValues[i], expectedValues[i], "Checking column entry #" + i);
}
function getColumnEntries(aCol) {
let entries = [];
let column = sTree.columns[aCol];
let numRows = sTree.view.rowCount;
for (let i = 0; i < numRows; i++)
entries.push(sTree.view.getCellText(i, column));
return entries;
}
let testCounter = 0;
let expectedValues;
function runNextTest() {
switch (testCounter++) {
case 0:
expectedValues = urls.slice().sort();
checkColumnEntries(0, expectedValues);
checkSortDirection(siteCol, true);
// Toggle sort direction on Host column
clickCol(siteCol);
break;
case 1:
expectedValues.reverse();
checkColumnEntries(0, expectedValues);
checkSortDirection(siteCol, false);
// Sort by Username
clickCol(userCol);
break;
case 2:
expectedValues = users.slice().sort();
checkColumnEntries(1, expectedValues);
checkSortDirection(userCol, true);
// Sort by Password
clickCol(passwordCol);
break;
case 3:
expectedValues = pwds.slice().sort();
checkColumnEntries(2, expectedValues);
checkSortDirection(passwordCol, true);
// Set filter
setFilter("moz");
break;
case 4:
expectedValues = [ "absolutely", "mozilla", "mozilla.com",
"mypass", "mypass", "super secret",
"very secret" ];
checkColumnEntries(2, expectedValues);
checkSortDirection(passwordCol, true);
// Reset filter
setFilter("");
break;
case 5:
expectedValues = pwds.slice().sort();
checkColumnEntries(2, expectedValues);
checkSortDirection(passwordCol, true);
// cleanup
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
// unregister ourself
Services.ww.unregisterNotification(arguments.callee);
pwmgr.removeAllLogins();
finish();
});
pwmgrdlg.close();
}
}
// Toggle Show Passwords to display Password column, then start tests
toggleShowPasswords(runNextTest);
}
}

View file

@ -0,0 +1,42 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const PROMPT_URL = "chrome://global/content/commonDialog.xul";
var { interfaces: Ci } = Components;
function test() {
waitForExplicitFinish();
let tab = gBrowser.addTab();
isnot(tab, gBrowser.selectedTab, "New tab shouldn't be selected");
let listener = {
onOpenWindow: function(window) {
var domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
waitForFocus(() => {
is(domwindow.document.location.href, PROMPT_URL, "Should have seen a prompt window");
is(domwindow.args.promptType, "promptUserAndPass", "Should be an authenticate prompt");
is(gBrowser.selectedTab, tab, "Should have selected the new tab");
domwindow.document.documentElement.cancelDialog();
}, domwindow);
},
onCloseWindow: function() {
}
};
Services.wm.addListener(listener);
registerCleanupFunction(() => {
Services.wm.removeListener(listener);
gBrowser.removeTab(tab);
});
tab.linkedBrowser.addEventListener("load", () => {
finish();
}, true);
tab.linkedBrowser.loadURI("http://example.com/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs");
}

View file

@ -0,0 +1,192 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
function test() {
waitForExplicitFinish();
let pwmgr = Cc["@mozilla.org/login-manager;1"].
getService(Ci.nsILoginManager);
pwmgr.removeAllLogins();
// Add some initial logins
let urls = [
"http://example.com/",
"http://example.org/",
"http://mozilla.com/",
"http://mozilla.org/",
"http://spreadfirefox.com/",
"http://planet.mozilla.org/",
"https://developer.mozilla.org/",
"http://hg.mozilla.org/",
"http://dxr.mozilla.org/",
"http://feeds.mozilla.org/",
];
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let logins = [
new nsLoginInfo(urls[0], urls[0], null, "user", "password", "u1", "p1"),
new nsLoginInfo(urls[1], urls[1], null, "username", "password", "u2", "p2"),
new nsLoginInfo(urls[2], urls[2], null, "ehsan", "mypass", "u3", "p3"),
new nsLoginInfo(urls[3], urls[3], null, "ehsan", "mypass", "u4", "p4"),
new nsLoginInfo(urls[4], urls[4], null, "john", "smith", "u5", "p5"),
new nsLoginInfo(urls[5], urls[5], null, "what?", "very secret", "u6", "p6"),
new nsLoginInfo(urls[6], urls[6], null, "really?", "super secret", "u7", "p7"),
new nsLoginInfo(urls[7], urls[7], null, "you sure?", "absolutely", "u8", "p8"),
new nsLoginInfo(urls[8], urls[8], null, "my user name", "mozilla", "u9", "p9"),
new nsLoginInfo(urls[9], urls[9], null, "my username", "mozilla.com", "u10", "p10"),
];
logins.forEach(login => pwmgr.addLogin(login));
// Open the password manager dialog
const PWMGR_DLG = "chrome://passwordmgr/content/passwordManager.xul";
let pwmgrdlg = window.openDialog(PWMGR_DLG, "Toolkit:PasswordManager", "");
SimpleTest.waitForFocus(doTest, pwmgrdlg);
// the meat of the test
function doTest() {
let doc = pwmgrdlg.document;
let win = doc.defaultView;
let filter = doc.getElementById("filter");
let tree = doc.getElementById("signonsTree");
let view = tree.view;
is(filter.value, "", "Filter box should initially be empty");
is(view.rowCount, 10, "There should be 10 passwords initially");
// Prepare a set of tests
// filter: the text entered in the filter search box
// count: the number of logins which should match the respective filter
// count2: the number of logins which should match the respective filter
// if the passwords are being shown as well
// Note: if a test doesn't have count2 set, count is used instead.
let tests = [
{filter: "pass", count: 0, count2: 4},
{filter: "", count: 10}, // test clearing the filter
{filter: "moz", count: 7},
{filter: "mozi", count: 7},
{filter: "mozil", count: 7},
{filter: "mozill", count: 7},
{filter: "mozilla", count: 7},
{filter: "mozilla.com", count: 1, count2: 2},
{filter: "user", count: 4},
{filter: "user ", count: 1},
{filter: " user", count: 2},
{filter: "http", count: 10},
{filter: "https", count: 1},
{filter: "secret", count: 0, count2: 2},
{filter: "secret!", count: 0},
];
let toggleCalls = 0;
function toggleShowPasswords(func) {
let toggleButton = doc.getElementById("togglePasswords");
let showMode = (toggleCalls++ % 2) == 0;
// only watch for a confirmation dialog every other time being called
if (showMode) {
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
if (aTopic == "domwindowclosed")
Services.ww.unregisterNotification(arguments.callee);
else if (aTopic == "domwindowopened") {
let targetWin = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
SimpleTest.waitForFocus(function() {
EventUtils.sendKey("RETURN", targetWin);
}, targetWin);
}
});
}
Services.obs.addObserver(function (aSubject, aTopic, aData) {
if (aTopic == "passwordmgr-password-toggle-complete") {
Services.obs.removeObserver(arguments.callee, aTopic);
func();
}
}, "passwordmgr-password-toggle-complete", false);
EventUtils.synthesizeMouse(toggleButton, 1, 1, {}, win);
}
function runTests(mode, endFunction) {
let testCounter = 0;
function setFilter(string) {
filter.value = string;
filter.doCommand();
}
function runOneTest(testCase) {
function tester() {
is(view.rowCount, expected, expected + " logins should match '" + testCase.filter + "'");
}
let expected;
switch (mode) {
case 1: // without showing passwords
expected = testCase.count;
break;
case 2: // showing passwords
expected = ("count2" in testCase) ? testCase.count2 : testCase.count;
break;
case 3: // toggle
expected = testCase.count;
tester();
toggleShowPasswords(function () {
expected = ("count2" in testCase) ? testCase.count2 : testCase.count;
tester();
toggleShowPasswords(proceed);
});
return;
}
tester();
proceed();
}
function proceed() {
// run the next test if necessary or proceed with the tests
if (testCounter != tests.length)
runNextTest();
else
endFunction();
}
function runNextTest() {
let testCase = tests[testCounter++];
setFilter(testCase.filter);
setTimeout(runOneTest, 0, testCase);
}
runNextTest();
}
function step1() {
runTests(1, step2);
}
function step2() {
toggleShowPasswords(function() {
runTests(2, step3);
});
}
function step3() {
toggleShowPasswords(function() {
runTests(3, lastStep);
});
}
function lastStep() {
// cleanup
Services.ww.registerNotification(function (aSubject, aTopic, aData) {
// unregister ourself
Services.ww.unregisterNotification(arguments.callee);
pwmgr.removeAllLogins();
finish();
});
pwmgrdlg.close();
}
step1();
}
}

View file

@ -0,0 +1,144 @@
/*
* Test username selection dialog, on password update from a p-only form,
* when there are multiple saved logins on the domain.
*/
// Copied from prompt_common.js. TODO: share the code.
function getSelectDialogDoc() {
// Trudge through all the open windows, until we find the one
// that has selectDialog.xul loaded.
var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator);
// var enumerator = wm.getEnumerator("navigator:browser");
var enumerator = wm.getXULWindowEnumerator(null);
while (enumerator.hasMoreElements()) {
var win = enumerator.getNext();
var windowDocShell = win.QueryInterface(Ci.nsIXULWindow).docShell;
var containedDocShells = windowDocShell.getDocShellEnumerator(
Ci.nsIDocShellTreeItem.typeChrome,
Ci.nsIDocShell.ENUMERATE_FORWARDS);
while (containedDocShells.hasMoreElements()) {
// Get the corresponding document for this docshell
var childDocShell = containedDocShells.getNext();
// We don't want it if it's not done loading.
if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
continue;
var childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
.contentViewer
.DOMDocument;
if (childDoc.location.href == "chrome://global/content/selectDialog.xul")
return childDoc;
}
}
return null;
}
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let login1 = new nsLoginInfo("http://example.com", "http://example.com", null,
"notifyu1", "notifyp1", "user", "pass");
let login1B = new nsLoginInfo("http://example.com", "http://example.com", null,
"notifyu1B", "notifyp1B", "user", "pass");
add_task(function* test_changeUPLoginOnPUpdateForm_accept() {
info("Select an u+p login from multiple logins, on password update form, and accept.");
Services.logins.addLogin(login1);
Services.logins.addLogin(login1B);
yield testSubmittingLoginForm("subtst_notifications_change_p.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "pass2", "Checking submitted password");
yield ContentTaskUtils.waitForCondition(() => {
return getSelectDialogDoc();
}, "Wait for selection dialog to be accessible.");
let doc = getSelectDialogDoc();
let dialog = doc.getElementsByTagName("dialog")[0];
let listbox = doc.getElementById("list");
is(listbox.selectedIndex, 0, "Checking selected index");
is(listbox.itemCount, 2, "Checking selected length");
['notifyu1', 'notifyu1B'].forEach((username, i) => {
is(listbox.getItemAtIndex(i).label, username, "Check username selection on dialog");
});
dialog.acceptDialog();
yield ContentTaskUtils.waitForCondition(() => {
return !getSelectDialogDoc();
}, "Wait for selection dialog to disappear.");
});
let logins = Services.logins.getAllLogins();
is(logins.length, 2, "Should have 2 logins");
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username unchanged");
is(login.password, "pass2", "Check the password changed");
is(login.timesUsed, 2, "Check times used");
login = SpecialPowers.wrap(logins[1]).QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1B", "Check the username unchanged");
is(login.password, "notifyp1B", "Check the password unchanged");
is(login.timesUsed, 1, "Check times used");
// cleanup
login1.password = "pass2";
Services.logins.removeLogin(login1);
login1.password = "notifyp1";
Services.logins.removeLogin(login1B);
});
add_task(function* test_changeUPLoginOnPUpdateForm_cancel() {
info("Select an u+p login from multiple logins, on password update form, and cancel.");
Services.logins.addLogin(login1);
Services.logins.addLogin(login1B);
yield testSubmittingLoginForm("subtst_notifications_change_p.html", function*(fieldValues) {
is(fieldValues.username, "null", "Checking submitted username");
is(fieldValues.password, "pass2", "Checking submitted password");
yield ContentTaskUtils.waitForCondition(() => {
return getSelectDialogDoc();
}, "Wait for selection dialog to be accessible.");
let doc = getSelectDialogDoc();
let dialog = doc.getElementsByTagName("dialog")[0];
let listbox = doc.getElementById("list");
is(listbox.selectedIndex, 0, "Checking selected index");
is(listbox.itemCount, 2, "Checking selected length");
['notifyu1', 'notifyu1B'].forEach((username, i) => {
is(listbox.getItemAtIndex(i).label, username, "Check username selection on dialog");
});
dialog.cancelDialog();
yield ContentTaskUtils.waitForCondition(() => {
return !getSelectDialogDoc();
}, "Wait for selection dialog to disappear.");
});
let logins = Services.logins.getAllLogins();
is(logins.length, 2, "Should have 2 logins");
let login = SpecialPowers.wrap(logins[0]).QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1", "Check the username unchanged");
is(login.password, "notifyp1", "Check the password unchanged");
is(login.timesUsed, 1, "Check times used");
login = SpecialPowers.wrap(logins[1]).QueryInterface(Ci.nsILoginMetaInfo);
is(login.username, "notifyu1B", "Check the username unchanged");
is(login.password, "notifyp1B", "Check the password unchanged");
is(login.timesUsed, 1, "Check times used");
// cleanup
Services.logins.removeLogin(login1);
Services.logins.removeLogin(login1B);
});

View file

@ -0,0 +1,10 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head>
<body onload="document.getElementById('form-basic-username').focus();">
<!-- Username field is focused by js onload -->
<form id="form-basic">
<input id="form-basic-username" name="username">
<input id="form-basic-password" name="password" type="password">
<input id="form-basic-submit" type="submit">
</form>
</body></html>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- Simplest form with username and password fields. -->
<form id="form-basic">
<input id="form-basic-username" name="username">
<input id="form-basic-password" name="password" type="password">
<input id="form-basic-submit" type="submit">
</form>
</body></html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<!-- Form in an iframe -->
<iframe src="https://example.org/browser/toolkit/components/passwordmgr/test/browser/form_basic.html" id="test-iframe"></iframe>
</body>
</html>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- Simplest form with username and password fields. -->
<form id="form-basic" action="http://another.domain/custom_action.html">
<input id="form-basic-username" name="username">
<input id="form-basic-password" name="password" type="password">
<input id="form-basic-submit" type="submit">
</form>
</body></html>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- Simplest form with username and password fields. -->
<form id="form-basic" action="https://another.domain/custom_action.html">
<input id="form-basic-username" name="username">
<input id="form-basic-password" name="password" type="password">
<input id="form-basic-submit" type="submit">
</form>
</body></html>

View file

@ -0,0 +1,12 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- Simplest form with username and password fields. -->
<form id="form-basic" action="./custom_action.html">
<input id="form-basic-username" name="username">
<input id="form-basic-password" name="password" type="password">
<input id="form-basic-submit" type="submit">
</form>
</body></html>

View file

@ -0,0 +1,18 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Simplest form with username and password fields. -->
<input id="form-basic-username" name="username">
<input id="form-basic-password" name="password" type="password">
<input id="form-basic-submit" type="submit">
<button id="add">Add input[type=password]</button>
<script>
document.getElementById("add").addEventListener("click", function () {
var node = document.createElement("input");
node.setAttribute("type", "password");
document.querySelector("body").appendChild(node);
});
</script>
</body></html>

View file

@ -0,0 +1,137 @@
const DIRECTORY_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
Cu.import("resource://testing-common/ContentTaskUtils.jsm", this);
registerCleanupFunction(function* cleanup_removeAllLoginsAndResetRecipes() {
Services.logins.removeAllLogins();
let recipeParent = LoginTestUtils.recipes.getRecipeParent();
if (!recipeParent) {
// No need to reset the recipes if the recipe module wasn't even loaded.
return;
}
yield recipeParent.then(recipeParentResult => recipeParentResult.reset());
});
/**
* Loads a test page in `DIRECTORY_URL` which automatically submits to formsubmit.sjs and returns a
* promise resolving with the field values when the optional `aTaskFn` is done.
*
* @param {String} aPageFile - test page file name which auto-submits to formsubmit.sjs
* @param {Function} aTaskFn - task which can be run before the tab closes.
* @param {String} [aOrigin="http://example.com"] - origin of the server to use
* to load `aPageFile`.
*/
function testSubmittingLoginForm(aPageFile, aTaskFn, aOrigin = "http://example.com") {
return BrowserTestUtils.withNewTab({
gBrowser,
url: aOrigin + DIRECTORY_PATH + aPageFile,
}, function*(browser) {
ok(true, "loaded " + aPageFile);
let fieldValues = yield ContentTask.spawn(browser, undefined, function*() {
yield ContentTaskUtils.waitForCondition(() => {
return content.location.pathname.endsWith("/formsubmit.sjs") &&
content.document.readyState == "complete";
}, "Wait for form submission load (formsubmit.sjs)");
let username = content.document.getElementById("user").textContent;
let password = content.document.getElementById("pass").textContent;
return {
username,
password,
};
});
ok(true, "form submission loaded");
if (aTaskFn) {
yield* aTaskFn(fieldValues);
}
return fieldValues;
});
}
function checkOnlyLoginWasUsedTwice({ justChanged }) {
// Check to make sure we updated the timestamps and use count on the
// existing login that was submitted for the test.
let logins = Services.logins.getAllLogins();
is(logins.length, 1, "Should only have 1 login");
ok(logins[0] instanceof Ci.nsILoginMetaInfo, "metainfo QI");
is(logins[0].timesUsed, 2, "check .timesUsed for existing login submission");
ok(logins[0].timeCreated < logins[0].timeLastUsed, "timeLastUsed bumped");
if (justChanged) {
is(logins[0].timeLastUsed, logins[0].timePasswordChanged, "timeLastUsed == timePasswordChanged");
} else {
is(logins[0].timeCreated, logins[0].timePasswordChanged, "timeChanged not updated");
}
}
// Begin popup notification (doorhanger) functions //
const REMEMBER_BUTTON = 0;
const NEVER_BUTTON = 1;
const CHANGE_BUTTON = 0;
const DONT_CHANGE_BUTTON = 1;
/**
* Checks if we have a password capture popup notification
* of the right type and with the right label.
*
* @param {String} aKind The desired `passwordNotificationType`
* @param {Object} [popupNotifications = PopupNotifications]
* @return the found password popup notification.
*/
function getCaptureDoorhanger(aKind, popupNotifications = PopupNotifications) {
ok(true, "Looking for " + aKind + " popup notification");
let notification = popupNotifications.getNotification("password");
if (notification) {
is(notification.options.passwordNotificationType, aKind, "Notification type matches.");
if (aKind == "password-change") {
is(notification.mainAction.label, "Update", "Main action label matches update doorhanger.");
} else if (aKind == "password-save") {
is(notification.mainAction.label, "Remember", "Main action label matches save doorhanger.");
}
}
return notification;
}
/**
* Clicks the specified popup notification button.
*
* @param {Element} aPopup Popup Notification element
* @param {Number} aButtonIndex Number indicating which button to click.
* See the constants in this file.
*/
function clickDoorhangerButton(aPopup, aButtonIndex) {
ok(true, "Looking for action at index " + aButtonIndex);
let notifications = aPopup.owner.panel.childNodes;
ok(notifications.length > 0, "at least one notification displayed");
ok(true, notifications.length + " notification(s)");
let notification = notifications[0];
if (aButtonIndex == 0) {
ok(true, "Triggering main action");
notification.button.doCommand();
} else if (aButtonIndex <= aPopup.secondaryActions.length) {
ok(true, "Triggering secondary action " + aButtonIndex);
notification.childNodes[aButtonIndex].doCommand();
}
}
/**
* Checks the doorhanger's username and password.
*
* @param {String} username The username.
* @param {String} password The password.
*/
function* checkDoorhangerUsernamePassword(username, password) {
yield BrowserTestUtils.waitForCondition(() => {
return document.getElementById("password-notification-username").value == username;
}, "Wait for nsLoginManagerPrompter writeDataToUI()");
is(document.getElementById("password-notification-username").value, username,
"Check doorhanger username");
is(document.getElementById("password-notification-password").value, password,
"Check doorhanger password");
}
// End popup notification (doorhanger) functions //

View file

@ -0,0 +1,9 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- This frame is initially loaded over HTTP. -->
<iframe id="test-iframe"
src="http://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html"/>
</body></html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<form>
<input name="password" type="password">
</form>
<!-- Link to reload this page over HTTPS. -->
<a id="test-link"
href="https://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html">HTTPS</a>
</body></html>

View file

@ -0,0 +1,129 @@
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<form class="test-form"
description="Password only form">
<input id='test-password-1' type='password' name='pname' value=''>
<input type='submit'>Submit</input>
</form>
<form class="test-form"
description="Username only form">
<input id='test-username-1' type='text' name='uname' value=''>
<input type='submit'>Submit</input>
</form>
<form class="test-form"
description="Simple username and password blank form">
<input id='test-username-2' type='text' name='uname' value=''>
<input id='test-password-2' type='password' name='pname' value=''>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Simple username and password form, prefilled username">
<input id='test-username-3' type='text' name='uname' value='testuser'>
<input id='test-password-3' type='password' name='pname' value=''>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Simple username and password form, prefilled username and password">
<input id='test-username-4' type='text' name='uname' value='testuser'>
<input id='test-password-4' type='password' name='pname' value='testpass'>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="One username and two passwords empty form">
<input id='test-username-5' type='text' name='uname'>
<input id='test-password-5' type='password' name='pname'>
<input id='test-password2-5' type='password' name='pname2'>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="One username and two passwords form, fields prefiled">
<input id='test-username-6' type='text' name='uname' value="testuser">
<input id='test-password-6' type='password' name='pname' value="testpass">
<input id='test-password2-6' type='password' name='pname2' value="testpass">
<button type='submit'>Submit</button>
</form>
<div class="test-form"
description="Username and password fields with no form">
<input id='test-username-7' type='text' name='uname' value="testuser">
<input id='test-password-7' type='password' name='pname' value="testpass">
</div>
<form class="test-form"
description="Simple username and password blank form, with disabled password">
<input id='test-username-8' type='text' name='uname' value=''>
<input id='test-password-8' type='password' name='pname' value='' disabled>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Simple username and password blank form, with disabled username">
<input id='test-username-9' type='text' name='uname' value='' disabled>
<input id='test-password-9' type='password' name='pname' value=''>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Simple username and password blank form, with readonly password">
<input id='test-username-10' type='text' name='uname' value=''>
<input id='test-password-10' type='password' name='pname' value='' readonly>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Simple username and password blank form, with readonly username">
<input id='test-username-11' type='text' name='uname' value='' readonly>
<input id='test-password-11' type='password' name='pname' value=''>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Two username and one passwords form, fields prefiled">
<input id='test-username-12' type='text' name='uname' value="testuser">
<input id='test-username2-12' type='text' name='uname2' value="testuser">
<input id='test-password-12' type='password' name='pname' value="testpass">
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Two username and one passwords form, one disabled username field">
<input id='test-username-13' type='text' name='uname'>
<input id='test-username2-13' type='text' name='uname2' disabled>
<input id='test-password-13' type='password' name='pname'>
<button type='submit'>Submit</button>
</form>
<div class="test-form"
description="Second username and password fields with no form">
<input id='test-username-14' type='text' name='uname'>
<input id='test-password-14' type='password' name='pname' expectedFail>
</div>
<!-- Form in an iframe -->
<iframe src="https://example.org/browser/toolkit/components/passwordmgr/test/browser/form_basic.html" id="test-iframe"></iframe>
</body>
</html>

View file

@ -0,0 +1,6 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function handleRequest(request, response) {
response.setHeader("Content-Type", "test/content", false);
}

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications - Basic 1un 1pw</title>
</head>
<body>
<h2>Subtest 1</h2>
<form id="form" action="formsubmit.sjs">
<input id="user" name="user">
<input id="pass" name="pass" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
userField.value = "notifyu1";
passField.value = "notifyp1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications</title>
</head>
<body>
<h2>Subtest 10</h2>
<form id="form" action="formsubmit.sjs">
<input id="pass" name="pass" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
passField.value = "notifyp1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications - Popup Windows</title>
</head>
<body>
<h2>Subtest 11 (popup windows)</h2>
<script>
// Ignore the '?' and split on |
[username, password, features, autoClose] = window.location.search.substring(1).split('|');
var url = "subtst_notifications_11_popup.html?" + username + "|" + password;
var popupWin = window.open(url, "subtst_11", features);
// Popup window will call this function on form submission.
function formSubmitted() {
if (autoClose)
popupWin.close();
}
</script>
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications</title>
</head>
<body>
<h2>Subtest 11</h2>
<form id="form" action="formsubmit.sjs">
<input id="user" name="user">
<input id="pass" name="pass" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
// Get the password from the query string (exclude '?').
[username, password] = window.location.search.substring(1).split('|');
userField.value = username;
passField.value = password;
form.submit();
window.opener.formSubmitted();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications - autocomplete=off on the username field</title>
</head>
<body>
<h2>Subtest 2</h2>
(username autocomplete=off)
<form id="form" action="formsubmit.sjs">
<input id="user" name="user" autocomplete="off">
<input id="pass" name="pass" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
userField.value = "notifyu1";
passField.value = "notifyp1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications with 2 password fields and no username</title>
</head>
<body>
<h2>Subtest 24</h2>
<form id="form" action="formsubmit.sjs">
<input id="pass1" name="pass1" type="password" value="staticpw">
<input id="pass" name="pass" type="password">
<button type="submit">Submit</button>
</form>
<script>
function submitForm() {
pass.value = "notifyp1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var pass = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications with 2 password fields and 1 username field and one other text field before the first password field</title>
</head>
<body>
<h2>1 username field followed by a text field followed by 2 username fields</h2>
<form id="form" action="formsubmit.sjs">
<input id="user" name="user" value="staticpw">
<input id="city" name="city" value="city">
<input id="pass" name="pass" type="password">
<input id="pin" name="pin" type="password" value="static-pin">
<button type="submit">Submit</button>
</form>
<script>
function submitForm() {
userField.value = "notifyu1";
passField.value = "notifyp1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications - autocomplete=off on the password field</title>
</head>
<body>
<h2>Subtest 3</h2>
(password autocomplete=off)
<form id="form" action="formsubmit.sjs">
<input id="user" name="user">
<input id="pass" name="pass" type="password" autocomplete="off">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
userField.value = "notifyu1";
passField.value = "notifyp1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" >
<title>Subtest for Login Manager notifications</title>
</head>
<body>
<h2>Subtest 4</h2>
(form autocomplete=off)
<form id="form" action="formsubmit.sjs" autocomplete="off">
<input id="user" name="user">
<input id="pass" name="pass" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
userField.value = "notifyu1";
passField.value = "notifyp1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications - Form with only a username field</title>
</head>
<body>
<h2>Subtest 5</h2>
<form id="form" action="formsubmit.sjs">
<input id="user" name="user">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
userField.value = "notifyu1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
</script>
</body>
</html>

View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications</title>
</head>
<body>
<h2>Subtest 6</h2>
(password-only form)
<form id="form" action="formsubmit.sjs">
<input id="pass" name="pass" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
passField.value = "notifyp1";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications</title>
</head>
<body>
<h2>Subtest 8</h2>
<form id="form" action="formsubmit.sjs">
<input id="user" name="user">
<input id="pass" name="pass" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
userField.value = "notifyu1";
passField.value = "pass2";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications</title>
</head>
<body>
<h2>Subtest 9</h2>
<form id="form" action="formsubmit.sjs">
<input id="user" name="user">
<input id="pass" name="pass" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
userField.value = "";
passField.value = "pass2";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
</script>
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Subtest for Login Manager notifications</title>
</head>
<body>
<h2>Change password</h2>
<form id="form" action="formsubmit.sjs">
<input id="pass_current" name="pass_current" type="password" value="notifyp1">
<input id="pass" name="pass" type="password">
<input id="pass_confirm" name="pass_confirm" type="password">
<button type='submit'>Submit</button>
</form>
<script>
function submitForm() {
passField.value = "pass2";
passConfirmField.value = "pass2";
form.submit();
}
window.onload = submitForm;
var form = document.getElementById("form");
var userField = document.getElementById("user");
var passField = document.getElementById("pass");
var passConfirmField = document.getElementById("pass_confirm");
</script>
</body>
</html>