Source code

Revision control

Copy as Markdown

Other Tools

Test Info:

<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="container"></div>
<script>
class MyControl extends HTMLElement {
static get formAssociated() { return true; }
constructor() {
super();
this.internals_ = this.attachInternals();
this.value_ = '';
}
get value() {
return this.value_;
}
set value(v) {
this.internals_.setFormValue(v);
this.value_ = v;
}
setValues(nameValues) {
const formData = new FormData();
for (let p of nameValues) {
formData.append(p[0], p[1]);
}
this.internals_.setFormValue(formData);
}
}
customElements.define('my-control', MyControl);
const $ = document.querySelector.bind(document);
function submitPromise(t, extractFromIframe) {
if (!extractFromIframe) {
extractFromIframe = (iframe) => iframe.contentWindow.location.search;
}
return new Promise((resolve, reject) => {
const iframe = $('iframe');
iframe.onload = () => resolve(extractFromIframe(iframe));
iframe.onerror = () => reject(new Error('iframe onerror fired'));
$('form').submit();
});
}
function testSerializedEntry({name, value, expected, description}) {
// urlencoded
{
const {name: expectedName, value: expectedValue} = expected.urlencoded;
promise_test(async t => {
$('#container').innerHTML = '<form action="/common/blank.html" target="if1">' +
'<my-control></my-control>' +
'</form>' +
'<iframe name="if1"></iframe>';
if (name !== undefined) {
$('my-control').setAttribute("name", name);
}
if (Array.isArray(value)) {
$('my-control').setValues(value);
} else {
$('my-control').value = value;
}
const query = await submitPromise(t);
assert_equals(query, `?${expectedName}=${expectedValue}`);
}, `${description} (urlencoded)`);
}
// formdata
{
const {name: expectedName, filename: expectedFilename, value: expectedValue} = expected.formdata;
promise_test(async t => {
$('#container').innerHTML =
'<form action="/FileAPI/file/resources/echo-content-escaped.py" method="post" enctype="multipart/form-data" target="if1">' +
'<my-control></my-control>' +
'</form>' +
'<iframe name="if1"></iframe>';
if (name !== undefined) {
$('my-control').setAttribute("name", name);
}
if (Array.isArray(value)) {
$('my-control').setValues(value);
} else {
$('my-control').value = value;
}
const escaped = await submitPromise(t, iframe => iframe.contentDocument.body.textContent);
const formdata = escaped
.replace(/\r\n?|\n/g, "\r\n")
.replace(
/\\x[0-9A-Fa-f]{2}/g,
escape => String.fromCodePoint(parseInt(escape.substring(2), 16))
);
const boundary = formdata.split("\r\n")[0];
const expected = [
boundary,
...(() => {
if (expectedFilename === undefined) {
return [`Content-Disposition: form-data; name="${expectedName}"`];
} else {
return [
`Content-Disposition: form-data; name="${expectedName}"; filename="${expectedFilename}"`,
"Content-Type: text/plain"
];
}
})(),
"",
expectedValue,
boundary + "--",
""
].join("\r\n");
assert_equals(formdata, expected);
}, `${description} (formdata)`);
}
}
promise_test(t => {
$('#container').innerHTML = '<form action="/common/blank.html" target="if1">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control></my-control>' +
'</form>' +
'<iframe name="if1"></iframe>';
return submitPromise(t).then(query => {
assert_equals(query, '?name-pd1=value-pd1');
});
}, 'Single value - name is missing');
promise_test(t => {
$('#container').innerHTML = '<form action="/common/blank.html" target="if1">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name=""></my-control>' +
'<input name=name-pd2 value="value-pd2">' +
'</form>' +
'<iframe name="if1"></iframe>';
$('my-control').value = 'value-ce1';
return submitPromise(t).then(query => {
assert_equals(query, '?name-pd1=value-pd1&name-pd2=value-pd2');
});
}, 'Single value - empty name exists');
promise_test(t => {
$('#container').innerHTML = '<form action="/common/blank.html" target="if1" accept-charset=utf-8>' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name="name-ce1"></my-control>' +
'<my-control name="name-usv"></my-control>' +
'<my-control name="name-file"></my-control>' +
'</form>' +
'<iframe name="if1"></iframe>';
const USV_INPUT = 'abc\uDC00\uD800def';
const USV_OUTPUT = 'abc\uFFFD\uFFFDdef';
const FILE_NAME = 'test_file.txt';
$('[name=name-usv]').value = USV_INPUT;
$('[name=name-file]').value = new File(['file content'], FILE_NAME);
return submitPromise(t).then(query => {
assert_equals(query, `?name-pd1=value-pd1&name-usv=${encodeURIComponent(USV_OUTPUT)}&name-file=${FILE_NAME}`);
});
}, 'Single value - Non-empty name exists');
promise_test(t => {
$('#container').innerHTML = '<form action="/common/blank.html" target="if1">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name="name-ce1"></my-control>' +
'<my-control name="name-ce2"></my-control>' +
'</form>' +
'<iframe name="if1"></iframe>';
$('my-control').value = null;
return submitPromise(t).then(query => {
assert_equals(query, '?name-pd1=value-pd1');
});
}, 'Null value should submit nothing');
promise_test(t => {
$('#container').innerHTML = '<form action="/common/blank.html" target="if1">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name=name-ce1></my-control>' +
'</form>' +
'<iframe name="if1"></iframe>';
$('my-control').value = 'value-ce1';
$('my-control').setValues([]);
$('my-control').setValues([['sub1', 'subvalue1'],
['sub2', 'subvalue2'],
['sub2', 'subvalue3']]);
return submitPromise(t).then(query => {
assert_equals(query, '?name-pd1=value-pd1&sub1=subvalue1&sub2=subvalue2&sub2=subvalue3');
});
}, 'Multiple values - name content attribute is ignored');
promise_test(t => {
$('#container').innerHTML = '<form action="/common/blank.html" target="if1">' +
'<input name=name-pd1 value="value-pd1">' +
'<my-control name=name-ce1></my-control>' +
'</form>' +
'<iframe name="if1"></iframe>';
$('my-control').value = 'value-ce1';
$('my-control').setValues([]);
return submitPromise(t).then(query => {
assert_equals(query, '?name-pd1=value-pd1');
});
}, 'setFormValue with an empty FormData should submit nothing');
testSerializedEntry({
name: 'a\nb',
value: 'c',
expected: {
urlencoded: {
name: 'a%0D%0Ab',
value: 'c'
},
formdata: {
name: 'a%0D%0Ab',
value: 'c'
}
},
description: 'Newline normalization - \\n in name'
});
testSerializedEntry({
name: 'a\rb',
value: 'c',
expected: {
urlencoded: {
name: 'a%0D%0Ab',
value: 'c'
},
formdata: {
name: 'a%0D%0Ab',
value: 'c'
}
},
description: 'Newline normalization - \\r in name'
});
testSerializedEntry({
name: 'a\r\nb',
value: 'c',
expected: {
urlencoded: {
name: 'a%0D%0Ab',
value: 'c'
},
formdata: {
name: 'a%0D%0Ab',
value: 'c'
}
},
description: 'Newline normalization - \\r\\n in name'
});
testSerializedEntry({
name: 'a\n\rb',
value: 'c',
expected: {
urlencoded: {
name: 'a%0D%0A%0D%0Ab',
value: 'c'
},
formdata: {
name: 'a%0D%0A%0D%0Ab',
value: 'c'
}
},
description: 'Newline normalization - \\n\\r in name'
});
testSerializedEntry({
name: 'a',
value: 'b\nc',
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
value: 'b\r\nc'
}
},
description: 'Newline normalization - \\n in value'
});
testSerializedEntry({
name: 'a',
value: 'b\rc',
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
value: 'b\r\nc'
}
},
description: 'Newline normalization - \\r in value'
});
testSerializedEntry({
name: 'a',
value: 'b\r\nc',
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
value: 'b\r\nc'
}
},
description: 'Newline normalization - \\r\\n in value'
});
testSerializedEntry({
name: 'a',
value: 'b\n\rc',
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0A%0D%0Ac'
},
formdata: {
name: 'a',
value: 'b\r\n\r\nc'
}
},
description: 'Newline normalization - \\n\\r in value'
});
testSerializedEntry({
name: 'a',
value: new File([], "b\nc", {type: "text/plain"}),
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
filename: 'b%0Ac',
value: ''
}
},
description: 'Newline normalization - \\n in filename'
});
testSerializedEntry({
name: 'a',
value: new File([], "b\rc", {type: "text/plain"}),
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
filename: 'b%0Dc',
value: ''
}
},
description: 'Newline normalization - \\r in filename'
});
testSerializedEntry({
name: 'a',
value: new File([], "b\r\nc", {type: "text/plain"}),
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
filename: 'b%0D%0Ac',
value: ''
}
},
description: 'Newline normalization - \\r\\n in filename'
});
testSerializedEntry({
name: 'a',
value: new File([], "b\n\rc", {type: "text/plain"}),
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0A%0D%0Ac'
},
formdata: {
name: 'a',
filename: 'b%0A%0Dc',
value: ''
}
},
description: 'Newline normalization - \\n\\r in filename'
});
testSerializedEntry({
value: [['a\nb', 'c']],
expected: {
urlencoded: {
name: 'a%0D%0Ab',
value: 'c'
},
formdata: {
name: 'a%0D%0Ab',
value: 'c'
}
},
description: 'Newline normalization - \\n in FormData name'
});
testSerializedEntry({
value: [['a\rb', 'c']],
expected: {
urlencoded: {
name: 'a%0D%0Ab',
value: 'c'
},
formdata: {
name: 'a%0D%0Ab',
value: 'c'
}
},
description: 'Newline normalization - \\r in FormData name'
});
testSerializedEntry({
value: [['a\r\nb', 'c']],
expected: {
urlencoded: {
name: 'a%0D%0Ab',
value: 'c'
},
formdata: {
name: 'a%0D%0Ab',
value: 'c'
}
},
description: 'Newline normalization - \\r\\n in FormData name'
});
testSerializedEntry({
value: [['a\n\rb', 'c']],
expected: {
urlencoded: {
name: 'a%0D%0A%0D%0Ab',
value: 'c'
},
formdata: {
name: 'a%0D%0A%0D%0Ab',
value: 'c'
}
},
description: 'Newline normalization - \\n\\r in FormData name'
});
testSerializedEntry({
value: [['a', 'b\nc']],
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
value: 'b\r\nc'
}
},
description: 'Newline normalization - \\n in FormData value'
});
testSerializedEntry({
value: [['a', 'b\rc']],
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
value: 'b\r\nc'
}
},
description: 'Newline normalization - \\r in FormData value'
});
testSerializedEntry({
value: [['a', 'b\r\nc']],
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
value: 'b\r\nc'
}
},
description: 'Newline normalization - \\r\\n in FormData value'
});
testSerializedEntry({
value: [['a', 'b\n\rc']],
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0A%0D%0Ac'
},
formdata: {
name: 'a',
value: 'b\r\n\r\nc'
}
},
description: 'Newline normalization - \\n\\r in FormData value'
});
testSerializedEntry({
value: [['a', new File([], 'b\nc', {type: "text/plain"})]],
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
filename: 'b%0Ac',
value: ''
}
},
description: 'Newline normalization - \\n in FormData filename'
});
testSerializedEntry({
value: [['a', new File([], 'b\rc', {type: "text/plain"})]],
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
filename: 'b%0Dc',
value: ''
}
},
description: 'Newline normalization - \\r in FormData filename'
});
testSerializedEntry({
value: [['a', new File([], 'b\r\nc', {type: "text/plain"})]],
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0Ac'
},
formdata: {
name: 'a',
filename: 'b%0D%0Ac',
value: ''
}
},
description: 'Newline normalization - \\r\\n in FormData filename'
});
testSerializedEntry({
value: [['a', new File([], 'b\n\rc', {type: "text/plain"})]],
expected: {
urlencoded: {
name: 'a',
value: 'b%0D%0A%0D%0Ac'
},
formdata: {
name: 'a',
filename: 'b%0A%0Dc',
value: ''
}
},
description: 'Newline normalization - \\n\\r in FormData filename'
});
test(() => {
class NotFormAssociatedElement extends HTMLElement {}
customElements.define('not-form-associated-element', NotFormAssociatedElement);
const element = new NotFormAssociatedElement();
const i = element.attachInternals();
assert_throws_dom('NotSupportedError', () => i.setFormValue("test"));
}, "ElementInternals.setFormValue() should throw NotSupportedError if the target element is not a form-associated custom element");
</script>