Bug 1590762 - Part 1: Test that we don't double submit forms. r=kmag

Differential Revision: https://phabricator.services.mozilla.com/D79145
This commit is contained in:
Andreas Farre 2020-06-23 13:41:27 +00:00
parent ba550838cd
commit d36c42d2e0
5 changed files with 254 additions and 0 deletions

View File

@ -0,0 +1,7 @@
<!doctype html>
<script>
"use strict";
let target = window.opener ? window.opener : window.parent;
onmessage = ({data}) => target.postMessage({}, "*");
</script>

View File

@ -0,0 +1,122 @@
"use strict";
let self = this;
Cu.import("resource://gre/modules/Timer.jsm");
const CC = Components.Constructor;
const BinaryInputStream = CC(
"@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream"
);
const BinaryOutputStream = CC(
"@mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream",
"setOutputStream"
);
function log(str) {
// dump(`LOG: ${str}\n`);
}
function* generateBody(fragments, size) {
let result = [];
let chunkSize = (size / fragments) | 0;
let remaining = size;
log(`Chunk size ${chunkSize}`)
while (remaining > 0) {
let data = new Uint8Array(Math.min(remaining, chunkSize));
for (let i = 0; i < data.length; ++i) {
// Generate a character in the [a-z] range.
data[i] = 97 + Math.random() * (123 - 97);
}
yield data;
log(`Remaining to chunk ${remaining}`)
remaining -= data.length;
}
}
function readStream(inputStream) {
let available = 0;
let result = [];
while ((available = inputStream.available()) > 0) {
result.push(inputStream.readBytes(available));
}
return result.join('');
}
function now() {
return Date.now();
}
async function handleRequest(request, response) {
log("Get query parameters");
Cu.importGlobalProperties(["URLSearchParams"]);
let params = new URLSearchParams(request.queryString);
let delay = parseInt(params.get("delay")) || 0;
let delayUntil = now() + delay;
log(`Delay until ${delayUntil}`);
let message = "good";
if (request.method !== "POST") {
message = "bad";
} else {
log("Read POST body");
let body = new URLSearchParams(readStream(new BinaryInputStream(request.bodyInputStream)));
message = body.get("token") || "bad";
log(`The result was ${message}`);
}
let fragments = parseInt(params.get("fragments")) || 1;
let size = parseInt(params.get("size")) || 1024;
let outputStream = new BinaryOutputStream(response.bodyOutputStream);
let header = "<!doctype html><!-- ";
let footer = ` --><script>"use strict"; let target = (opener || parent); target.postMessage('${message}', '*');</script>`;
log("Set headers")
response.setHeader("Content-Type", "text/html", false);
response.setHeader("Content-Length", `${size + header.length + footer.length}`, false);
response.setStatusLine(request.httpVersion, "200", "OK");
response.processAsync();
log("Write header");
response.write(header);
log("Write body")
for (let data of generateBody(fragments, size)) {
delay = Math.max(0, delayUntil - now())
log(`Delay sending fragment for ${delay / fragments}`);
let failed = false;
await new Promise(resolve => {
setTimeout(() => {
try {
outputStream.writeByteArray(data, data.length);
} catch (e) {
log(e.message);
failed = true;
}
resolve();
}, delay / fragments);
});
if (failed) {
log("Stopped sending data");
break;
}
fragments = Math.max(--fragments, 1);
log(`Fragments left ${fragments}`)
}
log("Write footer")
response.write(footer);
response.finish();
}

View File

@ -119,3 +119,9 @@ support-files = file_framedhistoryframes.html
[test_windowedhistoryframes.html]
[test_triggeringprincipal_location_seturi.html]
[test_bug1507702.html]
[test_double_submit.html]
skip-if = fission
support-files =
clicker.html
ping.html
double_submit.sjs

View File

@ -0,0 +1,6 @@
<!doctype html>
<script>
"use strict";
let target = (window.opener || window.parent);
target.postMessage("ping", "*");
</script>

View File

@ -0,0 +1,113 @@
<!doctype html>
<html>
<head>
<title>Test for Bug 1590762</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<iframe name="targetFrame" id="targetFrame"></iframe>
<form id="form" action="double_submit.sjs?size=4096&fragments=10&delay=1000" method="POST" target="targetFrame">
<input id="input" type="text" name="text" value="value">
<input id="token" type="text" name="token" value="">
<input id="button" type="submit">
</form>
<script>
"use strict";
// For some reason this triggers the (harmless) assert at
// https://searchfox.org/mozilla-central/source/layout/generic/nsLineLayout.cpp#958
// See bug 1645132.
SimpleTest.expectAssertions(1)
const CROSS_ORIGIN_URI = "http://test1.example.com/tests/docshell/test/mochitest/ping.html";
function generateBody(size) {
let data = new Uint8Array(size);
for (let i = 0; i < size; ++i) {
data[i] = 97 + Math.random() * (123 - 97);
}
return new TextDecoder().decode(data);
}
function asyncClick(counts) {
let frame = document.createElement('iframe');
frame.addEventListener(
'load', () => frame.contentWindow.postMessage({command: "start"}, "*"),
{ once:true });
frame.src = "clicker.html";
addEventListener('message', ({source}) => {
if (source === frame.contentWindow) {
counts.click++;
synthesizeMouse(document.getElementById('button'), 5, 5, {});
}
}, { once: true });
document.body.appendChild(frame);
return stop;
}
function click(button) {
synthesizeMouse(button, 5, 5, {});
}
add_task(async function runTest() {
let frame = document.getElementById('targetFrame');
await new Promise(resolve => {
addEventListener('message', resolve, {once: true});
frame.src = CROSS_ORIGIN_URI;
});
let form = document.getElementById('form');
let button = document.getElementById('button');
document.getElementById('input').value = generateBody(1014*1024*10);
let token = document.getElementById('token');
token.value = "first";
await new Promise((resolve, reject) => {
let counts = { click: 0, submit: 0 };
form.addEventListener('submit', () => counts.submit++);
asyncClick(counts);
form.requestSubmit(button);
token.value = "bad";
let steps = {
good: {
entered: false,
next: () => { steps.good.entered = true; resolve(); },
assertion: () => {
ok(steps.first.entered && !steps.bad.entered, "good comes after first, but not bad")
}
},
first: {
entered: false,
next: () => { steps.first.entered = true; token.value = "good"; click(button); },
assertion: () => {
ok(!steps.good.entered && !steps.bad.entered, "first message is first")
is(counts.click, 1, "clicked");
is(counts.submit, 2, "did submit");
}
},
bad: {
entered: false,
next: () => { reject(); },
assertion: () => ok(false, "we got a bad message")
}
};
addEventListener('message', ({source, data}) => {
if (source !== frame.contentWindow) {
return;
}
let step = steps[data] || reject;
step.assertion();
step.next();
})
});
});
</script>
</body>
</html>