LibWeb: Convert FormData to String and Vector storage

This makes use of the new [UseNewAKString] extended attribute. Using
Vector storage will make it easier to make this interface into an IDL
iterable. It seems the reason it didn't use Vector originally was due
to awkward DeprecatedString -> String conversions.
This commit is contained in:
Luke Wilde 2023-02-17 18:57:58 +00:00 committed by Andreas Kling
parent c0f22065ab
commit 3275d659bf
6 changed files with 88 additions and 106 deletions

View file

@ -139,7 +139,7 @@ CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface)
return { .name = "JS::Handle<JS::Object>", .sequence_storage_type = SequenceStorageType::MarkedVector };
if (type.name() == "File")
return { .name = "JS::NonnullGCPtr<FileAPI::File>", .sequence_storage_type = SequenceStorageType::MarkedVector };
return { .name = "JS::Handle<FileAPI::File>", .sequence_storage_type = SequenceStorageType::MarkedVector };
if (type.name() == "sequence") {
auto& parameterized_type = verify_cast<ParameterizedType>(type);

View file

@ -15,7 +15,7 @@
namespace Web::HTML {
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry
WebIDL::ExceptionOr<Entry> create_entry(JS::Realm& realm, String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename)
WebIDL::ExceptionOr<XHR::FormDataEntry> create_entry(JS::Realm& realm, String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename)
{
auto& vm = realm.vm();
@ -24,11 +24,11 @@ WebIDL::ExceptionOr<Entry> create_entry(JS::Realm& realm, String const& name, Va
auto entry_value = TRY(value.visit(
// 2. If value is a string, then set value to the result of converting value into a scalar value string.
[&](String const& string) -> WebIDL::ExceptionOr<Variant<JS::NonnullGCPtr<FileAPI::File>, String>> {
[&](String const& string) -> WebIDL::ExceptionOr<Variant<JS::Handle<FileAPI::File>, String>> {
return TRY_OR_THROW_OOM(vm, Infra::convert_to_scalar_value_string(string));
},
// 3. Otherwise:
[&](JS::NonnullGCPtr<FileAPI::Blob> const& blob) -> WebIDL::ExceptionOr<Variant<JS::NonnullGCPtr<FileAPI::File>, String>> {
[&](JS::NonnullGCPtr<FileAPI::Blob> const& blob) -> WebIDL::ExceptionOr<Variant<JS::Handle<FileAPI::File>, String>> {
// 1. If value is not a File object, then set value to a new File object, representing the same bytes, whose name attribute value is "blob".
// 2. If filename is given, then set value to a new File object, representing the same bytes, whose name attribute is filename.
String name_attribute;
@ -36,11 +36,11 @@ WebIDL::ExceptionOr<Entry> create_entry(JS::Realm& realm, String const& name, Va
name_attribute = filename.value();
else
name_attribute = TRY_OR_THROW_OOM(vm, String::from_utf8("blob"sv));
return TRY(FileAPI::File::create(realm, { JS::make_handle(*blob) }, name_attribute.to_deprecated_string(), {}));
return JS::make_handle(TRY(FileAPI::File::create(realm, { JS::make_handle(*blob) }, name_attribute.to_deprecated_string(), {})));
}));
// 4. Return an entry whose name is name and whose value is value.
return Entry {
return XHR::FormDataEntry {
.name = move(entry_name),
.value = move(entry_value),
};
@ -48,13 +48,13 @@ WebIDL::ExceptionOr<Entry> create_entry(JS::Realm& realm, String const& name, Va
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set
// FIXME: Add missing parameters optional submitter, and optional encoding
WebIDL::ExceptionOr<Optional<HashMapWithVectorOfFormDataEntryValue>> construct_entry_list(JS::Realm& realm, HTMLFormElement& form)
WebIDL::ExceptionOr<Optional<Vector<XHR::FormDataEntry>>> construct_entry_list(JS::Realm& realm, HTMLFormElement& form)
{
auto& vm = realm.vm();
// 1. If form's constructing entry list is true, then return null.
if (form.constructing_entry_list())
return Optional<HashMapWithVectorOfFormDataEntryValue> {};
return Optional<Vector<XHR::FormDataEntry>> {};
// 2. Set form's constructing entry list to true.
form.set_constructing_entry_list(true);
@ -63,7 +63,7 @@ WebIDL::ExceptionOr<Optional<HashMapWithVectorOfFormDataEntryValue>> construct_e
auto controls = TRY_OR_THROW_OOM(vm, form.get_submittable_elements());
// 4. Let entry list be a new empty entry list.
HashMapWithVectorOfFormDataEntryValue entry_list;
Vector<XHR::FormDataEntry> entry_list;
// 5. For each element field in controls, in tree order:
for (auto const& control : controls) {
@ -101,19 +101,17 @@ WebIDL::ExceptionOr<Optional<HashMapWithVectorOfFormDataEntryValue>> construct_e
continue;
// 5. Let name be the value of the field element's name attribute.
auto name = control->name();
auto form_data_entries = entry_list.contains(name) && entry_list.get(name).has_value()
? entry_list.get(name).value()
: Vector<XHR::FormDataEntryValue> {};
auto name = TRY_OR_THROW_OOM(vm, String::from_deprecated_string(control->name()));
// 6. If the field element is a select element, then for each option element in the select element's list of options whose selectedness is true and that is not disabled, create an entry with name and the value of the option element, and append it to entry list.
if (auto* select_element = dynamic_cast<HTML::HTMLSelectElement*>(control.ptr())) {
for (auto const& option_element : select_element->list_of_options()) {
if (option_element->selected() && !option_element->disabled())
TRY_OR_THROW_OOM(vm, form_data_entries.try_append(option_element->value()));
if (option_element->selected() && !option_element->disabled()) {
auto option_name = TRY_OR_THROW_OOM(vm, String::from_deprecated_string(option_element->name()));
auto option_value = TRY_OR_THROW_OOM(vm, String::from_deprecated_string(option_element->value()));
TRY_OR_THROW_OOM(vm, entry_list.try_append(XHR::FormDataEntry { .name = move(option_name), .value = move(option_value) }));
}
}
TRY_OR_THROW_OOM(vm, entry_list.try_set(name, form_data_entries));
}
// 7. Otherwise, if the field element is an input element whose type attribute is in the Checkbox state or the Radio Button state, then:
else if (auto* checkbox_or_radio_element = dynamic_cast<HTML::HTMLInputElement*>(control.ptr()); checkbox_or_radio_element && (checkbox_or_radio_element->type() == "checkbox" || checkbox_or_radio_element->type() == "radio") && checkbox_or_radio_element->checked()) {
@ -123,8 +121,9 @@ WebIDL::ExceptionOr<Optional<HashMapWithVectorOfFormDataEntryValue>> construct_e
value = "on";
// 2. Create an entry with name and value, and append it to entry list.
TRY_OR_THROW_OOM(vm, form_data_entries.try_append(value));
TRY_OR_THROW_OOM(vm, entry_list.try_set(name, form_data_entries));
auto checkbox_or_radio_element_name = TRY_OR_THROW_OOM(vm, String::from_deprecated_string(checkbox_or_radio_element->name()));
auto checkbox_or_radio_element_value = TRY_OR_THROW_OOM(vm, String::from_deprecated_string(value));
TRY_OR_THROW_OOM(vm, entry_list.try_append(XHR::FormDataEntry { .name = move(checkbox_or_radio_element_name), .value = move(checkbox_or_radio_element_value) }));
}
// 8. Otherwise, if the field element is an input element whose type attribute is in the File Upload state, then:
else if (auto* file_element = dynamic_cast<HTML::HTMLInputElement*>(control.ptr()); file_element && file_element->type() == "file") {
@ -133,16 +132,14 @@ WebIDL::ExceptionOr<Optional<HashMapWithVectorOfFormDataEntryValue>> construct_e
FileAPI::FilePropertyBag options {};
options.type = "application/octet-stream";
auto file = TRY(FileAPI::File::create(realm, {}, "", options));
TRY_OR_THROW_OOM(vm, form_data_entries.try_append(file));
TRY_OR_THROW_OOM(vm, entry_list.try_set(name, form_data_entries));
TRY_OR_THROW_OOM(vm, entry_list.try_append(XHR::FormDataEntry { .name = move(name), .value = JS::make_handle(file) }));
}
// 2. Otherwise, for each file in selected files, create an entry with name and a File object representing the file, and append it to entry list.
else {
for (size_t i = 0; i < file_element->files()->length(); i++) {
auto file = JS::NonnullGCPtr { *file_element->files()->item(i) };
TRY_OR_THROW_OOM(vm, form_data_entries.try_append(file));
TRY_OR_THROW_OOM(vm, entry_list.try_append(XHR::FormDataEntry { .name = move(name), .value = JS::make_handle(file) }));
}
TRY_OR_THROW_OOM(vm, entry_list.try_set(name, form_data_entries));
}
}
// FIXME: 9. Otherwise, if the field element is an input element whose type attribute is in the Hidden state and name is an ASCII case-insensitive match for "_charset_":
@ -152,8 +149,8 @@ WebIDL::ExceptionOr<Optional<HashMapWithVectorOfFormDataEntryValue>> construct_e
else {
auto* element = dynamic_cast<HTML::HTMLElement*>(control.ptr());
VERIFY(element);
TRY_OR_THROW_OOM(vm, form_data_entries.try_append(element->attribute("value"sv)));
TRY_OR_THROW_OOM(vm, entry_list.try_set(name, form_data_entries));
auto value_attribute = TRY_OR_THROW_OOM(vm, String::from_deprecated_string(element->attribute("value"sv)));
TRY_OR_THROW_OOM(vm, entry_list.try_append(XHR::FormDataEntry { .name = move(name), .value = move(value_attribute) }));
}
// FIXME: 11. If the element has a dirname attribute, and that attribute's value is not the empty string, then:
@ -175,7 +172,7 @@ WebIDL::ExceptionOr<Optional<HashMapWithVectorOfFormDataEntryValue>> construct_e
form.set_constructing_entry_list(false);
// 9. Return a clone of entry list.
return TRY_OR_THROW_OOM(vm, entry_list.clone());
return entry_list;
}
}

View file

@ -10,14 +10,7 @@
namespace Web::HTML {
using HashMapWithVectorOfFormDataEntryValue = HashMap<DeprecatedString, Vector<XHR::FormDataEntryValue>>;
struct Entry {
String name;
Variant<JS::NonnullGCPtr<FileAPI::File>, String> value;
};
WebIDL::ExceptionOr<Entry> create_entry(JS::Realm& realm, String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename = {});
WebIDL::ExceptionOr<Optional<HashMapWithVectorOfFormDataEntryValue>> construct_entry_list(JS::Realm&, HTMLFormElement&);
WebIDL::ExceptionOr<XHR::FormDataEntry> create_entry(JS::Realm& realm, String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename = {});
WebIDL::ExceptionOr<Optional<Vector<XHR::FormDataEntry>>> construct_entry_list(JS::Realm&, HTMLFormElement&);
}

View file

@ -18,7 +18,7 @@ namespace Web::XHR {
// https://xhr.spec.whatwg.org/#dom-formdata
WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> FormData::construct_impl(JS::Realm& realm, Optional<JS::NonnullGCPtr<HTML::HTMLFormElement>> form)
{
HashMap<DeprecatedString, Vector<FormDataEntryValue>> list;
Vector<FormDataEntry> list;
// 1. If form is given, then:
if (form.has_value()) {
// 1. Let list be the result of constructing the entry list for form.
@ -33,12 +33,12 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> FormData::construct_impl(JS::Rea
return construct_impl(realm, move(list));
}
WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> FormData::construct_impl(JS::Realm& realm, HashMap<DeprecatedString, Vector<FormDataEntryValue>> entry_list)
WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> FormData::construct_impl(JS::Realm& realm, Vector<FormDataEntry> entry_list)
{
return MUST_OR_THROW_OOM(realm.heap().allocate<FormData>(realm, realm, move(entry_list)));
}
FormData::FormData(JS::Realm& realm, HashMap<DeprecatedString, Vector<FormDataEntryValue>> entry_list)
FormData::FormData(JS::Realm& realm, Vector<FormDataEntry> entry_list)
: PlatformObject(realm)
, m_entry_list(move(entry_list))
{
@ -54,30 +54,17 @@ JS::ThrowCompletionOr<void> FormData::initialize(JS::Realm& realm)
return {};
}
void FormData::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
for (auto const& entry : m_entry_list) {
for (auto const& value : entry.value) {
if (auto* file = value.get_pointer<JS::NonnullGCPtr<FileAPI::File>>())
visitor.visit(*file);
}
}
}
// https://xhr.spec.whatwg.org/#dom-formdata-append
WebIDL::ExceptionOr<void> FormData::append(DeprecatedString const& name, DeprecatedString const& value)
WebIDL::ExceptionOr<void> FormData::append(String const& name, String const& value)
{
auto& vm = realm().vm();
return append_impl(TRY_OR_THROW_OOM(vm, String::from_deprecated_string(name)), TRY_OR_THROW_OOM(vm, String::from_deprecated_string(value)));
return append_impl(name, value);
}
// https://xhr.spec.whatwg.org/#dom-formdata-append-blob
WebIDL::ExceptionOr<void> FormData::append(DeprecatedString const& name, JS::NonnullGCPtr<FileAPI::Blob> const& blob_value, Optional<DeprecatedString> const& filename)
WebIDL::ExceptionOr<void> FormData::append(String const& name, JS::NonnullGCPtr<FileAPI::Blob> const& blob_value, Optional<String> const& filename)
{
auto& vm = realm().vm();
auto inner_filename = filename.has_value() ? TRY_OR_THROW_OOM(vm, String::from_deprecated_string(filename.value())) : Optional<String> {};
return append_impl(TRY_OR_THROW_OOM(vm, String::from_deprecated_string(name)), blob_value, inner_filename);
auto inner_filename = filename.has_value() ? filename.value() : Optional<String> {};
return append_impl(name, blob_value, inner_filename);
}
// https://xhr.spec.whatwg.org/#dom-formdata-append
@ -91,67 +78,67 @@ WebIDL::ExceptionOr<void> FormData::append_impl(String const& name, Variant<JS::
// 2. Let entry be the result of creating an entry with name, value, and filename if given.
auto entry = TRY(HTML::create_entry(realm, name, value, filename));
// FIXME: Remove this when our binding generator supports "new string".
auto form_data_entry_value = entry.value.has<String>()
? FormDataEntryValue { entry.value.get<String>().to_deprecated_string() }
: FormDataEntryValue { entry.value.get<JS::NonnullGCPtr<FileAPI::File>>() };
// 3. Append entry to thiss entry list.
if (auto entries = m_entry_list.get(entry.name.to_deprecated_string()); entries.has_value() && !entries->is_empty())
TRY_OR_THROW_OOM(vm, entries->try_append(form_data_entry_value));
else
TRY_OR_THROW_OOM(vm, m_entry_list.try_set(entry.name.to_deprecated_string(), { form_data_entry_value }));
TRY_OR_THROW_OOM(vm, m_entry_list.try_append(move(entry)));
return {};
}
// https://xhr.spec.whatwg.org/#dom-formdata-delete
void FormData::delete_(DeprecatedString const& name)
void FormData::delete_(String const& name)
{
// The delete(name) method steps are to remove all entries whose name is name from thiss entry list.
m_entry_list.remove(name);
m_entry_list.remove_all_matching([&name](FormDataEntry const& entry) {
return entry.name == name;
});
}
// https://xhr.spec.whatwg.org/#dom-formdata-get
Variant<JS::NonnullGCPtr<FileAPI::File>, DeprecatedString, Empty> FormData::get(DeprecatedString const& name)
Variant<JS::Handle<FileAPI::File>, String, Empty> FormData::get(String const& name)
{
// 1. If there is no entry whose name is name in thiss entry list, then return null.
if (!m_entry_list.contains(name))
auto entry_iterator = m_entry_list.find_if([&name](FormDataEntry const& entry) {
return entry.name == name;
});
if (entry_iterator.is_end())
return Empty {};
// 2. Return the value of the first entry whose name is name from thiss entry list.
return m_entry_list.get(name)->at(0);
return entry_iterator->value;
}
// https://xhr.spec.whatwg.org/#dom-formdata-getall
Vector<FormDataEntryValue> FormData::get_all(DeprecatedString const& name)
WebIDL::ExceptionOr<Vector<FormDataEntryValue>> FormData::get_all(String const& name)
{
// 1. If there is no entry whose name is name in thiss entry list, then return the empty list.
if (!m_entry_list.contains(name))
return {};
// 2. Return the values of all entries whose name is name, in order, from thiss entry list.
return *m_entry_list.get(name);
Vector<FormDataEntryValue> values;
for (auto const& entry : m_entry_list) {
if (entry.name == name)
TRY_OR_THROW_OOM(vm(), values.try_append(entry.value));
}
return values;
}
// https://xhr.spec.whatwg.org/#dom-formdata-has
bool FormData::has(DeprecatedString const& name)
bool FormData::has(String const& name)
{
// The has(name) method steps are to return true if there is an entry whose name is name in thiss entry list; otherwise false.
return m_entry_list.contains(name);
return !m_entry_list.find_if([&name](auto& entry) {
return entry.name == name;
})
.is_end();
}
// https://xhr.spec.whatwg.org/#dom-formdata-set
WebIDL::ExceptionOr<void> FormData::set(DeprecatedString const& name, DeprecatedString const& value)
WebIDL::ExceptionOr<void> FormData::set(String const& name, String const& value)
{
auto& vm = realm().vm();
return set_impl(TRY_OR_THROW_OOM(vm, String::from_deprecated_string(name)), TRY_OR_THROW_OOM(vm, String::from_deprecated_string(value)));
return set_impl(name, value);
}
// https://xhr.spec.whatwg.org/#dom-formdata-set-blob
WebIDL::ExceptionOr<void> FormData::set(DeprecatedString const& name, JS::NonnullGCPtr<FileAPI::Blob> const& blob_value, Optional<DeprecatedString> const& filename)
WebIDL::ExceptionOr<void> FormData::set(String const& name, JS::NonnullGCPtr<FileAPI::Blob> const& blob_value, Optional<String> const& filename)
{
auto& vm = realm().vm();
auto inner_filename = filename.has_value() ? TRY_OR_THROW_OOM(vm, String::from_deprecated_string(filename.value())) : Optional<String> {};
return set_impl(TRY_OR_THROW_OOM(vm, String::from_deprecated_string(name)), blob_value, inner_filename);
auto inner_filename = filename.has_value() ? filename.value() : Optional<String> {};
return set_impl(name, blob_value, inner_filename);
}
// https://xhr.spec.whatwg.org/#dom-formdata-set
@ -165,19 +152,20 @@ WebIDL::ExceptionOr<void> FormData::set_impl(String const& name, Variant<JS::Non
// 2. Let entry be the result of creating an entry with name, value, and filename if given.
auto entry = TRY(HTML::create_entry(realm, name, value, filename));
// FIXME: Remove this when our binding generator supports "new string".
auto form_data_entry_value = entry.value.has<String>()
? FormDataEntryValue { entry.value.get<String>().to_deprecated_string() }
: FormDataEntryValue { entry.value.get<JS::NonnullGCPtr<FileAPI::File>>() };
auto existing = m_entry_list.find_if([&name](auto& entry) {
return entry.name == name;
});
// 3. If there are entries in thiss entry list whose name is name, then replace the first such entry with entry and remove the others.
if (auto entries = m_entry_list.get(entry.name.to_deprecated_string()); entries.has_value() && !entries->is_empty()) {
entries->remove(0, entries->size());
TRY_OR_THROW_OOM(vm, entries->try_append(form_data_entry_value));
if (!existing.is_end()) {
existing->value = entry.value;
m_entry_list.remove_all_matching([&name, &existing](auto& entry) {
return &entry != &*existing && entry.name == name;
});
}
// 4. Otherwise, append entry to thiss entry list.
else {
TRY_OR_THROW_OOM(vm, m_entry_list.try_set(entry.name.to_deprecated_string(), { form_data_entry_value }));
TRY_OR_THROW_OOM(vm, m_entry_list.try_append(move(entry)));
}
return {};

View file

@ -15,7 +15,12 @@
namespace Web::XHR {
// https://xhr.spec.whatwg.org/#formdataentryvalue
using FormDataEntryValue = Variant<JS::NonnullGCPtr<FileAPI::File>, DeprecatedString>;
using FormDataEntryValue = Variant<JS::Handle<FileAPI::File>, String>;
struct FormDataEntry {
String name;
FormDataEntryValue value;
};
// https://xhr.spec.whatwg.org/#interface-formdata
class FormData : public Bindings::PlatformObject {
@ -25,27 +30,26 @@ public:
virtual ~FormData() override;
static WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> construct_impl(JS::Realm&, Optional<JS::NonnullGCPtr<HTML::HTMLFormElement>> form = {});
static WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> construct_impl(JS::Realm&, HashMap<DeprecatedString, Vector<FormDataEntryValue>> entry_list);
static WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> construct_impl(JS::Realm&, Vector<FormDataEntry> entry_list);
WebIDL::ExceptionOr<void> append(DeprecatedString const& name, DeprecatedString const& value);
WebIDL::ExceptionOr<void> append(DeprecatedString const& name, JS::NonnullGCPtr<FileAPI::Blob> const& blob_value, Optional<DeprecatedString> const& filename = {});
void delete_(DeprecatedString const& name);
Variant<JS::NonnullGCPtr<FileAPI::File>, DeprecatedString, Empty> get(DeprecatedString const& name);
Vector<FormDataEntryValue> get_all(DeprecatedString const& name);
bool has(DeprecatedString const& name);
WebIDL::ExceptionOr<void> set(DeprecatedString const& name, DeprecatedString const& value);
WebIDL::ExceptionOr<void> set(DeprecatedString const& name, JS::NonnullGCPtr<FileAPI::Blob> const& blob_value, Optional<DeprecatedString> const& filename = {});
WebIDL::ExceptionOr<void> append(String const& name, String const& value);
WebIDL::ExceptionOr<void> append(String const& name, JS::NonnullGCPtr<FileAPI::Blob> const& blob_value, Optional<String> const& filename = {});
void delete_(String const& name);
Variant<JS::Handle<FileAPI::File>, String, Empty> get(String const& name);
WebIDL::ExceptionOr<Vector<FormDataEntryValue>> get_all(String const& name);
bool has(String const& name);
WebIDL::ExceptionOr<void> set(String const& name, String const& value);
WebIDL::ExceptionOr<void> set(String const& name, JS::NonnullGCPtr<FileAPI::Blob> const& blob_value, Optional<String> const& filename = {});
private:
explicit FormData(JS::Realm&, HashMap<DeprecatedString, Vector<FormDataEntryValue>> entry_list = {});
explicit FormData(JS::Realm&, Vector<FormDataEntry> entry_list = {});
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
WebIDL::ExceptionOr<void> append_impl(String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename = {});
WebIDL::ExceptionOr<void> set_impl(String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename = {});
HashMap<DeprecatedString, Vector<FormDataEntryValue>> m_entry_list;
Vector<FormDataEntry> m_entry_list;
};
}

View file

@ -5,7 +5,7 @@
typedef (File or USVString) FormDataEntryValue;
// https://xhr.spec.whatwg.org/#interface-formdata
[Exposed=Window]
[Exposed=Window, UseNewAKString]
interface FormData {
constructor(optional HTMLFormElement form);