Bug 1145913 - Make the username in the password notification editable. r=MattN

This commit is contained in:
Paolo Amadini 2015-03-23 16:43:16 -07:00
Родитель 18a41c13b0
Коммит 3347b5b4de
3 изменённых файлов: 220 добавлений и 28 удалений

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

@ -62,7 +62,7 @@
<popupnotification id="password-notification" hidden="true">
<popupnotificationcontent orient="vertical">
<textbox id="password-notification-username" disabled="true"/>
<textbox id="password-notification-username"/>
<textbox id="password-notification-password" type="password"
disabled="true"/>
</popupnotificationcontent>

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

@ -10,6 +10,10 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/SharedPromptUtils.jsm");
const LoginInfo =
Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
"nsILoginInfo", "init");
/* Constants for password prompt telemetry.
* Mirrored in mobile/android/components/LoginManagerPrompter.js */
const PROMPT_DISPLAYED = 0;
@ -792,11 +796,13 @@ LoginManagerPrompter.prototype = {
_showLoginCaptureDoorhanger(login, type) {
let { browser } = this._getNotifyWindow();
let msgNames = type == "password-save" ? {
let saveMsgNames = {
prompt: "rememberPasswordMsgNoUsername",
buttonLabel: "notifyBarRememberPasswordButtonText",
buttonAccessKey: "notifyBarRememberPasswordButtonAccessKey",
} : {
};
let changeMsgNames = {
// We reuse the existing message, even if it expects a username, until we
// switch to the final terminology in bug 1144856.
prompt: "updatePasswordMsg",
@ -804,28 +810,93 @@ LoginManagerPrompter.prototype = {
buttonAccessKey: "notifyBarUpdateButtonAccessKey",
};
let initialMsgNames = type == "password-save" ? saveMsgNames
: changeMsgNames;
let histogramName = type == "password-save" ? "PWMGR_PROMPT_REMEMBER_ACTION"
: "PWMGR_PROMPT_UPDATE_ACTION";
let histogram = Services.telemetry.getHistogramById(histogramName);
histogram.add(PROMPT_DISPLAYED);
let chromeDoc = browser.ownerDocument;
let currentNotification;
let updateButtonLabel = () => {
let foundLogins = Services.logins.findLogins({}, login.hostname,
login.formSubmitURL,
login.httpRealm);
let logins = foundLogins.filter(l => l.username == login.username);
let msgNames = (logins.length == 0) ? saveMsgNames : changeMsgNames;
// Update the label based on whether this will be a new login or not.
let label = this._getLocalizedString(msgNames.buttonLabel);
let accessKey = this._getLocalizedString(msgNames.buttonAccessKey);
// Update the labels for the next time the panel is opened.
currentNotification.mainAction.label = label;
currentNotification.mainAction.accessKey = accessKey;
// Update the labels in real time if the notification is displayed.
let element = [...currentNotification.owner.panel.childNodes]
.find(n => n.notification == currentNotification);
if (element) {
element.setAttribute("buttonlabel", label);
element.setAttribute("buttonaccesskey", accessKey);
}
};
let writeDataToUI = () => {
chromeDoc.getElementById("password-notification-username")
.setAttribute("placeholder", usernamePlaceholder);
chromeDoc.getElementById("password-notification-username")
.setAttribute("value", login.username);
chromeDoc.getElementById("password-notification-password")
.setAttribute("value", login.password);
updateButtonLabel();
};
let readDataFromUI = () => {
login.username =
chromeDoc.getElementById("password-notification-username").value;
login.password =
chromeDoc.getElementById("password-notification-password").value;
};
let onUsernameInput = () => {
readDataFromUI();
updateButtonLabel();
};
let persistData = () => {
let foundLogins = Services.logins.findLogins({}, login.hostname,
login.formSubmitURL, null);
let logins = foundLogins.filter(l => l.username == login.username);
if (logins.length == 0) {
// The original login we have been provided with might have its own
// metadata, but we don't want it propagated to the newly created one.
Services.logins.addLogin(new LoginInfo(login.hostname,
login.formSubmitURL,
login.httpRealm,
login.username,
login.password,
login.usernameField,
login.passwordField));
} else if (logins.length == 1) {
this._updateLogin(logins[0], login.password);
} else {
Cu.reportError("Unexpected match of multiple logins.");
}
};
// The main action is the "Remember" or "Update" button.
let mainAction = {
label: this._getLocalizedString(msgNames.buttonLabel),
accessKey: this._getLocalizedString(msgNames.buttonAccessKey),
label: this._getLocalizedString(initialMsgNames.buttonLabel),
accessKey: this._getLocalizedString(initialMsgNames.buttonAccessKey),
callback: () => {
histogram.add(PROMPT_ADD_OR_UPDATE);
let foundLogins = Services.logins.findLogins({}, login.hostname,
login.formSubmitURL,
login.httpRealm);
let logins = foundLogins.filter(l => l.username == login.username);
if (logins.length == 0) {
Services.logins.addLogin(login);
} else if (logins.length == 1) {
this._updateLogin(logins[0], login.password);
} else {
Cu.reportError("Unexpected match of multiple logins.");
}
readDataFromUI();
persistData();
browser.focus();
}
};
@ -847,7 +918,7 @@ LoginManagerPrompter.prototype = {
this._getPopupNote().show(
browser,
"password",
this._getLocalizedString(msgNames.prompt, [displayHost]),
this._getLocalizedString(initialMsgNames.prompt, [displayHost]),
"password-notification-icon",
mainAction,
secondaryActions,
@ -856,18 +927,23 @@ LoginManagerPrompter.prototype = {
persistWhileVisible: true,
passwordNotificationType: type,
eventCallback: function (topic) {
if (topic != "showing") {
return false;
switch (topic) {
case "showing":
currentNotification = this;
writeDataToUI();
chromeDoc.getElementById("password-notification-username")
.addEventListener("input", onUsernameInput);
break;
case "dismissed":
readDataFromUI();
// Fall through.
case "removed":
currentNotification = null;
chromeDoc.getElementById("password-notification-username")
.removeEventListener("input", onUsernameInput);
break;
}
let chromeDoc = this.browser.ownerDocument;
chromeDoc.getElementById("password-notification-username")
.setAttribute("placeholder", usernamePlaceholder);
chromeDoc.getElementById("password-notification-username")
.setAttribute("value", login.username);
chromeDoc.getElementById("password-notification-password")
.setAttribute("value", login.password);
return false;
},
}
);

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

@ -81,3 +81,119 @@ add_task(function* test_save_change() {
Services.logins.removeAllLogins();
}
});
/**
* 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,
"Shown");
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;
// Modify the username in the dialog if requested.
if (testCase.usernameChangedTo) {
document.getElementById("password-notification-username")
.setAttribute("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);
let notificationElement = PopupNotifications.panel.childNodes[0];
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();
}
});