Bug 1289913 - Show autocomplete UI on password fields.; r=MattN

MozReview-Commit-ID: LGKM6igKbQB
This commit is contained in:
Sean Lee
2016-11-03 18:07:39 +08:00
parent ffa886adf2
commit 3cc8a23f07
12 changed files with 834 additions and 19 deletions

View File

@@ -2171,6 +2171,9 @@ pref("security.xcto_nosniff_block_images", false);
// OCSP must-staple
pref("security.ssl.enable_ocsp_must_staple", true);
// Insecure Form Field Warning
pref("security.insecure_field_warning.contextual.enabled", false);
// Disable pinning checks by default.
pref("security.cert_pinning.enforcement_level", 0);
// Do not process hpkp headers rooted by not built in roots by default.

View File

@@ -36,6 +36,7 @@ this.LoginHelper = {
formlessCaptureEnabled: Services.prefs.getBoolPref("signon.formlessCapture.enabled"),
schemeUpgrades: Services.prefs.getBoolPref("signon.schemeUpgrades"),
insecureAutofill: Services.prefs.getBoolPref("signon.autofillForms.http"),
showInsecureFieldWarning: Services.prefs.getBoolPref("security.insecure_field_warning.contextual.enabled"),
createLogger(aLogPrefix) {
let getMaxLogLevel = () => {
@@ -59,6 +60,10 @@ this.LoginHelper = {
logger.maxLogLevel = getMaxLogLevel();
}, false);
Services.prefs.addObserver("security.insecure_field_warning.", () => {
this.showInsecureFieldWarning = Services.prefs.getBoolPref("security.insecure_field_warning.contextual.enabled");
}, false);
return logger;
},

View File

@@ -10,7 +10,6 @@ this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
const PREF_INSECURE_FIELD_WARNING_ENABLED = "security.insecure_field_warning.contextual.enabled";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@@ -313,6 +312,7 @@ var LoginManagerContent = {
previousResult: previousResult,
rect: aRect,
isSecure: InsecurePasswordUtils.isFormSecure(form),
isPasswordField: aElement.type == "password",
remote: remote };
return this._sendRequest(messageManager, requestData,
@@ -974,6 +974,8 @@ var LoginManagerContent = {
return;
}
this._formFillService.markAsLoginManagerField(passwordField);
// If the password field is disabled or read-only, there's nothing to do.
if (passwordField.disabled || passwordField.readOnly) {
log("not filling form, password field disabled or read-only");
@@ -1027,8 +1029,9 @@ var LoginManagerContent = {
// Attach autocomplete stuff to the username field, if we have
// one. This is normally used to select from multiple accounts,
// but even with one account we should refill if the user edits.
if (usernameField)
if (usernameField) {
this._formFillService.markAsLoginManagerField(usernameField);
}
// Don't clobber an existing password.
if (passwordField.value && !clobberPassword) {
@@ -1217,7 +1220,7 @@ var LoginUtils = {
};
// nsIAutoCompleteResult implementation
function UserAutoCompleteResult (aSearchString, matchingLogins, {isSecure, messageManager}) {
function UserAutoCompleteResult (aSearchString, matchingLogins, {isSecure, messageManager, isPasswordField}) {
function loginSort(a, b) {
var userA = a.username.toLowerCase();
var userB = b.username.toLowerCase();
@@ -1231,15 +1234,32 @@ function UserAutoCompleteResult (aSearchString, matchingLogins, {isSecure, messa
return 0;
}
let prefShowInsecureFieldWarning =
Preferences.get(PREF_INSECURE_FIELD_WARNING_ENABLED, false);
function findDuplicates(loginList) {
let seen = new Set();
let duplicates = new Set();
for (let login of loginList) {
if (seen.has(login.username)) {
duplicates.add(login.username);
}
seen.add(login.username);
}
return duplicates;
}
this._showInsecureFieldWarning = (!isSecure && prefShowInsecureFieldWarning) ? 1 : 0;
if (!LoginHelper.insecureAutofill && !isSecure) {
matchingLogins = [];
}
this._showInsecureFieldWarning = (!isSecure && LoginHelper.showInsecureFieldWarning) ? 1 : 0;
this.searchString = aSearchString;
this.logins = matchingLogins.sort(loginSort);
this.matchCount = matchingLogins.length + this._showInsecureFieldWarning;
this._messageManager = messageManager;
this._isPasswordField = isPasswordField;
this._stringBundle = Services.strings.createBundle("chrome://passwordmgr/locale/passwordmgr.properties");
this._duplicateUsernames = findDuplicates(matchingLogins);
this._dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
{ day: "numeric", month: "short", year: "numeric" });
if (this.matchCount > 0) {
this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
@@ -1268,25 +1288,50 @@ UserAutoCompleteResult.prototype = {
matchCount : 0,
getValueAt(index) {
if (index < 0 || index >= this.matchCount)
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
if (this._showInsecureFieldWarning && index === 0) {
return "";
}
return this.logins[index - this._showInsecureFieldWarning].username;
let selectedLogin = this.logins[index - this._showInsecureFieldWarning];
return this._isPasswordField ? selectedLogin.password : selectedLogin.username;
},
getLabelAt(index) {
if (index < 0 || index >= this.matchCount)
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
if (this._showInsecureFieldWarning && index === 0) {
return this._stringBundle.GetStringFromName("insecureFieldWarningDescription");
}
return this.logins[index - this._showInsecureFieldWarning].username;
let that = this;
function getLocalizedString(key, formatArgs) {
if (formatArgs) {
return that._stringBundle.formatStringFromName(key, formatArgs, formatArgs.length);
}
return that._stringBundle.GetStringFromName(key);
}
let login = this.logins[index - this._showInsecureFieldWarning];
let username = login.username;
// If login is empty or duplicated we want to append a modification date to it.
if (!username || this._duplicateUsernames.has(username)) {
if (!username) {
username = getLocalizedString("noUsername");
}
let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
let time = this._dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
username = getLocalizedString("loginHostAge", [username, time]);
}
return username;
},
getCommentAt(index) {
@@ -1309,8 +1354,9 @@ UserAutoCompleteResult.prototype = {
},
removeValueAt(index, removeFromDB) {
if (index < 0 || index >= this.matchCount)
if (index < 0 || index >= this.matchCount) {
throw new Error("Index out of range.");
}
if (this._showInsecureFieldWarning && index === 0) {
// Ignore the warning message item.

View File

@@ -227,7 +227,8 @@ var LoginManagerParent = {
doAutocompleteSearch: function({ formOrigin, actionOrigin,
searchString, previousResult,
rect, requestId, isSecure, remote }, target) {
rect, requestId, isSecure, isPasswordField,
remote }, target) {
// Note: previousResult is a regular object, not an
// nsIAutoCompleteResult.
@@ -260,7 +261,11 @@ var LoginManagerParent = {
let match = fullMatch.username;
// Remove results that are too short, or have different prefix.
// Also don't offer empty usernames as possible results.
// Also don't offer empty usernames as possible results except
// for password field.
if (isPasswordField) {
return true;
}
return match && match.toLowerCase().startsWith(searchStringLower);
});

View File

@@ -484,6 +484,11 @@ LoginManager.prototype = {
let form = LoginFormFactory.createFromField(aElement);
let isSecure = InsecurePasswordUtils.isFormSecure(form);
let isPasswordField = aElement.type == "password";
if (isPasswordField) {
// The login items won't be filtered for password field.
aSearchString = "";
}
if (!this._remember) {
setTimeout(function() {
@@ -518,6 +523,7 @@ LoginManager.prototype = {
new UserAutoCompleteResult(aSearchString, logins, {
messageManager,
isSecure,
isPasswordField,
});
aCallback.onSearchCompletion(results);
})

View File

@@ -27,6 +27,8 @@ skip-if = toolkit == 'android' # Bug 1259768
skip-if = toolkit == 'android' # android:autocomplete.
[test_insecure_form_field_autocomplete.html]
skip-if = toolkit == 'android' # android:autocomplete.
[test_password_field_autocomplete.html]
skip-if = toolkit == 'android' # android:autocomplete.
[test_basic_form_html5.html]
[test_basic_form_pwevent.html]
[test_basic_form_pwonly.html]

View File

@@ -209,7 +209,8 @@ function spinEventLoop() {
}
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({"set": [["security.insecure_field_warning.contextual.enabled", false]]});
yield SpecialPowers.pushPrefEnv({"set": [["security.insecure_field_warning.contextual.enabled", false],
["signon.autofillForms.http", true]]});
listenForUnexpectedPopupShown();
});
@@ -222,6 +223,29 @@ add_task(function* test_form1_initial_empty() {
is(popupState.open, false, "Check popup is initially closed");
});
add_task(function* test_form1_menuitems() {
yield SimpleTest.promiseFocus(window);
// Trigger autocomplete popup
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
let results = yield shownPromise;
let popupState = yield getPopupState();
is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
let expectedMenuItems = ["tempuser1",
"testuser2",
"testuser3",
"zzzuser4"];
checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly.");
checkACForm("", ""); // value shouldn't update just by selecting
doKey("return"); // not "enter"!
yield spinEventLoop(); // let focus happen
checkACForm("", "");
});
add_task(function* test_form1_first_entry() {
yield SimpleTest.promiseFocus(window);
// Trigger autocomplete popup

View File

@@ -209,7 +209,8 @@ function spinEventLoop() {
}
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({"set": [["security.insecure_field_warning.contextual.enabled", true]]});
yield SpecialPowers.pushPrefEnv({"set": [["security.insecure_field_warning.contextual.enabled", true],
["signon.autofillForms.http", true]]});
listenForUnexpectedPopupShown();
});
@@ -228,11 +229,18 @@ add_task(function* test_form1_warning_entry() {
restoreForm();
let shownPromise = promiseACShown();
doKey("down"); // open
yield shownPromise;
let results = yield shownPromise;
let popupState = yield getPopupState();
is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
let expectedMenuItems = ["This connection is not secure. Logins entered here could be compromised.",
"tempuser1",
"testuser2",
"testuser3",
"zzzuser4"];
checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly.");
doKey("down"); // select insecure warning
checkACForm("", ""); // value shouldn't update just by selecting
doKey("return"); // not "enter"!

View File

@@ -0,0 +1,267 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test basic login autocomplete</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="satchel_common.js"></script>
<script type="text/javascript" src="pwmgr_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Login Manager test: multiple login autocomplete
<script>
var chromeScript = runChecksAfterCommonInit();
var setupScript = runInParent(function setup() {
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
// Create some logins just for this form, since we'll be deleting them.
var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
assert.ok(nsLoginInfo != null, "nsLoginInfo constructor");
// login0 has no username, so should be filtered out from the autocomplete list.
var login0 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"", "user0pass", "", "pword");
var login1 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"tempuser1", "temppass1", "uname", "pword");
var login2 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"testuser2", "testpass2", "uname", "pword");
var login3 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"testuser3", "testpass3", "uname", "pword");
var login4 = new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"zzzuser4", "zzzpass4", "uname", "pword");
// try/catch in case someone runs the tests manually, twice.
try {
Services.logins.addLogin(login0);
Services.logins.addLogin(login1);
Services.logins.addLogin(login2);
Services.logins.addLogin(login3);
Services.logins.addLogin(login4);
} catch (e) {
assert.ok(false, "addLogin threw: " + e);
}
addMessageListener("addLogin", loginVariableName => {
let login = eval(loginVariableName);
assert.ok(!!login, "Login to add is defined: " + loginVariableName);
Services.logins.addLogin(login);
});
addMessageListener("removeLogin", loginVariableName => {
let login = eval(loginVariableName);
assert.ok(!!login, "Login to delete is defined: " + loginVariableName);
Services.logins.removeLogin(login);
});
});
</script>
<p id="display"></p>
<!-- we presumably can't hide the content for this test. -->
<div id="content">
<!-- form1 tests multiple matching logins -->
<form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;">
<input type="text" name="uname">
<input type="password" name="pword">
<button type="submit">Submit</button>
</form>
<form id="form2" action="http://autocomplete:8888/formtest.js" onsubmit="return false;">
<input type="text" name="uname">
<input type="password" name="pword" readonly="true">
<button type="submit">Submit</button>
</form>
<form id="form3" action="http://autocomplete:8888/formtest.js" onsubmit="return false;">
<input type="text" name="uname">
<input type="password" name="pword" disabled="true">
<button type="submit">Submit</button>
</form>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Login Manager: multiple login autocomplete. **/
var uname = $_(1, "uname");
var pword = $_(1, "pword");
const shiftModifier = SpecialPowers.Ci.nsIDOMEvent.SHIFT_MASK;
// Restore the form to the default state.
function restoreForm(index) {
// Using innerHTML is for creating the autocomplete popup again, so the
// preference value will be applied to the constructor of
// UserAutoCompleteResult.
let form = document.getElementById("form" + index);
let temp = form.innerHTML;
form.innerHTML = "";
form.innerHTML = temp;
uname = $_(index, "uname");
pword = $_(index, "pword");
uname.value = "";
pword.value = "";
pword.focus();
}
function generateDateString(date) {
let dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
{ day: "numeric", month: "short", year: "numeric" });
return dateAndTimeFormatter.format(date);
}
const DATE_NOW_STRING = generateDateString(new Date());
// Check for expected username/password in form.
function checkACFormPasswordField(expectedPassword) {
var formID = uname.parentNode.id;
is(pword.value, expectedPassword, "Checking " + formID + " password is: " + expectedPassword);
}
function spinEventLoop() {
return Promise.resolve();
}
add_task(function* setup() {
listenForUnexpectedPopupShown();
});
add_task(function* test_form1_initial_empty() {
yield SimpleTest.promiseFocus(window);
// Make sure initial form is empty.
checkACFormPasswordField("");
let popupState = yield getPopupState();
is(popupState.open, false, "Check popup is initially closed");
});
add_task(function* test_form1_enabledInsecureFieldWarning_enabledInsecureAutoFillForm() {
yield SpecialPowers.pushPrefEnv({"set": [
["security.insecure_field_warning.contextual.enabled", true],
["signon.autofillForms.http", true]
]});
restoreForm(1);
yield SimpleTest.promiseFocus(window);
// Trigger autocomplete popup
let shownPromise = promiseACShown();
doKey("down"); // open
let results = yield shownPromise;
let popupState = yield getPopupState();
is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
let expectedMenuItems = ["This connection is not secure. Logins entered here could be compromised.",
"No username (" + DATE_NOW_STRING + ")",
"tempuser1",
"testuser2",
"testuser3",
"zzzuser4"];
checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly.");
doKey("down"); // select insecure warning
checkACFormPasswordField(""); // value shouldn't update just by selecting
doKey("return"); // not "enter"!
yield spinEventLoop(); // let focus happen
checkACFormPasswordField("");
});
add_task(function* test_form1_disabledInsecureFieldWarning_enabledInsecureAutoFillForm() {
yield SpecialPowers.pushPrefEnv({"set": [
["security.insecure_field_warning.contextual.enabled", false],
["signon.autofillForms.http", true]
]});
restoreForm(1);
yield SimpleTest.promiseFocus(window);
// Trigger autocomplete popup
let shownPromise = promiseACShown();
doKey("down"); // open
let results = yield shownPromise;
let popupState = yield getPopupState();
is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
let expectedMenuItems = ["No username (" + DATE_NOW_STRING + ")",
"tempuser1",
"testuser2",
"testuser3",
"zzzuser4"];
checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly.");
doKey("down"); // select first item
checkACFormPasswordField(""); // value shouldn't update just by selecting
doKey("return"); // not "enter"!
yield spinEventLoop(); // let focus happen
checkACFormPasswordField("user0pass");
});
add_task(function* test_form1_enabledInsecureFieldWarning_disabledInsecureAutoFillForm() {
yield SpecialPowers.pushPrefEnv({"set": [
["security.insecure_field_warning.contextual.enabled", true],
["signon.autofillForms.http", false]
]});
restoreForm(1);
yield SimpleTest.promiseFocus(window);
// Trigger autocomplete popup
let shownPromise = promiseACShown();
doKey("down"); // open
let results = yield shownPromise;
let popupState = yield getPopupState();
is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
let expectedMenuItems = ["This connection is not secure. Logins entered here could be compromised."];
checkArrayValues(results, expectedMenuItems, "Check all menuitems are displayed correctly.");
doKey("down"); // select insecure warning
checkACFormPasswordField(""); // value shouldn't update just by selecting
doKey("return"); // not "enter"!
yield spinEventLoop(); // let focus happen
checkACFormPasswordField("");
});
add_task(function* test_form1_disabledInsecureFieldWarning_disabledInsecureAutoFillForm() {
yield SpecialPowers.pushPrefEnv({"set": [
["security.insecure_field_warning.contextual.enabled", false],
["signon.autofillForms.http", false]
]});
restoreForm(1);
yield SimpleTest.promiseFocus(window);
// Trigger autocomplete popup
doKey("down"); // open
let popupState = yield getPopupState();
is(popupState.open, false, "Check popup is closed with no AutoFillForms.");
});
add_task(function* test_form2_password_readonly() {
// Trigger autocomplete popup
restoreForm(2);
doKey("down"); // open
let popupState = yield getPopupState();
is(popupState.open, false, "Check popup is closed for a readonly field.");
});
add_task(function* test_form3_password_disabled() {
// Trigger autocomplete popup
restoreForm(3);
doKey("down"); // open
let popupState = yield getPopupState();
is(popupState.open, false, "Check popup is closed for a disabled field.");
});
</script>
</pre>
</body>
</html>

View File

@@ -0,0 +1,448 @@
XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
Cu.import("resource://gre/modules/LoginManagerContent.jsm");
var nsLoginInfo = Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
const PREF_INSECURE_FIELD_WARNING_ENABLED = "security.insecure_field_warning.contextual.enabled";
const PREF_INSECURE_AUTOFILLFORMS_ENABLED = "signon.autofillForms.http";
let matchingLogins = [];
matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"", "emptypass1", "uname", "pword"));
matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"tempuser1", "temppass1", "uname", "pword"));
matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"testuser2", "testpass2", "uname", "pword"));
matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"testuser3", "testpass3", "uname", "pword"));
matchingLogins.push(new nsLoginInfo("http://mochi.test:8888", "http://autocomplete:8888", null,
"zzzuser4", "zzzpass4", "uname", "pword"));
let meta = matchingLogins[0].QueryInterface(Ci.nsILoginMetaInfo);
let dateAndTimeFormatter = new Intl.DateTimeFormat(undefined,
{ day: "numeric", month: "short", year: "numeric" });
let time = dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
const LABEL_NO_USERNAME = "No username (" + time + ")";
let expectedResults = [
{
insecureFieldWarningEnabled: true,
insecureAutoFillFormsEnabled: true,
isSecure: true,
isPasswordField: false,
matchingLogins: matchingLogins,
items: [{
value: "",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "tempuser1",
label: "tempuser1",
style: ""
}, {
value: "testuser2",
label: "testuser2",
style: ""
}, {
value: "testuser3",
label: "testuser3",
style: ""
}, {
value: "zzzuser4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: true,
insecureAutoFillFormsEnabled: true,
isSecure: false,
isPasswordField: false,
matchingLogins: matchingLogins,
items: [{
value: "",
label: "This connection is not secure. Logins entered here could be compromised.",
style: "insecureWarning"
}, {
value: "",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "tempuser1",
label: "tempuser1",
style: ""
}, {
value: "testuser2",
label: "testuser2",
style: ""
}, {
value: "testuser3",
label: "testuser3",
style: ""
}, {
value: "zzzuser4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: true,
insecureAutoFillFormsEnabled: true,
isSecure: true,
isPasswordField: true,
matchingLogins: matchingLogins,
items: [{
value: "emptypass1",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "temppass1",
label: "tempuser1",
style: ""
}, {
value: "testpass2",
label: "testuser2",
style: ""
}, {
value: "testpass3",
label: "testuser3",
style: ""
}, {
value: "zzzpass4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: true,
insecureAutoFillFormsEnabled: true,
isSecure: false,
isPasswordField: true,
matchingLogins: matchingLogins,
items: [{
value: "",
label: "This connection is not secure. Logins entered here could be compromised.",
style: "insecureWarning"
}, {
value: "emptypass1",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "temppass1",
label: "tempuser1",
style: ""
}, {
value: "testpass2",
label: "testuser2",
style: ""
}, {
value: "testpass3",
label: "testuser3",
style: ""
}, {
value: "zzzpass4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: false,
insecureAutoFillFormsEnabled: true,
isSecure: true,
isPasswordField: false,
matchingLogins: matchingLogins,
items: [{
value: "",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "tempuser1",
label: "tempuser1",
style: ""
}, {
value: "testuser2",
label: "testuser2",
style: ""
}, {
value: "testuser3",
label: "testuser3",
style: ""
}, {
value: "zzzuser4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: false,
insecureAutoFillFormsEnabled: true,
isSecure: false,
isPasswordField: false,
matchingLogins: matchingLogins,
items: [{
value: "",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "tempuser1",
label: "tempuser1",
style: ""
}, {
value: "testuser2",
label: "testuser2",
style: ""
}, {
value: "testuser3",
label: "testuser3",
style: ""
}, {
value: "zzzuser4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: false,
insecureAutoFillFormsEnabled: true,
isSecure: true,
isPasswordField: true,
matchingLogins: matchingLogins,
items: [{
value: "emptypass1",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "temppass1",
label: "tempuser1",
style: ""
}, {
value: "testpass2",
label: "testuser2",
style: ""
}, {
value: "testpass3",
label: "testuser3",
style: ""
}, {
value: "zzzpass4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: false,
insecureAutoFillFormsEnabled: true,
isSecure: false,
isPasswordField: true,
matchingLogins: matchingLogins,
items: [{
value: "emptypass1",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "temppass1",
label: "tempuser1",
style: ""
}, {
value: "testpass2",
label: "testuser2",
style: ""
}, {
value: "testpass3",
label: "testuser3",
style: ""
}, {
value: "zzzpass4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: true,
insecureAutoFillFormsEnabled: false,
isSecure: true,
isPasswordField: false,
matchingLogins: matchingLogins,
items: [{
value: "",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "tempuser1",
label: "tempuser1",
style: ""
}, {
value: "testuser2",
label: "testuser2",
style: ""
}, {
value: "testuser3",
label: "testuser3",
style: ""
}, {
value: "zzzuser4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: true,
insecureAutoFillFormsEnabled: false,
isSecure: false,
isPasswordField: false,
matchingLogins: matchingLogins,
items: [{
value: "",
label: "This connection is not secure. Logins entered here could be compromised.",
style: "insecureWarning"
}]
},
{
insecureFieldWarningEnabled: true,
insecureAutoFillFormsEnabled: false,
isSecure: true,
isPasswordField: true,
matchingLogins: matchingLogins,
items: [{
value: "emptypass1",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "temppass1",
label: "tempuser1",
style: ""
}, {
value: "testpass2",
label: "testuser2",
style: ""
}, {
value: "testpass3",
label: "testuser3",
style: ""
}, {
value: "zzzpass4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: true,
insecureAutoFillFormsEnabled: false,
isSecure: false,
isPasswordField: true,
matchingLogins: matchingLogins,
items: [{
value: "",
label: "This connection is not secure. Logins entered here could be compromised.",
style: "insecureWarning"
}]
},
{
insecureFieldWarningEnabled: false,
insecureAutoFillFormsEnabled: false,
isSecure: true,
isPasswordField: false,
matchingLogins: matchingLogins,
items: [{
value: "",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "tempuser1",
label: "tempuser1",
style: ""
}, {
value: "testuser2",
label: "testuser2",
style: ""
}, {
value: "testuser3",
label: "testuser3",
style: ""
}, {
value: "zzzuser4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: false,
insecureAutoFillFormsEnabled: false,
isSecure: false,
isPasswordField: false,
matchingLogins: matchingLogins,
items: []
},
{
insecureFieldWarningEnabled: false,
insecureAutoFillFormsEnabled: false,
isSecure: true,
isPasswordField: true,
matchingLogins: matchingLogins,
items: [{
value: "emptypass1",
label: LABEL_NO_USERNAME,
style: ""
}, {
value: "temppass1",
label: "tempuser1",
style: ""
}, {
value: "testpass2",
label: "testuser2",
style: ""
}, {
value: "testpass3",
label: "testuser3",
style: ""
}, {
value: "zzzpass4",
label: "zzzuser4",
style: ""
}]
},
{
insecureFieldWarningEnabled: false,
insecureAutoFillFormsEnabled: false,
isSecure: false,
isPasswordField: true,
matchingLogins: matchingLogins,
items: []
},
];
add_task(function* test_all_patterns() {
LoginHelper.createLogger("UserAutoCompleteResult");
expectedResults.forEach(pattern => {
Services.prefs.setBoolPref(PREF_INSECURE_FIELD_WARNING_ENABLED,
pattern.insecureFieldWarningEnabled);
Services.prefs.setBoolPref(PREF_INSECURE_AUTOFILLFORMS_ENABLED,
pattern.insecureAutoFillFormsEnabled);
let actual = new UserAutoCompleteResult("", pattern.matchingLogins,
{
isSecure: pattern.isSecure,
isPasswordField: pattern.isPasswordField
});
pattern.items.forEach((item, index) => {
equal(actual.getValueAt(index), item.value);
equal(actual.getLabelAt(index), item.label);
equal(actual.getStyleAt(index), item.style);
});
if (pattern.items.length != 0) {
Assert.throws(() => actual.getValueAt(pattern.items.length),
/Index out of range\./);
Assert.throws(() => actual.getLabelAt(pattern.items.length),
/Index out of range\./);
Assert.throws(() => actual.removeValueAt(pattern.items.length, true),
/Index out of range\./);
}
});
});

View File

@@ -31,6 +31,7 @@ run-if = buildapp == "browser"
[test_logins_change.js]
[test_logins_decrypt_failure.js]
skip-if = os == "android" # Bug 1171687: Needs fixing on Android
[test_user_autocomplete_result.js]
[test_logins_metainfo.js]
[test_logins_search.js]
[test_notifications.js]

View File

@@ -900,7 +900,7 @@ nsFormFillController::MaybeStartControllingInput(nsIDOMHTMLInputElement* aInput)
return;
nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aInput);
if (!formControl || !formControl->IsSingleLineTextControl(true))
if (!formControl || !formControl->IsSingleLineTextControl(false))
return;
bool isReadOnly = false;