Skip to content

Commit

Permalink
Add optional submitter parameter to FormData constructor
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=251220

Reviewed by Chris Dumez

Implement the new submitter parameter to the FormData constructor
Spec PR: whatwg/xhr#366
WPT PR: web-platform-tests/wpt#37895

Test: imported/w3c/web-platform-tests/xhr/formdata/constructor-submitter.html

* LayoutTests/imported/w3c/web-platform-tests/xhr/formdata/constructor-submitter-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/xhr/formdata/constructor-submitter.html: Added.
* Source/WebCore/html/DOMFormData.cpp:
(WebCore::DOMFormData::create): add submitter support
* Source/WebCore/html/DOMFormData.h: update signature
* Source/WebCore/html/DOMFormData.idl: update interface
* Source/WebCore/html/HTMLFormElement.cpp:
(WebCore::HTMLFormElement::requestSubmit): improve error messages#

Canonical link: https://commits.webkit.org/259558@main
  • Loading branch information
jenseng authored and cdumez committed Jan 30, 2023
1 parent 30df2fe commit 136a23f
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@




PASS FormData construction should allow a null submitter
PASS FormData construction should throw a TypeError if a non-null submitter is not a submit button
PASS FormData construction should throw a 'NotFoundError' DOMException if a non-null submitter is not owned by the form
PASS The constructed FormData object should contain an in-tree-order entry for a named submit button submitter
PASS The constructed FormData object should not contain an entry for an unnamed submit button submitter
PASS The constructed FormData object should contain in-tree-order entries for an activated Image Button submitter
PASS The constructed FormData object should contain in-tree-order entries for an unactivated Image Button submitter

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!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'>
<script src='/resources/testharness.js'></script>
<script src='/resources/testharnessreport.js'></script>

<button name=outerNamed value=GO form='myform'></button>
<form id='myform' onsubmit='return false'>
<input name=n1 value=v1>
<button name=named value=GO></button>
<button id=unnamed value=unnamed></button>
<button form="another" name=unassociated value=unassociated></button>
<input type=image name=namedImage src='/media/1x1-green.png'></button>
<input type=image id=unnamedImage src='/media/1x1-green.png'></button>
<input type=image name=unactivatedImage src='/media/1x1-green.png'></button>
<input name=n3 value=v3>
</form>

<form id='another'>
<button name=unassociated2 value=unassociated></button>
</form>

<script>
function assertFormDataEntries(formData, expectedEntries) {
const expectedEntryNames = expectedEntries.map((entry) => entry[0]);
const actualEntries = [...formData.entries()];
const actualEntryNames = actualEntries.map((entry) => entry[0]);
assert_array_equals(actualEntryNames, expectedEntryNames);
for (let i = 0; i < actualEntries.length; i++) {
assert_array_equals(actualEntries[i], expectedEntries[i]);
}
}

const form = document.querySelector('#myform');

test(() => {
assertFormDataEntries(
new FormData(form, null),
[['n1', 'v1'], ['n3', 'v3']]
);
}, 'FormData construction should allow a null submitter'); // the use case here is so web developers can avoid null checks, e.g. `new FormData(e.target, e.submitter)`

test(() => {
assert_throws_js(TypeError, () => new FormData(form, document.querySelector('[name=n1]')));
}, 'FormData construction should throw a TypeError if a non-null submitter is not a submit button');

test(() => {
assert_throws_dom('NotFoundError', () => new FormData(form, document.querySelector('[name=unassociated]')));
assert_throws_dom('NotFoundError', () => new FormData(form, document.querySelector('[name=unassociated2]')));
}, "FormData construction should throw a 'NotFoundError' DOMException if a non-null submitter is not owned by the form");

test(() => {
assertFormDataEntries(
new FormData(form, document.querySelector('[name=named]')),
[['n1', 'v1'], ['named', 'GO'], ['n3', 'v3']]
);
assertFormDataEntries(
new FormData(form, document.querySelector('[name=outerNamed]')),
[['outerNamed', 'GO'], ['n1', 'v1'], ['n3', 'v3']]
);
}, 'The constructed FormData object should contain an in-tree-order entry for a named submit button submitter');

test(() => {
assertFormDataEntries(
new FormData(form, document.querySelector('#unnamed')),
[['n1', 'v1'], ['n3', 'v3']]
);
}, 'The constructed FormData object should not contain an entry for an unnamed submit button submitter');

