summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/forms/form-submission-0
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/semantics/forms/form-submission-0')
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js21
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/SubmitEvent.window.js41
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/constructing-form-data-set.html161
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js187
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js99
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv-form.html27
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv.html52
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-2.html49
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-3.html50
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-default-action.html108
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-multiple-targets.html37
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault-click.html67
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault.html59
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-requestsubmit.html142
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-to-different-origin-frame.html63
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit.html50
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-echo.py7
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submission-algorithm.html165
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html23
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/getactionurl.html11
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/historical.window.js19
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/implicit-submission.optional.html47
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html32
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-navigation-then-form-submit.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js357
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/newline-normalization.html112
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html15
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/request-submit-activation.html30
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/file-submission.py10
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form-submission.py12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/getactionurl-iframe.html14
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html12
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/location.html4
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/targetted-form.js38
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/submission-checks.window.js62
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-entity-body.html114
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-file.sub.html25
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js223
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/url-encoded.html51
-rw-r--r--testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js223
41 files changed, 2853 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js
new file mode 100644
index 0000000000..4890fd8623
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/FormDataEvent.window.js
@@ -0,0 +1,21 @@
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-formdataevent-interface
+
+test(() => {
+ let fd = new FormData();
+ assert_throws_js(TypeError, () => { FormDataEvent('', {formData:fd}) }, "Calling FormDataEvent constructor without 'new' must throw");
+ assert_throws_js(TypeError, () => { new FormDataEvent() }, '0 arguments');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo') }, '1 argument');
+ assert_throws_js(TypeError, () => { new FormDataEvent(fd, fd) }, '2 invalid arguments');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', null) }, 'Null dictionary');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', undefined) }, 'Undefined dictionary');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', { formData: null }) }, 'Null formData');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', { formData: undefined }) }, 'Undefined formData');
+ assert_throws_js(TypeError, () => { new FormDataEvent('foo', { formData: 'bar' }) }, 'Wrong type of formData');
+}, 'Failing FormDataEvent constructor');
+
+test(() => {
+ let fd = new FormData();
+ let event = new FormDataEvent('bar', { formData: fd, bubbles: true });
+ assert_equals(event.formData, fd);
+ assert_true(event.bubbles);
+}, 'Successful FormDataEvent constructor');
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/SubmitEvent.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/SubmitEvent.window.js
new file mode 100644
index 0000000000..3821815515
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/SubmitEvent.window.js
@@ -0,0 +1,41 @@
+// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-submitevent-interface
+
+test(() => {
+ assert_throws_js(TypeError, () => SubmitEvent(""), "Calling SubmitEvent constructor without 'new' must throw");
+ assert_throws_js(TypeError, () => { new SubmitEvent() }, '0 arguments');
+ assert_throws_js(TypeError, () => { new SubmitEvent('foo', { submitter: 'bar' }) }, 'Wrong type of submitter');
+}, 'Failing SubmitEvent constructor');
+
+test(() => {
+ let button = document.createElement('button');
+ let event = new SubmitEvent('bar', { submitter: button, bubbles: true });
+ assert_equals(event.submitter, button);
+ assert_true(event.bubbles);
+}, 'Successful SubmitEvent constructor');
+
+test(() => {
+ let event1 = new SubmitEvent('bar', {submitter: null});
+ assert_equals(event1.submitter, null);
+ let event2 = new SubmitEvent('baz', {submitter: undefined});
+ assert_equals(event2.submitter, null);
+}, 'Successful SubmitEvent constructor; null/undefined submitter');
+
+test(() => {
+ let event1 = new SubmitEvent('bar', null);
+ assert_equals(event1.submitter, null);
+ let event2 = new SubmitEvent('baz', undefined);
+ assert_equals(event2.submitter, null);
+}, 'Successful SubmitEvent constructor; null/undefined dictionary');
+
+test(() => {
+ let event1 = new SubmitEvent('bar', {});
+ assert_equals(event1.submitter, null);
+ let button = document.createElement('button');
+ let event2 = new SubmitEvent("bax", button);
+ assert_equals(event2.submitter, null);
+}, 'Successful SubmitEvent constructor; empty dictionary');
+
+test(() => {
+ let event = new SubmitEvent('bar');
+ assert_equals(event.submitter, null);
+}, 'Successful SubmitEvent constructor; missing dictionary');
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/constructing-form-data-set.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/constructing-form-data-set.html
new file mode 100644
index 0000000000..c27270ea30
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/constructing-form-data-set.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set">
+<link ref="help" href="https://xhr.spec.whatwg.org/#dom-formdata">
+<link rel="help" href="https://fetch.spec.whatwg.org/#concept-bodyinit-extract">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+
+<iframe name="frame1" id="frame1"></iframe>
+<form accept-charset="iso-8859-1" target="frame1" action="/common/blank.html" id="form1">
+ <input type="hidden" name="_charset_">
+</form>
+
+<iframe name="frame2" id="frame2"></iframe>
+<form target="frame2" action="/common/blank.html" id="form2">
+ <input type="text" name="foo">
+ <button type="close" name="close" value="true">close</button>
+ <button type="button" name="button" value="true">button</button>
+ <button type="reset" name="reset" value="true">reset</button>
+ <button type="submit" name="submit" value="true">submit</button>
+</form>
+
+<script>
+
+const form1 = document.getElementById('form1'),
+ form2 = document.getElementById('form2'),
+ frame1 = document.getElementById('frame1'),
+ frame2 = document.getElementById('frame2');
+
+test(() => {
+ const formData = new FormData(form1);
+ assert_equals(formData.get('_charset_'), 'UTF-8');
+}, 'FormData constructor always produces UTF-8 _charset_ value.');
+
+async_test(t => {
+ frame1.onload = t.step_func(() => {
+ if (frame1.contentWindow.location.href == "about:blank") { return; }
+ assert_not_equals(frame1.contentDocument.URL.indexOf('_charset_=windows-1252'), -1,"should see _charset_=windows-1252 in "+frame1.contentDocument.URL);
+ t.done();
+ });
+ form1.submit();
+},'_charset_ control sets the expected encoding name.');
+
+async_test(t => {
+ frame2.onload = t.step_func_done(() => {
+ assert_equals(frame2.contentDocument.URL.split("?")[1], 'foo=&submit=true');
+ });
+ form2.submit.click();
+}, 'The button cannot be setted if it is not a submitter.');
+
+test(() => {
+ let didCallHandler = false;
+ let wasBubbles = false;
+ let wasCancelable = true;
+ let form = populateForm();
+ document.addEventListener('formdata', e => {
+ didCallHandler = true;
+ wasBubbles = e.bubbles;
+ wasCancelable = e.cancelable;
+ });
+ new FormData(form);
+ assert_true(didCallHandler);
+ assert_true(wasBubbles);
+ assert_false(wasCancelable);
+}, '"formdata" event bubbles, and is not cancelable.');
+
+test(() => {
+ let didCallHandler = false;
+ let form = populateForm();
+ let orphanRoot = document.createElement('div');
+ orphanRoot.appendChild(form);
+ orphanRoot.addEventListener('formdata', e => {
+ didCallHandler = true;
+ });
+ new FormData(form);
+ assert_true(didCallHandler);
+}, '"formdata" event bubbles in an orphan tree.');
+
+for (const enctype of ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]) {
+ test((t) => {
+ let form = populateForm('<input name=file type=file><input name=empty type=file>');
+ form.enctype = enctype;
+
+ const file = new File([], "filename");
+ const dataTransfer = new DataTransfer();
+ dataTransfer.items.add(file);
+ form.querySelector('input[name=file]').files = dataTransfer.files;
+
+ form.addEventListener('formdata', t.step_func(e => {
+ assert_true(e.formData.has('file'));
+ assert_equals(e.formData.get('file'), file);
+ assert_true(e.formData.has('empty'));
+ assert_true(e.formData.get('empty') instanceof File);
+ }));
+ form.submit();
+ }, `Files in a ${enctype} form show up as File objects in the "formData" IDL attribute`);
+}
+
+test(() => {
+ let listener1ok = false;
+ let listeern2ok = false;
+ let form = populateForm('<input name=n1 value=v1>');
+ form.addEventListener('formdata', e => {
+ listener1ok = e.formData.get('n1') == 'v1';
+ e.formData.append('h1', 'vh1');
+ e.formData.append('h2', 'vh2');
+ });
+ form.addEventListener('formdata', e => {
+ if (e.formData.get('h1') == 'vh1' && e.formData.get('h2') == 'vh2')
+ listener2ok = true;
+ });
+ form.submit();
+ assert_true(listener1ok);
+ assert_true(listener2ok);
+}, '"formData" IDL attribute should have entries for form-associated elements' +
+ ' in the first event handler, and the second handler can read entries ' +
+ 'set by the first handler.');
+
+let t1 = async_test('Entries added to "formData" IDL attribute should be submitted.');
+t1.step(() => {
+ let form = populateForm('<input name=n1 value=v1>');
+ form.addEventListener('formdata', e => {
+ e.formData.append('h1', 'vh1');
+ });
+ let iframe = form.previousSibling;
+ iframe.onload = t1.step_func(() => {
+ // The initial about:blank load event can be fired before the form navigation occurs.
+ // See https://github.com/whatwg/html/issues/490 for more information.
+ if (iframe.contentWindow.location.href == "about:blank") { return; }
+ assert_true(iframe.contentWindow.location.search.indexOf('n1=v1&h1=vh1') != -1);
+ t1.done();
+ });
+ form.submit();
+});
+
+test(() => {
+ const form = populateForm('');
+ form.addEventListener('formdata', e => {
+ e.formData.append('a\nb', 'c\rd');
+ });
+ const formData = new FormData(form);
+ const [name, value] = [...formData][0];
+ assert_equals(name, 'a\nb');
+ assert_equals(value, 'c\rd');
+}, 'Entries added to the "formdata" IDL attribute shouldn\'t be newline normalized in the resulting FormData');
+
+test(() => {
+ let form = populateForm('<input name=n1 value=v1><button type=submit name=n2 value=v2></button>');
+ let formDataInEvent = null;
+ let submitter = form.querySelector('button[type=submit]');
+ form.addEventListener('submit', e => {
+ e.preventDefault();
+ formDataInEvent = new FormData(e.target);
+ });
+
+ submitter.click();
+ assert_equals(formDataInEvent.get('n1'), 'v1');
+ assert_false(formDataInEvent.has('n2'));
+}, 'The constructed FormData object should not contain an entry for the submit button that was used to submit the form.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js
new file mode 100644
index 0000000000..0f0d68163d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/enctypes-helper.js
@@ -0,0 +1,187 @@
+// This file exposes the `formSubmissionTemplate` function, which can be used
+// to create tests for the form submission encoding of various enctypes:
+//
+// const urlencodedTest = formSubmissionTemplate(
+// "application/x-www-form-urlencoded",
+// (expected, _actualFormBody) => expected
+// );
+//
+// urlencodedTest({
+// name: "a",
+// value: "b",
+// expected: "a=b",
+// formEncoding: "UTF-8", // optional
+// description: "Simple urlencoded test"
+// });
+//
+// The above call to `urlencodedTest` tests the urlencoded form submission for a
+// form whose entry list contains a single entry with name "a" and value "b",
+// and it checks that the form payload matches the `expected` property after
+// isomorphic-encoding.
+//
+// Since per the spec no normalization of the form entries should happen before
+// the actual form encoding, each call to `urlencodedTest` will in fact add two
+// tests: one submitting the entry as a form control (marked "normal form"), and
+// one adding the entry through the `formdata` event (marked "formdata event").
+// Both cases are compared against the same expected value.
+//
+// Since multipart/form-data boundary strings can't be predicted ahead of time,
+// the second parameter of `formSubmissionTemplate` allows transforming the
+// expected value passed to each test. The second argument of that callback
+// is the actual form body (isomorphic-decoded). When this callback is used, the
+// `expected` property doesn't need to be a string.
+
+(() => {
+ // Using echo-content-escaped.py rather than
+ // /fetch/api/resources/echo-content.py to work around WebKit not
+ // percent-encoding \x00, which causes the response to be detected as
+ // a binary file and served as a download.
+ const ACTION_URL = "/FileAPI/file/resources/echo-content-escaped.py";
+
+ const IFRAME_NAME = "formtargetframe";
+
+ // Undoes the escapes from echo-content-escaped.py
+ function unescape(str) {
+ return str
+ .replace(/\r\n?|\n/g, "\r\n")
+ .replace(
+ /\\x[0-9A-Fa-f]{2}/g,
+ (escape) => String.fromCodePoint(parseInt(escape.substring(2), 16)),
+ )
+ .replace(/\\\\/g, "\\");
+ }
+
+ // Tests the form submission of an entry list containing a single entry.
+ //
+ // `expectedBuilder` is a function that takes in the actual form body
+ // (necessary to get the multipart/form-data payload) and returns the form
+ // body that should be expected.
+ //
+ // If `testFormData` is false, the form entry will be submitted in for
+ // controls. If it is true, it will submitted by modifying the entry list
+ // during the `formdata` event.
+ async function formSubmissionTest({
+ name,
+ value,
+ expectedBuilder,
+ enctype,
+ formEncoding,
+ testFormData = false,
+ testCase,
+ }) {
+ if (document.readyState !== "complete") {
+ await new Promise((resolve) => addEventListener("load", resolve));
+ }
+
+ const formTargetFrame = Object.assign(document.createElement("iframe"), {
+ name: IFRAME_NAME,
+ });
+ document.body.append(formTargetFrame);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(formTargetFrame);
+ });
+
+ const form = Object.assign(document.createElement("form"), {
+ acceptCharset: formEncoding,
+ action: ACTION_URL,
+ method: "POST",
+ enctype,
+ target: IFRAME_NAME,
+ });
+ document.body.append(form);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+
+ if (!testFormData) {
+ const input = document.createElement("input");
+ input.name = name;
+ if (value instanceof File) {
+ input.type = "file";
+ const dataTransfer = new DataTransfer();
+ dataTransfer.items.add(value);
+ input.files = dataTransfer.files;
+ } else {
+ input.type = "hidden";
+ input.value = value;
+ }
+ form.append(input);
+ } else {
+ form.addEventListener("formdata", (evt) => {
+ evt.formData.append(name, value);
+ });
+ }
+
+ await new Promise((resolve) => {
+ form.submit();
+ formTargetFrame.onload = resolve;
+ });
+
+ const serialized = unescape(
+ formTargetFrame.contentDocument.body.textContent,
+ );
+ const expected = expectedBuilder(serialized);
+ assert_equals(serialized, expected);
+ }
+
+ // This function returns a function to add individual form tests corresponding
+ // to some enctype.
+ // `expectedBuilder` is an optional callback that takes two parameters:
+ // `expected` (the `expected` property passed to a test) and `actualFormBody`
+ // (the actual form body submitted by the browser, isomorphic-decoded). It
+ // must return the correct form body that should have been submitted,
+ // isomorphic-encoded. This is necessary in order to account for the
+ // multipart/form-data boundary.
+ //
+ // The returned function takes an object with the following properties:
+ // - `name`, the form entry's name. Must be a string.
+ // - `value`, the form entry's value, either a string or a `File` object.
+ // - `expected`, the expected form body. Usually a string, but it can be
+ // anything depending on `expectedBuilder`.
+ // - `formEncoding` (optional), the character encoding used for submitting the
+ // form.
+ // - `description`, used as part of the testharness test's description.
+ window.formSubmissionTemplate = (
+ enctype,
+ expectedBuilder = (expected) => expected
+ ) => {
+ function form({
+ name,
+ value,
+ expected,
+ formEncoding = "utf-8",
+ description,
+ }) {
+ const commonParams = {
+ name,
+ value,
+ expectedBuilder: expectedBuilder.bind(null, expected),
+ enctype,
+ formEncoding,
+ };
+
+ // Normal form
+ promise_test(
+ (testCase) =>
+ formSubmissionTest({
+ ...commonParams,
+ testCase,
+ }),
+ `${enctype}: ${description} (normal form)`,
+ );
+
+ // formdata event
+ promise_test(
+ (testCase) =>
+ formSubmissionTest({
+ ...commonParams,
+ testFormData: true,
+ testCase,
+ }),
+ `${enctype}: ${description} (formdata event)`,
+ );
+ }
+
+ return form;
+ };
+})();
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js
new file mode 100644
index 0000000000..7df128515c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-empty-file.window.js
@@ -0,0 +1,99 @@
+test(t => {
+ const form = document.body.appendChild(document.createElement("form")),
+ input = form.appendChild(document.createElement("input"));
+ input.type = "file";
+ input.name = "hi";
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+ const fd = new FormData(form),
+ value = fd.get(input.name);
+ assert_true(value instanceof File, "value is a File");
+ assert_equals(value.name, "", "name");
+ assert_equals(value.type, "application/octet-stream", "type");
+ assert_equals(value.size, 0, "expected value to be an empty File");
+}, "Empty <input type=file> is still added to the form's entry list");
+
+async_test((t) => {
+ const form = document.body.appendChild(document.createElement("form")),
+ input = form.appendChild(document.createElement("input")),
+ target = document.createElement("iframe");
+ target.name = "target1";
+ document.body.appendChild(target);
+ form.method = "POST";
+ form.action = "/fetch/api/resources/echo-content.py";
+ form.enctype = "application/x-www-form-urlencoded";
+ form.target = target.name;
+ input.type = "file";
+ input.name = "hi";
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ document.body.removeChild(target);
+ });
+
+ target.addEventListener("load", t.step_func_done(() => {
+ assert_equals(target.contentDocument.body.textContent, "hi=");
+ }));
+ form.submit();
+}, "Empty <input type=file> shows up in the urlencoded serialization");
+
+async_test((t) => {
+ const form = document.body.appendChild(document.createElement("form")),
+ input = form.appendChild(document.createElement("input")),
+ target = document.createElement("iframe");
+ target.name = "target2";
+ document.body.appendChild(target);
+ form.method = "POST";
+ form.action = "/fetch/api/resources/echo-content.py";
+ form.enctype = "multipart/form-data";
+ form.target = target.name;
+ input.type = "file";
+ input.name = "hi";
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ document.body.removeChild(target);
+ });
+
+ target.addEventListener("load", t.step_func_done(() => {
+ // We use \n rather than \r\n because newlines get normalized as a result
+ // of HTML parsing.
+ const found = target.contentDocument.body.textContent;
+ const boundary = found.split("\n")[0];
+ const expected = [
+ boundary,
+ 'Content-Disposition: form-data; name="hi"; filename=""',
+ "Content-Type: application/octet-stream",
+ "",
+ "",
+ boundary + "--",
+ "",
+ ].join("\n");
+ assert_equals(found, expected);
+ }));
+ form.submit();
+}, "Empty <input type=file> shows up in the multipart/form-data serialization");
+
+async_test((t) => {
+ const form = document.body.appendChild(document.createElement("form")),
+ input = form.appendChild(document.createElement("input")),
+ target = document.createElement("iframe");
+ target.name = "target3";
+ document.body.appendChild(target);
+ form.method = "POST";
+ form.action = "/fetch/api/resources/echo-content.py";
+ form.enctype = "text/plain";
+ form.target = target.name;
+ input.type = "file";
+ input.name = "hi";
+ t.add_cleanup(() => {
+ document.body.removeChild(form);
+ document.body.removeChild(target);
+ });
+
+ target.addEventListener("load", t.step_func_done(() => {
+ // The actual result is "hi=\r\n"; the newline gets normalized as a side
+ // effect of the HTML parsing.
+ assert_equals(target.contentDocument.body.textContent, "hi=\n");
+ }));
+ form.submit();
+}, "Empty <input type=file> shows up in the text/plain serialization");
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv-form.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv-form.html
new file mode 100644
index 0000000000..ce87abd957
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv-form.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>This is the form that will be submitted</title>
+
+<form action="form-echo.py" method="post" enctype="text/plain">
+ <input id="input1" type="text">
+ <select id="input2">
+ <option selected>option
+ </select>
+ <input id="input3" type="radio" checked>
+ <input id="input4" type="checkbox" checked>
+</form>
+
+<script>
+"use strict";
+
+const form = document.querySelector("form");
+
+for (let el of Array.from(form.querySelectorAll("input"))) { // Firefox/Edge support
+ el.name = el.id + "\uDC01";
+ el.value = el.id + "\uDC01";
+}
+
+const select = document.querySelector("select");
+select.name = select.id + "\uDC01";
+select.firstElementChild.value = select.id + "\uDC01";
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv.html
new file mode 100644
index 0000000000..4ac26092ee
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-data-set-usv.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Submitting a form data set that contains unpaired surrogates must convert to Unicode scalar values</title>
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/forms.html#constructing-form-data-set">
+<link rel="help" href="https://github.com/whatwg/html/issues/1490">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe id="testframe" src="form-data-set-usv-form.html"></iframe>
+<iframe id="testframe2" src="form-data-set-usv-form.html"></iframe>
+
+<script>
+"use strict";
+
+async_test(t => {
+ window.addEventListener("load", t.step_func(() => {
+ const iframe = document.querySelector("#testframe");
+ const form = iframe.contentWindow.document.querySelector("form");
+
+ iframe.onload = t.step_func_done(() => {
+ const result = iframe.contentWindow.document.body.textContent;
+
+ assert_equals(result,
+ "69 6e 70 75 74 31 ef bf bd 3d 69 6e 70 75 74 31 ef bf bd " + // input1\uFFFD=input1\uFFFD
+ "0d 0a " + // \r\n
+ "69 6e 70 75 74 32 ef bf bd 3d 69 6e 70 75 74 32 ef bf bd " + // input2\uFFFD=input2\uFFFD
+ "0d 0a " + // \r\n
+ "69 6e 70 75 74 33 ef bf bd 3d 69 6e 70 75 74 33 ef bf bd " + // input3\uFFFD=input3\uFFFD
+ "0d 0a " + // \r\n
+ "69 6e 70 75 74 34 ef bf bd 3d 69 6e 70 75 74 34 ef bf bd " + // input4\uFFFD=input4\uFFFD
+ "0d 0a" // \r\n
+ );
+
+ // ef bf bd is the UTF-8 encoding of U+FFFD
+ });
+
+ form.submit();
+ }));
+}, 'Strings from form controls should be converted to Unicode scalar values in form submission');
+
+async_test(t => {
+ window.addEventListener("load", t.step_func_done(() => {
+ const iframe = document.querySelector("#testframe2");
+ const formData = new FormData(iframe.contentWindow.document.querySelector("form"));
+ assert_equals(formData.get("input1\uFFFD"), "input1\uFFFD");
+ assert_equals(formData.get("input2\uFFFD"), "input2\uFFFD");
+ assert_equals(formData.get("input3\uFFFD"), "input3\uFFFD");
+ assert_equals(formData.get("input4\uFFFD"), "input4\uFFFD");
+ }));
+}, 'Strings from form controls should be converted to Unicode scalar values in FormData');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-2.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-2.html
new file mode 100644
index 0000000000..f7939e0fa3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-2.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- The onclick submit() should *not* get superseded in this case by the
+ default action submit(), because onclick here calls preventDefault().
+ -->
+
+
+
+
+<label for=frame1 style="display:block">This frame should stay blank</label>
+<iframe name=frame1 id=frame1></iframe>
+<label for=frame2 style="display:block">This frame should navigate (to 404)</label>
+<iframe name=frame2 id=frame2></iframe>
+<form id="form1" target="frame2" action="nonexistent.html">
+ <input type=hidden name=navigated value=1>
+ <input id=submitbutton type=submit>
+</form>
+
+<script>
+let frame1 = document.getElementById('frame1');
+let frame2 = document.getElementById('frame2');
+let form1 = document.getElementById('form1');
+let submitbutton = document.getElementById('submitbutton');
+
+async_test(t => {
+ window.addEventListener('load', () => {
+ frame1.addEventListener('load', t.step_func_done(() => {
+ assert_unreached("Frame1 should not get navigated by this test.");
+ }));
+ frame2.addEventListener('load', t.step_func_done(() => {
+ let params = (new URL(frame2.contentWindow.location)).searchParams;
+ let wasNavigated = !!params.get("navigated");
+ assert_true(wasNavigated);
+ }));
+ form1.addEventListener('click', t.step_func(() => {
+ form1.submit();
+ form1.target='frame1';
+ event.preventDefault(); // Prevent default here
+ }));
+ submitbutton.click();
+ });
+}, 'preventDefault should allow onclick submit() to succeed');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-3.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-3.html
new file mode 100644
index 0000000000..fbb6a42577
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-3.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- <button> should have the same double-submit protection that
+ <input type=submit> has.
+ -->
+
+
+
+
+<label for=frame1 style="display:block">This frame should stay blank</label>
+<iframe name=frame1 id=frame1></iframe>
+<label for=frame2 style="display:block">This frame should navigate (to 404)</label>
+<iframe name=frame2 id=frame2></iframe>
+<form id="form1" target="frame1" action="nonexistent.html">
+ <input type=hidden name=navigated value=1>
+ <button id=submitbutton>submit</button>
+</form>
+
+<script>
+let frame1 = document.getElementById('frame1');
+let frame2 = document.getElementById('frame2');
+let form1 = document.getElementById('form1');
+let submitbutton = document.getElementById('submitbutton');
+
+async_test(t => {
+ window.addEventListener('load', () => {
+ frame1.addEventListener('load', t.step_func_done(() => {
+ assert_unreached("Frame1 should not get navigated by this test.");
+ }));
+ frame2.addEventListener('load', t.step_func_done(() => {
+ let params = (new URL(frame2.contentWindow.location)).searchParams;
+ let wasNavigated = !!params.get("navigated");
+ assert_true(wasNavigated)
+ }));
+ form1.addEventListener('click', t.step_func(() => {
+ form1.submit();
+ form1.target='frame2';
+
+ }));
+ submitbutton.click();
+ });
+}, '<button> should have the same double-submit protection as <input type=submit>');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-default-action.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-default-action.html
new file mode 100644
index 0000000000..a14cfe7afa
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-default-action.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<!--
+ The submit() in event handler should get superseded by the default action
+ submit(), which isn't preventDefaulted. This is per the Form Submission
+ Algorithm [1], step 24, which says that new planned navigations replace old
+ planned navigations.
+ [1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
+-->
+<body>
+<script>
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ submitter.addEventListener('click', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v4");
+}, 'default submit action should supersede input onclick submit()');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><button>submit</button>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("button");
+ submitter.addEventListener('click', (e) => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v4");
+}, 'default submit action should supersede button onclick submit()');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('click', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v4");
+}, 'default submit action should supersede form onclick submit()');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('submit', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v4");
+}, 'default submit action should supersede form onsubmit submit()');
+
+promise_test(async (t) => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('click', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ form.addEventListener('submit', () => {
+ input.value = 'v5';
+ form.submit();
+ input.value = 'v6';
+ form.submit();
+ input.value = 'v7';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v7");
+}, 'default submit action should supersede form onclick/onsubmit submit()');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-multiple-targets.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-multiple-targets.html
new file mode 100644
index 0000000000..2b5a589b53
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-multiple-targets.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<!-- The expected behavior of this test is not explicitly specified. -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<form id=myform name=myform action="/formaction.html"></form>
+<iframe id=frame1 name=target1></iframe>
+<iframe id=frame2 name=target2></iframe>
+<iframe id=frame3 name=target3></iframe>
+
+<script>
+
+promise_test(async () => {
+ const frame1LoadPromise = new Promise(resolve => frame1.onload = resolve);
+ const frame2LoadPromise = new Promise(resolve => frame2.onload = resolve);
+ const frame3LoadPromise = new Promise(resolve => frame3.onload = resolve);
+
+ myform.target = 'target1';
+ myform.submit();
+ myform.target = 'target2';
+ myform.submit();
+ myform.target = 'target3';
+ myform.submit();
+
+ await Promise.all([frame1LoadPromise, frame2LoadPromise, frame3LoadPromise]);
+
+ assert_equals(frame1.contentDocument.location.pathname, '/formaction.html');
+ assert_equals(frame2.contentDocument.location.pathname, '/formaction.html');
+ assert_equals(frame3.contentDocument.location.pathname, '/formaction.html');
+
+}, 'Verifies that one form used to target multiple frames in succession navigates all of them.');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault-click.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault-click.html
new file mode 100644
index 0000000000..68dc9c10a8
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault-click.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<!--
+ The submit() in event handler should *not* get superseded in this case by the
+ default action submit(), because event handler here calls preventDefault().
+-->
+<body>
+<script>
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ submitter.addEventListener('click', (e) => {
+ e.preventDefault();
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v3");
+}, 'PreventDefaulting input onclick should allow submit() to succeed');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><button>submit</button>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("button");
+ submitter.addEventListener('click', (e) => {
+ e.preventDefault();
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v3");
+}, 'PreventDefaulting button onclick should allow submit() to succeed');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('click', (e) => {
+ e.preventDefault();
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v3");
+}, 'PreventDefaulting form onclick should allow submit() to succeed');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault.html
new file mode 100644
index 0000000000..b63b78916c
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-preventdefault.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<!--
+ The submit() in event handler should *not* get superseded in this case by the
+ default action submit(), because event handler here calls preventDefault().
+-->
+<body>
+<script>
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1><input type=submit>');
+ let iframe = form.previousSibling;
+ let input = form.querySelector("input[name=n1]");
+ let submitter = form.querySelector("input[type=submit]");
+ form.addEventListener('click', () => {
+ input.value = 'v2';
+ form.submit();
+ input.value = 'v3';
+ form.submit();
+ input.value = 'v4';
+ });
+ form.addEventListener('submit', (e) => {
+ e.preventDefault();
+ input.value = 'v5';
+ form.submit();
+ input.value = 'v6';
+ form.submit();
+ input.value = 'v7';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v6");
+}, 'PreventDefaulting form onsubmit should allow submit() to succeed');
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit><input name=n1 value=v1>');
+ let iframe = form.previousSibling;
+ let input = form['n1'];
+ let submitter = form.querySelector('input[type=submit]');
+ form.addEventListener('submit', e => {
+ e.preventDefault();
+ input.value = 'v2';
+ form.submit();
+
+ input.value = 'v3';
+ form.remove();
+ form.submit();
+ document.body.insertBefore(form, iframe.nextSibling);
+ input.value = 'v4';
+ });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, "n1"), "v2");
+}, 'PreventDefaulting form onsubmit should allow submit() to succeed and the second submit() which is invalid should not supersede first one');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-requestsubmit.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-requestsubmit.html
new file mode 100644
index 0000000000..b4028784ed
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-requestsubmit.html
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help"
+ href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+
+<!-- The onclick requestSubmit() should get superseded by the default
+ action submit, which isn't preventDefaulted by onclick here.
+ This is per the Form Submission Algorithm [1], which
+ says that new planned navigations replace old planned navigations.
+ [1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#planned-navigation
+ -->
+
+<body>
+ <script>
+ function runTest({ submitterType, preventDefaultSubmitButton, preventDefaultRequestSubmit, passSubmitter, testName }) {
+ if (preventDefaultRequestSubmit && preventDefaultSubmitButton) {
+ // In this case, no submit action will take place.
+ return;
+ }
+
+ promise_test(async () => {
+ const form = populateForm(`<input name=n1 value=v1><${submitterType} type=submit name=n2 value=v2>${submitterType == 'button' ? '</button>' : ''}`);
+ const input = form.elements[0];
+ const submitter = form.elements[1];
+ submitter.addEventListener('click', e => {
+ form.addEventListener('submit', e => {
+ submitter.value = 'v3';
+ if (preventDefaultRequestSubmit) {
+ e.preventDefault();
+ }
+ }, { once: true });
+
+ form.requestSubmit(passSubmitter ? submitter : null);
+ input.value = 'v2';
+
+ form.addEventListener('submit', e => {
+ submitter.value = 'v4';
+ if (preventDefaultSubmitButton) {
+ e.preventDefault();
+ }
+ }, { once: true });
+ });
+
+ let formDataInEvent;
+ form.addEventListener('formdata', e => {
+ formDataInEvent = e.formData;
+ });
+
+ submitter.click();
+ assert_equals(formDataInEvent.get('n1'), preventDefaultSubmitButton ? 'v1' : 'v2');
+ if (preventDefaultSubmitButton && !passSubmitter) {
+ assert_false(formDataInEvent.has('n2'));
+ } else {
+ assert_equals(formDataInEvent.get('n2'),
+ preventDefaultSubmitButton && passSubmitter ? 'v3' : 'v4')
+ }
+
+ let iframe = form.previousSibling;
+ await loadPromise(iframe);
+ assert_equals(getParamValue(iframe, 'n1'), preventDefaultSubmitButton ? 'v1' : 'v2');
+ if (preventDefaultSubmitButton && !passSubmitter) {
+ assert_equals(getParamValue(iframe, 'n2'), null);
+ } else {
+ assert_equals(getParamValue(iframe, 'n2'),
+ preventDefaultSubmitButton && passSubmitter ? 'v3' : 'v4');
+ }
+ }, testName);
+ }
+
+ function runTest2({ submitterType, callRequestSubmit, callSubmit, preventDefault, passSubmitter, testName }) {
+ if (!callSubmit && preventDefault) {
+ // Without callSubmit, preventDefault will cause the form to not get
+ // submitted.
+ return;
+ }
+
+ promise_test(async () => {
+ const form = populateForm(`<input name=n1 value=v1><${submitterType} type=submit name=n2 value=v3>${submitterType == 'button' ? '</button>' : ''}`);
+ const input = form.elements[0];
+ const submitter = form.elements[1];
+
+ form.addEventListener('submit', e => {
+ if (callRequestSubmit) {
+ form.requestSubmit(passSubmitter ? submitter : null);
+ input.value = 'v2';
+ }
+ if (callSubmit) {
+ form.submit();
+ }
+ if (preventDefault) {
+ e.preventDefault();
+ }
+ });
+
+ form.requestSubmit(passSubmitter ? submitter : null);
+ let iframe = form.previousSibling;
+ await loadPromise(iframe);
+
+ assert_equals(getParamValue(iframe, 'n1'), callRequestSubmit ? 'v2' : 'v1');
+ if (callSubmit || !passSubmitter) {
+ assert_equals(getParamValue(iframe, 'n2'), null);
+ } else {
+ assert_equals(getParamValue(iframe, 'n2'), 'v3')
+ }
+ }, testName);
+ }
+
+ function callWithArgs(test, argsLeft, args) {
+ if (argsLeft.length == 0) {
+ args.testName = 'test ' + test.name + ' with ' + Object.entries(args).map(([key, value]) => `${key}: ${value}`).join(', ');
+ test(args);
+ return;
+ }
+
+ let [name, values] = argsLeft[0];
+ for (let value of values) {
+ callWithArgs(test, argsLeft.slice(1), { ...args, [name]: value })
+ }
+ }
+
+ let args = {
+ submitterType: ['input', 'button'],
+ preventDefaultRequestSubmit: [true, false],
+ preventDefaultSubmitButton: [true, false],
+ passSubmitter: [true, false],
+ };
+ callWithArgs(runTest, Object.entries(args), {});
+
+ args = {
+ submitterType: ['input', 'button'],
+ callRequestSubmit: [true, false],
+ callSubmit: [true, false],
+ preventDefault: [true, false],
+ passSubmitter: [true, false],
+ };
+ callWithArgs(runTest2, Object.entries(args), {});
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-to-different-origin-frame.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-to-different-origin-frame.html
new file mode 100644
index 0000000000..00a46bfd43
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit-to-different-origin-frame.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<!-- The onclick submit() should get superseded by the default
+ action submit(), which isn't preventDefaulted by onclick here.
+ This is per the Form Submission Algorithm [1], step 22.3, which
+ says that new planned navigations replace old planned navigations.
+ [1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
+ -->
+
+<label for=frame1 style="display:block">This frame should stay blank</label>
+<iframe name=frame1 id=frame1></iframe>
+<label for=frame2 style="display:block">This frame should navigate (to 404)</label>
+<iframe name=frame2 id=frame2></iframe>
+<form id="form1" target="frame1" action="nonexistent.html">
+ <input type=hidden name=navigated value=1>
+ <input id=submitbutton type=submit>
+</form>
+
+<script>
+promise_test(async () => {
+ function getLoadPromise(frame) {
+ return new Promise(resolve => {
+ frame.addEventListener('load', resolve);
+ });
+ }
+
+ const frame1 = document.getElementById('frame1');
+ const frame2 = document.getElementById('frame2');
+ await getLoadPromise(window);
+
+ const frame1LoadPromise = getLoadPromise(frame1);
+ let frame2LoadPromise = getLoadPromise(frame2);
+ const subframeUrl = get_host_info().REMOTE_ORIGIN;
+ frame1.src = subframeUrl;
+ frame2.src = subframeUrl;
+ await frame1LoadPromise;
+ await frame2LoadPromise;
+ assert_false(!!frame1.contentDocument, 'frame1 should have a different origin.');
+ assert_false(!!frame2.contentDocument, 'frame2 should have a different origin.');
+
+ window.frame1Navigated = false;
+ frame1.addEventListener('load', () => {
+ window.frame1Navigated = true;
+ });
+ frame2LoadPromise = getLoadPromise(frame2);
+
+ form1.addEventListener('click', () => {
+ form1.submit();
+ form1.target = 'frame2';
+ });
+ submitbutton.click();
+ await frame2LoadPromise;
+
+ assert_false(window.frame1Navigated, 'frame1 should not be navigated by the form submission.');
+}, 'default submit action should supersede onclick submit() for cross-origin iframes');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit.html
new file mode 100644
index 0000000000..b6ea0cefab
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-double-submit.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" href="mailto:masonf@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- The onclick submit() should get superseded by the default
+ action submit(), which isn't preventDefaulted by onclick here.
+ This is per the Form Submission Algorithm [1], step 22.3, which
+ says that new planned navigations replace old planned navigations.
+ [1] https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm
+ -->
+
+<label for=frame1 style="display:block">This frame should stay blank</label>
+<iframe name=frame1 id=frame1></iframe>
+<label for=frame2 style="display:block">This frame should navigate (to 404)</label>
+<iframe name=frame2 id=frame2></iframe>
+<form id="form1" target="frame1" action="nonexistent.html">
+ <input type=hidden name=navigated value=1>
+ <input id=submitbutton type=submit>
+</form>
+
+<script>
+let frame1 = document.getElementById('frame1');
+let frame2 = document.getElementById('frame2');
+let form1 = document.getElementById('form1');
+let submitbutton = document.getElementById('submitbutton');
+
+async_test(t => {
+ window.addEventListener('load', () => {
+ frame1.addEventListener('load', t.step_func_done(() => {
+ assert_unreached("Frame1 should not get navigated by this test.");
+ }));
+ frame2.addEventListener('load', t.step_func_done(() => {
+ let params = (new URL(frame2.contentWindow.location)).searchParams;
+ let wasNavigated = !!params.get("navigated");
+ assert_true(wasNavigated)
+ }));
+ form1.addEventListener('click', t.step_func(() => {
+ form1.submit();
+ form1.target='frame2';
+
+ }));
+ submitbutton.click();
+ });
+}, 'default submit action should supersede onclick submit()');
+
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-echo.py b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-echo.py
new file mode 100644
index 0000000000..72f1f51ce5
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-echo.py
@@ -0,0 +1,7 @@
+def main(request, response):
+ bytes = bytearray(request.raw_input.read())
+ bytes_string = b" ".join(b"%02x" % b for b in bytes)
+ return (
+ [(b"Content-Type", b"text/plain")],
+ bytes_string
+ )
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submission-algorithm.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submission-algorithm.html
new file mode 100644
index 0000000000..0f0fd4ede0
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submission-algorithm.html
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<body>
+<script>
+test(() => {
+ let form = populateForm('<input name=n10 value=v10>');
+ let counter = 0;
+ form.addEventListener('formdata', e => {
+ ++counter;
+ form.submit();
+ });
+ form.submit();
+ assert_equals(counter, 1);
+ new FormData(form);
+ assert_equals(counter, 2);
+}, 'If constructing entry list flag of form is true, then return');
+
+test(() => {
+ let form = populateForm('<input><input type=submit>');
+ let submitter1 = form.querySelector('input[type=submit]');
+ let valid = form.elements[0];
+ let counter = 0;
+ valid.oninvalid = () => {
+ ++counter;
+ };
+ form.onsubmit = () => {
+ valid.required = true;
+ submitter1.dispatchEvent(new MouseEvent("click"));
+ };
+ submitter1.dispatchEvent(new MouseEvent("click"));
+ assert_equals(counter, 0);
+}, "If firing submission events flag of form is true, then return");
+
+test(() => {
+ let form = populateForm('<input required><input type=submit><button type=submit></button>');
+ let submitter1 = form.querySelector('input[type=submit]');
+ let submitter2 = form.querySelector('button[type=submit]');
+ let invalid = form.querySelector('[required]');
+ let counter = 0;
+ invalid.oninvalid = () => {
+ ++counter;
+ // Needs to click different one because click() has reentrancy protection.
+ submitter2.click();
+ };
+ submitter1.click();
+ assert_equals(counter, 1);
+}, "If form's firing submission events is true, then return; 'invalid' event");
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit name=n value=i><button type=submit name=n value=b>');
+ let submitter1 = form.querySelector('input[type=submit]');
+ let submitter2 = form.querySelector('button[type=submit]');
+ let iframe = form.previousSibling;
+ form.onsubmit = () => {
+ // Needs to click different one because click() has reentrancy protection.
+ submitter2.click();
+ };
+ submitter1.click();
+ // We actually submit the form in order to check which 'click()' submits it.
+ await loadPromise(iframe);
+ assert_not_equals(iframe.contentWindow.location.search.indexOf('n=i'), -1);
+}, "If form's firing submission events is true, then return; 'submit' event");
+
+promise_test(async () => {
+ let form = populateForm('<button type=submit></button><input name=n1 value=submit type=submit>');
+ let iframe = form.previousSibling;
+ let submitter = form.querySelector('input[type=submit]');
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, submitter);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; clicking a submit button');
+
+promise_test(async () => {
+ let form = populateForm('<input type=image name=n1>');
+ let iframe = form.previousSibling;
+ let submitter = form.querySelector('input[type=image]');
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ submitter.click();
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, submitter);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; clicking an image button');
+
+promise_test(async () => {
+ let form = populateForm('');
+ let iframe = form.previousSibling;
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ form.requestSubmit();
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, null);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; form.requestSubmit()');
+
+promise_test(async () => {
+ let form = populateForm('');
+ let iframe = form.previousSibling;
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ form.requestSubmit(null);
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, null);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; form.requestSubmit(null)');
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit><button type=submit></button>');
+ let iframe = form.previousSibling;
+ let submitter = form.querySelector('button');
+ let event;
+ form.addEventListener('submit', e => { event = e; });
+ form.requestSubmit(submitter);
+ await loadPromise(iframe);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, submitter);
+ assert_true(event instanceof SubmitEvent);
+}, 'firing an event named submit; form.requestSubmit(submitter)');
+
+promise_test(async () => {
+ let form = populateForm('<input name=n1 value=v1>');
+ form.onformdata = (e) => { e.target.remove(); };
+ let wasLoaded = false;
+ let iframe = form.previousSibling;
+ // Request to load '/common/dummy.xhtml', and immediately submit the form to
+ // the same frame. If the form submission is aborted, the first request
+ // will be completed.
+ iframe.addEventListener("load", () => {
+ // This may be complicated by loads of the initial about:blank;
+ // we need to ignore them and only look at a load that isn't about:blank.
+ if (iframe.contentWindow.location == "about:blank") { return; }
+ wasLoaded = true;
+ });
+ iframe.src = '/common/dummy.xhtml';
+ assert_false(wasLoaded, 'Make sure the first loading is ongoing.');
+ form.submit();
+ await loadPromise(iframe);
+ assert_true(iframe.contentWindow.location.search.indexOf('n1=v1') == -1);
+}, 'Cannot navigate (after constructing the entry list)');
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit>');
+ let iframe = form.previousSibling;
+ let event;
+ form.submit();
+ await loadPromise(iframe);
+ assert_true(iframe.contentWindow.location.href.includes("?"));
+}, 'Submission URL should always have a non-null query part');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html
new file mode 100644
index 0000000000..ad2943e2bb
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/form-submit-iframe-then-location-navigate.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- This behavior is not explicitly specified. -->
+
+<iframe id=myframe name=framename></iframe>
+<form id=myform target=framename action="resources/form.html"></form>
+
+<script>
+async_test(t => {
+ myframe.onload = t.step_func_done(() => {
+ assert_equals(
+ myframe.contentDocument.location.pathname,
+ '/html/semantics/forms/form-submission-0/resources/location.html');
+ });
+ myform.submit();
+ myframe.contentDocument.location = 'resources/location.html';
+}, 'Verifies that location navigations take precedence when following form submissions.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/getactionurl.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/getactionurl.html
new file mode 100644
index 0000000000..2e21828ae6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/getactionurl.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<iframe src="resources/getactionurl-iframe.html"></iframe>
+<script>
+async_test(t => {
+ window.onmessage = t.step_func_done(event => assert_equals(event.data, 'PASS'));
+}, `Verifies that a form element's target can be a data url.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/historical.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/historical.window.js
new file mode 100644
index 0000000000..fcc47d90f6
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/historical.window.js
@@ -0,0 +1,19 @@
+// META: script=./resources/targetted-form.js
+
+test(t => {
+ const form = populateForm('<input required><input type=submit>');
+ t.add_cleanup(() => {
+ form.previousElementSibling.remove();
+ form.remove();
+ });
+ const submitter = form.querySelector('input[type=submit]');
+ let invalid = form.querySelector('[required]');
+ let targets = [];
+ const listener = e => targets.push(e.target.localName);
+ form.addEventListener("invalid", t.step_func(listener));
+ form.oninvalid = t.step_func(listener);
+ invalid.addEventListener("invalid", t.step_func(listener));
+ invalid.oninvalid = t.step_func(listener);
+ submitter.click();
+ assert_array_equals(targets, ["input", "input"]);
+}, "invalid event is only supported for form controls");
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/implicit-submission.optional.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/implicit-submission.optional.html
new file mode 100644
index 0000000000..379a4396ac
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/implicit-submission.optional.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/C/#implicit-submission">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<body>
+<script>
+// This test file is "optional" because triggering implicit submission by
+// "Enter" key is not standardized.
+
+const ENTER = '\uE007';
+
+promise_test(async () => {
+ let form = populateForm('<input name=text value=abc><input name=submitButton type=submit>');
+ let event;
+ form.text.focus();
+ form.addEventListener('submit', e => { event = e; });
+ await test_driver.send_keys(form.text, ENTER);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, form.submitButton);
+ assert_true(event instanceof SubmitEvent);
+}, 'Submit event with a submit button');
+
+promise_test(async () => {
+ let form = populateForm('<input name=text value=abc>');
+ let event;
+ form.text.focus();
+ form.addEventListener('submit', e => { event = e; });
+ await test_driver.send_keys(form.text, ENTER);
+ assert_true(event.bubbles);
+ assert_true(event.cancelable);
+ assert_equals(event.submitter, null);
+ assert_true(event instanceof SubmitEvent);
+}, 'Submit event with no submit button');
+
+promise_test(async (test) => {
+ let form = populateForm('<input name=text value=abc><input name=submitButton type=submit disabled>');
+ form.text.focus();
+ form.addEventListener('submit', test.unreached_func('submit event should not be dispatched'));
+ await test_driver.send_keys(form.text, ENTER);
+}, 'Submit event with disabled submit button');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html
new file mode 100644
index 0000000000..f476308b7d
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- This behavior is not explicitly specified. -->
+
+<iframe id=frameid name=framename></iframe>
+<form id=formid target=framename action="resources/form.html"></form>
+
+<script>
+async_test(t => {
+ frameid.src = 'resources/jsurl-form-submit-iframe.html';
+
+ frameid.onload = t.step_func(() => {
+ assert_equals(
+ frameid.contentDocument.location.pathname,
+ '/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html');
+
+ frameid.onload = t.step_func_done(() => {
+ assert_equals(
+ frameid.contentDocument.location.pathname,
+ '/html/semantics/forms/form-submission-0/resources/form.html');
+ });
+
+ frameid.contentDocument.getElementById('anchorid').click();
+ });
+
+}, `Verifies that form submissions scheduled inside javascript: urls take precedence over the javascript: url's return value.`);
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-navigation-then-form-submit.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-navigation-then-form-submit.html
new file mode 100644
index 0000000000..93a4ea6004
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/jsurl-navigation-then-form-submit.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="author" title="Joey Arhar" href="mailto:jarhar@chromium.org">
+
+<!-- The expected behavior of this test is not explicitly specified. -->
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+async_test(t => {
+ window.onload = t.step_func(() => {
+ const iframe = document.createElement('iframe');
+ iframe.name = 'myframe';
+
+ iframe.onload = t.step_func_done(() => {
+ assert_equals(iframe.contentDocument.location.pathname, '/formaction.html');
+ });
+
+ const form = document.createElement('form');
+ form.target = iframe.name;
+ form.action = '/formaction.html';
+ document.body.appendChild(form);
+
+ iframe.src = 'javascript:false';
+ document.body.appendChild(iframe);
+ form.submit();
+ });
+}, 'Verifies that form submissions cancel javascript navigations to prevent duplicate load events.');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js
new file mode 100644
index 0000000000..ca69c7dac7
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/multipart-formdata.window.js
@@ -0,0 +1,357 @@
+// META: script=enctypes-helper.js
+
+// Form submissions in multipart/form-data are also tested in
+// /FileAPI/file/send-file*
+
+// The `expected` property of objects passed to `formTest` must be an object
+// with `name`, `value` and optionally `filename` properties, which represent
+// the corresponding data in a multipart/form-data part.
+const formTest = formSubmissionTemplate(
+ "multipart/form-data",
+ ({ name, filename, value }, serialized) => {
+ let headers;
+ if (filename === undefined) {
+ headers = [`Content-Disposition: form-data; name="${name}"`];
+ } else {
+ headers = [
+ `Content-Disposition: form-data; name="${name}"; filename="${filename}"`,
+ "Content-Type: text/plain",
+ ];
+ }
+
+ const boundary = serialized.split("\r\n")[0];
+
+ return [
+ boundary,
+ ...headers,
+ "",
+ value,
+ boundary + "--",
+ "",
+ ].join("\r\n");
+ },
+);
+
+formTest({
+ name: "basic",
+ value: "test",
+ expected: {
+ name: "basic",
+ value: "test",
+ },
+ description: "Basic test",
+});
+
+formTest({
+ name: "basic",
+ value: new File([], "file-test.txt", { type: "text/plain" }),
+ expected: {
+ name: "basic",
+ filename: "file-test.txt",
+ value: "",
+ },
+ description: "Basic File test",
+});
+
+formTest({
+ name: "a\0b",
+ value: "c",
+ expected: {
+ name: "a\0b",
+ value: "c",
+ },
+ description: "0x00 in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\0c",
+ expected: {
+ name: "a",
+ value: "b\0c",
+ },
+ description: "0x00 in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\0c", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b\0c",
+ value: "",
+ },
+ description: "0x00 in filename",
+});
+
+formTest({
+ name: "a\nb",
+ value: "c",
+ expected: {
+ name: "a%0D%0Ab",
+ value: "c",
+ },
+ description: "\\n in name",
+});
+
+formTest({
+ name: "a\rb",
+ value: "c",
+ expected: {
+ name: "a%0D%0Ab",
+ value: "c",
+ },
+ description: "\\r in name",
+});
+
+formTest({
+ name: "a\r\nb",
+ value: "c",
+ expected: {
+ name: "a%0D%0Ab",
+ value: "c",
+ },
+ description: "\\r\\n in name",
+});
+
+formTest({
+ name: "a\n\rb",
+ value: "c",
+ expected: {
+ name: "a%0D%0A%0D%0Ab",
+ value: "c",
+ },
+ description: "\\n\\r in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\nc",
+ expected: {
+ name: "a",
+ value: "b\r\nc",
+ },
+ description: "\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\rc",
+ expected: {
+ name: "a",
+ value: "b\r\nc",
+ },
+ description: "\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\r\nc",
+ expected: {
+ name: "a",
+ value: "b\r\nc",
+ },
+ description: "\\r\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\n\rc",
+ expected: {
+ name: "a",
+ value: "b\r\n\r\nc",
+ },
+ description: "\\n\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\nc", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%0Ac",
+ value: "",
+ },
+ description: "\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\rc", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%0Dc",
+ value: "",
+ },
+ description: "\\r in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\r\nc", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%0D%0Ac",
+ value: "",
+ },
+ description: "\\r\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\n\rc", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%0A%0Dc",
+ value: "",
+ },
+ description: "\\n\\r in filename",
+});
+
+formTest({
+ name: 'a"b',
+ value: "c",
+ expected: {
+ name: "a%22b",
+ value: "c",
+ },
+ description: "double quote in name",
+});
+
+formTest({
+ name: "a",
+ value: 'b"c',
+ expected: {
+ name: "a",
+ value: 'b"c',
+ },
+ description: "double quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], 'b"c', { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b%22c",
+ value: "",
+ },
+ description: "double quote in filename",
+});
+
+formTest({
+ name: "a'b",
+ value: "c",
+ expected: {
+ name: "a'b",
+ value: "c",
+ },
+ description: "single quote in name",
+});
+
+formTest({
+ name: "a",
+ value: "b'c",
+ expected: {
+ name: "a",
+ value: "b'c",
+ },
+ description: "single quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b'c", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b'c",
+ value: "",
+ },
+ description: "single quote in filename",
+});
+
+formTest({
+ name: "a\\b",
+ value: "c",
+ expected: {
+ name: "a\\b",
+ value: "c",
+ },
+ description: "backslash in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\\c",
+ expected: {
+ name: "a",
+ value: "b\\c",
+ },
+ description: "backslash in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\\c", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "b\\c",
+ value: "",
+ },
+ description: "backslash in filename",
+});
+
+formTest({
+ name: "áb",
+ value: "ç",
+ expected: {
+ name: "\xC3\xA1b",
+ value: "\xC3\xA7",
+ },
+ description: "non-ASCII in name and value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "ə.txt", { type: "text/plain" }),
+ expected: {
+ name: "a",
+ filename: "\xC9\x99.txt",
+ value: "",
+ },
+ description: "non-ASCII in filename",
+});
+
+formTest({
+ name: "aəb",
+ value: "c\uFFFDd",
+ formEncoding: "windows-1252",
+ expected: {
+ name: "a&#601;b",
+ value: "c&#65533;d",
+ },
+ description: "characters not in encoding in name and value",
+});
+
+formTest({
+ name: "á",
+ value: new File([], "💩", { type: "text/plain" }),
+ formEncoding: "windows-1252",
+ expected: {
+ name: "\xE1",
+ filename: "&#128169;",
+ value: "",
+ },
+ description: "character not in encoding in filename",
+});
+
+formTest({
+ name: "\uD800",
+ value: "\uD800",
+ formEncoding: "windows-1252",
+ expected: {
+ name: "&#65533;",
+ value: "&#65533;"
+ },
+ description: "lone surrogate in name and value",
+});
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/newline-normalization.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/newline-normalization.html
new file mode 100644
index 0000000000..2c83c5a1e9
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/newline-normalization.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>
+ Constructing the entry list shouldn't perform newline normalization
+ </title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <script>
+ function createForm(testCase, name, value) {
+ const form = document.createElement("form");
+ const input = document.createElement("input");
+ input.type = "hidden";
+ input.name = name;
+ input.value = value;
+ form.appendChild(input);
+ document.body.appendChild(form);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+ return form;
+ }
+
+ function createFormWithFile(testCase, name, filename) {
+ const form = document.createElement("form");
+ const input = document.createElement("input");
+ input.type = "file";
+ input.name = name;
+ const dataTransfer = new DataTransfer();
+ dataTransfer.items.add(new File([], filename, { type: "text/plain" }));
+ input.files = dataTransfer.files;
+ form.appendChild(input);
+ document.body.appendChild(form);
+ testCase.add_cleanup(() => {
+ document.body.removeChild(form);
+ });
+ return form;
+ }
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a", "b\nc"));
+ assert_equals(formData.get("a"), "b\nc");
+ }, document.title + ": \\n in the value");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a", "b\rc"));
+ assert_equals(formData.get("a"), "b\rc");
+ }, document.title + ": \\r in the value");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a", "b\r\nc"));
+ assert_equals(formData.get("a"), "b\r\nc");
+ }, document.title + ": \\r\\n in the value");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a", "b\n\rc"));
+ assert_equals(formData.get("a"), "b\n\rc");
+ }, document.title + ": \\n\\r in the value");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a\nb", "c"));
+ assert_equals([...formData][0][0], "a\nb");
+ }, document.title + ": \\n in the name");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a\rb", "c"));
+ assert_equals([...formData][0][0], "a\rb");
+ }, document.title + ": \\r in the name");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a\r\nb", "c"));
+ assert_equals([...formData][0][0], "a\r\nb");
+ }, document.title + ": \\r\\n in the name");
+
+ test((testCase) => {
+ const formData = new FormData(createForm(testCase, "a\n\rb", "c"));
+ assert_equals([...formData][0][0], "a\n\rb");
+ }, document.title + ": \\n\\r in the name");
+
+ test((testCase) => {
+ const formData = new FormData(
+ createFormWithFile(testCase, "a", "b\nc")
+ );
+ assert_equals(formData.get("a").name, "b\nc");
+ }, document.title + ": \\n in the filename");
+
+ test((testCase) => {
+ const formData = new FormData(
+ createFormWithFile(testCase, "a", "b\rc")
+ );
+ assert_equals(formData.get("a").name, "b\rc");
+ }, document.title + ": \\r in the filename");
+
+ test((testCase) => {
+ const formData = new FormData(
+ createFormWithFile(testCase, "a", "b\r\nc")
+ );
+ assert_equals(formData.get("a").name, "b\r\nc");
+ }, document.title + ": \\r\\n in the filename");
+
+ test((testCase) => {
+ const formData = new FormData(
+ createFormWithFile(testCase, "a", "b\n\rc")
+ );
+ assert_equals(formData.get("a").name, "b\n\rc");
+ }, document.title + ": \\n\\r in the filename");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html
new file mode 100644
index 0000000000..6b50bf599b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/reparent-form-during-planned-navigation-task.html
@@ -0,0 +1,15 @@
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<iframe id="i" src="about:blank"></iframe>
+<script>
+async_test(t => {
+ var form = i.contentDocument.createElement('form');
+ form.action = '/common/blank.html';
+ i.contentDocument.body.appendChild(form);
+ i.onload = t.step_func_done(() => {});
+ form.submit();
+ new Document().prepend(form);
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/request-submit-activation.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/request-submit-activation.html
new file mode 100644
index 0000000000..0d1e54daf3
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/request-submit-activation.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/targetted-form.js"></script>
+<body>
+<script>
+promise_test(async () => {
+ let form = populateForm('<input type=submit name=n1 value=v1><button type=submit name=n2 value=v2></button>');
+ let submitter = form.querySelector('button');
+ let iframe = form.previousSibling;
+ let event;
+ form.requestSubmit(submitter);
+ await loadPromise(iframe);
+ assert_true(iframe.contentWindow.location.search.indexOf('n1=v1') == -1, "n1=v1");
+ assert_true(iframe.contentWindow.location.search.indexOf('n2=v2') > 0), "n2=v2";
+}, 'Test activation of submitter for requestSubmit');
+
+promise_test(async () => {
+ let form = populateForm('<input type=submit name=n1 value=v1><button type=submit name=n2 value=v2></button>');
+ let submitter = form.querySelector('input');
+ let iframe = form.previousSibling;
+ let event;
+ form.requestSubmit(submitter);
+ await loadPromise(iframe);
+ assert_true(iframe.contentWindow.location.search.indexOf('n1=v1') > 0, "n1=v1");
+ assert_true(iframe.contentWindow.location.search.indexOf('n2=v2') == -1), "n2=v2";
+}, 'Test activation of submitter for requestSubmit 2');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/file-submission.py b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/file-submission.py
new file mode 100644
index 0000000000..89cd182add
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/file-submission.py
@@ -0,0 +1,10 @@
+import json
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ headers = [(b"Content-Type", b"text/html")]
+ testinput = request.POST.first(b"testinput")
+ value = isomorphic_decode(testinput.value)
+ body = u"<script>parent.postMessage(" + json.dumps(value) + u", '*');</script>"
+ return headers, body
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form-submission.py b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form-submission.py
new file mode 100644
index 0000000000..f0c2d4cf61
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form-submission.py
@@ -0,0 +1,12 @@
+def main(request, response):
+ if request.headers.get(b'Content-Type') == b'application/x-www-form-urlencoded':
+ result = request.body == b'foo=bara'
+ elif request.headers.get(b'Content-Type') == b'text/plain':
+ result = request.body == b'qux=baz\r\n'
+ else:
+ result = request.POST.first(b'foo') == b'bar'
+
+ result = result and request.url_parts.query == u'query=1'
+
+ return ([(b"Content-Type", b"text/plain")],
+ b"OK" if result else b"FAIL")
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form.html
new file mode 100644
index 0000000000..8b16672d6b
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/form.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+ form.html
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/getactionurl-iframe.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/getactionurl-iframe.html
new file mode 100644
index 0000000000..116371a995
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/getactionurl-iframe.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+<script>
+ const form = document.createElement('form');
+ document.body.appendChild(form);
+ form.action = `data:text/html,
+ <!DOCTYPE html>
+ <body>
+ <script>
+ window.top.postMessage('PASS', '*');
+ <\/script>
+ `;
+ form.submit();
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html
new file mode 100644
index 0000000000..00a1eefd0f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/jsurl-form-submit-iframe.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<form id=formid action="form.html"></form>
+<a id=anchorid href="javascript:jsurl()">anchor</a>
+
+<script>
+function jsurl() {
+ formid.submit();
+ return 'jsurl return value';
+}
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/location.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/location.html
new file mode 100644
index 0000000000..6724189eff
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/location.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+ location.html
+</body>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/targetted-form.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/targetted-form.js
new file mode 100644
index 0000000000..52482c859f
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/resources/targetted-form.js
@@ -0,0 +1,38 @@
+let frameCounter = 0;
+
+function populateForm(optionalContentHtml) {
+ if (!optionalContentHtml)
+ optionalContentHtml = '';
+ const frameName = "form-test-target-" + frameCounter++;
+ document.body.insertAdjacentHTML(
+ 'afterbegin',
+ `<iframe name="${frameName}"></iframe>` +
+ `<form action="/common/blank.html" target="` +
+ `${frameName}">${optionalContentHtml}</form>`);
+ return document.getElementsByName(frameName)[0].nextSibling;
+}
+
+function submitPromise(form, iframe) {
+ return new Promise((resolve, reject) => {
+ iframe.onload = () => resolve(iframe.contentWindow.location.search);
+ iframe.onerror = () => reject(new Error('iframe onerror fired'));
+ form.submit();
+ });
+}
+
+function loadPromise(iframe) {
+ return new Promise((resolve, reject) => {
+ iframe.onload = function() {
+ // The initial about:blank load event can be fired before the form navigation occurs.
+ // See https://github.com/whatwg/html/issues/490 for more information.
+ if (iframe.contentWindow.location == "about:blank") { return; }
+ resolve();
+ };
+ iframe.onerror = () => reject(new Error('iframe onerror fired'));
+ });
+}
+
+function getParamValue(iframe, paramName) {
+ let params = (new URL(iframe.contentWindow.location)).searchParams;
+ return params.get(paramName);
+}
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/submission-checks.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submission-checks.window.js
new file mode 100644
index 0000000000..e242ce830a
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submission-checks.window.js
@@ -0,0 +1,62 @@
+async_test(t => {
+ const frame = document.createElement("frame"),
+ form = document.createElement("form");
+ t.add_cleanup(() => frame.remove());
+ form.action = "/common/blank.html";
+ form.target = "doesnotmattertwobits";
+ frame.name = "doesnotmattertwobits";
+ document.body.appendChild(frame);
+ frame.onload = t.step_func(() => {
+ if(frame.contentWindow.location.href === "about:blank")
+ return;
+ assert_unreached();
+ });
+ form.submit();
+ t.step_timeout(() => {
+ assert_equals(frame.contentWindow.location.href, "about:blank");
+ t.done();
+ }, 500);
+}, "<form> not connected to a document cannot navigate");
+
+async_test(t => {
+ const frame = document.createElement("frame"),
+ form = document.createElement("form");
+ t.add_cleanup(() => frame.remove());
+ form.action = "/common/blank.html";
+ form.target = "doesnotmattertwoqbits";
+ form.onsubmit = t.step_func(() => form.remove());
+ frame.name = "doesnotmattertwoqbits";
+ document.body.appendChild(frame);
+ document.body.appendChild(form);
+ frame.onload = t.step_func(() => {
+ if(frame.contentWindow.location.href === "about:blank")
+ return;
+ assert_unreached();
+ });
+ const submit = form.appendChild(document.createElement("input"));
+ submit.type = "submit"
+ submit.click();
+ t.step_timeout(() => {
+ assert_equals(frame.contentWindow.location.href, "about:blank");
+ t.done();
+ }, 500);
+}, "<form> not connected to a document after submit event cannot navigate");
+
+async_test(t => {
+ const frame = document.createElement("frame"),
+ form = document.createElement("form");
+ t.add_cleanup(() => frame.remove());
+ form.action = "/";
+ document.body.appendChild(frame);
+ frame.contentDocument.body.appendChild(form);
+ frame.onload = t.step_func(() => {
+ if(frame.contentWindow.location.href === "about:blank")
+ return;
+ form.submit();
+ t.step_timeout(() => {
+ assert_equals(frame.contentWindow.location.pathname, "/common/blank.html");
+ t.done();
+ }, 500)
+ });
+ frame.src = "/common/blank.html";
+}, "<form> in a navigated document cannot navigate");
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-entity-body.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-entity-body.html
new file mode 100644
index 0000000000..be9c5f0696
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-entity-body.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var simple_tests = [
+ {
+ name: "form submission from form should navigate to url with x-www-form-urlencoded",
+ input: "<input name=foo value=bara>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "",
+ submitaction: function(doc) { doc.getElementById("testform").submit(); }
+ },
+ {
+ name: "form submission from form should navigate to url with multipart/form-data",
+ input: "<textarea name=foo>bar</textarea>",
+ enctype: "multipart/form-data",
+ submitelement: "",
+ submitaction: function(doc) { doc.getElementById("testform").submit(); }
+ },
+ {
+ name: "form submission from form should navigate to url with text/plain",
+ input: "<textarea name=qux>baz</textarea>",
+ enctype: "text/plain",
+ submitelement: "",
+ submitaction: function(doc) { doc.getElementById("testform").submit(); }
+ },
+ {
+ name: "form submission from button should navigate to url with x-www-form-urlencoded",
+ input: "<input name=foo value=bara>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "<button id=buttonsubmit type=\"submit\">Submit</button>",
+ submitaction: function(doc) { doc.getElementById("buttonsubmit").click(); }
+ },
+ {
+ name: "form submission from button should navigate to url with multipart/form-data",
+ input: "<textarea name=foo>bar</textarea>",
+ enctype: "multipart/form-data",
+ submitelement: "<button id=buttonsubmit type=\"submit\">Submit</button>",
+ submitaction: function(doc) { doc.getElementById("buttonsubmit").click(); }
+ },
+ {
+ name: "form submission from button should navigate to url with text/plain",
+ input: "<textarea name=qux>baz</textarea>",
+ enctype: "text/plain",
+ submitelement: "<button id=buttonsubmit type=\"submit\">Submit</button>",
+ submitaction: function(doc) { doc.getElementById("buttonsubmit").click(); }
+ },
+ {
+ name: "form submission from input should navigate to url with x-www-form-urlencoded",
+ input: "<input name=foo value=bara>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "<input id=inputsubmit type=\"submit\">Submit</input>",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ },
+ {
+ name: "form submission from input should navigate to url with multipart/form-data",
+ input: "<textarea name=foo>bar</textarea>",
+ enctype: "multipart/form-data",
+ submitelement: "<input id=inputsubmit type=\"submit\">Submit</input>",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ },
+ {
+ name: "form submission from input should navigate to url with text/plain",
+ input: "<input name=qux value=baz>",
+ enctype: "text/plain",
+ submitelement: "<input id=inputsubmit type=\"submit\">Submit</input>",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ },
+ {
+ name: "form submission from submit input should contain submit button value",
+ input: "<button type=submit name=notclicked value=nope>not clicked</button>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "<button id=inputsubmit type=\"submit\" name=foo value=bara>Submit</button>",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ }
+,
+ {
+ name: "form submission from submit button should contain submit button value",
+ input: "<input type=submit name=notclicked value=nope/>",
+ enctype: "application/x-www-form-urlencoded",
+ submitelement: "<input id=inputsubmit type=\"submit\" name=foo value=bara >",
+ submitaction: function(doc) { doc.getElementById("inputsubmit").click(); }
+ }
+];
+simple_tests.forEach(function(test_obj) {
+ test_obj.test = async_test(test_obj.name);
+});
+function run_simple_test() {
+ if (simple_tests.length == 0) {
+ return;
+ }
+ var test_obj = simple_tests.pop();
+ var t = test_obj.test;
+ var testframe = document.getElementById("testframe");
+ var testdocument = testframe.contentWindow.document;
+ testdocument.body.innerHTML =
+ "<form id=testform method=post action=\"/html/semantics/forms/form-submission-0/resources/form-submission.py?query=1\" enctype=\"" + test_obj.enctype + "\">" +
+ test_obj.input +
+ test_obj.submitelement +
+ "</form>";
+ testframe.onload = function() {
+ t.step(function (){
+ var response = testframe.contentDocument.documentElement.textContent;
+ assert_equals(response, "OK");
+ });
+ t.done();
+ run_simple_test();
+ };
+ test_obj.submitaction(testdocument);
+}
+</script>
+<iframe id=testframe src="/common/blank.html" onload="run_simple_test();"></iframe>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-file.sub.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-file.sub.html
new file mode 100644
index 0000000000..aab60ba949
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/submit-file.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<iframe id=testframe name=testframe></iframe>
+<form id=testform method=post action="//{{domains[www1]}}:{{location[port]}}/html/semantics/forms/form-submission-0/resources/file-submission.py" target=testframe enctype="multipart/form-data">
+<input name=testinput id=testinput type=file>
+</form>
+<script>
+async_test(t => {
+ const dataTransfer = new DataTransfer();
+ dataTransfer.items.add(new File(["foobar"], "name"));
+ assert_equals(1, dataTransfer.files.length);
+
+ testinput.files = dataTransfer.files;
+ testform.submit();
+
+ onmessage = t.step_func(e => {
+ if (e.source !== testframe) return;
+ assert_equals(e.data, "foobar");
+ t.done();
+ });
+}, 'Posting a File');
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js
new file mode 100644
index 0000000000..9ff77aed12
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/text-plain.window.js
@@ -0,0 +1,223 @@
+// META: script=enctypes-helper.js
+
+const formTest = formSubmissionTemplate("text/plain");
+
+formTest({
+ name: "basic",
+ value: "test",
+ expected: "basic=test\r\n",
+ description: "Basic test",
+});
+
+formTest({
+ name: "basic",
+ value: new File([], "file-test.txt"),
+ expected: "basic=file-test.txt\r\n",
+ description: "Basic File test",
+});
+
+formTest({
+ name: "a\0b",
+ value: "c",
+ expected: "a\0b=c\r\n",
+ description: "0x00 in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\0c",
+ expected: "a=b\0c\r\n",
+ description: "0x00 in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\0c"),
+ expected: "a=b\0c\r\n",
+ description: "0x00 in filename",
+});
+
+formTest({
+ name: "a\nb",
+ value: "c",
+ expected: "a\r\nb=c\r\n",
+ description: "\\n in name",
+});
+
+formTest({
+ name: "a\rb",
+ value: "c",
+ expected: "a\r\nb=c\r\n",
+ description: "\\r in name",
+});
+
+formTest({
+ name: "a\r\nb",
+ value: "c",
+ expected: "a\r\nb=c\r\n",
+ description: "\\r\\n in name",
+});
+
+formTest({
+ name: "a\n\rb",
+ value: "c",
+ expected: "a\r\n\r\nb=c\r\n",
+ description: "\\n\\r in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\nc",
+ expected: "a=b\r\nc\r\n",
+ description: "\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\rc",
+ expected: "a=b\r\nc\r\n",
+ description: "\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\r\nc",
+ expected: "a=b\r\nc\r\n",
+ description: "\\r\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\n\rc",
+ expected: "a=b\r\n\r\nc\r\n",
+ description: "\\n\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\nc"),
+ expected: "a=b\r\nc\r\n",
+ description: "\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\rc"),
+ expected: "a=b\r\nc\r\n",
+ description: "\\r in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\r\nc"),
+ expected: "a=b\r\nc\r\n",
+ description: "\\r\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\n\rc"),
+ expected: "a=b\r\n\r\nc\r\n",
+ description: "\\n\\r in filename",
+});
+
+formTest({
+ name: 'a"b',
+ value: "c",
+ expected: 'a"b=c\r\n',
+ description: "double quote in name",
+});
+
+formTest({
+ name: "a",
+ value: 'b"c',
+ expected: 'a=b"c\r\n',
+ description: "double quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], 'b"c'),
+ expected: 'a=b"c\r\n',
+ description: "double quote in filename",
+});
+
+formTest({
+ name: "a'b",
+ value: "c",
+ expected: "a'b=c\r\n",
+ description: "single quote in name",
+});
+
+formTest({
+ name: "a",
+ value: "b'c",
+ expected: "a=b'c\r\n",
+ description: "single quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b'c"),
+ expected: "a=b'c\r\n",
+ description: "single quote in filename",
+});
+
+formTest({
+ name: "a\\b",
+ value: "c",
+ expected: "a\\b=c\r\n",
+ description: "backslash in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\\c",
+ expected: "a=b\\c\r\n",
+ description: "backslash in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\\c"),
+ expected: "a=b\\c\r\n",
+ description: "backslash in filename",
+});
+
+formTest({
+ name: "áb",
+ value: "ç",
+ expected: "\xC3\xA1b=\xC3\xA7\r\n",
+ description: "non-ASCII in name and value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "ə.txt"),
+ expected: "a=\xC9\x99.txt\r\n",
+ description: "non-ASCII in filename",
+});
+
+formTest({
+ name: "aəb",
+ value: "c\uFFFDd",
+ formEncoding: "windows-1252",
+ expected: "a&#601;b=c&#65533;d\r\n",
+ description: "characters not in encoding in name and value",
+});
+
+formTest({
+ name: "á",
+ value: new File([], "💩"),
+ formEncoding: "windows-1252",
+ expected: "\xE1=&#128169;\r\n",
+ description: "character not in encoding in filename",
+});
+
+formTest({
+ name: "\uD800",
+ value: "\uD800",
+ formEncoding: "windows-1252",
+ expected: "&#65533;=&#65533;\r\n",
+ description: "lone surrogate in name and value",
+});
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/url-encoded.html b/testing/web-platform/tests/html/semantics/forms/form-submission-0/url-encoded.html
new file mode 100644
index 0000000000..d05364387e
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/url-encoded.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<iframe id=testframe src="/common/blank.html"></iframe>
+<script>
+var simple_tests = [
+ {
+ name: "text.simple",
+ input: "<input name=foo value=bara>",
+ output: "foo=bara"
+ },
+ {
+ name: "textarea.simple",
+ input: "<textarea name=foo>bar</textarea>",
+ output: "foo=bar"
+ },
+ {
+ name: "nokeygen.simple",
+ input: "<input name=foo value=barb><keygen>",
+ output: "foo=barb"
+ }
+];
+simple_tests.forEach(function(test_obj) {
+ test_obj.test = async_test(test_obj.name);
+});
+function run_simple_test() {
+ if (simple_tests.length == 0) {
+ return;
+ }
+ test_obj = simple_tests.pop();
+ var t = test_obj.test;
+ var testframe = document.getElementById("testframe");
+ var testdocument = testframe.contentWindow.document;
+ testdocument.body.innerHTML =
+ "<form id=testform action=\"/common/blank.html\">" +
+ test_obj.input +
+ "</form>";
+ testframe.onload = function() {
+ t.step(function (){
+ var get_url = testframe.contentWindow.location.toString();
+ var encoded = get_url.substr(get_url.indexOf("?") + 1);
+ assert_equals(encoded, test_obj.output);
+ });
+ t.done();
+ run_simple_test();
+ };
+ testdocument.getElementById("testform").submit();
+}
+document.getElementById("testframe").onload = run_simple_test;
+</script>
diff --git a/testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js b/testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js
new file mode 100644
index 0000000000..7818f68619
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/form-submission-0/urlencoded2.window.js
@@ -0,0 +1,223 @@
+// META: script=enctypes-helper.js
+
+const formTest = formSubmissionTemplate("application/x-www-form-urlencoded");
+
+formTest({
+ name: "basic",
+ value: "test",
+ expected: "basic=test",
+ description: "Basic test",
+});
+
+formTest({
+ name: "basic",
+ value: new File([], "file-test.txt"),
+ expected: "basic=file-test.txt",
+ description: "Basic File test",
+});
+
+formTest({
+ name: "a\0b",
+ value: "c",
+ expected: "a%00b=c",
+ description: "0x00 in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\0c",
+ expected: "a=b%00c",
+ description: "0x00 in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\0c"),
+ expected: "a=b%00c",
+ description: "0x00 in filename",
+});
+
+formTest({
+ name: "a\nb",
+ value: "c",
+ expected: "a%0D%0Ab=c",
+ description: "\\n in name",
+});
+
+formTest({
+ name: "a\rb",
+ value: "c",
+ expected: "a%0D%0Ab=c",
+ description: "\\r in name",
+});
+
+formTest({
+ name: "a\r\nb",
+ value: "c",
+ expected: "a%0D%0Ab=c",
+ description: "\\r\\n in name",
+});
+
+formTest({
+ name: "a\n\rb",
+ value: "c",
+ expected: "a%0D%0A%0D%0Ab=c",
+ description: "\\n\\r in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\nc",
+ expected: "a=b%0D%0Ac",
+ description: "\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\rc",
+ expected: "a=b%0D%0Ac",
+ description: "\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\r\nc",
+ expected: "a=b%0D%0Ac",
+ description: "\\r\\n in value",
+});
+
+formTest({
+ name: "a",
+ value: "b\n\rc",
+ expected: "a=b%0D%0A%0D%0Ac",
+ description: "\\n\\r in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\nc"),
+ expected: "a=b%0D%0Ac",
+ description: "\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\rc"),
+ expected: "a=b%0D%0Ac",
+ description: "\\r in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\r\nc"),
+ expected: "a=b%0D%0Ac",
+ description: "\\r\\n in filename",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\n\rc"),
+ expected: "a=b%0D%0A%0D%0Ac",
+ description: "\\n\\r in filename",
+});
+
+formTest({
+ name: 'a"b',
+ value: "c",
+ expected: "a%22b=c",
+ description: "double quote in name",
+});
+
+formTest({
+ name: "a",
+ value: 'b"c',
+ expected: "a=b%22c",
+ description: "double quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], 'b"c'),
+ expected: "a=b%22c",
+ description: "double quote in filename",
+});
+
+formTest({
+ name: "a'b",
+ value: "c",
+ expected: "a%27b=c",
+ description: "single quote in name",
+});
+
+formTest({
+ name: "a",
+ value: "b'c",
+ expected: "a=b%27c",
+ description: "single quote in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b'c"),
+ expected: "a=b%27c",
+ description: "single quote in filename",
+});
+
+formTest({
+ name: "a\\b",
+ value: "c",
+ expected: "a%5Cb=c",
+ description: "backslash in name",
+});
+
+formTest({
+ name: "a",
+ value: "b\\c",
+ expected: "a=b%5Cc",
+ description: "backslash in value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "b\\c"),
+ expected: "a=b%5Cc",
+ description: "backslash in filename",
+});
+
+formTest({
+ name: "áb",
+ value: "ç",
+ expected: "%C3%A1b=%C3%A7",
+ description: "non-ASCII in name and value",
+});
+
+formTest({
+ name: "a",
+ value: new File([], "ə.txt"),
+ expected: "a=%C9%99.txt",
+ description: "non-ASCII in filename",
+});
+
+formTest({
+ name: "aəb",
+ value: "c\uFFFDd",
+ formEncoding: "windows-1252",
+ expected: "a%26%23601%3Bb=c%26%2365533%3Bd",
+ description: "characters not in encoding in name and value",
+});
+
+formTest({
+ name: "á",
+ value: new File([], "💩"),
+ formEncoding: "windows-1252",
+ expected: "%E1=%26%23128169%3B",
+ description: "character not in encoding in filename",
+});
+
+formTest({
+ name: "\uD800",
+ value: "\uD800",
+ formEncoding: "windows-1252",
+ expected: "%26%2365533%3B=%26%2365533%3B",
+ description: "lone surrogate in name and value",
+});