зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
204f8dfcc3
Коммит
472ffc6a96
|
@ -94,7 +94,11 @@ var LoginManagerContent = {
|
|||
return this.__formFillService;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* onFormPassword
|
||||
*
|
||||
* Called when an <input type="password"> element is added to the page
|
||||
*/
|
||||
onFormPassword: function (event) {
|
||||
if (!event.isTrusted)
|
||||
return;
|
||||
|
@ -145,7 +149,7 @@ var LoginManagerContent = {
|
|||
|
||||
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)
|
||||
return;
|
||||
|
||||
this._fillForm(acForm, true, true, true, null);
|
||||
this._fillForm(acForm, true, true, true, true, null);
|
||||
} else {
|
||||
// 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
|
||||
* passed in. The logins are returned so they can be reused for
|
||||
* optimization. Success of action is also returned in format
|
||||
* [success, foundLogins]. autofillForm denotes if we should fill the form
|
||||
* in automatically, ignoreAutocomplete denotes if we should ignore
|
||||
* autocomplete=off attributes, and foundLogins is an array of nsILoginInfo
|
||||
* for optimization
|
||||
* [success, foundLogins].
|
||||
*
|
||||
* - autofillForm denotes if we should fill the form in automatically
|
||||
* - 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,
|
||||
clobberPassword, foundLogins) {
|
||||
clobberPassword, userTriggered, foundLogins) {
|
||||
// Heuristically determine what the user/pass fields are
|
||||
// We do this before checking to see if logins are stored,
|
||||
// so that the user isn't prompted for a master password
|
||||
|
@ -652,7 +660,16 @@ var LoginManagerContent = {
|
|||
let matchingLogins = logins.filter(function(l)
|
||||
l.username.toLowerCase() == username);
|
||||
if (matchingLogins.length) {
|
||||
selectedLogin = matchingLogins[0];
|
||||
// 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];
|
||||
}
|
||||
} else {
|
||||
didntFillReason = "existingUsername";
|
||||
log("Password not filled. None of the stored logins match the username already present.");
|
||||
|
@ -680,9 +697,24 @@ var LoginManagerContent = {
|
|||
var didFillForm = false;
|
||||
if (selectedLogin && autofillForm && !isFormDisabled) {
|
||||
// Fill the form
|
||||
// Don't modify the username field if it's disabled or readOnly so we preserve its case.
|
||||
if (usernameField && !(usernameField.disabled || usernameField.readOnly))
|
||||
usernameField.value = selectedLogin.username;
|
||||
|
||||
if (usernameField) {
|
||||
// Don't modify the username field if it's disabled or readOnly so we preserve its case.
|
||||
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;
|
||||
}
|
||||
}
|
||||
passwordField.value = selectedLogin.password;
|
||||
didFillForm = true;
|
||||
} else if (selectedLogin && !autofillForm) {
|
||||
|
|
|
@ -541,7 +541,7 @@ LoginManager.prototype = {
|
|||
*/
|
||||
fillForm : function (form) {
|
||||
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
|
||||
|
|
|
@ -37,6 +37,7 @@ support-files =
|
|||
[test_basic_form_3pw_1.html]
|
||||
[test_basic_form_autocomplete.html]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_case_differences.html]
|
||||
[test_basic_form_html5.html]
|
||||
[test_basic_form_observer_autocomplete.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>
|
||||
|
Загрузка…
Ссылка в новой задаче