summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKenneth Myhra <kennethmyhra@gmail.com>2023-02-03 21:50:31 +0100
committerLinus Groh <mail@linusgroh.de>2023-02-12 00:18:09 +0000
commitfc886b4556e7560468c76c18a152d736fd2a1af9 (patch)
tree3167128a0b6a279d62f0e4a23b2cd8634702a2cc
parent9d13537fc760d20ba3fe02efe7bf169a7be4d00d (diff)
downloadserenity-fc886b4556e7560468c76c18a152d736fd2a1af9.zip
LibWeb: Implement algorithm 'construct the entry list given a form'
-rw-r--r--Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp135
-rw-r--r--Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h3
-rw-r--r--Userland/Libraries/LibWeb/XHR/FormData.cpp19
3 files changed, 151 insertions, 6 deletions
diff --git a/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp b/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp
index 4718215da0..22ef44e664 100644
--- a/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp
+++ b/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp
@@ -5,6 +5,11 @@
*/
#include <LibWeb/HTML/FormControlInfrastructure.h>
+#include <LibWeb/HTML/FormDataEvent.h>
+#include <LibWeb/HTML/HTMLButtonElement.h>
+#include <LibWeb/HTML/HTMLDataListElement.h>
+#include <LibWeb/HTML/HTMLOptionElement.h>
+#include <LibWeb/HTML/HTMLSelectElement.h>
#include <LibWeb/Infra/Strings.h>
namespace Web::HTML {
@@ -41,4 +46,134 @@ 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)
+{
+ // 1. If form's constructing entry list is true, then return null.
+ if (form.constructing_entry_list())
+ return Optional<HashMapWithVectorOfFormDataEntryValue> {};
+
+ // 2. Set form's constructing entry list to true.
+ form.set_constructing_entry_list(true);
+
+ // 3. Let controls be a list of all the submittable elements whose form owner is form, in tree order.
+ auto controls = TRY_OR_THROW_OOM(realm.vm(), form.get_submittable_elements());
+
+ // 4. Let entry list be a new empty entry list.
+ HashMapWithVectorOfFormDataEntryValue entry_list;
+
+ // 5. For each element field in controls, in tree order:
+ for (auto const& control : controls) {
+ // 1. If any of the following is true, then continue:
+ // - The field element has a datalist element ancestor.
+ if (control->first_ancestor_of_type<HTML::HTMLDataListElement>())
+ continue;
+ // - The field element is disabled.
+ if (control->is_actually_disabled())
+ continue;
+ // FIXME: - The field element is a button but it is not submitter.
+ // - The field element is an input element whose type attribute is in the Checkbox state and whose checkedness is false.
+ // - The field element is an input element whose type attribute is in the Radio Button state and whose checkedness is false.
+ if (auto* input_element = dynamic_cast<HTML::HTMLInputElement*>(control.ptr())) {
+ if ((input_element->type() == "checkbox" || input_element->type() == "radio") && !input_element->checked())
+ continue;
+ }
+
+ // 2. If the field element is an input element whose type attribute is in the Image Button state, then:
+ if (auto* input_element = dynamic_cast<HTML::HTMLInputElement*>(control.ptr()); input_element->type() == "image") {
+ // FIXME: 1. If the field element has a name attribute specified and its value is not the empty string, let name be that value followed by a single U+002E FULL STOP character (.). Otherwise, let name be the empty string.
+ // FIXME: 2. Let namex be the string consisting of the concatenation of name and a single U0078 LATIN SMALL LETTER X character (x).
+ // FIXME: 3. Let namey be the string consisting of the concatenation of name and a single U+0079 LATIN SMALL LETTER Y character (y).
+ // FIXME: 4. The field element is submitter, and before this algorithm was invoked the user indicated a coordinate. Let x be the x-component of the coordinate selected by the user, and let y be the y-component of the coordinate selected by the user.
+ // FIXME: 5. Create an entry with namex and x, and append it to entry list.
+ // FIXME: 6. Create an entry with namey and y, and append it to entry list.
+ // 7. Continue.
+ continue;
+ }
+
+ // FIXME: 3. If the field is a form-associated custom element, then perform the entry construction algorithm given field and entry list, then continue.
+
+ // 4. If either the field element does not have a name attribute specified, or its name attribute's value is the empty string, then continue.
+ if (control->name().is_empty())
+ 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> {};
+
+ // 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(realm.vm(), form_data_entries.try_append(option_element->value()));
+ }
+ TRY_OR_THROW_OOM(realm.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()) {
+ // 1. If the field element has a value attribute specified, then let value be the value of that attribute; otherwise, let value be the string "on".
+ auto value = checkbox_or_radio_element->value();
+ if (value.is_empty())
+ value = "on";
+
+ // 2. Create an entry with name and value, and append it to entry list.
+ TRY_OR_THROW_OOM(realm.vm(), form_data_entries.try_append(value));
+ TRY_OR_THROW_OOM(realm.vm(), entry_list.try_set(name, form_data_entries));
+ }
+ // 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") {
+ // 1. If there are no selected files, then create an entry with name and a new File object with an empty name, application/octet-stream as type, and an empty body, and append it to entry list.
+ if (file_element->files()->length() == 0) {
+ FileAPI::FilePropertyBag options {};
+ options.type = "application/octet-stream";
+ auto file = TRY(FileAPI::File::create(realm, {}, "", options));
+ TRY_OR_THROW_OOM(realm.vm(), form_data_entries.try_append(file));
+ TRY_OR_THROW_OOM(realm.vm(), entry_list.try_set(name, form_data_entries));
+ }
+ // 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(realm.vm(), form_data_entries.try_append(file));
+ }
+ TRY_OR_THROW_OOM(realm.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_":
+ // FIXME: 1. Let charset be the name of encoding if encoding is given, and "UTF-8" otherwise.
+ // FIXME: 2. Create an entry with name and charset, and append it to entry list.
+ // 10. Otherwise, create an entry with name and the value of the field element, and append it to entry list.
+ else {
+ auto* input_element = dynamic_cast<HTML::HTMLInputElement*>(control.ptr());
+ VERIFY(input_element);
+ TRY_OR_THROW_OOM(realm.vm(), form_data_entries.try_append(input_element->value()));
+ TRY_OR_THROW_OOM(realm.vm(), entry_list.try_set(name, form_data_entries));
+ }
+
+ // FIXME: 11. If the element has a dirname attribute, and that attribute's value is not the empty string, then:
+ // FIXME: 1. Let dirname be the value of the element's dirname attribute.
+ // FIXME: 2. Let dir be the string "ltr" if the directionality of the element is 'ltr', and "rtl" otherwise (i.e., when the directionality of the element is 'rtl').
+ // FIXME: 3. Create an entry with dirname and dir, and append it to entry list.
+ }
+ // 6. Let form data be a new FormData object associated with entry list.
+ auto form_data = TRY(XHR::FormData::construct_impl(realm, entry_list));
+
+ // 7. Fire an event named formdata at form using FormDataEvent, with the formData attribute initialized to form data and the bubbles attribute initialized to true.
+ FormDataEventInit init {};
+ init.form_data = form_data;
+ auto form_data_event = TRY(FormDataEvent::construct_impl(realm, HTML::EventNames::formdata, init));
+ form_data_event->set_bubbles(true);
+ form.dispatch_event(form_data_event);
+
+ // 8. Set form's constructing entry list to false.
+ form.set_constructing_entry_list(false);
+
+ // 9. Return a clone of entry list.
+ return TRY_OR_THROW_OOM(realm.vm(), entry_list.clone());
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h b/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h
index 8e247c967c..3ea46634e5 100644
--- a/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h
+++ b/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h
@@ -10,11 +10,14 @@
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&);
}
diff --git a/Userland/Libraries/LibWeb/XHR/FormData.cpp b/Userland/Libraries/LibWeb/XHR/FormData.cpp
index 3cf994acce..82014ba4f3 100644
--- a/Userland/Libraries/LibWeb/XHR/FormData.cpp
+++ b/Userland/Libraries/LibWeb/XHR/FormData.cpp
@@ -16,14 +16,21 @@
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>>)
+WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> FormData::construct_impl(JS::Realm& realm, Optional<JS::NonnullGCPtr<HTML::HTMLFormElement>> form)
{
- // FIXME: 1. If form is given, then:
- // FIXME: 1. Let list be the result of constructing the entry list for form.
- // FIXME: 2. If list is null, then throw an "InvalidStateError" DOMException.
- // FIXME: 3. Set this’s entry list to list.
+ HashMap<DeprecatedString, Vector<FormDataEntryValue>> list;
+ // 1. If form is given, then:
+ if (form.has_value()) {
+ // 1. Let list be the result of constructing the entry list for form.
+ auto entry_list = TRY(construct_entry_list(realm, form.value()));
+ // 2. If list is null, then throw an "InvalidStateError" DOMException.
+ if (!entry_list.has_value())
+ return WebIDL::InvalidStateError::create(realm, "Form element does not contain any entries.");
+ // 3. Set this’s entry list to list.
+ list = entry_list.release_value();
+ }
- return construct_impl(realm);
+ return construct_impl(realm, move(list));
}
WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> FormData::construct_impl(JS::Realm& realm, HashMap<DeprecatedString, Vector<FormDataEntryValue>> entry_list)