test(() => {
const submitter1 = document.querySelector('[name=namedImage]');
submitter1.click();
const submitter2 = document.querySelector('#unnamedImage');
submitter2.click();
assertFormDataEntries(
new FormData(form, submitter1),
[['n1', 'v1'], ['namedImage.x', '0'], ['namedImage.y', '0'], ['n3', 'v3']]
);
assertFormDataEntries(
new FormData(form, submitter2),
[['n1', 'v1'], ['x', '0'], ['y', '0'], ['n3', 'v3']]
);
}, 'The constructed FormData object should contain in-tree-order entries for an activated Image Button submitter');

test(() => {
assertFormDataEntries(
new FormData(form, document.querySelector('[name=unactivatedImage]')),
[['n1', 'v1'], ['unactivatedImage.x', '0'], ['unactivatedImage.y', '0'], ['n3', 'v3']]
);
}, 'The constructed FormData object should contain in-tree-order entries for an unactivated Image Button submitter');
</script>
15 changes: 12 additions & 3 deletions Source/WebCore/html/DOMFormData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,22 @@ DOMFormData::DOMFormData(ScriptExecutionContext* context, const PAL::TextEncodin
{
}

ExceptionOr<Ref<DOMFormData>> DOMFormData::create(ScriptExecutionContext& context, HTMLFormElement* form)
ExceptionOr<Ref<DOMFormData>> DOMFormData::create(ScriptExecutionContext& context, HTMLFormElement* form, HTMLElement* submitter)
{
// https://xhr.spec.whatwg.org/#dom-formdata
auto formData = adoptRef(*new DOMFormData(&context));
if (!form)
return formData;

auto result = form->constructEntryList(nullptr, WTFMove(formData), nullptr);

RefPtr<HTMLFormControlElement> control;
if (submitter) {
control = dynamicDowncast<HTMLFormControlElement>(*submitter);
if (!control || !control->isSubmitButton())
return Exception { TypeError, "The specified element is not a submit button."_s };
if (control->form() != form)
return Exception { NotFoundError, "The specified element is not owned by this form element."_s };
}
auto result = form->constructEntryList(control.get(), WTFMove(formData), nullptr);

if (!result)
return Exception { InvalidStateError, "Already constructing Form entry list."_s };
Expand Down
3 changes: 2 additions & 1 deletion Source/WebCore/html/DOMFormData.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
namespace WebCore {

template<typename> class ExceptionOr;
class HTMLElement;
class HTMLFormElement;

class DOMFormData : public RefCounted<DOMFormData>, public ContextDestructionObserver {
Expand All @@ -50,7 +51,7 @@ class DOMFormData : public RefCounted<DOMFormData>, public ContextDestructionObs
FormDataEntryValue data;
};

static ExceptionOr<Ref<DOMFormData>> create(ScriptExecutionContext&, HTMLFormElement*);
static ExceptionOr<Ref<DOMFormData>> create(ScriptExecutionContext&, HTMLFormElement*, HTMLElement*);
static Ref<DOMFormData> create(ScriptExecutionContext*, const PAL::TextEncoding&);

const Vector<Item>& items() const { return m_items; }
Expand Down
2 changes: 1 addition & 1 deletion Source/WebCore/html/DOMFormData.idl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ typedef (File or USVString) FormDataEntryValue;
JSGenerateToJSObject,
InterfaceName=FormData,
] interface DOMFormData {
[CallWith=CurrentScriptExecutionContext] constructor(optional HTMLFormElement form);
[CallWith=CurrentScriptExecutionContext] constructor(optional HTMLFormElement form, optional HTMLElement? submitter = null);

undefined append(USVString name, USVString value);
undefined append(USVString name, Blob blobValue, optional USVString filename);
Expand Down
10 changes: 4 additions & 6 deletions Source/WebCore/html/HTMLFormElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,13 +354,11 @@ ExceptionOr<void> HTMLFormElement::requestSubmit(HTMLElement* submitter)
RefPtr<HTMLFormControlElement> control;
if (submitter) {
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-requestsubmit
if (!is<HTMLFormControlElement>(submitter))
return Exception { TypeError };
control = downcast<HTMLFormControlElement>(submitter);
if (!control->isSubmitButton())
return Exception { TypeError };
control = dynamicDowncast<HTMLFormControlElement>(*submitter);
if (!control || !control->isSubmitButton())
return Exception { TypeError, "The specified element is not a submit button."_s };
if (control->form() != this)
return Exception { NotFoundError };
return Exception { NotFoundError, "The specified element is not owned by this form element."_s };
}

submitIfPossible(nullptr, control.get(), SubmittedByJavaScript);
Expand Down

0 comments on commit 136a23f

Please sign in to comment.