2020-01-18 09:38:21 +01:00
/*
2021-04-20 23:34:49 +02:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
2023-02-03 21:50:14 +01:00
* Copyright ( c ) 2023 , Kenneth Myhra < kennethmyhra @ serenityos . org >
2023-06-18 16:30:23 +01:00
* Copyright ( c ) 2023 , Luke Wilde < lukew @ serenityos . org >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2024-01-10 10:45:28 -07:00
# include <AK/QuickSort.h>
2019-11-25 21:21:55 +01:00
# include <AK/StringBuilder.h>
2023-06-18 16:30:23 +01:00
# include <LibTextCodec/Decoder.h>
# include <LibWeb/Bindings/ExceptionOrUtils.h>
2021-08-24 16:28:08 +02:00
# include <LibWeb/DOM/Document.h>
2022-12-22 19:32:20 -05:00
# include <LibWeb/DOM/Event.h>
2023-08-19 13:58:54 +12:00
# include <LibWeb/DOM/HTMLFormControlsCollection.h>
2021-11-18 15:01:28 +01:00
# include <LibWeb/HTML/BrowsingContext.h>
2020-11-21 21:53:18 +00:00
# include <LibWeb/HTML/EventNames.h>
2023-06-18 16:30:23 +01:00
# include <LibWeb/HTML/FormControlInfrastructure.h>
2022-02-25 21:19:06 +01:00
# include <LibWeb/HTML/HTMLButtonElement.h>
# include <LibWeb/HTML/HTMLFieldSetElement.h>
2020-07-26 15:08:16 +02:00
# include <LibWeb/HTML/HTMLFormElement.h>
2024-01-10 10:45:28 -07:00
# include <LibWeb/HTML/HTMLImageElement.h>
2020-07-26 15:08:16 +02:00
# include <LibWeb/HTML/HTMLInputElement.h>
2022-02-25 21:19:06 +01:00
# include <LibWeb/HTML/HTMLObjectElement.h>
# include <LibWeb/HTML/HTMLOutputElement.h>
# include <LibWeb/HTML/HTMLSelectElement.h>
# include <LibWeb/HTML/HTMLTextAreaElement.h>
2020-11-21 21:53:18 +00:00
# include <LibWeb/HTML/SubmitEvent.h>
2023-06-18 16:30:23 +01:00
# include <LibWeb/Infra/CharacterTypes.h>
# include <LibWeb/Infra/Strings.h>
2021-08-24 16:28:08 +02:00
# include <LibWeb/Page/Page.h>
2021-09-13 00:33:23 +03:00
# include <LibWeb/URL/URL.h>
2019-11-25 21:21:55 +01:00
2020-07-28 18:20:36 +02:00
namespace Web : : HTML {
2020-03-07 10:27:02 +01:00
2023-11-19 19:47:52 +01:00
JS_DEFINE_ALLOCATOR ( HTMLFormElement ) ;
2022-02-18 21:00:52 +01:00
HTMLFormElement : : HTMLFormElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 11:20:15 +01:00
: HTMLElement ( document , move ( qualified_name ) )
2019-11-25 21:21:55 +01:00
{
2024-01-10 10:45:28 -07:00
m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
. supports_indexed_properties = true ,
. supports_named_properties = true ,
. has_legacy_unenumerable_named_properties_interface_extended_attribute = true ,
. has_legacy_override_built_ins_interface_extended_attribute = true ,
} ;
2019-11-25 21:21:55 +01:00
}
2022-03-14 13:21:51 -06:00
HTMLFormElement : : ~ HTMLFormElement ( ) = default ;
2019-11-25 21:21:55 +01:00
2023-08-07 08:41:28 +02:00
void HTMLFormElement : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2023-08-07 08:41:28 +02:00
Base : : initialize ( realm ) ;
2023-11-22 12:55:21 +13:00
set_prototype ( & Bindings : : ensure_web_prototype < Bindings : : HTMLFormElementPrototype > ( realm , " HTMLFormElement " _fly_string ) ) ;
2023-01-10 06:28:20 -05:00
}
2022-08-28 13:42:07 +02:00
void HTMLFormElement : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2022-11-24 19:04:05 +01:00
visitor . visit ( m_elements ) ;
2022-08-28 13:42:07 +02:00
for ( auto & element : m_associated_elements )
2023-11-19 16:18:00 +13:00
visitor . visit ( element ) ;
2022-08-28 13:42:07 +02:00
}
2023-06-18 16:30:23 +01:00
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-form-submit
WebIDL : : ExceptionOr < void > HTMLFormElement : : submit_form ( JS : : NonnullGCPtr < HTMLElement > submitter , bool from_submit_binding )
2019-11-25 21:21:55 +01:00
{
2023-06-18 16:30:23 +01:00
auto & vm = this - > vm ( ) ;
auto & realm = this - > realm ( ) ;
// 1. If form cannot navigate, then return.
2020-11-21 21:53:18 +00:00
if ( cannot_navigate ( ) )
2023-03-01 20:10:01 +01:00
return { } ;
2020-11-21 21:53:18 +00:00
2023-06-18 16:30:23 +01:00
// 2. If form's constructing entry list is true, then return.
if ( m_constructing_entry_list )
2023-03-01 20:10:01 +01:00
return { } ;
2019-11-25 21:21:55 +01:00
2023-06-18 16:30:23 +01:00
// 3. Let form document be form's node document.
JS : : NonnullGCPtr < DOM : : Document > form_document = this - > document ( ) ;
2020-11-07 15:57:37 +00:00
2023-06-18 16:30:23 +01:00
// 4. If form document's active sandboxing flag set has its sandboxed forms browsing context flag set, then return.
2023-08-28 11:57:21 +02:00
if ( has_flag ( form_document - > active_sandboxing_flag_set ( ) , HTML : : SandboxingFlagSet : : SandboxedForms ) )
2023-06-18 16:30:23 +01:00
return { } ;
2019-11-25 21:21:55 +01:00
2023-06-18 16:30:23 +01:00
// 5. If the submitted from submit() method flag is not set, then:
2020-11-21 21:53:18 +00:00
if ( ! from_submit_binding ) {
2023-06-18 16:30:23 +01:00
// 1. If form's firing submission events is true, then return.
2020-11-21 21:53:18 +00:00
if ( m_firing_submission_events )
2023-03-01 20:10:01 +01:00
return { } ;
2020-11-21 21:53:18 +00:00
2023-06-18 16:30:23 +01:00
// 2. Set form's firing submission events to true.
2020-11-21 21:53:18 +00:00
m_firing_submission_events = true ;
2023-06-18 16:30:23 +01:00
// FIXME: 3. If the submitter element's no-validate state is false, then interactively validate the constraints
// of form and examine the result. If the result is negative (i.e., the constraint validation concluded
// that there were invalid fields and probably informed the user of this), then:
// 1. Set form's firing submission events to false.
// 2. Return.
2020-11-21 21:53:18 +00:00
2023-06-18 16:30:23 +01:00
// 4. Let submitterButton be null if submitter is form. Otherwise, let submitterButton be submitter.
2022-08-28 13:42:07 +02:00
JS : : GCPtr < HTMLElement > submitter_button ;
2020-11-21 21:53:18 +00:00
if ( submitter ! = this )
submitter_button = submitter ;
2023-06-18 16:30:23 +01:00
// 5. Let shouldContinue be the result of firing an event named submit at form using SubmitEvent, with the
// submitter attribute initialized to submitterButton, the bubbles attribute initialized to true, and the
// cancelable attribute initialized to true.
2021-10-01 18:46:37 +03:00
SubmitEventInit event_init { } ;
event_init . submitter = submitter_button ;
2023-08-13 13:05:26 +02:00
auto submit_event = SubmitEvent : : create ( realm , EventNames : : submit , event_init ) ;
2020-11-21 21:53:18 +00:00
submit_event - > set_bubbles ( true ) ;
submit_event - > set_cancelable ( true ) ;
2023-06-18 16:30:23 +01:00
bool should_continue = dispatch_event ( * submit_event ) ;
2020-11-21 21:53:18 +00:00
2023-06-18 16:30:23 +01:00
// 6. Set form's firing submission events to false.
2020-11-21 21:53:18 +00:00
m_firing_submission_events = false ;
2023-06-18 16:30:23 +01:00
// 7. If shouldContinue is false, then return.
if ( ! should_continue )
2023-03-01 20:10:01 +01:00
return { } ;
2020-11-21 21:53:18 +00:00
2023-06-18 16:30:23 +01:00
// 8. If form cannot navigate, then return.
// Spec Note: Cannot navigate is run again as dispatching the submit event could have changed the outcome.
2020-11-21 21:53:18 +00:00
if ( cannot_navigate ( ) )
2023-03-01 20:10:01 +01:00
return { } ;
2020-11-21 21:53:18 +00:00
}
2023-06-18 16:30:23 +01:00
// 6. Let encoding be the result of picking an encoding for the form.
auto encoding = TRY_OR_THROW_OOM ( vm , pick_an_encoding ( ) ) ;
if ( encoding ! = " UTF-8 " sv ) {
dbgln ( " FIXME: Support encodings other than UTF-8 in form submission. Returning from form submission. " ) ;
2023-03-01 20:10:01 +01:00
return { } ;
2020-11-07 15:57:37 +00:00
}
2023-06-18 16:30:23 +01:00
// 7. Let entry list be the result of constructing the entry list with form, submitter, and encoding.
auto entry_list_or_null = TRY ( construct_entry_list ( realm , * this , submitter , encoding ) ) ;
// 8. Assert: entry list is not null.
VERIFY ( entry_list_or_null . has_value ( ) ) ;
auto entry_list = entry_list_or_null . release_value ( ) ;
// 9. If form cannot navigate, then return.
// Spec Note: Cannot navigate is run again as dispatching the formdata event in constructing the entry list could
// have changed the outcome.
if ( cannot_navigate ( ) )
return { } ;
// 10. Let method be the submitter element's method.
auto method = method_state_from_form_element ( submitter ) ;
// 11. If method is dialog, then:
if ( method = = MethodAttributeState : : Dialog ) {
// FIXME: 1. If form does not have an ancestor dialog element, then return.
// FIXME: 2. Let subject be form's nearest ancestor dialog element.
// FIXME: 3. Let result be null.
// FIXME: 4. If submitter is an input element whose type attribute is in the Image Button state, then:
// 1. Let (x, y) be the selected coordinate.
// 2. Set result to the concatenation of x, ",", and y.
// FIXME: 5. Otherwise, if submitter has a value, then set result to that value.
// FIXME: 6. Close the dialog subject with result.
// FIXME: 7. Return.
dbgln ( " FIXME: Implement form submission with `dialog` action. Returning from form submission. " ) ;
2023-03-01 20:10:01 +01:00
return { } ;
2020-11-07 15:57:37 +00:00
}
2023-06-18 16:30:23 +01:00
// 12. Let action be the submitter element's action.
auto action = action_from_form_element ( submitter ) ;
2019-11-25 21:21:55 +01:00
2023-06-18 16:30:23 +01:00
// 13. If action is the empty string, let action be the URL of the form document.
if ( action . is_empty ( ) )
2023-12-24 15:51:37 +13:00
action = form_document - > url_string ( ) ;
2019-11-25 21:21:55 +01:00
2023-06-18 16:30:23 +01:00
// 14. Parse a URL given action, relative to the submitter element's node document. If this fails, return.
// 15. Let parsed action be the resulting URL record.
auto parsed_action = document ( ) . parse_url ( action ) ;
if ( ! parsed_action . is_valid ( ) ) {
dbgln ( " Failed to submit form: Invalid URL: {} " , action ) ;
return { } ;
2020-09-28 11:56:26 +02:00
}
2019-11-25 21:21:55 +01:00
2023-06-18 16:30:23 +01:00
// 16. Let scheme be the scheme of parsed action.
auto const & scheme = parsed_action . scheme ( ) ;
// 17. Let enctype be the submitter element's enctype.
auto encoding_type = encoding_type_state_from_form_element ( submitter ) ;
// 18. Let target be the submitter element's formtarget attribute value, if the element is a submit button and has
// such an attribute. Otherwise, let it be the result of getting an element's target given submitter's form
// owner.
2023-11-20 13:34:37 +13:00
auto target = submitter - > attribute ( AttributeNames : : formtarget ) . value_or ( get_an_elements_target ( ) ) ;
2023-06-18 16:30:23 +01:00
// 19. Let noopener be the result of getting an element's noopener with form and target.
auto no_opener = get_an_elements_noopener ( target ) ;
2020-09-28 11:56:26 +02:00
2023-06-18 16:30:23 +01:00
// 20. Let targetNavigable be the first return value of applying the rules for choosing a navigable given target, form's node navigable, and noopener.
2023-06-23 00:32:10 +03:00
auto target_navigable = form_document - > navigable ( ) - > choose_a_navigable ( target , no_opener ) . navigable ;
2023-06-18 16:30:23 +01:00
// 21. If targetNavigable is null, then return.
if ( ! target_navigable ) {
dbgln ( " Failed to submit form: choose_a_browsing_context returning a null browsing context " ) ;
return { } ;
2020-09-28 11:56:26 +02:00
}
2023-06-18 16:30:23 +01:00
// 22. Let historyHandling be "push".
// NOTE: This is `Default` in the old spec.
auto history_handling = HistoryHandlingBehavior : : Default ;
// 23. If form document has not yet completely loaded, then set historyHandling to "replace".
if ( ! form_document - > is_completely_loaded ( ) )
history_handling = HistoryHandlingBehavior : : Replace ;
// 24. Select the appropriate row in the table below based on scheme as given by the first cell of each row.
// Then, select the appropriate cell on that row based on method as given in the first cell of each column.
// Then, jump to the steps named in that cell and defined below the table.
// | GET | POST
// ------------------------------------------------------
// http | Mutate action URL | Submit as entity body
// https | Mutate action URL | Submit as entity body
// ftp | Get action URL | Get action URL
// javascript | Get action URL | Get action URL
// data | Mutate action URL | Get action URL
// mailto | Mail with headers | Mail as body
// If scheme is not one of those listed in this table, then the behavior is not defined by this specification.
// User agents should, in the absence of another specification defining this, act in a manner analogous to that defined
// in this specification for similar schemes.
// This should have been handled above.
VERIFY ( method ! = MethodAttributeState : : Dialog ) ;
if ( scheme . is_one_of ( " http " sv , " https " sv ) ) {
if ( method = = MethodAttributeState : : GET )
2023-06-25 14:15:24 +12:00
TRY_OR_THROW_OOM ( vm , mutate_action_url ( move ( parsed_action ) , move ( entry_list ) , move ( encoding ) , * target_navigable , history_handling ) ) ;
2023-06-18 16:30:23 +01:00
else
TRY_OR_THROW_OOM ( vm , submit_as_entity_body ( move ( parsed_action ) , move ( entry_list ) , encoding_type , move ( encoding ) , * target_navigable , history_handling ) ) ;
} else if ( scheme . is_one_of ( " ftp " sv , " javascript " sv ) ) {
get_action_url ( move ( parsed_action ) , * target_navigable , history_handling ) ;
} else if ( scheme = = " data " sv ) {
if ( method = = MethodAttributeState : : GET )
2023-06-25 14:15:24 +12:00
TRY_OR_THROW_OOM ( vm , mutate_action_url ( move ( parsed_action ) , move ( entry_list ) , move ( encoding ) , * target_navigable , history_handling ) ) ;
2023-06-18 16:30:23 +01:00
else
get_action_url ( move ( parsed_action ) , * target_navigable , history_handling ) ;
} else if ( scheme = = " mailto " sv ) {
if ( method = = MethodAttributeState : : GET )
TRY_OR_THROW_OOM ( vm , mail_with_headers ( move ( parsed_action ) , move ( entry_list ) , move ( encoding ) , * target_navigable , history_handling ) ) ;
else
TRY_OR_THROW_OOM ( vm , mail_as_body ( move ( parsed_action ) , move ( entry_list ) , encoding_type , move ( encoding ) , * target_navigable , history_handling ) ) ;
} else {
dbgln ( " Failed to submit form: Unknown scheme: {} " , scheme ) ;
return { } ;
}
2023-03-01 20:10:01 +01:00
return { } ;
2019-11-25 21:21:55 +01:00
}
2020-03-07 10:27:02 +01:00
2022-12-22 19:32:20 -05:00
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#resetting-a-form
void HTMLFormElement : : reset_form ( )
{
// 1. Let reset be the result of firing an event named reset at form, with the bubbles and cancelable attributes initialized to true.
2023-08-13 13:05:26 +02:00
auto reset_event = DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : reset ) ;
2022-12-22 19:32:20 -05:00
reset_event - > set_bubbles ( true ) ;
reset_event - > set_cancelable ( true ) ;
2023-02-14 22:43:17 +01:00
bool reset = dispatch_event ( reset_event ) ;
2022-12-22 19:32:20 -05:00
// 2. If reset is true, then invoke the reset algorithm of each resettable element whose form owner is form.
if ( reset ) {
for ( auto element : m_associated_elements ) {
VERIFY ( is < FormAssociatedElement > ( * element ) ) ;
auto & form_associated_element = dynamic_cast < FormAssociatedElement & > ( * element ) ;
if ( form_associated_element . is_resettable ( ) )
form_associated_element . reset_algorithm ( ) ;
}
}
}
2023-03-01 20:10:01 +01:00
WebIDL : : ExceptionOr < void > HTMLFormElement : : submit ( )
2020-11-21 21:53:18 +00:00
{
2023-06-18 16:30:23 +01:00
return submit_form ( * this , true ) ;
2020-11-21 21:53:18 +00:00
}
2022-12-22 19:32:20 -05:00
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-reset
void HTMLFormElement : : reset ( )
{
// 1. If the form element is marked as locked for reset, then return.
if ( m_locked_for_reset )
return ;
// 2. Mark the form element as locked for reset.
m_locked_for_reset = true ;
// 3. Reset the form element.
reset_form ( ) ;
// 4. Unmark the form element as locked for reset.
m_locked_for_reset = false ;
}
2021-04-20 23:34:49 +02:00
void HTMLFormElement : : add_associated_element ( Badge < FormAssociatedElement > , HTMLElement & element )
{
m_associated_elements . append ( element ) ;
}
void HTMLFormElement : : remove_associated_element ( Badge < FormAssociatedElement > , HTMLElement & element )
{
m_associated_elements . remove_first_matching ( [ & ] ( auto & entry ) { return entry . ptr ( ) = = & element ; } ) ;
2024-01-10 10:45:28 -07:00
// If an element listed in a form element's past names map changes form owner, then its entries must be removed from that map.
m_past_names_map . remove_all_matching ( [ & ] ( auto & , auto const & entry ) { return entry . node = = & element ; } ) ;
2021-04-20 23:34:49 +02:00
}
2023-06-18 16:30:23 +01:00
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fs-action
2023-12-24 15:51:37 +13:00
String HTMLFormElement : : action_from_form_element ( JS : : NonnullGCPtr < HTMLElement > element ) const
2022-02-18 19:46:10 -05:00
{
2023-06-18 16:30:23 +01:00
// The action of an element is the value of the element's formaction attribute, if the element is a submit button
// and has such an attribute, or the value of its form owner's action attribute, if it has one, or else the empty
// string.
if ( auto const * form_associated_element = dynamic_cast < FormAssociatedElement const * > ( element . ptr ( ) ) ;
2023-12-24 15:51:37 +13:00
form_associated_element & & form_associated_element - > is_submit_button ( ) ) {
if ( auto maybe_attribute = element - > attribute ( AttributeNames : : formaction ) ; maybe_attribute . has_value ( ) )
return maybe_attribute . release_value ( ) ;
}
2023-06-18 16:30:23 +01:00
2023-12-24 15:51:37 +13:00
if ( auto maybe_attribute = attribute ( AttributeNames : : action ) ; maybe_attribute . has_value ( ) )
return maybe_attribute . release_value ( ) ;
2023-06-18 16:30:23 +01:00
2023-12-24 15:51:37 +13:00
return String { } ;
2023-06-18 16:30:23 +01:00
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-attributes:attr-fs-method-2
static HTMLFormElement : : MethodAttributeState method_attribute_to_method_state ( StringView method )
{
# define __ENUMERATE_FORM_METHOD_ATTRIBUTE(keyword, state) \
if ( Infra : : is_ascii_case_insensitive_match ( # keyword # # sv , method ) ) \
return HTMLFormElement : : MethodAttributeState : : state ;
ENUMERATE_FORM_METHOD_ATTRIBUTES
# undef __ENUMERATE_FORM_METHOD_ATTRIBUTE
// The method attribute's invalid value default and missing value default are both the GET state.
return HTMLFormElement : : MethodAttributeState : : GET ;
}
2022-02-18 19:46:10 -05:00
2023-06-18 16:30:23 +01:00
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fs-method
HTMLFormElement : : MethodAttributeState HTMLFormElement : : method_state_from_form_element ( JS : : NonnullGCPtr < HTMLElement const > element ) const
{
// If the element is a submit button and has a formmethod attribute, then the element's method is that attribute's state;
// otherwise, it is the form owner's method attribute's state.
if ( auto const * form_associated_element = dynamic_cast < FormAssociatedElement const * > ( element . ptr ( ) ) ;
2024-01-12 09:25:01 +01:00
form_associated_element & & form_associated_element - > is_submit_button ( ) ) {
if ( auto maybe_formmethod = element - > attribute ( AttributeNames : : formmethod ) ; maybe_formmethod . has_value ( ) ) {
// NOTE: `formmethod` is the same as `method`, except that it has no missing value default.
// This is handled by not calling `method_attribute_to_method_state` in the first place if there is no `formmethod` attribute.
return method_attribute_to_method_state ( maybe_formmethod . value ( ) ) ;
}
2023-06-18 16:30:23 +01:00
}
2024-01-12 09:25:01 +01:00
if ( auto maybe_method = attribute ( AttributeNames : : method ) ; maybe_method . has_value ( ) ) {
return method_attribute_to_method_state ( maybe_method . value ( ) ) ;
}
2023-06-18 16:30:23 +01:00
2024-01-12 09:25:01 +01:00
return MethodAttributeState : : GET ;
2023-06-18 16:30:23 +01:00
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-attributes:attr-fs-enctype-2
static HTMLFormElement : : EncodingTypeAttributeState encoding_type_attribute_to_encoding_type_state ( StringView encoding_type )
{
# define __ENUMERATE_FORM_METHOD_ENCODING_TYPE(keyword, state) \
if ( Infra : : is_ascii_case_insensitive_match ( keyword # # sv , encoding_type ) ) \
return HTMLFormElement : : EncodingTypeAttributeState : : state ;
ENUMERATE_FORM_METHOD_ENCODING_TYPES
# undef __ENUMERATE_FORM_METHOD_ENCODING_TYPE
// The enctype attribute's invalid value default and missing value default are both the application/x-www-form-urlencoded state.
return HTMLFormElement : : EncodingTypeAttributeState : : FormUrlEncoded ;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fs-enctype
HTMLFormElement : : EncodingTypeAttributeState HTMLFormElement : : encoding_type_state_from_form_element ( JS : : NonnullGCPtr < HTMLElement > element ) const
{
// If the element is a submit button and has a formenctype attribute, then the element's enctype is that attribute's state;
// otherwise, it is the form owner's enctype attribute's state.
if ( auto const * form_associated_element = dynamic_cast < FormAssociatedElement const * > ( element . ptr ( ) ) ;
2024-01-12 09:25:01 +01:00
form_associated_element & & form_associated_element - > is_submit_button ( ) ) {
if ( auto formenctype = element - > attribute ( AttributeNames : : formenctype ) ; formenctype . has_value ( ) ) {
// NOTE: `formenctype` is the same as `enctype`, except that it has nomissing value default.
// This is handled by not calling `encoding_type_attribute_to_encoding_type_state` in the first place if there is no
// `formenctype` attribute.
return encoding_type_attribute_to_encoding_type_state ( formenctype . value ( ) ) ;
}
2022-02-18 19:46:10 -05:00
}
2024-01-12 09:25:01 +01:00
if ( auto maybe_enctype = attribute ( AttributeNames : : enctype ) ; maybe_enctype . has_value ( ) )
return encoding_type_attribute_to_encoding_type_state ( maybe_enctype . value ( ) ) ;
2023-06-18 16:30:23 +01:00
2024-01-12 09:25:01 +01:00
return EncodingTypeAttributeState : : FormUrlEncoded ;
2022-02-18 19:46:10 -05:00
}
2024-01-10 10:45:28 -07:00
// https://html.spec.whatwg.org/multipage/forms.html#category-listed
static bool is_listed_element ( DOM : : Element const & element )
2022-02-25 21:19:06 +01:00
{
2024-01-10 10:45:28 -07:00
// Denotes elements that are listed in the form.elements and fieldset.elements APIs.
// These elements also have a form content attribute, and a matching form IDL attribute,
// that allow authors to specify an explicit form owner.
// => button, fieldset, input, object, output, select, textarea, form-associated custom elements
2022-02-25 21:19:06 +01:00
if ( is < HTMLButtonElement > ( element )
| | is < HTMLFieldSetElement > ( element )
2024-01-10 10:45:28 -07:00
| | is < HTMLInputElement > ( element )
2022-02-25 21:19:06 +01:00
| | is < HTMLObjectElement > ( element )
| | is < HTMLOutputElement > ( element )
| | is < HTMLSelectElement > ( element )
| | is < HTMLTextAreaElement > ( element ) ) {
return true ;
}
2024-01-10 10:45:28 -07:00
// FIXME: Form-associated custom elements return also true
return false ;
}
static bool is_form_control ( DOM : : Element const & element , HTMLFormElement const & form )
{
// The elements IDL attribute must return an HTMLFormControlsCollection rooted at the form element's root,
// whose filter matches listed elements whose form owner is the form element,
// with the exception of input elements whose type attribute is in the Image Button state, which must,
// for historical reasons, be excluded from this particular collection.
if ( ! is_listed_element ( element ) )
return false ;
2022-02-25 21:19:06 +01:00
if ( is < HTMLInputElement > ( element )
2024-01-10 10:45:28 -07:00
& & static_cast < HTMLInputElement const & > ( element ) . type_state ( ) = = HTMLInputElement : : TypeAttributeState : : ImageButton ) {
return false ;
2022-02-25 21:19:06 +01:00
}
2024-01-10 10:45:28 -07:00
auto const & form_associated_element = dynamic_cast < FormAssociatedElement const & > ( element ) ;
if ( form_associated_element . form ( ) ! = & form )
return false ;
2023-12-10 10:42:41 +01:00
2024-01-10 10:45:28 -07:00
return true ;
2022-02-25 21:19:06 +01:00
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-elements
2023-08-19 13:58:54 +12:00
JS : : NonnullGCPtr < DOM : : HTMLFormControlsCollection > HTMLFormElement : : elements ( ) const
2022-02-25 21:19:06 +01:00
{
2022-11-24 19:04:05 +01:00
if ( ! m_elements ) {
2024-01-10 10:45:28 -07:00
auto & root = verify_cast < ParentNode > ( const_cast < HTMLFormElement * > ( this ) - > root ( ) ) ;
m_elements = DOM : : HTMLFormControlsCollection : : create ( root , DOM : : HTMLCollection : : Scope : : Descendants , [ this ] ( Element const & element ) {
return is_form_control ( element , * this ) ;
2023-08-13 13:05:26 +02:00
} ) ;
2022-11-24 19:04:05 +01:00
}
return * m_elements ;
2022-02-25 21:19:06 +01:00
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-length
unsigned HTMLFormElement : : length ( ) const
{
// The length IDL attribute must return the number of nodes represented by the elements collection.
return elements ( ) - > length ( ) ;
}
2023-03-04 23:58:25 +01:00
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-checkvalidity
WebIDL : : ExceptionOr < bool > HTMLFormElement : : check_validity ( )
{
dbgln ( " (STUBBED) HTMLFormElement::check_validity(). Called on: {} " , debug_description ( ) ) ;
return true ;
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-reportvalidity
WebIDL : : ExceptionOr < bool > HTMLFormElement : : report_validity ( )
{
dbgln ( " (STUBBED) HTMLFormElement::report_validity(). Called on: {} " , debug_description ( ) ) ;
return true ;
}
2023-02-03 21:50:14 +01:00
// https://html.spec.whatwg.org/multipage/forms.html#category-submit
ErrorOr < Vector < JS : : NonnullGCPtr < DOM : : Element > > > HTMLFormElement : : get_submittable_elements ( )
{
Vector < JS : : NonnullGCPtr < DOM : : Element > > submittable_elements = { } ;
for ( size_t i = 0 ; i < elements ( ) - > length ( ) ; i + + ) {
auto * element = elements ( ) - > item ( i ) ;
TRY ( populate_vector_with_submittable_elements_in_tree_order ( * element , submittable_elements ) ) ;
}
return submittable_elements ;
}
ErrorOr < void > HTMLFormElement : : populate_vector_with_submittable_elements_in_tree_order ( JS : : NonnullGCPtr < DOM : : Element > element , Vector < JS : : NonnullGCPtr < DOM : : Element > > & elements )
{
if ( auto * form_associated_element = dynamic_cast < HTML : : FormAssociatedElement * > ( element . ptr ( ) ) ) {
if ( form_associated_element - > is_submittable ( ) )
TRY ( elements . try_append ( element ) ) ;
}
for ( size_t i = 0 ; i < element - > children ( ) - > length ( ) ; i + + ) {
auto * child = element - > children ( ) - > item ( i ) ;
TRY ( populate_vector_with_submittable_elements_in_tree_order ( * child , elements ) ) ;
}
return { } ;
}
2023-06-18 16:30:23 +01:00
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-fs-method
2023-09-03 15:44:24 +12:00
StringView HTMLFormElement : : method ( ) const
2023-06-18 16:30:23 +01:00
{
// The method and enctype IDL attributes must reflect the respective content attributes of the same name, limited to only known values.
2023-09-03 15:44:24 +12:00
// FIXME: This should probably be `Reflect` in the IDL.
2023-06-18 16:30:23 +01:00
auto method_state = method_state_from_form_element ( * this ) ;
switch ( method_state ) {
case MethodAttributeState : : GET :
return " get " sv ;
case MethodAttributeState : : POST :
return " post " sv ;
case MethodAttributeState : : Dialog :
return " dialog " sv ;
}
2023-09-03 15:44:24 +12:00
VERIFY_NOT_REACHED ( ) ;
2023-06-18 16:30:23 +01:00
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-fs-method
2023-09-03 15:44:24 +12:00
WebIDL : : ExceptionOr < void > HTMLFormElement : : set_method ( String const & method )
2023-06-18 16:30:23 +01:00
{
// The method and enctype IDL attributes must reflect the respective content attributes of the same name, limited to only known values.
return set_attribute ( AttributeNames : : method , method ) ;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-fs-action
2023-09-03 15:44:24 +12:00
String HTMLFormElement : : action ( ) const
2023-06-18 16:30:23 +01:00
{
// The action IDL attribute must reflect the content attribute of the same name, except that on getting, when the
// content attribute is missing or its value is the empty string, the element's node document's URL must be returned
// instead.
2024-01-12 09:25:01 +01:00
if ( auto maybe_action = attribute ( AttributeNames : : action ) ;
maybe_action . has_value ( ) & & ! maybe_action . value ( ) . is_empty ( ) ) {
return maybe_action . value ( ) ;
}
2023-06-18 16:30:23 +01:00
2024-01-12 09:25:01 +01:00
return MUST ( document ( ) . url ( ) . to_string ( ) ) ;
2023-06-18 16:30:23 +01:00
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-fs-action
2023-09-03 15:44:24 +12:00
WebIDL : : ExceptionOr < void > HTMLFormElement : : set_action ( String const & value )
2023-06-18 16:30:23 +01:00
{
return set_attribute ( AttributeNames : : action , value ) ;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#picking-an-encoding-for-the-form
ErrorOr < String > HTMLFormElement : : pick_an_encoding ( ) const
{
// 1. Let encoding be the document's character encoding.
auto encoding = document ( ) . encoding_or_default ( ) ;
// 2. If the form element has an accept-charset attribute, set encoding to the return value of running these substeps:
2024-01-12 09:25:01 +01:00
if ( auto maybe_input = attribute ( AttributeNames : : accept_charset ) ; maybe_input . has_value ( ) ) {
2023-06-18 16:30:23 +01:00
// 1. Let input be the value of the form element's accept-charset attribute.
2024-01-12 09:25:01 +01:00
auto input = maybe_input . release_value ( ) ;
2023-06-18 16:30:23 +01:00
// 2. Let candidate encoding labels be the result of splitting input on ASCII whitespace.
2024-01-12 09:25:01 +01:00
auto candidate_encoding_labels = input . bytes_as_string_view ( ) . split_view_if ( Infra : : is_ascii_whitespace ) ;
2023-06-18 16:30:23 +01:00
// 3. Let candidate encodings be an empty list of character encodings.
Vector < StringView > candidate_encodings ;
// 4. For each token in candidate encoding labels in turn (in the order in which they were found in input),
// get an encoding for the token and, if this does not result in failure, append the encoding to candidate
// encodings.
for ( auto const & token : candidate_encoding_labels ) {
auto candidate_encoding = TextCodec : : get_standardized_encoding ( token ) ;
if ( candidate_encoding . has_value ( ) )
TRY ( candidate_encodings . try_append ( candidate_encoding . value ( ) ) ) ;
}
// 5. If candidate encodings is empty, return UTF-8.
if ( candidate_encodings . is_empty ( ) )
return " UTF-8 " _string ;
// 6. Return the first encoding in candidate encodings.
return String : : from_utf8 ( candidate_encodings . first ( ) ) ;
}
// 3. Return the result of getting an output encoding from encoding.
2023-09-14 19:03:38 +12:00
return MUST ( String : : from_utf8 ( TextCodec : : get_output_encoding ( encoding ) ) ) ;
2023-06-18 16:30:23 +01:00
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#convert-to-a-list-of-name-value-pairs
static ErrorOr < Vector < URL : : QueryParam > > convert_to_list_of_name_value_pairs ( Vector < XHR : : FormDataEntry > const & entry_list )
{
// 1. Let list be an empty list of name-value pairs.
Vector < URL : : QueryParam > list ;
// 2. For each entry of entry list:
for ( auto const & entry : entry_list ) {
// 1. Let name be entry's name, with every occurrence of U+000D (CR) not followed by U+000A (LF), and every occurrence of U+000A (LF)
// not preceded by U+000D (CR), replaced by a string consisting of U+000D (CR) and U+000A (LF).
auto name = TRY ( normalize_line_breaks ( entry . name ) ) ;
// 2. If entry's value is a File object, then let value be entry's value's name. Otherwise, let value be entry's value.
String value ;
entry . value . visit (
[ & value ] ( JS : : Handle < FileAPI : : File > const & file ) {
value = file - > name ( ) ;
} ,
[ & value ] ( String const & string ) {
value = string ;
} ) ;
// 3. Replace every occurrence of U+000D (CR) not followed by U+000A (LF), and every occurrence of
// U+000A (LF) not preceded by U+000D (CR), in value, by a string consisting of U+000D (CR) and U+000A (LF).
auto normalized_value = TRY ( normalize_line_breaks ( value ) ) ;
// 4. Append to list a new name-value pair whose name is name and whose value is value.
TRY ( list . try_append ( URL : : QueryParam { . name = move ( name ) , . value = move ( normalized_value ) } ) ) ;
}
// 3. Return list.
return list ;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#text/plain-encoding-algorithm
static ErrorOr < String > plain_text_encode ( Vector < URL : : QueryParam > const & pairs )
{
// 1. Let result be the empty string.
StringBuilder result ;
// 2. For each pair in pairs:
for ( auto const & pair : pairs ) {
// 1. Append pair's name to result.
TRY ( result . try_append ( pair . name ) ) ;
// 2. Append a single U+003D EQUALS SIGN character (=) to result.
TRY ( result . try_append ( ' = ' ) ) ;
// 3. Append pair's value to result.
TRY ( result . try_append ( pair . value ) ) ;
// 4. Append a U+000D CARRIAGE RETURN (CR) U+000A LINE FEED (LF) character pair to result.
TRY ( result . try_append ( " \r \n " sv ) ) ;
}
// 3. Return result.
return result . to_string ( ) ;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-mutate-action
2023-06-23 00:32:10 +03:00
ErrorOr < void > HTMLFormElement : : mutate_action_url ( AK : : URL parsed_action , Vector < XHR : : FormDataEntry > entry_list , String encoding , JS : : NonnullGCPtr < Navigable > target_navigable , HistoryHandlingBehavior history_handling )
2023-06-18 16:30:23 +01:00
{
// 1. Let pairs be the result of converting to a list of name-value pairs with entry list.
auto pairs = TRY ( convert_to_list_of_name_value_pairs ( entry_list ) ) ;
// 2. Let query be the result of running the application/x-www-form-urlencoded serializer with pairs and encoding.
2023-06-25 14:15:24 +12:00
auto query = TRY ( url_encode ( pairs , encoding ) ) ;
2023-06-18 16:30:23 +01:00
// 3. Set parsed action's query component to query.
2023-08-12 19:28:19 +12:00
parsed_action . set_query ( query ) ;
2023-06-18 16:30:23 +01:00
// 4. Plan to navigate to parsed action.
2023-06-23 00:32:10 +03:00
plan_to_navigate_to ( move ( parsed_action ) , Empty { } , target_navigable , history_handling ) ;
2023-06-18 16:30:23 +01:00
return { } ;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-body
2023-06-23 00:32:10 +03:00
ErrorOr < void > HTMLFormElement : : submit_as_entity_body ( AK : : URL parsed_action , Vector < XHR : : FormDataEntry > entry_list , EncodingTypeAttributeState encoding_type , [[maybe_unused]] String encoding , JS : : NonnullGCPtr < Navigable > target_navigable , HistoryHandlingBehavior history_handling )
2023-06-18 16:30:23 +01:00
{
// 1. Assert: method is POST.
2023-06-23 00:32:10 +03:00
POSTResource : : RequestContentType mime_type { } ;
2023-06-18 16:30:23 +01:00
ByteBuffer body ;
// 2. Switch on enctype:
switch ( encoding_type ) {
case EncodingTypeAttributeState : : FormUrlEncoded : {
// -> application/x-www-form-urlencoded
// 1. Let pairs be the result of converting to a list of name-value pairs with entry list.
auto pairs = TRY ( convert_to_list_of_name_value_pairs ( entry_list ) ) ;
// 2. Let body be the result of running the application/x-www-form-urlencoded serializer with pairs and encoding.
2023-06-25 14:15:24 +12:00
body = TRY ( ByteBuffer : : copy ( TRY ( url_encode ( pairs , encoding ) ) . bytes ( ) ) ) ;
2023-06-18 16:30:23 +01:00
// 3. Set body to the result of encoding body.
// NOTE: `encoding` refers to `UTF-8 encode`, which body already is encoded as because it uses AK::String.
// 4. Let mimeType be `application/x-www-form-urlencoded`.
2023-06-23 00:32:10 +03:00
mime_type = POSTResource : : RequestContentType : : ApplicationXWWWFormUrlencoded ;
2023-06-18 16:30:23 +01:00
break ;
}
case EncodingTypeAttributeState : : FormData : {
// -> multipart/form-data
// 1. Let body be the result of running the multipart/form-data encoding algorithm with entry list and encoding.
auto body_and_mime_type = TRY ( serialize_to_multipart_form_data ( entry_list ) ) ;
body = move ( body_and_mime_type . serialized_data ) ;
// 2. Let mimeType be the isomorphic encoding of the concatenation of "multipart/form-data; boundary=" and the multipart/form-data
// boundary string generated by the multipart/form-data encoding algorithm.
2023-06-23 00:32:10 +03:00
mime_type = POSTResource : : RequestContentType : : MultipartFormData ;
2023-06-18 16:30:23 +01:00
return { } ;
}
case EncodingTypeAttributeState : : PlainText : {
// -> text/plain
// 1. Let pairs be the result of converting to a list of name-value pairs with entry list.
auto pairs = TRY ( convert_to_list_of_name_value_pairs ( entry_list ) ) ;
// 2. Let body be the result of running the text/plain encoding algorithm with pairs.
body = TRY ( ByteBuffer : : copy ( TRY ( plain_text_encode ( pairs ) ) . bytes ( ) ) ) ;
// FIXME: 3. Set body to the result of encoding body using encoding.
// 4. Let mimeType be `text/plain`.
2023-06-23 00:32:10 +03:00
mime_type = POSTResource : : RequestContentType : : TextPlain ;
2023-06-18 16:30:23 +01:00
break ;
}
default :
VERIFY_NOT_REACHED ( ) ;
}
2023-06-23 00:32:10 +03:00
// 3. Plan to navigate to parsed action given a POST resource whose request body is body and request content-type is mimeType.
plan_to_navigate_to ( parsed_action , POSTResource { . request_body = move ( body ) , . request_content_type = mime_type } , target_navigable , history_handling ) ;
2023-06-18 16:30:23 +01:00
return { } ;
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-get-action
2023-06-23 00:32:10 +03:00
void HTMLFormElement : : get_action_url ( AK : : URL parsed_action , JS : : NonnullGCPtr < Navigable > target_navigable , Web : : HTML : : HistoryHandlingBehavior history_handling )
2023-06-18 16:30:23 +01:00
{
// 1. Plan to navigate to parsed action.
// Spec Note: entry list is discarded.
2023-06-23 00:32:10 +03:00
plan_to_navigate_to ( move ( parsed_action ) , Empty { } , target_navigable , history_handling ) ;
2023-06-18 16:30:23 +01:00
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#submit-mailto-headers
2023-06-23 00:32:10 +03:00
ErrorOr < void > HTMLFormElement : : mail_with_headers ( AK : : URL parsed_action , Vector < XHR : : FormDataEntry > entry_list , [[maybe_unused]] String encoding , JS : : NonnullGCPtr < Navigable > target_navigable , HistoryHandlingBehavior history_handling )
2023-06-18 16:30:23 +01:00
{
// 1. Let pairs be the result of converting to a list of name-value pairs with entry list.
auto pairs = TRY ( convert_to_list_of_name_value_pairs ( entry_list ) ) ;
// 2. Let headers be the result of running the application/x-www-form-urlencoded serializer with pairs and encoding.
2023-06-25 14:15:24 +12:00
auto headers = TRY ( url_encode ( pairs , encoding ) ) ;
2023-06-18 16:30:23 +01:00
// 3. Replace occurrences of U+002B PLUS SIGN characters (+) in headers with the string "%20".
TRY ( headers . replace ( " + " sv , " %20 " sv , ReplaceMode : : All ) ) ;
// 4. Set parsed action's query to headers.
2023-08-12 19:28:19 +12:00
parsed_action . set_query ( headers ) ;
2023-06-18 16:30:23 +01:00
// 5. Plan to navigate to parsed action.
2023-06-23 00:32:10 +03:00
plan_to_navigate_to ( move ( parsed_action ) , Empty { } , target_navigable , history_handling ) ;
2023-06-18 16:30:23 +01:00
return { } ;
}
2023-06-23 00:32:10 +03:00
ErrorOr < void > HTMLFormElement : : mail_as_body ( AK : : URL parsed_action , Vector < XHR : : FormDataEntry > entry_list , EncodingTypeAttributeState encoding_type , [[maybe_unused]] String encoding , JS : : NonnullGCPtr < Navigable > target_navigable , HistoryHandlingBehavior history_handling )
2023-06-18 16:30:23 +01:00
{
// 1. Let pairs be the result of converting to a list of name-value pairs with entry list.
auto pairs = TRY ( convert_to_list_of_name_value_pairs ( entry_list ) ) ;
String body ;
// 2. Switch on enctype:
switch ( encoding_type ) {
case EncodingTypeAttributeState : : PlainText : {
// -> text/plain
// 1. Let body be the result of running the text/plain encoding algorithm with pairs.
body = TRY ( plain_text_encode ( pairs ) ) ;
// 2. Set body to the result of running UTF-8 percent-encode on body using the default encode set. [URL]
// NOTE: body is already UTF-8 encoded due to using AK::String, so we only have to do the percent encoding.
// NOTE: "default encode set" links to "path percent-encode-set": https://url.spec.whatwg.org/#default-encode-set
auto percent_encoded_body = AK : : URL : : percent_encode ( body , AK : : URL : : PercentEncodeSet : : Path ) ;
body = TRY ( String : : from_utf8 ( percent_encoded_body . view ( ) ) ) ;
break ;
}
default :
// -> Otherwise
// Let body be the result of running the application/x-www-form-urlencoded serializer with pairs and encoding.
2023-06-25 14:15:24 +12:00
body = TRY ( url_encode ( pairs , encoding ) ) ;
2023-06-18 16:30:23 +01:00
break ;
}
// 3. If parsed action's query is null, then set it to the empty string.
2023-08-12 19:28:19 +12:00
if ( ! parsed_action . query ( ) . has_value ( ) )
parsed_action . set_query ( String { } ) ;
2023-06-18 16:30:23 +01:00
StringBuilder query_builder ;
2023-08-12 19:28:19 +12:00
query_builder . append ( * parsed_action . query ( ) ) ;
2023-06-18 16:30:23 +01:00
// 4. If parsed action's query is not the empty string, then append a single U+0026 AMPERSAND character (&) to it.
2023-08-12 19:28:19 +12:00
if ( ! parsed_action . query ( ) - > is_empty ( ) )
2023-06-18 16:30:23 +01:00
TRY ( query_builder . try_append ( ' & ' ) ) ;
// 5. Append "body=" to parsed action's query.
TRY ( query_builder . try_append ( " body= " sv ) ) ;
// 6. Append body to parsed action's query.
TRY ( query_builder . try_append ( body ) ) ;
2023-08-12 19:28:19 +12:00
parsed_action . set_query ( MUST ( query_builder . to_string ( ) ) ) ;
2023-06-18 16:30:23 +01:00
// 7. Plan to navigate to parsed action.
2023-06-23 00:32:10 +03:00
plan_to_navigate_to ( move ( parsed_action ) , Empty { } , target_navigable , history_handling ) ;
2023-06-18 16:30:23 +01:00
return { } ;
}
2023-06-23 00:32:10 +03:00
// FIXME:
static Bindings : : NavigationHistoryBehavior to_navigation_history_behavior ( HistoryHandlingBehavior b )
2023-06-18 16:30:23 +01:00
{
2023-06-23 00:32:10 +03:00
switch ( b ) {
case HistoryHandlingBehavior : : Push :
return Bindings : : NavigationHistoryBehavior : : Push ;
case HistoryHandlingBehavior : : Replace :
return Bindings : : NavigationHistoryBehavior : : Replace ;
default :
return Bindings : : NavigationHistoryBehavior : : Auto ;
}
}
2023-06-18 16:30:23 +01:00
2023-06-23 00:32:10 +03:00
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plan-to-navigate
void HTMLFormElement : : plan_to_navigate_to ( AK : : URL url , Variant < Empty , String , POSTResource > post_resource , JS : : NonnullGCPtr < Navigable > target_navigable , HistoryHandlingBehavior history_handling )
{
2023-06-18 16:30:23 +01:00
// 1. Let referrerPolicy be the empty string.
2023-06-23 00:32:10 +03:00
ReferrerPolicy : : ReferrerPolicy referrer_policy = ReferrerPolicy : : ReferrerPolicy : : EmptyString ;
2023-06-18 16:30:23 +01:00
// 2. If the form element's link types include the noreferrer keyword, then set referrerPolicy to "no-referrer".
2023-09-03 14:58:18 +12:00
auto rel = deprecated_attribute ( HTML : : AttributeNames : : rel ) . to_lowercase ( ) ;
2023-06-18 16:30:23 +01:00
auto link_types = rel . view ( ) . split_view_if ( Infra : : is_ascii_whitespace ) ;
if ( link_types . contains_slow ( " noreferrer " sv ) )
referrer_policy = ReferrerPolicy : : ReferrerPolicy : : NoReferrer ;
// 3. If the form has a non-null planned navigation, remove it from its task queue.
if ( m_planned_navigation ) {
HTML : : main_thread_event_loop ( ) . task_queue ( ) . remove_tasks_matching ( [ this ] ( Task const & task ) {
return & task = = m_planned_navigation ;
} ) ;
}
// 4. Queue an element task on the DOM manipulation task source given the form element and the following steps:
// NOTE: `this`, `actual_resource` and `target_navigable` are protected by JS::SafeFunction.
2023-06-23 00:32:10 +03:00
queue_an_element_task ( Task : : Source : : DOMManipulation , [ this , url , post_resource , target_navigable , history_handling , referrer_policy ] ( ) {
2023-06-18 16:30:23 +01:00
// 1. Set the form's planned navigation to null.
m_planned_navigation = nullptr ;
2023-06-23 00:32:10 +03:00
// 2. Navigate targetNavigable to url using the form element's node document, with historyHandling set to historyHandling,
// referrerPolicy set to referrerPolicy, documentResource set to postResource, and cspNavigationType set to "form-submission".
2023-10-10 16:05:38 +02:00
MUST ( target_navigable - > navigate ( { . url = url ,
. source_document = this - > document ( ) ,
. document_resource = post_resource ,
. response = nullptr ,
. exceptions_enabled = false ,
. history_handling = to_navigation_history_behavior ( history_handling ) ,
. referrer_policy = referrer_policy } ) ) ;
2023-06-18 16:30:23 +01:00
} ) ;
// 5. Set the form's planned navigation to the just-queued task.
m_planned_navigation = HTML : : main_thread_event_loop ( ) . task_queue ( ) . last_added_task ( ) ;
VERIFY ( m_planned_navigation ) ;
}
2024-01-10 10:45:28 -07:00
// https://html.spec.whatwg.org/multipage/forms.html#the-form-element:supported-property-indices
bool HTMLFormElement : : is_supported_property_index ( u32 index ) const
{
// The supported property indices at any instant are the indices supported by the object returned by the elements attribute at that instant.
return index < elements ( ) - > length ( ) ;
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-item
WebIDL : : ExceptionOr < JS : : Value > HTMLFormElement : : item_value ( size_t index ) const
{
// To determine the value of an indexed property for a form element, the user agent must return the value returned by
// the item method on the elements collection, when invoked with the given index as its argument.
return elements ( ) - > item ( index ) ;
}
// https://html.spec.whatwg.org/multipage/forms.html#the-form-element:supported-property-names
Vector < FlyString > HTMLFormElement : : supported_property_names ( ) const
{
// The supported property names consist of the names obtained from the following algorithm, in the order obtained from this algorithm:
// 1. Let sourced names be an initially empty ordered list of tuples consisting of a string, an element, a source,
// where the source is either id, name, or past, and, if the source is past, an age.
struct SourcedName {
FlyString name ;
JS : : GCPtr < DOM : : Element const > element ;
enum class Source {
Id ,
Name ,
Past ,
} source ;
Duration age ;
} ;
Vector < SourcedName > sourced_names ;
// 2. For each listed element candidate whose form owner is the form element, with the exception of any
// input elements whose type attribute is in the Image Button state:
for ( auto const & candidate : m_associated_elements ) {
if ( ! is_form_control ( * candidate , * this ) )
continue ;
// 1. If candidate has an id attribute, add an entry to sourced names with that id attribute's value as the
// string, candidate as the element, and id as the source.
2024-01-12 09:25:01 +01:00
if ( candidate - > id ( ) . has_value ( ) )
2024-01-10 10:45:28 -07:00
sourced_names . append ( SourcedName { candidate - > id ( ) . value ( ) , candidate , SourcedName : : Source : : Id , { } } ) ;
// 2. If candidate has a name attribute, add an entry to sourced names with that name attribute's value as the
// string, candidate as the element, and name as the source.
2024-01-12 09:25:01 +01:00
if ( auto maybe_name = candidate - > attribute ( HTML : : AttributeNames : : name ) ; maybe_name . has_value ( ) )
sourced_names . append ( SourcedName { maybe_name . value ( ) , candidate , SourcedName : : Source : : Name , { } } ) ;
2024-01-10 10:45:28 -07:00
}
// 3. For each img element candidate whose form owner is the form element:
for ( auto const & candidate : m_associated_elements ) {
if ( ! is < HTMLImageElement > ( * candidate ) )
continue ;
// Every element in m_associated_elements has this as the form owner.
// 1. If candidate has an id attribute, add an entry to sourced names with that id attribute's value as the
// string, candidate as the element, and id as the source.
2024-01-12 09:25:01 +01:00
if ( candidate - > id ( ) . has_value ( ) )
2024-01-10 10:45:28 -07:00
sourced_names . append ( SourcedName { candidate - > id ( ) . value ( ) , candidate , SourcedName : : Source : : Id , { } } ) ;
// 2. If candidate has a name attribute, add an entry to sourced names with that name attribute's value as the
// string, candidate as the element, and name as the source.
2024-01-12 09:25:01 +01:00
if ( auto maybe_name = candidate - > attribute ( HTML : : AttributeNames : : name ) ; maybe_name . has_value ( ) )
sourced_names . append ( SourcedName { maybe_name . value ( ) , candidate , SourcedName : : Source : : Name , { } } ) ;
2024-01-10 10:45:28 -07:00
}
// 4. For each entry past entry in the past names map add an entry to sourced names with the past entry's name as
// the string, past entry's element as the element, past as the source, and the length of time past entry has
// been in the past names map as the age.
auto const now = MonotonicTime : : now ( ) ;
for ( auto const & entry : m_past_names_map )
sourced_names . append ( SourcedName { entry . key , static_cast < DOM : : Element const * > ( entry . value . node . ptr ( ) ) , SourcedName : : Source : : Past , now - entry . value . insertion_time } ) ;
// 5. Sort sourced names by tree order of the element entry of each tuple, sorting entries with the same element by
// putting entries whose source is id first, then entries whose source is name, and finally entries whose source
// is past, and sorting entries with the same element and source by their age, oldest first.
// FIXME: Require less const casts here by changing the signature of DOM::Node::compare_document_position
quick_sort ( sourced_names , [ ] ( auto const & lhs , auto const & rhs ) - > bool {
if ( lhs . element ! = rhs . element )
return const_cast < DOM : : Element * > ( lhs . element . ptr ( ) ) - > compare_document_position ( const_cast < DOM : : Element * > ( rhs . element . ptr ( ) ) ) & DOM : : Node : : DOCUMENT_POSITION_FOLLOWING ;
if ( lhs . source ! = rhs . source )
return lhs . source < rhs . source ;
return lhs . age < rhs . age ;
} ) ;
// FIXME: Surely there's a more efficient way to do this without so many FlyStrings and collections?
// 6. Remove any entries in sourced names that have the empty string as their name.
// 7. Remove any entries in sourced names that have the same name as an earlier entry in the map.
// 8. Return the list of names from sourced names, maintaining their relative order.
OrderedHashTable < FlyString > names ;
names . ensure_capacity ( sourced_names . size ( ) ) ;
for ( auto const & entry : sourced_names ) {
if ( entry . name . is_empty ( ) )
continue ;
names . set ( entry . name , AK : : HashSetExistingEntryBehavior : : Keep ) ;
}
Vector < FlyString > result ;
result . ensure_capacity ( names . size ( ) ) ;
for ( auto const & name : names )
result . unchecked_append ( name ) ;
return result ;
}
// https://html.spec.whatwg.org/multipage/forms.html#dom-form-nameditem
WebIDL : : ExceptionOr < JS : : Value > HTMLFormElement : : named_item_value ( FlyString const & name ) const
{
auto & realm = this - > realm ( ) ;
auto & root = verify_cast < ParentNode > ( this - > root ( ) ) ;
// To determine the value of a named property name for a form element, the user agent must run the following steps:
// 1. Let candidates be a live RadioNodeList object containing all the listed elements, whose form owner is the form
// element, that have either an id attribute or a name attribute equal to name, with the exception of input
// elements whose type attribute is in the Image Button state, in tree order.
auto candidates = DOM : : RadioNodeList : : create ( realm , root , DOM : : LiveNodeList : : Scope : : Descendants , [ this , name ] ( auto & node ) - > bool {
if ( ! is < DOM : : Element > ( node ) )
return false ;
auto const & element = static_cast < DOM : : Element const & > ( node ) ;
// Form controls are defined as listed elements, with the exception of input elements in the Image Button state,
// whose form owner is the form element.
if ( ! is_form_control ( element , * this ) )
return false ;
// FIXME: DOM::Element::name() isn't cached
return name = = element . id ( ) | | name = = element . attribute ( HTML : : AttributeNames : : name ) ;
} ) ;
// 2. If candidates is empty, let candidates be a live RadioNodeList object containing all the img elements,
// whose form owner is the form element, that have either an id attribute or a name attribute equal to name,
// in tree order.
if ( candidates - > length ( ) = = 0 ) {
candidates = DOM : : RadioNodeList : : create ( realm , root , DOM : : LiveNodeList : : Scope : : Descendants , [ this , name ] ( auto & node ) - > bool {
if ( ! is < HTMLImageElement > ( node ) )
return false ;
auto const & element = static_cast < HTMLImageElement const & > ( node ) ;
if ( element . form ( ) ! = this )
return false ;
// FIXME: DOM::Element::name() isn't cached
return name = = element . id ( ) | | name = = element . attribute ( HTML : : AttributeNames : : name ) ;
} ) ;
}
auto length = candidates - > length ( ) ;
// 3. If candidates is empty, name is the name of one of the entries in the form element's past names map: return the object associated with name in that map.
if ( length = = 0 ) {
auto it = m_past_names_map . find ( name ) ;
if ( it ! = m_past_names_map . end ( ) )
return it - > value . node ;
}
// 4. If candidates contains more than one node, return candidates.
if ( length > 1 )
return candidates ;
// 5. Otherwise, candidates contains exactly one node. Add a mapping from name to the node in candidates in the form
// element's past names map, replacing the previous entry with the same name, if any.
auto const * node = candidates - > item ( 0 ) ;
m_past_names_map . set ( name , HTMLFormElement : : PastNameEntry { . node = node , . insertion_time = MonotonicTime : : now ( ) } ) ;
// 6. Return the node in candidates.
return node ;
}
2020-03-07 10:27:02 +01:00
}