Bug 1388674 - Update tests to use setUserInput, ensuring we get an input event for field modifications. r=MattN

Depends on D53055

Differential Revision: https://phabricator.services.mozilla.com/D51718

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Sam Foster 2019-11-20 22:44:18 +00:00
parent 0150488e14
commit 2831a02039
30 changed files with 378 additions and 247 deletions

View File

@ -96,14 +96,14 @@ async function submitSomeCrossSiteFrames(locationMode) {
await SpecialPowers.spawn(outerFrameBC, [], () => { await SpecialPowers.spawn(outerFrameBC, [], () => {
let doc = content.document; let doc = content.document;
doc.getElementById("outer-username").value = "outer"; doc.getElementById("outer-username").setUserInput("outer");
doc.getElementById("outer-password").value = "outerpass"; doc.getElementById("outer-password").setUserInput("outerpass");
}); });
await SpecialPowers.spawn(innerFrameBC, [locationMode], doClick => { await SpecialPowers.spawn(innerFrameBC, [locationMode], doClick => {
let doc = content.document; let doc = content.document;
doc.getElementById("inner-username").value = "inner"; doc.getElementById("inner-username").setUserInput("inner");
doc.getElementById("inner-password").value = "innerpass"; doc.getElementById("inner-password").setUserInput("innerpass");
if (doClick) { if (doClick) {
doc.getElementById("inner-gobutton").click(); doc.getElementById("inner-gobutton").click();
} else { } else {
@ -138,8 +138,8 @@ async function submitSomeCrossSiteFrames(locationMode) {
await SpecialPowers.spawn(outerFrameBC2, [locationMode], doClick => { await SpecialPowers.spawn(outerFrameBC2, [locationMode], doClick => {
let doc = content.document; let doc = content.document;
doc.getElementById("outer-username").value = "outer2"; doc.getElementById("outer-username").setUserInput("outer2");
doc.getElementById("outer-password").value = "outerpass2"; doc.getElementById("outer-password").setUserInput("outerpass2");
if (doClick) { if (doClick) {
doc.getElementById("outer-gobutton").click(); doc.getElementById("outer-gobutton").click();
} else { } else {

View File

@ -85,10 +85,12 @@ add_task(async function test_doorhanger_shown_on_un_with_invalid_ccnumber() {
let processedPromise = listenForTestNotification("FormSubmit"); let processedPromise = listenForTestNotification("FormSubmit");
await ContentTask.spawn(browser, null, async () => { await ContentTask.spawn(browser, null, async () => {
content.document.getElementById("form-basic-username").value = content.document
"1234123412341234"; .getElementById("form-basic-username")
content.document.getElementById("form-basic-password").value = "411"; .setUserInput("1234123412341234");
content.document
.getElementById("form-basic-password")
.setUserInput("411");
content.document.getElementById("form-basic-submit").click(); content.document.getElementById("form-basic-submit").click();
}); });
await processedPromise; await processedPromise;

View File

@ -22,8 +22,8 @@ add_task(async function test_empty_password() {
// case. This will cause the doorhanger notification to be displayed. // case. This will cause the doorhanger notification to be displayed.
await ContentTask.spawn(browser, null, async function() { await ContentTask.spawn(browser, null, async function() {
let doc = content.document; let doc = content.document;
doc.getElementById("form-basic-username").value = "username"; doc.getElementById("form-basic-username").setUserInput("username");
doc.getElementById("form-basic-password").value = "pw"; doc.getElementById("form-basic-password").setUserInput("pw");
doc.getElementById("form-basic").submit(); doc.getElementById("form-basic").submit();
}); });

View File

@ -14,12 +14,14 @@
add_task(async function test_edit_password() { add_task(async function test_edit_password() {
let testCases = [ let testCases = [
{ {
description: "No saved logins, update password in doorhanger",
usernameInPage: "username", usernameInPage: "username",
passwordInPage: "password", passwordInPage: "password",
passwordChangedTo: "newPassword", passwordChangedTo: "newPassword",
timesUsed: 1, timesUsed: 1,
}, },
{ {
description: "Login is saved, update password in doorhanger",
usernameInPage: "username", usernameInPage: "username",
usernameInPageExists: true, usernameInPageExists: true,
passwordInPage: "password", passwordInPage: "password",
@ -28,6 +30,8 @@ add_task(async function test_edit_password() {
timesUsed: 2, timesUsed: 2,
}, },
{ {
description:
"Change username in doorhanger to match saved login, update password in doorhanger",
usernameInPage: "username", usernameInPage: "username",
usernameChangedTo: "newUsername", usernameChangedTo: "newUsername",
usernameChangedToExists: true, usernameChangedToExists: true,
@ -36,6 +40,8 @@ add_task(async function test_edit_password() {
timesUsed: 2, timesUsed: 2,
}, },
{ {
description:
"Change username in doorhanger to match saved login, dont update password in doorhanger",
usernameInPage: "username", usernameInPage: "username",
usernameChangedTo: "newUsername", usernameChangedTo: "newUsername",
usernameChangedToExists: true, usernameChangedToExists: true,
@ -45,6 +51,8 @@ add_task(async function test_edit_password() {
checkPasswordNotUpdated: true, checkPasswordNotUpdated: true,
}, },
{ {
description:
"Change username and password in doorhanger to match saved empty-username login",
usernameInPage: "newUsername", usernameInPage: "newUsername",
usernameChangedTo: "", usernameChangedTo: "",
usernameChangedToExists: true, usernameChangedToExists: true,
@ -99,31 +107,22 @@ add_task(async function test_edit_password() {
contentTestCase contentTestCase
) { ) {
let doc = content.document; let doc = content.document;
doc.getElementById("form-basic-username").value = doc
contentTestCase.usernameInPage; .getElementById("form-basic-username")
doc.getElementById("form-basic-password").value = .setUserInput(contentTestCase.usernameInPage);
contentTestCase.passwordInPage; doc
.getElementById("form-basic-password")
.setUserInput(contentTestCase.passwordInPage);
doc.getElementById("form-basic").submit(); doc.getElementById("form-basic").submit();
}); });
await promiseShown; await promiseShown;
let notificationElement = PopupNotifications.panel.childNodes[0]; let notificationElement = PopupNotifications.panel.childNodes[0];
// Style flush to make sure binding is attached
notificationElement.querySelector("#password-notification-password")
.clientTop;
// Modify the username in the dialog if requested. // Modify the username & password in the dialog if requested.
if (testCase.usernameChangedTo) { await updateDoorhangerInputValues({
notificationElement.querySelector( username: testCase.usernameChangedTo,
"#password-notification-username" password: testCase.passwordChangedTo,
).value = testCase.usernameChangedTo; });
}
// Modify the password in the dialog if requested.
if (testCase.passwordChangedTo) {
notificationElement.querySelector(
"#password-notification-password"
).value = testCase.passwordChangedTo;
}
// We expect a modifyLogin notification if the final username used by the // We expect a modifyLogin notification if the final username used by the
// dialog exists in the logins database, otherwise an addLogin one. // dialog exists in the logins database, otherwise an addLogin one.
@ -142,8 +141,15 @@ add_task(async function test_edit_password() {
"passwordmgr-storage-changed", "passwordmgr-storage-changed",
(_, data) => data == expectedNotification (_, data) => data == expectedNotification
); );
let promiseHidden = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphidden"
);
notificationElement.button.doCommand(); notificationElement.button.doCommand();
let [result] = await promiseLogin; let [result] = await promiseLogin;
await promiseHidden;
// Check that the values in the database match the expected values. // Check that the values in the database match the expected values.
let login = expectModifyLogin let login = expectModifyLogin
@ -151,24 +157,27 @@ add_task(async function test_edit_password() {
.QueryInterface(Ci.nsIArray) .QueryInterface(Ci.nsIArray)
.queryElementAt(1, Ci.nsILoginInfo) .queryElementAt(1, Ci.nsILoginInfo)
: result.QueryInterface(Ci.nsILoginInfo); : result.QueryInterface(Ci.nsILoginInfo);
Assert.equal(
login.username,
testCase.usernameChangedTo || testCase.usernameInPage
);
Assert.equal(
login.password,
testCase.passwordChangedTo || testCase.passwordInPage
);
let meta = login.QueryInterface(Ci.nsILoginMetaInfo); let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
Assert.equal(meta.timesUsed, testCase.timesUsed);
let expectedLogin = {
username:
"usernameChangedTo" in testCase
? testCase.usernameChangedTo
: testCase.usernameInPage,
password:
"passwordChangedTo" in testCase
? testCase.passwordChangedTo
: testCase.passwordInPage,
timesUsed: testCase.timesUsed,
};
// Check that the password was not updated if the user is empty // Check that the password was not updated if the user is empty
if (testCase.checkPasswordNotUpdated) { if (testCase.checkPasswordNotUpdated) {
Assert.ok(meta.timeLastUsed > meta.timeCreated); expectedLogin.usedSince = meta.timeCreated;
Assert.ok(meta.timeCreated == meta.timePasswordChanged); expectedLogin.timeCreated = meta.timePasswordChanged;
} }
verifyLogins([expectedLogin]);
await cleanupDoorhanger();
} }
); );

View File

@ -93,55 +93,48 @@ add_task(async function test_save_change() {
async function(browser) { async function(browser) {
// Submit the form in the content page with the credentials from the test // Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed. // case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown",
event => event.target == PopupNotifications.panel
);
await ContentTask.spawn(browser, [username, password], async function([ await ContentTask.spawn(browser, [username, password], async function([
contentUsername, contentUsername,
contentPassword, contentPassword,
]) { ]) {
let doc = content.document; let doc = content.document;
doc.getElementById("form-basic-username").value = contentUsername; doc
doc.getElementById("form-basic-password").value = contentPassword; .getElementById("form-basic-username")
.setUserInput(contentUsername);
doc
.getElementById("form-basic-password")
.setUserInput(contentPassword);
doc.getElementById("form-basic").submit(); doc.getElementById("form-basic").submit();
}); });
await promiseShown;
let notif = PopupNotifications.getNotification("password", browser);
let notificationElement = PopupNotifications.panel.childNodes[0];
// Style flush to make sure binding is attached
notificationElement.querySelector("#password-notification-password")
.clientTop;
// Check the actual content of the popup notification.
Assert.equal(
notificationElement.querySelector("#password-notification-username")
.value,
username
);
Assert.equal(
notificationElement.querySelector("#password-notification-password")
.value,
password
);
// Simulate the action on the notification to request the login to be // 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 // saved, and wait for the data to be updated or saved based on the type
// of operation we expect. // of operation we expect.
let expectedNotification; let expectedNotification, expectedDoorhanger;
if (oldPassword !== undefined && oldUsername !== undefined) { if (oldPassword !== undefined && oldUsername !== undefined) {
expectedNotification = "addLogin"; expectedNotification = "addLogin";
expectedDoorhanger = "password-save";
} else if (oldPassword !== undefined) { } else if (oldPassword !== undefined) {
expectedNotification = "modifyLogin"; expectedNotification = "modifyLogin";
expectedDoorhanger = "password-change";
} else { } else {
expectedNotification = "addLogin"; expectedNotification = "addLogin";
expectedDoorhanger = "password-save";
} }
let notif = getCaptureDoorhanger(
expectedDoorhanger,
PopupNotifications,
browser
);
// Check the actual content of the popup notification.
await checkDoorhangerUsernamePassword(username, password);
let promiseLogin = TestUtils.topicObserved( let promiseLogin = TestUtils.topicObserved(
"passwordmgr-storage-changed", "passwordmgr-storage-changed",
(_, data) => data == expectedNotification (_, data) => data == expectedNotification
); );
notificationElement.button.doCommand(); await clickDoorhangerButton(notif, REMEMBER_BUTTON);
await promiseLogin; await promiseLogin;
await cleanupDoorhanger(notif); // clean slate for the next test await cleanupDoorhanger(notif); // clean slate for the next test

View File

@ -18,18 +18,17 @@ add_task(async function test_toggle_password() {
async function(browser) { async function(browser) {
// Submit the form in the content page with the credentials from the test // Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed. // case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown",
event => event.target == PopupNotifications.panel
);
await ContentTask.spawn(browser, null, async function() { await ContentTask.spawn(browser, null, async function() {
let doc = content.document; let doc = content.document;
doc.getElementById("form-basic-username").value = "username"; doc.getElementById("form-basic-username").setUserInput("username");
doc.getElementById("form-basic-password").value = "pw"; doc.getElementById("form-basic-password").setUserInput("pw");
doc.getElementById("form-basic").submit(); doc.getElementById("form-basic").submit();
}); });
await promiseShown; let notif = await getCaptureDoorhangerThatMayOpen("password-save");
ok(notif, "got notification popup");
// Check the actual content of the popup notification.
await checkDoorhangerUsernamePassword("username", "pw");
let notificationElement = PopupNotifications.panel.childNodes[0]; let notificationElement = PopupNotifications.panel.childNodes[0];
let passwordTextbox = notificationElement.querySelector( let passwordTextbox = notificationElement.querySelector(
@ -40,7 +39,7 @@ add_task(async function test_toggle_password() {
); );
await EventUtils.synthesizeMouseAtCenter(toggleCheckbox, {}); await EventUtils.synthesizeMouseAtCenter(toggleCheckbox, {});
Assert.ok(toggleCheckbox.checked); Assert.ok(toggleCheckbox.checked, "Toggle is checked");
Assert.equal( Assert.equal(
passwordTextbox.type, passwordTextbox.type,
"text", "text",
@ -54,6 +53,7 @@ add_task(async function test_toggle_password() {
"password", "password",
"Password textbox changed to * text" "Password textbox changed to * text"
); );
await cleanupDoorhanger(notif);
} }
); );
}); });
@ -72,21 +72,19 @@ add_task(async function test_checkbox_disabled_if_has_master_password() {
async function(browser) { async function(browser) {
// Submit the form in the content page with the credentials from the test // Submit the form in the content page with the credentials from the test
// case. This will cause the doorhanger notification to be displayed. // case. This will cause the doorhanger notification to be displayed.
let promiseShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown",
event => event.target == PopupNotifications.panel
);
LoginTestUtils.masterPassword.enable(); LoginTestUtils.masterPassword.enable();
await ContentTask.spawn(browser, null, async function() { await ContentTask.spawn(browser, null, async function() {
let doc = content.document; let doc = content.document;
doc.getElementById("form-basic-username").value = "username"; doc.getElementById("form-basic-username").setUserInput("username");
doc.getElementById("form-basic-password").value = "pass"; doc.getElementById("form-basic-password").setUserInput("pass");
doc.getElementById("form-basic").submit(); doc.getElementById("form-basic").submit();
}); });
await promiseShown; let notif = await getCaptureDoorhangerThatMayOpen("password-save");
ok(notif, "got notification popup");
// Check the actual content of the popup notification.
await checkDoorhangerUsernamePassword("username", "pass");
let notificationElement = PopupNotifications.panel.childNodes[0]; let notificationElement = PopupNotifications.panel.childNodes[0];
let passwordTextbox = notificationElement.querySelector( let passwordTextbox = notificationElement.querySelector(
@ -105,6 +103,7 @@ add_task(async function test_checkbox_disabled_if_has_master_password() {
toggleCheckbox.getAttribute("hidden"), toggleCheckbox.getAttribute("hidden"),
"checkbox is hidden when master password is set" "checkbox is hidden when master password is set"
); );
await cleanupDoorhanger(notif);
} }
); );

View File

@ -94,8 +94,10 @@ add_task(async function test_edit_username() {
testCase.usernameInPage, testCase.usernameInPage,
async function(usernameInPage) { async function(usernameInPage) {
let doc = content.document; let doc = content.document;
doc.getElementById("form-basic-username").value = usernameInPage; doc
doc.getElementById("form-basic-password").value = "password"; .getElementById("form-basic-username")
.setUserInput(usernameInPage);
doc.getElementById("form-basic-password").setUserInput("password");
doc.getElementById("form-basic").submit(); doc.getElementById("form-basic").submit();
} }
); );
@ -107,9 +109,9 @@ add_task(async function test_edit_username() {
// Modify the username in the dialog if requested. // Modify the username in the dialog if requested.
if (testCase.usernameChangedTo) { if (testCase.usernameChangedTo) {
notificationElement.querySelector( await updateDoorhangerInputValues({
"#password-notification-username" username: testCase.usernameChangedTo,
).value = testCase.usernameChangedTo; });
} }
// We expect a modifyLogin notification if the final username used by the // We expect a modifyLogin notification if the final username used by the
@ -128,8 +130,13 @@ add_task(async function test_edit_username() {
"passwordmgr-storage-changed", "passwordmgr-storage-changed",
(_, data) => data == expectedNotification (_, data) => data == expectedNotification
); );
let promiseHidden = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphidden"
);
notificationElement.button.doCommand(); notificationElement.button.doCommand();
let [result] = await promiseLogin; let [result] = await promiseLogin;
await promiseHidden;
// Check that the values in the database match the expected values. // Check that the values in the database match the expected values.
let login = expectModifyLogin let login = expectModifyLogin

View File

@ -6,10 +6,12 @@
async function fillTestPage(aBrowser) { async function fillTestPage(aBrowser) {
await ContentTask.spawn(aBrowser, null, async function() { await ContentTask.spawn(aBrowser, null, async function() {
content.document.getElementById("form-basic-username").value = content.document
"my_username"; .getElementById("form-basic-username")
content.document.getElementById("form-basic-password").value = .setUserInput("my_username");
"my_password"; content.document
.getElementById("form-basic-password")
.setUserInput("my_password");
}); });
info("fields filled"); info("fields filled");
} }

View File

@ -88,6 +88,9 @@ function verifyLogins(expectedLogins = []) {
"Check timePasswordChanged" "Check timePasswordChanged"
); );
} }
if (typeof expected.timeCreated !== "undefined") {
is(login.timeCreated, expected.timeCreated, "Check timeCreated");
}
} }
} }
return allLogins; return allLogins;
@ -108,7 +111,11 @@ async function submitFormAndGetResults(
selectorValues, selectorValues,
responseSelectors responseSelectors
) { ) {
function contentSubmitForm([contentFormAction, contentSelectorValues]) { async function contentSubmitForm([contentFormAction, contentSelectorValues]) {
const { WrapPrivileged } = ChromeUtils.import(
"resource://specialpowers/WrapPrivileged.jsm",
this
);
let doc = content.document; let doc = content.document;
let form = doc.querySelector("form"); let form = doc.querySelector("form");
if (contentFormAction) { if (contentFormAction) {
@ -116,9 +123,20 @@ async function submitFormAndGetResults(
} }
for (let [sel, value] of Object.entries(contentSelectorValues)) { for (let [sel, value] of Object.entries(contentSelectorValues)) {
try { try {
doc.querySelector(sel).setUserInput(value); let field = doc.querySelector(sel);
let gotInput = ContentTaskUtils.waitForEvent(
field,
"input",
"Got input event on " + sel
);
// we don't get an input event if the new value == the old
field.value = "###";
WrapPrivileged.wrap(field).setUserInput(value);
await gotInput;
} catch (ex) { } catch (ex) {
throw new Error(`submitForm: Couldn't set value of field at: ${sel}`); throw new Error(
`submitForm: Couldn't set value of field at: ${sel}: ${ex.message}`
);
} }
} }
form.submit(); form.submit();

View File

@ -14,8 +14,8 @@
<script> <script>
function submitForm() { function submitForm() {
userField.value = "notifyu1"; SpecialPowers.wrap(userField).setUserInput("notifyu1");
passField.value = "notifyp1"; SpecialPowers.wrap(passField).setUserInput("notifyp1");
form.submit(); form.submit();
} }

View File

@ -13,7 +13,7 @@
<script> <script>
function submitForm() { function submitForm() {
passField.value = "notifyp1"; SpecialPowers.wrap(passField).setUserInput("notifyp1");
form.submit(); form.submit();
} }

View File

@ -16,8 +16,8 @@
function submitForm() { function submitForm() {
// Get the password from the query string (exclude '?'). // Get the password from the query string (exclude '?').
let [username, password] = window.location.search.substring(1).split("|"); let [username, password] = window.location.search.substring(1).split("|");
userField.value = username; SpecialPowers.wrap(userField).setUserInput(username);
passField.value = password; SpecialPowers.wrap(passField).setUserInput(password);
form.submit(); form.submit();
window.opener.formSubmitted(); window.opener.formSubmitted();
} }

View File

@ -15,8 +15,8 @@
<script> <script>
function submitForm() { function submitForm() {
userField.value = "notifyu1"; SpecialPowers.wrap(userField).setUserInput("notifyu1");
passField.value = "notifyp1"; SpecialPowers.wrap(passField).setUserInput("notifyp1");
form.submit(); form.submit();
} }

View File

@ -14,7 +14,7 @@
<script> <script>
function submitForm() { function submitForm() {
pass.value = "notifyp1"; SpecialPowers.wrap(pass).setUserInput("notifyp1");
form.submit(); form.submit();
} }

View File

@ -16,8 +16,8 @@
<script> <script>
function submitForm() { function submitForm() {
userField.value = "notifyu1"; SpecialPowers.wrap(userField).setUserInput("notifyu1");
passField.value = "notifyp1"; SpecialPowers.wrap(passField).setUserInput("notifyp1");
form.submit(); form.submit();
} }

View File

@ -15,8 +15,8 @@
<script> <script>
function submitForm() { function submitForm() {
userField.value = "notifyu1"; SpecialPowers.wrap(userField).setUserInput("notifyu1");
passField.value = "notifyp1"; SpecialPowers.wrap(passField).setUserInput("notifyp1");
form.submit(); form.submit();
} }

View File

@ -15,8 +15,8 @@
<script> <script>
function submitForm() { function submitForm() {
userField.value = "notifyu1"; SpecialPowers.wrap(userField).setUserInput("notifyu1");
passField.value = "notifyp1"; SpecialPowers.wrap(passField).setUserInput("notifyp1");
form.submit(); form.submit();
} }

View File

@ -13,7 +13,7 @@
<script> <script>
function submitForm() { function submitForm() {
userField.value = "notifyu1"; SpecialPowers.wrap(userField).setUserInput("notifyu1");
form.submit(); form.submit();
} }

View File

@ -14,7 +14,7 @@
<script> <script>
function submitForm() { function submitForm() {
passField.value = "notifyp1"; SpecialPowers.wrap(passField).setUserInput("notifyp1");
form.submit(); form.submit();
} }

View File

@ -14,8 +14,8 @@
<script> <script>
function submitForm() { function submitForm() {
userField.value = "notifyu1"; SpecialPowers.wrap(userField).setUserInput("notifyu1");
passField.value = "pass2"; SpecialPowers.wrap(passField).setUserInput("pass2");
form.submit(); form.submit();
} }

View File

@ -14,8 +14,8 @@
<script> <script>
function submitForm() { function submitForm() {
userField.value = ""; SpecialPowers.wrap(userField).setUserInput("");
passField.value = "pass2"; SpecialPowers.wrap(passField).setUserInput("pass2");
form.submit(); form.submit();
} }

View File

@ -15,8 +15,8 @@
<script> <script>
function submitForm() { function submitForm() {
passField.value = "pass2"; SpecialPowers.wrap(passField).setUserInput("pass2");
passConfirmField.value = "pass2"; SpecialPowers.wrap(passConfirmField).setUserInput("pass2");
form.submit(); form.submit();
} }

View File

@ -113,6 +113,57 @@ function getIframeBrowsingContext(window, iframeNumber = 0) {
return SpecialPowers.unwrap(bc.getChildren()[iframeNumber]); return SpecialPowers.unwrap(bc.getChildren()[iframeNumber]);
} }
/**
* Set input values via setUserInput to emulate user input
* and distinguish them from declarative or script-assigned values
*/
function setUserInputValues(parentNode, selectorValues) {
for (let [selector, newValue] of Object.entries(selectorValues)) {
info(`setUserInputValues, selector: ${selector}`);
try {
let field = SpecialPowers.wrap(parentNode.querySelector(selector));
if (field.value == newValue) {
// we don't get an input event if the new value == the old
field.value += "#";
}
field.setUserInput(newValue);
} catch (ex) {
info(ex.message);
info(ex.stack);
ok(
false,
`setUserInputValues: Couldn't set value of field: ${ex.message}`
);
}
}
}
/**
* @param {Function} [aFilterFn = undefined] Function to filter out irrelevant submissions.
* @return {Promise} resolving when a relevant form submission was processed.
*/
function getSubmitMessage(aFilterFn = undefined) {
info("getSubmitMessage");
return new Promise((resolve, reject) => {
PWMGR_COMMON_PARENT.addMessageListener(
"formSubmissionProcessed",
function processed(...args) {
if (aFilterFn && !aFilterFn(...args)) {
// This submission isn't the one we're waiting for.
return;
}
info("got formSubmissionProcessed");
PWMGR_COMMON_PARENT.removeMessageListener(
"formSubmissionProcessed",
processed
);
resolve(...args);
}
);
});
}
/** /**
* Check for expected username/password in form. * Check for expected username/password in form.
* @see `checkForm` below for a similar function. * @see `checkForm` below for a similar function.

View File

@ -36,9 +36,9 @@ function startTest() {
// Fill in the username and password fields, for account creation. // Fill in the username and password fields, for account creation.
// Form 1 // Form 1
$_(1, "uname").value = "newuser1"; SpecialPowers.wrap($_(1, "uname")).setUserInput("newuser1");
$_(1, "pword").value = "newpass1"; SpecialPowers.wrap($_(1, "pword")).setUserInput("newpass1");
$_(1, "qword").value = "newpass1"; SpecialPowers.wrap($_(1, "qword")).setUserInput("newpass1");
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
var button = getFormSubmitButton(1); var button = getFormSubmitButton(1);

View File

@ -46,7 +46,10 @@ const DEFAULT_ORIGIN = "http://mochi.test:8888";
const TESTCASES = [ const TESTCASES = [
{ {
// Inputs // Inputs
document: `<input type=password value="pass1">`, document: `<input type=password value="">`,
selectorValues: {
"[type=password]": "pass1",
},
inputIndexForFormLike: 0, inputIndexForFormLike: 0,
// Expected outputs similar to PasswordManager:onFormSubmit // Expected outputs similar to PasswordManager:onFormSubmit
@ -57,8 +60,12 @@ const TESTCASES = [
oldPasswordFieldValue: null, oldPasswordFieldValue: null,
}, },
{ {
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="pass1">`, <input type=password value="">`,
selectorValues: {
"#u1": "user1",
"[type=password]": "pass1",
},
inputIndexForFormLike: 0, inputIndexForFormLike: 0,
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -67,8 +74,12 @@ const TESTCASES = [
oldPasswordFieldValue: null, oldPasswordFieldValue: null,
}, },
{ {
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="pass1">`, <input type=password value="">`,
selectorValues: {
"#u1": "user1",
"[type=password]": "pass1",
},
inputIndexForFormLike: 1, inputIndexForFormLike: 1,
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -77,9 +88,14 @@ const TESTCASES = [
oldPasswordFieldValue: null, oldPasswordFieldValue: null,
}, },
{ {
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="pass1"> <input id="p1" type=password value="">
<input type=password value="pass2">`, <input id="p2" type=password value="">`,
selectorValues: {
"#u1": "user1",
"#p1": "pass1",
"#p2": "pass2",
},
inputIndexForFormLike: 2, inputIndexForFormLike: 2,
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -88,10 +104,16 @@ const TESTCASES = [
oldPasswordFieldValue: "pass1", oldPasswordFieldValue: "pass1",
}, },
{ {
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="pass1"> <input id="p1" type=password value="">
<input type=password value="pass2"> <input id="p2" type=password value="">
<input type=password value="pass2">`, <input id="p3" type=password value="">`,
selectorValues: {
"#u1": "user1",
"#p1": "pass1",
"#p2": "pass2",
"#p3": "pass2",
},
inputIndexForFormLike: 3, inputIndexForFormLike: 3,
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -100,13 +122,20 @@ const TESTCASES = [
oldPasswordFieldValue: "pass1", oldPasswordFieldValue: "pass1",
}, },
{ {
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="user2" form="form1"> <input id="p1" type=password value="" form="form1">
<input type=password value="pass1"> <input id="p2" type=password value="">
<form id="form1"> <form id="form1">
<input value="user3"> <input id="u2" value="">
<input type=password value="pass2"> <input id="p3" type=password value="">
</form>`, </form>`,
selectorValues: {
"#u1": "user1",
"#p1": "user2",
"#p2": "pass1",
"#u2": "user3",
"#p3": "pass2",
},
inputIndexForFormLike: 2, inputIndexForFormLike: 2,
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -116,10 +145,16 @@ const TESTCASES = [
}, },
{ {
document: `<!-- recipe field override --> document: `<!-- recipe field override -->
<input name="recipeuname" value="username from recipe"> <input name="recipeuname" value="">
<input value="default field username"> <input id="u1" value="">
<input type=password value="pass1"> <input id="p1" type=password value="">
<input name="recipepword" type=password value="pass2">`, <input name="recipepword" type=password value="">`,
selectorValues: {
"[name='recipeuname']": "username from recipe",
"#u1": "default field username",
"#p1": "pass1",
"[name='recipepword']": "pass2",
},
inputIndexForFormLike: 2, inputIndexForFormLike: 2,
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -129,36 +164,30 @@ const TESTCASES = [
}, },
]; ];
function getSubmitMessage() {
info("getSubmitMessage");
return new Promise((resolve, reject) => {
PWMGR_COMMON_PARENT.addMessageListener("formSubmissionProcessed", function processed(...args) {
info("got formSubmissionProcessed");
PWMGR_COMMON_PARENT.removeMessageListener("formSubmissionProcessed", processed);
resolve(...args);
});
});
}
add_task(async function test() { add_task(async function test() {
let count = 0; let count = 0;
let loginFrame = document.getElementById("loginFrame"); let loginFrame = document.getElementById("loginFrame");
for (let tc of TESTCASES) { for (let tc of TESTCASES) {
let frameDoc = loginFrame.contentWindow.document; let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document;
info("Starting testcase: " + JSON.stringify(tc)); info("Starting testcase: " + JSON.stringify(tc));
let formProcessed = promiseFormsProcessed();
// eslint-disable-next-line no-unsanitized/property // eslint-disable-next-line no-unsanitized/property
frameDoc.documentElement.innerHTML = tc.document; frameDoc.documentElement.innerHTML = tc.document;
await formProcessed;
// We eliminate no user input as a reason for not capturing by modifying the value
setUserInputValues(frameDoc.documentElement, tc.selectorValues);
let inputForFormLike = frameDoc.querySelectorAll("input")[tc.inputIndexForFormLike]; let inputForFormLike = frameDoc.querySelectorAll("input")[tc.inputIndexForFormLike];
let formLike = LoginFormFactory.createFromField(inputForFormLike); let formLike = LoginFormFactory.createFromField(inputForFormLike);
info("Calling _onFormSubmit with FormLike"); info("Calling _onFormSubmit with FormLike");
let processedPromise = getSubmitMessage(); let submitProcessed = getSubmitMessage();
LoginManagerChild.forWindow(frameDoc.defaultView)._onFormSubmit(formLike); LoginManagerChild.forWindow(frameDoc.defaultView)._onFormSubmit(formLike);
let submittedResult = await processedPromise; let submittedResult = await submitProcessed;
// Check data sent via PasswordManager:onFormSubmit // Check data sent via PasswordManager:onFormSubmit
is(submittedResult.origin, tc.origin, "Check origin"); is(submittedResult.origin, tc.origin, "Check origin");

View File

@ -47,7 +47,10 @@ const SCRIPTS = {
const TESTCASES = [ const TESTCASES = [
{ {
// Inputs // Inputs
document: `<input type=password value="pass1">`, document: `<input type=password value="">`,
selectorValues: {
"[type=password]": "pass1",
},
// Expected outputs similar to PasswordManager:onFormSubmit // Expected outputs similar to PasswordManager:onFormSubmit
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
@ -57,8 +60,12 @@ const TESTCASES = [
oldPasswordFieldValue: null, oldPasswordFieldValue: null,
}, },
{ {
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="pass1">`, <input type=password value="">`,
selectorValues: {
"#u1": "user1",
"[type=password]": "pass1",
},
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -67,9 +74,14 @@ const TESTCASES = [
oldPasswordFieldValue: null, oldPasswordFieldValue: null,
}, },
{ {
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="pass1"> <input id="p1" type=password value="">
<input type=password value="pass2">`, <input id="p2" type=password value="">`,
selectorValues: {
"#u1": "user1",
"#p1": "pass1",
"#p2": "pass2",
},
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -78,10 +90,16 @@ const TESTCASES = [
oldPasswordFieldValue: "pass1", oldPasswordFieldValue: "pass1",
}, },
{ {
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="pass1"> <input id="p1" type=password value="">
<input type=password value="pass2"> <input id="p2" type=password value="">
<input type=password value="pass2">`, <input id="p3" type=password value="">`,
selectorValues: {
"#u1": "user1",
"#p1": "pass1",
"#p2": "pass2",
"#p3": "pass2",
},
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -94,13 +112,20 @@ const TESTCASES = [
// one FormLike's password fields with a magic "ignore-form-submission" // one FormLike's password fields with a magic "ignore-form-submission"
// value so we can just focus on the other form. We then repeat the testcase // value so we can just focus on the other form. We then repeat the testcase
// below with the other FormLike ignored. // below with the other FormLike ignored.
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="ignore-form-submission" form="form1"> <input type=password id="p1" value="" form="form1">
<input type=password value="pass1"> <input type=password id="p2" value="">
<form id="form1"> <form id="form1">
<input value="user3"> <input id="u2" value="">
<input type=password value="ignore-form-submission"> <input id="p3" type=password value="">
</form>`, </form>`,
selectorValues: {
"#u1": "user1",
"#p1": "ignore-form-submission",
"#p2": "pass1",
"#u2": "user3",
"#p3": "ignore-form-submission",
},
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -109,13 +134,20 @@ const TESTCASES = [
oldPasswordFieldValue: null, oldPasswordFieldValue: null,
}, },
{ // Same as above but with the other form ignored. { // Same as above but with the other form ignored.
document: `<input value="user1"> document: `<input id="u1" value="">
<input type=password value="pass2" form="form1"> <input id="p1" type=password value="" form="form1">
<input type=password value="ignore-form-submission"> <input id="p2" type=password value="">
<form id="form1"> <form id="form1">
<input value="user3"> <input id="u2" value="">
<input type=password value="pass2"> <input id="p3" type=password value="">
</form>`, </form>`,
selectorValues: {
"#u1": "user1",
"#p1": "pass2",
"#p2": "ignore-form-submission",
"#u2": "user3",
"#p3": "pass2",
},
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -125,10 +157,16 @@ const TESTCASES = [
}, },
{ {
document: `<!-- recipe field override --> document: `<!-- recipe field override -->
<input name="recipeuname" value="username from recipe"> <input name="recipeuname" value="">
<input value="default field username"> <input id="u1" value="">
<input type=password value="pass1"> <input id="p1" type=password value="">
<input name="recipepword" type=password value="pass2">`, <input name="recipepword" type=password value="">`,
selectorValues: {
"[name='recipeuname']": "username from recipe",
"#u1": "default field username",
"#p1": "pass1",
"[name='recipepword']": "pass2",
},
origin: DEFAULT_ORIGIN, origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN, formActionOrigin: DEFAULT_ORIGIN,
@ -138,26 +176,6 @@ const TESTCASES = [
}, },
]; ];
/**
* @param {Function} [aFilterFn = undefined] Function to filter out irrelevant submissions.
* @return {Promise} resolving when a relevant form submission was processed.
*/
function getSubmitMessage(aFilterFn = undefined) {
info("getSubmitMessage");
return new Promise((resolve, reject) => {
PWMGR_COMMON_PARENT.addMessageListener("formSubmissionProcessed", function processed(...args) {
if (aFilterFn && !aFilterFn(...args)) {
// This submission isn't the one we're waiting for.
return;
}
info("got formSubmissionProcessed");
PWMGR_COMMON_PARENT.removeMessageListener("formSubmissionProcessed", processed);
resolve(...args);
});
});
}
function filterFormSubmissions(data) { function filterFormSubmissions(data) {
return data.newPasswordField.value != "ignore-form-submission"; return data.newPasswordField.value != "ignore-form-submission";
} }
@ -189,15 +207,20 @@ add_task(async function test() {
testDoc = "<form>" + testDoc + "</form>"; testDoc = "<form>" + testDoc + "</form>";
} }
let formProcessed = promiseFormsProcessed();
// eslint-disable-next-line no-unsanitized/property // eslint-disable-next-line no-unsanitized/property
frameDoc.documentElement.innerHTML = testDoc; frameDoc.documentElement.innerHTML = testDoc;
// Wait for the form to be processed before trying to submit. await formProcessed;
await promiseFormsProcessed(); // We eliminate no user input as a reason for not capturing by modifying the value
let processedPromise = getSubmitMessage(filterFormSubmissions); setUserInputValues(frameDoc.documentElement, tc.selectorValues)
let submitProcessed = getSubmitMessage(filterFormSubmissions);
info("Running " + scriptName + " script to cause a submission"); info("Running " + scriptName + " script to cause a submission");
frameDoc.defaultView.eval(SCRIPTS[scriptName]); frameDoc.defaultView.eval(SCRIPTS[scriptName]);
let submittedResult = await processedPromise; info("Waiting for formSubmissionProcsssed message");
let submittedResult = await submitProcessed;
info("Got for formSubmissionProcsssed message");
// Check data sent via PasswordManager:onFormSubmit // Check data sent via PasswordManager:onFormSubmit
is(submittedResult.origin, tc.origin, "Check origin"); is(submittedResult.origin, tc.origin, "Check origin");

View File

@ -53,22 +53,34 @@ const TESTCASES = [
// Begin test cases that shouldn't trigger capture. // Begin test cases that shouldn't trigger capture.
{ {
// Empty password field in a form // Empty password field in a form
document: `<form><input type=password value=""></form>`, document: `<form><input type=password value="xxx"></form>`,
selectorValues: {
"[type=password]": "",
},
}, },
{ {
// Empty password field // Empty password field
document: `<input type=password value="">`, document: `<input type=password value="">`,
selectorValues: {
"[type=password]": "",
},
}, },
{ {
// Test with an input that would normally be captured but with SCRIPTS that // Test with an input that would normally be captured but with SCRIPTS that
// shouldn't trigger capture. // shouldn't trigger capture.
document: `<input type=password value="pass2">`, document: `<input type=password value="">`,
selectorValues: {
"[type=password]": "pass2",
},
wouldCapture: true, wouldCapture: true,
}, },
{ {
// Test with an input that would normally be captured but with SCRIPTS that // Test with an input that would normally be captured but with SCRIPTS that
// shouldn't trigger capture. // shouldn't trigger capture.
document: `<form><input type=password value="pass2"></form>`, document: `<form><input type=password value=""></form>`,
selectorValues: {
"[type=password]": "pass2",
},
wouldCapture: true, wouldCapture: true,
}, },
]; ];
@ -104,9 +116,8 @@ add_task(async function test() {
let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document; let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document;
// eslint-disable-next-line no-unsanitized/property // eslint-disable-next-line no-unsanitized/property
frameDoc.documentElement.innerHTML = tc.document; frameDoc.documentElement.innerHTML = tc.document;
// We eliminate no user input as a reason for not capturing by modifying the value
// Wait for the form to be processed before trying to submit. setUserInputValues(frameDoc.documentElement, tc.selectorValues);
await promiseFormsProcessed();
info("Running " + scriptName + " script to check for a submission"); info("Running " + scriptName + " script to check for a submission");
frameDoc.defaultView.eval(SCRIPTS[scriptName]); frameDoc.defaultView.eval(SCRIPTS[scriptName]);

View File

@ -63,6 +63,15 @@ async function loadFormIntoIframe(origin, html) {
// Wait for the form to be processed before trying to submit. // Wait for the form to be processed before trying to submit.
await promiseFormsProcessed(); await promiseFormsProcessed();
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [html], function(contentHtml) {
let doc = this.content.document;
for (let field of doc.querySelectorAll("input")) {
let actualValue = field.value;
field.value = "";
SpecialPowers.wrap(field).setUserInput(actualValue);
}
});
} }
add_task(async function setup() { add_task(async function setup() {
@ -127,20 +136,6 @@ const TESTCASES = [
}, },
]; ];
/**
* @return {Promise} resolving when form submission was processed.
*/
function getSubmitMessage() {
info("getSubmitMessage");
return new Promise((resolve, reject) => {
PWMGR_COMMON_PARENT.addMessageListener("formSubmissionProcessed", function processed(...args) {
info("got formSubmissionProcessed");
PWMGR_COMMON_PARENT.removeMessageListener("formSubmissionProcessed", processed);
resolve(...args);
});
});
}
add_task(async function test_new_logins() { add_task(async function test_new_logins() {
for (let tc of TESTCASES) { for (let tc of TESTCASES) {
info("Starting testcase: " + JSON.stringify(tc)); info("Starting testcase: " + JSON.stringify(tc));
@ -193,8 +188,8 @@ add_task(async function test_no_autofill_munged_username_matching_password() {
await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() { await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
let doc = this.content.document; let doc = this.content.document;
Assert.equal(doc.querySelector("[name='uname']").value, "", "Check username didn't get autofilled"); Assert.equal(doc.querySelector("[name='uname']").value, "", "Check username didn't get autofilled");
doc.querySelector("[name='uname']").setUserInput("real••••user"); SpecialPowers.wrap(doc.querySelector("[name='uname']")).setUserInput("real••••user");
doc.querySelector("[name='pword']").setUserInput("pass1"); SpecialPowers.wrap(doc.querySelector("[name='pword']")).setUserInput("pass1");
}); });
// we shouldn't get the save password doorhanger... // we shouldn't get the save password doorhanger...

View File

@ -12,17 +12,6 @@
<script> <script>
let chromeScript = runChecksAfterCommonInit(); let chromeScript = runChecksAfterCommonInit();
function getSubmitMessage() {
info("getSubmitMessage");
return new Promise((resolve, reject) => {
chromeScript.addMessageListener("formSubmissionProcessed", function processed(...args) {
info("got formSubmissionProcessed");
chromeScript.removeMessageListener("formSubmissionProcessed", processed);
resolve(...args);
});
});
}
SimpleTest.requestFlakyTimeout("Giving a chance for the unexpected popupshown to occur"); SimpleTest.requestFlakyTimeout("Giving a chance for the unexpected popupshown to occur");
</script> </script>
<p id="display"></p> <p id="display"></p>
@ -45,8 +34,8 @@
let password = document.getElementById("pfield"); let password = document.getElementById("pfield");
let submitButton = document.getElementById("submitBtn"); let submitButton = document.getElementById("submitBtn");
username.value = "user"; SpecialPowers.wrap(username).setUserInput("user");
password.value = "pass"; SpecialPowers.wrap(password).setUserInput("pass");
let processedPromise = getSubmitMessage(); let processedPromise = getSubmitMessage();
let promptShownPromise = promisePromptShown("passwordmgr-prompt-save"); let promptShownPromise = promisePromptShown("passwordmgr-prompt-save");

View File

@ -40,12 +40,15 @@ Login Manager test: input value change right after onsubmit event
<script> <script>
/** Test for Login Manager: input value change right after onsubmit event **/ /** Test for Login Manager: input value change right after onsubmit event **/
add_task(async function checkFormValues() { add_task(async function checkFormValues() {
document.getElementById("ufield").value = "testuser"; SpecialPowers.wrap(document.getElementById("ufield")).setUserInput("testuser");
document.getElementById("pfield").value = "testpass"; SpecialPowers.wrap(document.getElementById("pfield")).setUserInput("testpass");
is($_(1, "uname").value, "testuser", "Checking for filled username"); is($_(1, "uname").value, "testuser", "Checking for filled username");
is($_(1, "pword").value, "testpass", "Checking for filled password"); is($_(1, "pword").value, "testpass", "Checking for filled password");
document.getElementById("form1").addEventListener("submit", () => { document.getElementById("form1").addEventListener("submit", () => {
// deliberately assign to .value rather than setUserInput:
// the scenario under test here is that script is changing/populating
// fields after the user has clicked the submit button
document.getElementById("ufield").value = "newuser"; document.getElementById("ufield").value = "newuser";
document.getElementById("pfield").value = "newpass"; document.getElementById("pfield").value = "newpass";
}, true); }, true);