Bug 499649: fix auto-filling behavior when text doesn't match stored username case, r=dolske

--HG--
rename : toolkit/components/passwordmgr/test/test_basic_form_autocomplete.html => toolkit/components/passwordmgr/test/test_case_differences.html
This commit is contained in:
Gavin Sharp 2014-02-24 21:39:16 -08:00
Родитель 204f8dfcc3
Коммит 472ffc6a96
4 изменённых файлов: 263 добавлений и 13 удалений

Просмотреть файл

@ -94,7 +94,11 @@ var LoginManagerContent = {
return this.__formFillService; return this.__formFillService;
}, },
/*
* onFormPassword
*
* Called when an <input type="password"> element is added to the page
*/
onFormPassword: function (event) { onFormPassword: function (event) {
if (!event.isTrusted) if (!event.isTrusted)
return; return;
@ -145,7 +149,7 @@ var LoginManagerContent = {
let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isWindowPrivate(doc.defaultView); let autofillForm = gAutofillForms && !PrivateBrowsingUtils.isWindowPrivate(doc.defaultView);
this._fillForm(form, autofillForm, false, false, null); this._fillForm(form, autofillForm, false, false, false, null);
}, },
@ -192,7 +196,7 @@ var LoginManagerContent = {
if (!Services.logins.isLoggedIn) if (!Services.logins.isLoggedIn)
return; return;
this._fillForm(acForm, true, true, true, null); this._fillForm(acForm, true, true, true, true, null);
} else { } else {
// Ignore the event, it's for some input we don't care about. // Ignore the event, it's for some input we don't care about.
} }
@ -544,13 +548,17 @@ var LoginManagerContent = {
* an array of logins if not given any, otherwise it will use the logins * an array of logins if not given any, otherwise it will use the logins
* passed in. The logins are returned so they can be reused for * passed in. The logins are returned so they can be reused for
* optimization. Success of action is also returned in format * optimization. Success of action is also returned in format
* [success, foundLogins]. autofillForm denotes if we should fill the form * [success, foundLogins].
* in automatically, ignoreAutocomplete denotes if we should ignore *
* autocomplete=off attributes, and foundLogins is an array of nsILoginInfo * - autofillForm denotes if we should fill the form in automatically
* for optimization * - ignoreAutocomplete denotes if we should ignore autocomplete=off
* attributes
* - userTriggered is an indication of whether this filling was triggered by
* the user
* - foundLogins is an array of nsILoginInfo for optimization
*/ */
_fillForm : function (form, autofillForm, ignoreAutocomplete, _fillForm : function (form, autofillForm, ignoreAutocomplete,
clobberPassword, foundLogins) { clobberPassword, userTriggered, foundLogins) {
// Heuristically determine what the user/pass fields are // Heuristically determine what the user/pass fields are
// We do this before checking to see if logins are stored, // We do this before checking to see if logins are stored,
// so that the user isn't prompted for a master password // so that the user isn't prompted for a master password
@ -652,7 +660,16 @@ var LoginManagerContent = {
let matchingLogins = logins.filter(function(l) let matchingLogins = logins.filter(function(l)
l.username.toLowerCase() == username); l.username.toLowerCase() == username);
if (matchingLogins.length) { if (matchingLogins.length) {
// If there are multiple, and one matches case, use it
for (let l of matchingLogins) {
if (l.username == usernameField.value) {
selectedLogin = l;
}
}
// Otherwise just use the first
if (!selectedLogin) {
selectedLogin = matchingLogins[0]; selectedLogin = matchingLogins[0];
}
} else { } else {
didntFillReason = "existingUsername"; didntFillReason = "existingUsername";
log("Password not filled. None of the stored logins match the username already present."); log("Password not filled. None of the stored logins match the username already present.");
@ -680,9 +697,24 @@ var LoginManagerContent = {
var didFillForm = false; var didFillForm = false;
if (selectedLogin && autofillForm && !isFormDisabled) { if (selectedLogin && autofillForm && !isFormDisabled) {
// Fill the form // Fill the form
if (usernameField) {
// Don't modify the username field if it's disabled or readOnly so we preserve its case. // Don't modify the username field if it's disabled or readOnly so we preserve its case.
if (usernameField && !(usernameField.disabled || usernameField.readOnly)) let disabledOrReadOnly = usernameField.disabled || usernameField.readOnly;
// Don't replace the username if it differs only in case, and the user triggered
// this autocomplete. We assume that if it was user-triggered the entered text
// is desired.
dump("field value: " + usernameField.value + "\n");
dump("selectedLogin value: " + selectedLogin.username + "\n");
let userEnteredDifferentCase = userTriggered &&
(usernameField.value != selectedLogin.username &&
usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase());
if (!disabledOrReadOnly && !userEnteredDifferentCase) {
usernameField.value = selectedLogin.username; usernameField.value = selectedLogin.username;
}
}
passwordField.value = selectedLogin.password; passwordField.value = selectedLogin.password;
didFillForm = true; didFillForm = true;
} else if (selectedLogin && !autofillForm) { } else if (selectedLogin && !autofillForm) {

Просмотреть файл

@ -541,7 +541,7 @@ LoginManager.prototype = {
*/ */
fillForm : function (form) { fillForm : function (form) {
log("fillForm processing form[ id:", form.id, "]"); log("fillForm processing form[ id:", form.id, "]");
return LoginManagerContent._fillForm(form, true, true, false, null)[0]; return LoginManagerContent._fillForm(form, true, true, false, false, null)[0];
}, },
}; // end of LoginManager implementation }; // end of LoginManager implementation

Просмотреть файл

@ -37,6 +37,7 @@ support-files =
[test_basic_form_3pw_1.html] [test_basic_form_3pw_1.html]
[test_basic_form_autocomplete.html] [test_basic_form_autocomplete.html]
skip-if = toolkit == 'android' skip-if = toolkit == 'android'
[test_case_differences.html]
[test_basic_form_html5.html] [test_basic_form_html5.html]
[test_basic_form_observer_autocomplete.html] [test_basic_form_observer_autocomplete.html]
[test_basic_form_observer_autofillForms.html] [test_basic_form_observer_autofillForms.html]

Просмотреть файл

@ -0,0 +1,217 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for Login Manager</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="text/javascript" src="pwmgr_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
Login Manager test: multiple login autocomplete
<script>
commonInit();
SimpleTest.waitForExplicitFinish();
// Get the pwmgr service
var pwmgr = SpecialPowers.Cc["@mozilla.org/login-manager;1"]
.getService(SpecialPowers.Ci.nsILoginManager);
ok(pwmgr != null, "nsLoginManager service");
// Create some logins just for this form, since we'll be deleting them.
var nsLoginInfo =
SpecialPowers.wrap(SpecialPowers.Components).Constructor("@mozilla.org/login-manager/loginInfo;1",
SpecialPowers.Ci.nsILoginInfo, "init");
ok(nsLoginInfo != null, "nsLoginInfo constructor");
var login0 = new nsLoginInfo(
"http://mochi.test:8888", "http://autocomplete:8888", null,
"name", "pass", "uname", "pword");
var login1 = new nsLoginInfo(
"http://mochi.test:8888", "http://autocomplete:8888", null,
"Name", "Pass", "uname", "pword");
var login2 = new nsLoginInfo(
"http://mochi.test:8888", "http://autocomplete:8888", null,
"USER", "PASS", "uname", "pword");
try {
pwmgr.addLogin(login0);
pwmgr.addLogin(login1);
pwmgr.addLogin(login2);
} catch (e) {
ok(false, "addLogin threw: " + e);
}
</script>
<p id="display"></p>
<!-- we presumably can't hide the content for this test. -->
<div id="content">
<!-- form1 tests multiple matching logins -->
<form id="form1" action="http://autocomplete:8888/formtest.js" onsubmit="return false;">
<input type="text" name="uname">
<input type="password" name="pword">
<button type="submit">Submit</button>
</form>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Login Manager: multiple login autocomplete. **/
var uname = $_(1, "uname");
var pword = $_(1, "pword");
// Restore the form to the default state.
function restoreForm() {
uname.value = "";
pword.value = "";
uname.focus();
}
// Check for expected username/password in form.
function checkACForm(expectedUsername, expectedPassword) {
var formID = uname.parentNode.id;
is(uname.value, expectedUsername, "Checking " + formID + " username");
is(pword.value, expectedPassword, "Checking " + formID + " password");
}
function sendFakeAutocompleteEvent(element) {
var acEvent = document.createEvent("HTMLEvents");
acEvent.initEvent("DOMAutoComplete", true, false);
element.dispatchEvent(acEvent);
}
var gLastTest = 6;
function addPopupListener(eventName, func, capture) {
autocompletePopup.addEventListener(eventName, func, capture);
}
function removePopupListener(eventName, func, capture) {
autocompletePopup.removeEventListener(eventName, func, capture);
}
/*
* Main section of test...
*
* This is a bit hacky, because the events are either being sent or
* processes asynchronously, so we need to interrupt our flow with lots of
* setTimeout() calls. The case statements are executed in order, one per
* timeout.
*/
function runTest(testNum) {
ok(true, "Starting test #" + testNum);
addPopupListener("popupshown", function() {
removePopupListener("popupshown", arguments.callee, false);
if (testNum != gLastTest) {
window.setTimeout(runTest, 0, testNum + 1);
}
}, false);
switch(testNum) {
case 1:
// Make sure initial form is empty.
checkACForm("", "");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 2:
// Check first entry
doKey("down");
checkACForm("", ""); // value shouldn't update
doKey("return"); // not "enter"!
checkACForm("name", "pass");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 3:
// Check second entry
doKey("down");
doKey("down");
doKey("return"); // not "enter"!
checkACForm("Name", "Pass");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 4:
// Check third entry
doKey("down");
doKey("down");
doKey("down");
doKey("return");
checkACForm("USER", "PASS");
// Trigger autocomplete popup
restoreForm();
uname.value = "user";
doKey("down");
break;
case 5:
// Check that we don't clobber user-entered text when tabbing away
doKey("tab");
checkACForm("user", "PASS");
// Trigger autocomplete popup
restoreForm();
doKey("down");
break;
case 6:
SimpleTest.finish();
return;
default:
ok(false, "Unexpected invocation of test #" + testNum);
SimpleTest.finish();
return;
}
}
var autocompletePopup;
function startTest() {
var Ci = SpecialPowers.Ci;
chromeWin = SpecialPowers.wrap(window)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
// shouldn't reach into browser internals like this and
// shouldn't assume ID is consistent across products
autocompletePopup = chromeWin.document.getElementById("PopupAutoComplete");
ok(autocompletePopup, "Got autocomplete popup");
runTest(1);
}
window.onload = startTest;
</script>
</pre>
</body>
</html>