зеркало из 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;
|
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>
|
||||||
|
|
Загрузка…
Ссылка в новой задаче