1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 18:47:34 +00:00

LibWeb: Introduce the FormData interface from the XHR specification

This commit is contained in:
Kenneth Myhra 2023-02-03 21:50:05 +01:00 committed by Linus Groh
parent b74d5a423d
commit d5b5b94a35
9 changed files with 317 additions and 0 deletions

View file

@ -34,6 +34,7 @@ static bool is_platform_object(Type const& type)
"DocumentType"sv,
"EventTarget"sv,
"FileList"sv,
"FormData"sv,
"ImageData"sv,
"MutationRecord"sv,
"NamedNodeMap"sv,
@ -133,6 +134,9 @@ CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface)
if (type.name() == "BufferSource")
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 };
if (type.name() == "sequence") {
auto& parameterized_type = verify_cast<ParameterizedType>(type);
auto& sequence_type = parameterized_type.parameters().first();

View file

@ -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

View file

@ -480,6 +480,7 @@ class WebGLRenderingContextBase;
}
namespace Web::XHR {
class FormData;
class ProgressEvent;
class XMLHttpRequest;
class XMLHttpRequestEventTarget;

View file

@ -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),
};
}
}

View file

@ -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 = {});
}

View file

@ -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 thiss 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 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 }));
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 thiss 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 thiss 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 thiss 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 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);
}
// 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 thiss 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 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));
}
// 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 }));
}
return {};
}
}

View file

@ -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;
};
}

View file

@ -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>;
};

View file

@ -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)