2020-08-01 03:04:26 +01:00
/*
2021-04-28 22:46:44 +02:00
* Copyright ( c ) 2020 , the SerenityOS developers .
2020-08-01 03:04:26 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-08-01 03:04:26 +01:00
*/
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/HTMLButtonElementPrototype.h>
2025-10-15 11:57:00 +02:00
# include <LibWeb/CSS/ComputedProperties.h>
# include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
2022-03-01 21:12:32 +00:00
# include <LibWeb/DOM/Document.h>
2025-01-14 15:15:38 +11:00
# include <LibWeb/DOM/Event.h>
2025-04-04 14:30:56 +02:00
# include <LibWeb/HTML/CommandEvent.h>
2020-08-01 03:04:26 +01:00
# include <LibWeb/HTML/HTMLButtonElement.h>
2022-03-01 21:12:32 +00:00
# include <LibWeb/HTML/HTMLFormElement.h>
2025-04-04 14:30:56 +02:00
# include <LibWeb/Namespace.h>
2020-08-01 03:04:26 +01:00
namespace Web : : HTML {
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( HTMLButtonElement ) ;
2023-11-19 19:47:52 +01:00
2022-02-18 21:00:52 +01:00
HTMLButtonElement : : HTMLButtonElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2022-03-23 18:55:54 -04:00
: HTMLElement ( document , move ( qualified_name ) )
2020-08-01 03:04:26 +01:00
{
}
2022-03-14 13:21:51 -06:00
HTMLButtonElement : : ~ HTMLButtonElement ( ) = default ;
2020-08-01 03:04:26 +01:00
2023-08-07 08:41:28 +02:00
void HTMLButtonElement : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( HTMLButtonElement ) ;
2025-04-20 16:22:57 +02:00
Base : : initialize ( realm ) ;
2023-01-10 06:28:20 -05:00
}
2025-10-15 11:57:00 +02:00
void HTMLButtonElement : : adjust_computed_style ( CSS : : ComputedProperties & style )
{
// https://html.spec.whatwg.org/multipage/rendering.html#button-layout
// If the computed value of 'display' is 'inline-grid', 'grid', 'inline-flex', 'flex', 'none', or 'contents', then behave as the computed value.
auto display = style . display ( ) ;
if ( display . is_flex_inside ( ) | | display . is_grid_inside ( ) | | display . is_none ( ) | | display . is_contents ( ) ) {
// No-op
} else if ( display . is_inline_outside ( ) ) {
// Otherwise, if the computed value of 'display' is a value such that the outer display type is 'inline', then behave as 'inline-block'.
2025-11-13 14:05:19 +01:00
// AD-HOC: See https://github.com/whatwg/html/issues/11857
2025-10-15 11:57:00 +02:00
style . set_property ( CSS : : PropertyID : : Display , CSS : : DisplayStyleValue : : create ( CSS : : Display : : from_short ( CSS : : Display : : Short : : InlineBlock ) ) ) ;
} else {
// Otherwise, behave as 'flow-root'.
style . set_property ( CSS : : PropertyID : : Display , CSS : : DisplayStyleValue : : create ( CSS : : Display : : from_short ( CSS : : Display : : Short : : FlowRoot ) ) ) ;
}
}
2022-03-01 21:06:29 +00:00
HTMLButtonElement : : TypeAttributeState HTMLButtonElement : : type_state ( ) const
{
2024-01-16 19:04:45 +01:00
auto value = get_attribute_value ( HTML : : AttributeNames : : type ) ;
2022-03-01 21:06:29 +00:00
# define __ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE(keyword, state) \
2023-03-10 08:48:54 +01:00
if ( value . equals_ignoring_ascii_case ( # keyword # # sv ) ) \
2022-03-01 21:06:29 +00:00
return HTMLButtonElement : : TypeAttributeState : : state ;
ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTES
# undef __ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE
2025-02-20 17:01:28 +00:00
// The attribute's missing value default and invalid value default are both the Auto state.
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-type-auto-state
return HTMLButtonElement : : TypeAttributeState : : Auto ;
2022-03-01 21:06:29 +00:00
}
2025-02-20 17:01:28 +00:00
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-button-type
String HTMLButtonElement : : type_for_bindings ( ) const
2022-03-01 21:06:29 +00:00
{
2025-02-20 17:01:28 +00:00
// The type getter steps are:
// 1. If this is a submit button, then return "submit".
if ( is_submit_button ( ) )
return " submit " _string ;
// 2. Let state be this's type attribute.
auto state = type_state ( ) ;
// 3. Assert: state is not in the Submit Button state.
VERIFY ( state ! = TypeAttributeState : : Submit ) ;
// 4. If state is in the Auto state, then return "button".
if ( state = = TypeAttributeState : : Auto )
return " button " _string ;
// 5. Return the keyword value corresponding to state.
switch ( state ) {
# define __ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE(keyword, state) \
case TypeAttributeState : : state : \
return # keyword # # _string ;
ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTES
# undef __ENUMERATE_HTML_BUTTON_TYPE_ATTRIBUTE
}
VERIFY_NOT_REACHED ( ) ;
}
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-button-type
2025-10-31 12:30:47 +00:00
void HTMLButtonElement : : set_type_for_bindings ( String const & type )
2025-02-20 17:01:28 +00:00
{
// The type setter steps are to set the type content attribute to the given value.
2025-10-31 12:30:47 +00:00
set_attribute_value ( HTML : : AttributeNames : : type , type ) ;
2022-03-01 21:06:29 +00:00
}
2025-07-22 00:58:28 +02:00
void HTMLButtonElement : : form_associated_element_attribute_changed ( FlyString const & name , Optional < String > const & , Optional < String > const & value , Optional < FlyString > const & namespace_ )
2024-12-12 19:53:04 +00:00
{
2025-11-27 13:52:14 +00:00
PopoverTargetAttributes : : associated_attribute_changed ( name , value , namespace_ ) ;
2024-12-12 19:53:04 +00:00
}
2024-11-23 23:06:06 +01:00
void HTMLButtonElement : : visit_edges ( Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2025-11-27 13:52:14 +00:00
PopoverTargetAttributes : : visit_edges ( visitor ) ;
2025-04-04 01:27:55 +02:00
visitor . visit ( m_command_for_element ) ;
2024-11-23 23:06:06 +01:00
}
2022-11-05 03:58:14 +00:00
// https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex
i32 HTMLButtonElement : : default_tab_index_value ( ) const
{
// See the base function for the spec comments.
return 0 ;
}
2023-06-18 15:08:15 +01:00
// https://html.spec.whatwg.org/multipage/forms.html#concept-submit-button
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-submit-button
bool HTMLButtonElement : : is_submit_button ( ) const
{
2025-02-20 17:01:28 +00:00
// A button element is said to be a submit button if any of the following are true:
switch ( type_state ( ) ) {
// - the type attribute is in the Auto state and both the command and commandfor content attributes are not present; or
case TypeAttributeState : : Auto :
return ! has_attribute ( AttributeNames : : command ) & & ! has_attribute ( AttributeNames : : commandfor ) ;
// - the type attribute is in the Submit Button state.
case TypeAttributeState : : Submit :
return true ;
default :
return false ;
}
2023-06-18 15:08:15 +01:00
}
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-fe-value
2025-07-26 12:19:56 -04:00
Utf16String HTMLButtonElement : : value ( ) const
2023-06-18 15:08:15 +01:00
{
2025-07-08 11:28:10 +01:00
// The element's value is the value of the element's value attribute, if there is one; otherwise the empty string.
2025-07-26 12:19:56 -04:00
return Utf16String : : from_utf8 ( attribute ( AttributeNames : : value ) . value_or ( String { } ) ) ;
2023-06-18 15:08:15 +01:00
}
2025-07-08 11:28:10 +01:00
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-fe-optional-value
Optional < String > HTMLButtonElement : : optional_value ( ) const
{
// The element's optional value is the value of the element's value attribute, if there is one; otherwise null.
return attribute ( AttributeNames : : value ) ;
}
2023-11-18 10:48:09 +01:00
bool HTMLButtonElement : : has_activation_behavior ( ) const
{
return true ;
}
2025-11-17 13:10:41 +01:00
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:activation-behaviour
2024-01-18 12:58:22 -07:00
void HTMLButtonElement : : activation_behavior ( DOM : : Event const & event )
2023-11-18 10:48:09 +01:00
{
// 1. If element is disabled, then return.
if ( ! enabled ( ) )
return ;
2024-01-18 12:58:22 -07:00
// 2. If element's node document is not fully active, then return.
2023-11-18 10:48:09 +01:00
if ( ! this - > document ( ) . is_fully_active ( ) )
return ;
2025-02-12 16:58:03 +00:00
// 3. If element has a form owner:
2024-01-18 12:58:22 -07:00
if ( form ( ) ! = nullptr ) {
2025-02-27 10:53:48 +11:00
// 1. If element is a submit button, then submit element's form owner from element with userInvolvement set to event's user navigation involvement, and return.
2025-02-12 16:58:03 +00:00
if ( is_submit_button ( ) ) {
2024-01-18 12:58:22 -07:00
form ( ) - > submit_form ( * this , { . user_involvement = user_navigation_involvement ( event ) } ) . release_value_but_fixme_should_propagate_errors ( ) ;
2025-02-27 10:53:48 +11:00
return ;
2025-02-12 16:58:03 +00:00
}
2025-02-27 10:53:48 +11:00
// 2. If element's type attribute is in the Reset Button state, then reset element's form owner, and return.
2025-02-12 16:58:03 +00:00
if ( type_state ( ) = = TypeAttributeState : : Reset ) {
2024-01-18 12:58:22 -07:00
form ( ) - > reset_form ( ) ;
2025-02-27 10:53:48 +11:00
return ;
2024-01-18 12:58:22 -07:00
}
2025-02-27 10:53:48 +11:00
// 3. If element's type attribute is in the Auto state, then return.
if ( type_state ( ) = = TypeAttributeState : : Auto )
return ;
2023-11-18 10:48:09 +01:00
}
2024-01-18 12:58:22 -07:00
2025-05-22 14:53:53 +10:00
// 4. Let target be the result of running element's get the commandfor-associated element.
2025-04-04 14:30:56 +02:00
// AD-HOC: Target needs to be an HTML Element in the following steps.
GC : : Ptr < HTMLElement > target = as_if < HTMLElement > ( m_command_for_element . ptr ( ) ) ;
if ( ! target ) {
auto target_id = attribute ( AttributeNames : : commandfor ) ;
if ( target_id . has_value ( ) ) {
root ( ) . for_each_in_inclusive_subtree_of_type < HTMLElement > ( [ & ] ( auto & candidate ) {
if ( candidate . attribute ( HTML : : AttributeNames : : id ) = = target_id . value ( ) ) {
target = & candidate ;
return TraversalDecision : : Break ;
}
return TraversalDecision : : Continue ;
} ) ;
}
}
// 5. If target is not null:
if ( target ) {
// 1. Let command be element's command attribute.
auto command = this - > command ( ) ;
// 2. If command is in the Unknown state, then return.
if ( command . is_empty ( ) ) {
return ;
}
2025-07-08 10:21:00 +01:00
// 3. Let isPopover be true if target's popover attribute is not in the No Popover state; otherwise false.
2025-04-04 14:30:56 +02:00
auto is_popover = target - > popover ( ) . has_value ( ) ;
// 4. If isPopover is false and command is not in the Custom state:
auto command_is_in_custom_state = command . starts_with_bytes ( " -- " sv ) ;
2025-05-22 14:53:53 +10:00
if ( ! is_popover & & ! command_is_in_custom_state ) {
2025-04-04 14:30:56 +02:00
// 1. Assert: target's namespace is the HTML namespace.
VERIFY ( target - > namespace_uri ( ) = = Namespace : : HTML ) ;
2025-11-27 13:52:14 +00:00
// 2. If this standard does not define is valid command steps for target's local name, then return.
// 3. Otherwise, if the result of running target's corresponding is valid command steps given command is false, then return.
if ( ! target - > is_valid_command ( command ) )
2025-04-04 14:30:56 +02:00
return ;
}
2025-11-17 13:10:41 +01:00
// 5. Let continue be the result of firing an event named command at target, using CommandEvent, with its
// command attribute initialized to command, its source attribute initialized to element, and its cancelable
// and composed attributes initialized to true.
// NOTE: DOM standard issue #1328 tracks how to better standardize associated event data in a way which makes
// sense on Events. Currently an event attribute initialized to a value cannot also have a getter, and so
// an internal slot (or map of additional fields) is required to properly specify this.
2025-04-04 14:30:56 +02:00
CommandEventInit event_init { } ;
event_init . command = command ;
event_init . source = this ;
event_init . cancelable = true ;
event_init . composed = true ;
auto event = CommandEvent : : create ( realm ( ) , HTML : : EventNames : : command , move ( event_init ) ) ;
event - > set_is_trusted ( true ) ;
auto continue_ = target - > dispatch_event ( event ) ;
// 6. If continue is false, then return.
if ( ! continue_ )
return ;
// 7. If target is not connected, then return.
if ( ! target - > is_connected ( ) )
return ;
// 8. If command is in the Custom state, then return.
if ( command_is_in_custom_state )
return ;
// 9. If command is in the Hide Popover state:
if ( command = = " hide-popover " ) {
2025-05-15 12:44:40 +01:00
// 1. If the result of running check popover validity given target, true, false, and null is true,
2025-06-04 15:35:43 +10:00
// then run the hide popover algorithm given target, true, true, false, and element.
2025-04-04 14:30:56 +02:00
if ( MUST ( target - > check_popover_validity ( ExpectedToBeShowing : : Yes , ThrowExceptions : : No , nullptr , IgnoreDomState : : No ) ) ) {
2025-06-04 15:35:43 +10:00
MUST ( target - > hide_popover ( FocusPreviousElement : : Yes , FireEvents : : Yes , ThrowExceptions : : No , IgnoreDomState : : No , this ) ) ;
2025-04-04 14:30:56 +02:00
}
}
// 10. Otherwise, if command is in the Toggle Popover state:
else if ( command = = " toggle-popover " ) {
2025-05-15 12:44:40 +01:00
// 1. If the result of running check popover validity given target, false, false, and null is true,
// then run the show popover algorithm given target, false, and this.
2025-04-04 14:30:56 +02:00
if ( MUST ( target - > check_popover_validity ( ExpectedToBeShowing : : No , ThrowExceptions : : No , nullptr , IgnoreDomState : : No ) ) ) {
MUST ( target - > show_popover ( ThrowExceptions : : No , this ) ) ;
}
2025-06-24 11:18:14 +01:00
// 2. Otherwise, if the result of running check popover validity given target, true, false, and null is true,
2025-06-04 15:35:43 +10:00
// then run the hide popover algorithm given target, true, true, false and element.
2025-04-04 14:30:56 +02:00
else if ( MUST ( target - > check_popover_validity ( ExpectedToBeShowing : : Yes , ThrowExceptions : : No , nullptr , IgnoreDomState : : No ) ) ) {
2025-06-04 15:35:43 +10:00
MUST ( target - > hide_popover ( FocusPreviousElement : : Yes , FireEvents : : Yes , ThrowExceptions : : No , IgnoreDomState : : No , this ) ) ;
2025-04-04 14:30:56 +02:00
}
}
// 11. Otherwise, if command is in the Show Popover state:
else if ( command = = " show-popover " ) {
2025-05-15 12:44:40 +01:00
// 1. If the result of running check popover validity given target, false, false, and null is true,
// then run the show popover algorithm given target, false, and this.
2025-04-04 14:30:56 +02:00
if ( MUST ( target - > check_popover_validity ( ExpectedToBeShowing : : No , ThrowExceptions : : No , nullptr , IgnoreDomState : : No ) ) ) {
MUST ( target - > show_popover ( ThrowExceptions : : No , this ) ) ;
}
}
2025-11-27 13:52:14 +00:00
// 12. Otherwise, if this standard defines command steps for target's local name,
// then run the corresponding command steps given target, element, and command.
2025-04-04 14:30:56 +02:00
else {
2025-11-27 13:52:14 +00:00
target - > command_steps ( * this , command ) ;
2025-04-04 14:30:56 +02:00
}
}
2025-02-20 17:01:28 +00:00
// 6. Otherwise, run the popover target attribute activation behavior given element and event's target.
2025-04-04 14:30:56 +02:00
else if ( event . target ( ) & & event . target ( ) - > is_dom_node ( ) )
2025-11-27 13:52:14 +00:00
PopoverTargetAttributes : : popover_target_activation_behaviour ( * this , as < DOM : : Node > ( * event . target ( ) ) ) ;
2023-11-18 10:48:09 +01:00
}
2024-11-02 19:17:20 +01:00
bool HTMLButtonElement : : is_focusable ( ) const
{
return enabled ( ) ;
}
2025-04-04 01:16:44 +02:00
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-button-command
String HTMLButtonElement : : command ( ) const
{
// 1. Let command be this's command attribute.
auto command = get_attribute ( AttributeNames : : command ) ;
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-button-command
// The command attribute is an enumerated attribute with the following keywords and states:
// Keyword State Brief description
// toggle-popover Toggle Popover Shows or hides the targeted popover element.
// show-popover Show Popover Shows the targeted popover element.
// hide-popover Hide Popover Hides the targeted popover element.
// close Close Closes the targeted dialog element.
2025-06-04 15:35:43 +10:00
// request-close Request Close Requests to close the targeted dialog element.
2025-04-04 01:16:44 +02:00
// show-modal Show Modal Opens the targeted dialog element as modal.
// A custom command keyword Custom Only dispatches the command event on the targeted element.
2025-06-04 15:35:43 +10:00
Array valid_values { " toggle-popover " _string , " show-popover " _string , " hide-popover " _string , " close " _string , " request-close " _string , " show-modal " _string } ;
2025-04-04 01:16:44 +02:00
// 2. If command is in the Custom state, then return command's value.
// A custom command keyword is a string that starts with "--".
if ( command . has_value ( ) & & command . value ( ) . starts_with_bytes ( " -- " sv ) ) {
return command . value ( ) ;
}
// NOTE: Steps are re-ordered a bit.
2025-06-24 11:18:14 +01:00
// 4. Return the keyword corresponding to the value of command.return
2025-04-04 01:16:44 +02:00
if ( command . has_value ( ) ) {
auto command_value = command . value ( ) ;
for ( auto const & value : valid_values ) {
if ( value . equals_ignoring_ascii_case ( command_value ) ) {
return value ;
}
}
}
// 3. If command is in the Unknown state, then return the empty string.
// The attribute's missing value default and invalid value default are both the Unknown state.
return { } ;
}
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:dom-button-command-2
2025-10-31 12:30:47 +00:00
void HTMLButtonElement : : set_command ( String const & value )
2025-04-04 01:16:44 +02:00
{
2025-10-31 12:30:47 +00:00
set_attribute_value ( AttributeNames : : command , value ) ;
2025-04-04 01:16:44 +02:00
}
2020-08-01 03:04:26 +01:00
}