diff options
Diffstat (limited to 'testing/web-platform/tests/html/semantics/forms/form-submission-0')
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əb", + value: "c�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: "💩", + value: "", + }, + description: "character not in encoding in filename", +}); + +formTest({ + name: "\uD800", + value: "\uD800", + formEncoding: "windows-1252", + expected: { + name: "�", + value: "�" + }, + 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əb=c�d\r\n", + description: "characters not in encoding in name and value", +}); + +formTest({ + name: "á", + value: new File([], "💩"), + formEncoding: "windows-1252", + expected: "\xE1=💩\r\n", + description: "character not in encoding in filename", +}); + +formTest({ + name: "\uD800", + value: "\uD800", + formEncoding: "windows-1252", + expected: "�=�\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", +}); |