зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1145913 - Make the username in the password notification editable. r=MattN
This commit is contained in:
Родитель
18a41c13b0
Коммит
3347b5b4de
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче