mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 21:07:35 +00:00
LibWeb: Introduce the FormData interface from the XHR specification
This commit is contained in:
parent
b74d5a423d
commit
d5b5b94a35
9 changed files with 317 additions and 0 deletions
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -480,6 +480,7 @@ class WebGLRenderingContextBase;
|
|||
}
|
||||
|
||||
namespace Web::XHR {
|
||||
class FormData;
|
||||
class ProgressEvent;
|
||||
class XMLHttpRequest;
|
||||
class XMLHttpRequestEventTarget;
|
||||
|
|
44
Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp
Normal file
44
Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.cpp
Normal 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),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
20
Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h
Normal file
20
Userland/Libraries/LibWeb/HTML/FormControlInfrastructure.h
Normal 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 = {});
|
||||
|
||||
}
|
172
Userland/Libraries/LibWeb/XHR/FormData.cpp
Normal file
172
Userland/Libraries/LibWeb/XHR/FormData.cpp
Normal 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 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 {};
|
||||
}
|
||||
|
||||
}
|
50
Userland/Libraries/LibWeb/XHR/FormData.h
Normal file
50
Userland/Libraries/LibWeb/XHR/FormData.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
23
Userland/Libraries/LibWeb/XHR/FormData.idl
Normal file
23
Userland/Libraries/LibWeb/XHR/FormData.idl
Normal 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>;
|
||||
};
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue