summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorKenneth Myhra <kennethmyhra@gmail.com>2023-02-03 21:50:05 +0100
committerLinus Groh <mail@linusgroh.de>2023-02-12 00:18:09 +0000
commitd5b5b94a3569036d93726f25b778ea8c8a6d1af2 (patch)
tree910b8385e430fd43eb7b923297b56dc4b3bb3a41 /Userland/Libraries
parentb74d5a423deeb0a9e504f6f3dece6b3472152bc4 (diff)
downloadserenity-d5b5b94a3569036d93726f25b778ea8c8a6d1af2.zip
LibWeb: Introduce the FormData interface from the XHR specification
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibWeb/CMakeLists.txt2
-rw-r--r--Userland/Libraries/LibWeb/Forward.h1
-rw-r--r--Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp44
-rw-r--r--Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h20
-rw-r--r--Userland/Libraries/LibWeb/XHR/FormData.cpp172
-rw-r--r--Userland/Libraries/LibWeb/XHR/FormData.h50
-rw-r--r--Userland/Libraries/LibWeb/XHR/FormData.idl23
-rw-r--r--Userland/Libraries/LibWeb/idl_files.cmake1
8 files changed, 313 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt
index a1bd191deb..88f8a7cf01 100644
--- a/Userland/Libraries/LibWeb/CMakeLists.txt
+++ b/Userland/Libraries/LibWeb/CMakeLists.txt
@@ -185,6 +185,7 @@ set(SOURCES
HTML/EventNames.cpp
HTML/Focus.cpp
HTML/FormAssociatedElement.cpp
+ HTML/FormControlInfrastructure.cpp
HTML/GlobalEventHandlers.cpp
HTML/History.cpp
HTML/HTMLAnchorElement.cpp
@@ -466,6 +467,7 @@ set(SOURCES
WebIDL/Promise.cpp
WebSockets/WebSocket.cpp
XHR/EventNames.cpp
+ XHR/FormData.cpp
XHR/ProgressEvent.cpp
XHR/XMLHttpRequest.cpp
XHR/XMLHttpRequestEventTarget.cpp
diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h
index e528172c86..85f5af61b2 100644
--- a/Userland/Libraries/LibWeb/Forward.h
+++ b/Userland/Libraries/LibWeb/Forward.h
@@ -480,6 +480,7 @@ class WebGLRenderingContextBase;
}
namespace Web::XHR {
+class FormData;
class ProgressEvent;
class XMLHttpRequest;
class XMLHttpRequestEventTarget;
diff --git a/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp b/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp
new file mode 100644
index 0000000000..4718215da0
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibWeb/HTML/FormControlInfrastructure.h>
+#include <LibWeb/Infra/Strings.h>
+
+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)
+{
+ auto& vm = realm.vm();
+
+ // 1. Set name to the result of converting name into a scalar value string.
+ auto entry_name = TRY_OR_THROW_OOM(realm.vm(), Infra::convert_to_scalar_value_string(name));
+
+ 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>> {
+ 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>> {
+ // 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;
+ if (filename.has_value())
+ 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(), {}));
+ }));
+
+ // 4. Return an entry whose name is name and whose value is value.
+ return Entry {
+ .name = move(entry_name),
+ .value = move(entry_value),
+ };
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h b/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h
new file mode 100644
index 0000000000..8e247c967c
--- /dev/null
+++ b/Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/XHR/FormData.h>
+
+namespace Web::HTML {
+
+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 = {});
+
+}
diff --git a/Userland/Libraries/LibWeb/XHR/FormData.cpp b/Userland/Libraries/LibWeb/XHR/FormData.cpp
new file mode 100644
index 0000000000..3cf994acce
--- /dev/null
+++ b/Userland/Libraries/LibWeb/XHR/FormData.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/TypeCasts.h>
+#include <LibJS/Runtime/Completion.h>
+#include <LibWeb/Bindings/Intrinsics.h>
+#include <LibWeb/FileAPI/Blob.h>
+#include <LibWeb/FileAPI/File.h>
+#include <LibWeb/HTML/FormControlInfrastructure.h>
+#include <LibWeb/WebIDL/DOMException.h>
+#include <LibWeb/XHR/FormData.h>
+
+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>>)
+{
+ // 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.
+
+ return construct_impl(realm);
+}
+
+WebIDL::ExceptionOr<JS::NonnullGCPtr<FormData>> FormData::construct_impl(JS::Realm& realm, HashMap<DeprecatedString, Vector<FormDataEntryValue>> 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)
+ : PlatformObject(realm)
+ , m_entry_list(move(entry_list))
+{
+ set_prototype(&Bindings::ensure_web_prototype<Bindings::FormDataPrototype>(realm, "FormData"));
+}
+
+FormData::~FormData() = default;
+
+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)
+{
+ 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)));
+}
+
+// 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)
+{
+ 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);
+}
+
+// https://xhr.spec.whatwg.org/#dom-formdata-append
+// https://xhr.spec.whatwg.org/#dom-formdata-append-blob
+WebIDL::ExceptionOr<void> FormData::append_impl(String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename)
+{
+ auto& realm = this->realm();
+ auto& vm = realm.vm();
+
+ // 1. Let value be value if given; otherwise blobValue.
+ // 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 this’s 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 }));
+
+ return {};
+}
+
+// https://xhr.spec.whatwg.org/#dom-formdata-delete
+void FormData::delete_(DeprecatedString const& name)
+{
+ // The delete(name) method steps are to remove all entries whose name is name from this’s entry list.
+ m_entry_list.remove(name);
+}
+
+// https://xhr.spec.whatwg.org/#dom-formdata-get
+Variant<JS::NonnullGCPtr<FileAPI::File>, DeprecatedString, Empty> FormData::get(DeprecatedString const& name)
+{
+ // 1. If there is no entry whose name is name in this’s entry list, then return null.
+ if (!m_entry_list.contains(name))
+ return Empty {};
+ // 2. Return the value of the first entry whose name is name from this’s entry list.
+ return m_entry_list.get(name)->at(0);
+}
+
+// https://xhr.spec.whatwg.org/#dom-formdata-getall
+Vector<FormDataEntryValue> FormData::get_all(DeprecatedString const& name)
+{
+ // 1. If there is no entry whose name is name in this’s 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 this’s entry list.
+ return *m_entry_list.get(name);
+}
+
+// https://xhr.spec.whatwg.org/#dom-formdata-has
+bool FormData::has(DeprecatedString const& name)
+{
+ // The has(name) method steps are to return true if there is an entry whose name is name in this’s entry list; otherwise false.
+ return m_entry_list.contains(name);
+}
+
+// https://xhr.spec.whatwg.org/#dom-formdata-set
+WebIDL::ExceptionOr<void> FormData::set(DeprecatedString const& name, DeprecatedString 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)));
+}
+
+// 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)
+{
+ 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);
+}
+
+// https://xhr.spec.whatwg.org/#dom-formdata-set
+// https://xhr.spec.whatwg.org/#dom-formdata-set-blob
+WebIDL::ExceptionOr<void> FormData::set_impl(String const& name, Variant<JS::NonnullGCPtr<FileAPI::Blob>, String> const& value, Optional<String> const& filename)
+{
+ auto& realm = this->realm();
+ auto& vm = realm.vm();
+
+ // 1. Let value be value if given; otherwise blobValue.
+ // 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. If there are entries in this’s 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));
+ }
+ // 4. Otherwise, append entry to this’s entry list.
+ else {
+ TRY_OR_THROW_OOM(vm, m_entry_list.try_set(entry.name.to_deprecated_string(), { form_data_entry_value }));
+ }
+
+ return {};
+}
+
+}
diff --git a/Userland/Libraries/LibWeb/XHR/FormData.h b/Userland/Libraries/LibWeb/XHR/FormData.h
new file mode 100644
index 0000000000..3156de0057
--- /dev/null
+++ b/Userland/Libraries/LibWeb/XHR/FormData.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2023, Kenneth Myhra <kennethmyhra@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/Bindings/FormDataPrototype.h>
+#include <LibWeb/Bindings/PlatformObject.h>
+#include <LibWeb/Forward.h>
+#include <LibWeb/HTML/HTMLFormElement.h>
+#include <LibWeb/WebIDL/ExceptionOr.h>
+
+namespace Web::XHR {
+
+// https://xhr.spec.whatwg.org/#formdataentryvalue
+using FormDataEntryValue = Variant<JS::NonnullGCPtr<FileAPI::File>, DeprecatedString>;
+
+// https://xhr.spec.whatwg.org/#interface-formdata
+class FormData : public Bindings::PlatformObject {
+ WEB_PLATFORM_OBJECT(FormData, Bindings::PlatformObject);
+
+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);
+
+ 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 = {});
+
+private:
+ explicit FormData(JS::Realm&, HashMap<DeprecatedString, Vector<FormDataEntryValue>> entry_list = {});
+
+ 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;
+};
+
+}
diff --git a/Userland/Libraries/LibWeb/XHR/FormData.idl b/Userland/Libraries/LibWeb/XHR/FormData.idl
new file mode 100644
index 0000000000..ca5b170026
--- /dev/null
+++ b/Userland/Libraries/LibWeb/XHR/FormData.idl
@@ -0,0 +1,23 @@
+#import <FileAPI/Blob.idl>
+#import <FileAPI/File.idl>
+#import <HTML/HTMLFormElement.idl>
+
+typedef (File or USVString) FormDataEntryValue;
+
+// https://xhr.spec.whatwg.org/#interface-formdata
+[Exposed=Window]
+interface FormData {
+ constructor(optional HTMLFormElement form);
+
+ undefined append(USVString name, USVString value);
+ undefined append(USVString name, Blob blobValue, optional USVString filename);
+ undefined delete(USVString name);
+ // FIXME: The BindingsGenerator is not able to resolve the Variant's visit for FormDataEntryValue when
+ // the return value for one function returns an optional FormDataEntryValue while the others does not.
+ (File or USVString)? get(USVString name);
+ sequence<FormDataEntryValue> getAll(USVString name);
+ boolean has(USVString name);
+ undefined set(USVString name, USVString value);
+ undefined set(USVString name, Blob blobValue, optional USVString filename);
+ // FIXME: iterable<USVString, FormDataEntryValue>;
+};
diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake
index 0148066b3e..e28ec3f418 100644
--- a/Userland/Libraries/LibWeb/idl_files.cmake
+++ b/Userland/Libraries/LibWeb/idl_files.cmake
@@ -195,6 +195,7 @@ libweb_js_bindings(WebGL/WebGLContextEvent)
libweb_js_bindings(WebGL/WebGLRenderingContext)
libweb_js_bindings(WebIDL/DOMException)
libweb_js_bindings(WebSockets/WebSocket)
+libweb_js_bindings(XHR/FormData)
libweb_js_bindings(XHR/ProgressEvent)
libweb_js_bindings(XHR/XMLHttpRequest)
libweb_js_bindings(XHR/XMLHttpRequestEventTarget)