Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test capturing of fields due to form removal</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="pwmgr_common.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script type="application/javascript">
let loadPromise = new Promise(resolve => {
document.addEventListener("DOMContentLoaded", _event => {
document.getElementById("loginFrame").addEventListener("load", _e => resolve());
});
});
add_setup(async () => {
info("Waiting for page and frame loads");
await loadPromise;
await loadRecipes({
siteRecipes: [{
hosts: ["test1.mochi.test:8888"],
usernameSelector: "input[name='recipeuname']",
passwordSelector: "input[name='recipepword']",
}],
});
});
const DEFAULT_ORIGIN = "http://test1.mochi.test:8888";
const SCRIPTS = {
// Test removing the form(or form-less password field), their parent node, and the top-level node.
REMOVE_FORM: `let e = document.querySelector("form"); e.parentNode.removeChild(e);`,
REMOVE_PASSWORD: `let e = document.querySelector("[type=password]"); e.parentNode.removeChild(e);`,
REMOVE_FORM_PARENT: `let e = document.querySelector("form").parentNode; e.parentNode.removeChild(e);`,
REMOVE_PASSWORD_PARENT: `let e = document.querySelector("[type=password]").parentNode; e.parentNode.removeChild(e);`,
REMOVE_TOP: `let e = document.querySelector("html"); e.parentNode.removeChild(e);`,
// Add testcases related to page navigation here to ensure these cases still work
// when we have set up form removal observer.
PUSHSTATE: `history.pushState({}, "Pushed state", "?pushed");`,
WINDOW_LOCATION: `window.location = "data:text/html;charset=utf-8,window.location";`,
};
const TESTCASES = [
{
// Inputs
document: `<input type=password value="">`,
selectorValues: {
"[type=password]": "pass1",
},
expectedFormsCount: 1,
// Expected outputs similar to PasswordManager:onFormSubmit
origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN,
usernameFieldValue: null,
newPasswordFieldValue: "pass1",
oldPasswordFieldValue: null,
},
{
document: `<input id="u1" value="">
<input type=password value="">`,
selectorValues: {
"#u1": "user1",
"[type=password]": "pass1",
},
expectedFormsCount: 1,
origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN,
usernameFieldValue: "user1",
newPasswordFieldValue: "pass1",
oldPasswordFieldValue: null,
},
{
document: `<input id="u1" value="">
<input id="p1" type=password value="">
<input id="p2" type=password value="">`,
selectorValues: {
"#u1": "user1",
"#p1": "pass1",
"#p2": "pass2",
},
expectedFormsCount: 1,
origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN,
usernameFieldValue: "user1",
newPasswordFieldValue: "pass2",
oldPasswordFieldValue: "pass1",
},
{
document: `<input id="u1" value="">
<input id="p1" type=password value="">
<input id="p2" type=password value="">
<input id="p3" type=password value="">`,
selectorValues: {
"#u1": "user1",
"#p1": "pass1",
"#p2": "pass2",
"#p3": "pass2",
},
expectedFormsCount: 1,
origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN,
usernameFieldValue: "user1",
newPasswordFieldValue: "pass2",
oldPasswordFieldValue: "pass1",
},
{
// Since there are two FormLikes to auto-submit in this case we mark
// 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
// below with the other FormLike ignored.
document: `<input id="u1" value="">
<input type=password id="p1" value="" form="form1">
<input type=password id="p2" value="">
<form id="form1">
<input id="u2" value="">
<input id="p3" type=password value="">
</form>`,
selectorValues: {
"#u1": "user1",
"#p1": "ignore-form-submission",
"#p2": "pass1",
"#u2": "user3",
"#p3": "ignore-form-submission",
},
expectedFormsCount: 2,
origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN,
usernameFieldValue: "user1",
newPasswordFieldValue: "pass1",
oldPasswordFieldValue: null,
removePassword: true,
},
{ // Same as above but with the other form ignored.
document: `<input id="u1" value="">
<input id="p1" type=password value="" form="form1">
<input id="p2" type=password value="">
<form id="form1">
<input id="u2" value="">
<input id="p3" type=password value="">
</form>`,
selectorValues: {
"#u1": "user1",
"#p1": "pass2",
"#p2": "ignore-form-submission",
"#u2": "user3",
"#p3": "pass2",
},
expectedFormsCount: 2,
origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN,
usernameFieldValue: null,
newPasswordFieldValue: "pass2",
oldPasswordFieldValue: null,
},
/*
XXX - Bug 1698498 :
This test case fails because when we call querySelector in LoginRecipes.sys.mjs
after the form is removed, querySelector can't find the element.
{
document: `<!-- recipe field override -->
<input name="recipeuname" value="">
<input id="u1" value="">
<input id="p1" type=password value="">
<input name="recipepword" type=password value="">`,
selectorValues: {
"[name='recipeuname']": "username from recipe",
"#u1": "default field username",
"#p1": "pass1",
"[name='recipepword']": "pass2",
},
expectedFormsCount: 1,
origin: DEFAULT_ORIGIN,
formActionOrigin: DEFAULT_ORIGIN,
usernameFieldValue: "username from recipe",
newPasswordFieldValue: "pass2",
oldPasswordFieldValue: null,
},*/
];
function filterFormSubmissions({ data }) {
return data.newPasswordField.value != "ignore-form-submission";
}
async function testFormlesSubmitFormRemoval(tc, testDoc, scriptName) {
let loginFrame = document.getElementById("loginFrame");
let loadedPromise = new Promise(resolve => {
loginFrame.addEventListener("load", _e => resolve(), {once: true});
});
loginFrame.src = DEFAULT_ORIGIN + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html";
await loadedPromise;
let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document;
let formsProcessed = promiseFormsProcessed(tc.expectedFormsCount);
frameDoc.documentElement.innerHTML = testDoc;
await formsProcessed;
// We eliminate no user input as a reason for not capturing by modifying the value
setUserInputValues(frameDoc.documentElement, tc.selectorValues)
await SpecialPowers.spawn(frameDoc.defaultView, [], async () => {
});
let submitProcessed = getSubmitMessage(filterFormSubmissions);
info("Running " + scriptName + " script to cause a submission");
frameDoc.defaultView.eval(SCRIPTS[scriptName]);
info("Waiting for formSubmissionProcsssed message");
let { origin, data } = await submitProcessed;
info("Got for formSubmissionProcsssed message");
// Check data sent via PasswordManager:onFormSubmit
is(origin, tc.origin, "Check origin");
is(data.formActionOrigin, tc.formActionOrigin, "Check formActionOrigin");
if (tc.usernameFieldValue === null) {
is(data.usernameField, tc.usernameFieldValue, "Check usernameField");
} else {
is(data.usernameField.value, tc.usernameFieldValue, "Check usernameField");
}
is(data.newPasswordField.value, tc.newPasswordFieldValue, "Check newPasswordFieldValue");
if (tc.oldPasswordFieldValue === null) {
is(data.oldPasswordField, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue");
} else {
is(data.oldPasswordField.value, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue");
}
};
let count = 0;
for (let tc of TESTCASES) {
for (let scriptName of Object.keys(SCRIPTS)) {
for (let surroundDocumentWithForm of [false, true]) {
let testDoc = tc.document;
if (surroundDocumentWithForm) {
if (testDoc.includes("<form")) {
info("Skipping surroundDocumentWithForm case since document already contains a <form>");
continue;
}
testDoc = "<form>" + testDoc + "</form>";
}
if (["REMOVE_FORM", "REMOVE_FORM_PARENT"].includes(scriptName) &&
(!testDoc.includes("<form") || tc.removePassword)) {
continue;
} else if (["REMOVE_PASSWORD","REMOVE_PASSWORD_PARENT"].includes(scriptName) &&
testDoc.includes("<form")) {
continue;
}
let taskName = `testcase-${count}-${scriptName}${surroundDocumentWithForm ? '-formWrapped' : ''}`;
let tmp = {
async [taskName]() {
info("Starting testcase with script " + scriptName + " and " +
(surroundDocumentWithForm ? "a" : "no") + " form wrapper: " + JSON.stringify(tc));
await testFormlesSubmitFormRemoval(tc, testDoc, scriptName);
},
};
add_task(tmp[taskName]);
}
}
count++;
}
</script>
<p id="display"></p>
<div id="content">
</div>
<pre id="test"></pre>
</body>
</html>