2021-10-18 18:53:40 +03:00
/*
2022-01-31 21:07:22 +03:00
* Copyright ( c ) 2021 , Tim Flynn < trflynn89 @ serenityos . org >
2022-08-08 16:19:46 +03:00
* Copyright ( c ) 2022 , Andreas Kling < kling @ serenityos . org >
2021-10-18 18:53:40 +03:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/StringBuilder.h>
2024-04-27 03:09:58 +03:00
# include <LibWeb/Bindings/DOMTokenListPrototype.h>
2021-10-18 18:53:40 +03:00
# include <LibWeb/DOM/DOMTokenList.h>
2022-08-08 16:19:46 +03:00
# include <LibWeb/DOM/Document.h>
2021-10-18 18:53:40 +03:00
# include <LibWeb/DOM/Element.h>
2024-05-16 20:07:46 +03:00
# include <LibWeb/HTML/HTMLLinkElement.h>
2022-10-01 20:14:32 +03:00
# include <LibWeb/Infra/CharacterTypes.h>
2022-09-25 19:28:46 +03:00
# include <LibWeb/WebIDL/DOMException.h>
2021-10-18 18:53:40 +03:00
namespace {
// https://infra.spec.whatwg.org/#set-append
2023-08-12 12:30:21 +03:00
inline void append_to_ordered_set ( Vector < String > & set , String item )
2021-10-18 18:53:40 +03:00
{
if ( ! set . contains_slow ( item ) )
set . append ( move ( item ) ) ;
}
// https://infra.spec.whatwg.org/#list-remove
2023-08-12 12:30:21 +03:00
inline void remove_from_ordered_set ( Vector < String > & set , StringView item )
2021-10-18 18:53:40 +03:00
{
set . remove_first_matching ( [ & ] ( auto const & value ) { return value = = item ; } ) ;
}
// https://infra.spec.whatwg.org/#set-replace
2023-08-12 12:30:21 +03:00
inline void replace_in_ordered_set ( Vector < String > & set , String const & item , String replacement )
2021-10-18 18:53:40 +03:00
{
auto item_index = set . find_first_index ( item ) ;
VERIFY ( item_index . has_value ( ) ) ;
auto replacement_index = set . find_first_index ( replacement ) ;
if ( ! replacement_index . has_value ( ) ) {
set [ * item_index ] = move ( replacement ) ;
return ;
}
auto index_to_set = min ( * item_index , * replacement_index ) ;
auto index_to_remove = max ( * item_index , * replacement_index ) ;
if ( index_to_set = = index_to_remove )
return ;
set [ index_to_set ] = move ( replacement ) ;
set . remove ( index_to_remove ) ;
}
}
namespace Web : : DOM {
2023-11-19 21:47:52 +03:00
JS_DEFINE_ALLOCATOR ( DOMTokenList ) ;
2023-08-12 12:30:21 +03:00
JS : : NonnullGCPtr < DOMTokenList > DOMTokenList : : create ( Element & associated_element , FlyString associated_attribute )
2021-10-18 18:53:40 +03:00
{
2022-10-01 02:16:16 +03:00
auto & realm = associated_element . realm ( ) ;
2023-08-13 14:05:26 +03:00
return realm . heap ( ) . allocate < DOMTokenList > ( realm , associated_element , move ( associated_attribute ) ) ;
2021-10-18 18:53:40 +03:00
}
// https://dom.spec.whatwg.org/#ref-for-domtokenlist%E2%91%A0%E2%91%A2
2023-08-12 12:30:21 +03:00
DOMTokenList : : DOMTokenList ( Element & associated_element , FlyString associated_attribute )
2024-01-10 02:05:03 +03:00
: Bindings : : PlatformObject ( associated_element . realm ( ) )
2022-08-08 16:19:46 +03:00
, m_associated_element ( associated_element )
2021-10-18 18:53:40 +03:00
, m_associated_attribute ( move ( associated_attribute ) )
{
2024-01-10 02:05:03 +03:00
m_legacy_platform_object_flags = LegacyPlatformObjectFlags { . supports_indexed_properties = 1 } ;
2024-01-16 21:04:45 +03:00
associated_attribute_changed ( associated_element . get_attribute_value ( m_associated_attribute ) ) ;
2021-10-18 18:53:40 +03:00
}
2023-08-07 09:41:28 +03:00
void DOMTokenList : : initialize ( JS : : Realm & realm )
2023-01-10 14:56:59 +03:00
{
2023-08-07 09:41:28 +03:00
Base : : initialize ( realm ) ;
2024-03-16 15:13:08 +03:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( DOMTokenList ) ;
2023-01-10 14:56:59 +03:00
}
2023-01-01 17:32:48 +03:00
void DOMTokenList : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_associated_element ) ;
}
2021-10-18 18:53:40 +03:00
// https://dom.spec.whatwg.org/#ref-for-domtokenlist%E2%91%A0%E2%91%A1
void DOMTokenList : : associated_attribute_changed ( StringView value )
{
m_token_set . clear ( ) ;
if ( value . is_empty ( ) )
return ;
2022-10-03 00:30:35 +03:00
auto split_values = value . split_view_if ( Infra : : is_ascii_whitespace ) ;
2021-10-18 18:53:40 +03:00
for ( auto const & split_value : split_values )
2023-08-12 12:30:21 +03:00
append_to_ordered_set ( m_token_set , String : : from_utf8 ( split_value ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2021-10-18 18:53:40 +03:00
}
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-indices%E2%91%A3
bool DOMTokenList : : is_supported_property_index ( u32 index ) const
{
return index < m_token_set . size ( ) ;
}
// https://dom.spec.whatwg.org/#dom-domtokenlist-item
2023-08-12 12:30:21 +03:00
Optional < String > DOMTokenList : : item ( size_t index ) const
2021-10-18 18:53:40 +03:00
{
// 1. If index is equal to or greater than this’ s token set’ s size, then return null.
if ( index > = m_token_set . size ( ) )
2023-08-12 12:30:21 +03:00
return { } ;
2021-10-18 18:53:40 +03:00
// 2. Return this’ s token set[index].
return m_token_set [ index ] ;
}
// https://dom.spec.whatwg.org/#dom-domtokenlist-contains
2023-08-12 12:30:21 +03:00
bool DOMTokenList : : contains ( String const & token )
2021-10-18 18:53:40 +03:00
{
return m_token_set . contains_slow ( token ) ;
}
// https://dom.spec.whatwg.org/#dom-domtokenlist-add
2023-08-12 12:30:21 +03:00
WebIDL : : ExceptionOr < void > DOMTokenList : : add ( Vector < String > const & tokens )
2021-10-18 18:53:40 +03:00
{
// 1. For each token in tokens:
for ( auto const & token : tokens ) {
// a. If token is the empty string, then throw a "SyntaxError" DOMException.
// b. If token contains any ASCII whitespace, then throw an "InvalidCharacterError" DOMException.
2022-03-22 15:37:47 +03:00
TRY ( validate_token ( token ) ) ;
2021-10-18 18:53:40 +03:00
// 2. For each token in tokens, append token to this’ s token set.
append_to_ordered_set ( m_token_set , token ) ;
}
// 3. Run the update steps.
run_update_steps ( ) ;
return { } ;
}
// https://dom.spec.whatwg.org/#dom-domtokenlist-remove
2023-08-12 12:30:21 +03:00
WebIDL : : ExceptionOr < void > DOMTokenList : : remove ( Vector < String > const & tokens )
2021-10-18 18:53:40 +03:00
{
// 1. For each token in tokens:
for ( auto const & token : tokens ) {
// a. If token is the empty string, then throw a "SyntaxError" DOMException.
// b. If token contains any ASCII whitespace, then throw an "InvalidCharacterError" DOMException.
2022-03-22 15:37:47 +03:00
TRY ( validate_token ( token ) ) ;
2021-10-18 18:53:40 +03:00
// 2. For each token in tokens, remove token from this’ s token set.
remove_from_ordered_set ( m_token_set , token ) ;
}
// 3. Run the update steps.
run_update_steps ( ) ;
return { } ;
}
// https://dom.spec.whatwg.org/#dom-domtokenlist-toggle
2023-08-12 12:30:21 +03:00
WebIDL : : ExceptionOr < bool > DOMTokenList : : toggle ( String const & token , Optional < bool > force )
2021-10-18 18:53:40 +03:00
{
// 1. If token is the empty string, then throw a "SyntaxError" DOMException.
// 2. If token contains any ASCII whitespace, then throw an "InvalidCharacterError" DOMException.
2022-03-22 15:37:47 +03:00
TRY ( validate_token ( token ) ) ;
2021-10-18 18:53:40 +03:00
// 3. If this’ s token set[token] exists, then:
if ( contains ( token ) ) {
// a. If force is either not given or is false, then remove token from this’ s token set, run the update steps and return false.
if ( ! force . has_value ( ) | | ! force . value ( ) ) {
remove_from_ordered_set ( m_token_set , token ) ;
run_update_steps ( ) ;
return false ;
}
// b. Return true.
return true ;
}
// 4. Otherwise, if force not given or is true, append token to this’ s token set, run the update steps, and return true.
if ( ! force . has_value ( ) | | force . value ( ) ) {
append_to_ordered_set ( m_token_set , token ) ;
run_update_steps ( ) ;
return true ;
}
// 5. Return false.
return false ;
}
// https://dom.spec.whatwg.org/#dom-domtokenlist-replace
2023-08-12 12:30:21 +03:00
WebIDL : : ExceptionOr < bool > DOMTokenList : : replace ( String const & token , String const & new_token )
2021-10-18 18:53:40 +03:00
{
// 1. If either token or newToken is the empty string, then throw a "SyntaxError" DOMException.
// 2. If either token or newToken contains any ASCII whitespace, then throw an "InvalidCharacterError" DOMException.
2022-03-22 15:37:47 +03:00
TRY ( validate_token ( token ) ) ;
TRY ( validate_token ( new_token ) ) ;
2021-10-18 18:53:40 +03:00
// 3. If this’ s token set does not contain token, then return false.
if ( ! contains ( token ) )
return false ;
// 4. Replace token in this’ s token set with newToken.
replace_in_ordered_set ( m_token_set , token , new_token ) ;
// 5. Run the update steps.
run_update_steps ( ) ;
// 6. Return true.
return true ;
}
// https://dom.spec.whatwg.org/#dom-domtokenlist-supports
// https://dom.spec.whatwg.org/#concept-domtokenlist-validation
2024-05-16 20:07:46 +03:00
WebIDL : : ExceptionOr < bool > DOMTokenList : : supports ( StringView token )
2021-10-18 18:53:40 +03:00
{
2024-05-16 20:07:46 +03:00
static HashMap < FlyString , Vector < StringView > > supported_tokens_map = {
// NOTE: The supported values for rel were taken from HTMLLinkElement::Relationship
{ HTML : : AttributeNames : : rel , { " alternate " sv , " stylesheet " sv , " preload " sv , " dns-prefetch " sv , " preconnect " sv , " icon " sv } } ,
} ;
2021-10-18 18:53:40 +03:00
// 1. If the associated attribute’ s local name does not define supported tokens, throw a TypeError.
2024-05-16 20:07:46 +03:00
auto supported_tokens = supported_tokens_map . get ( m_associated_attribute ) ;
if ( ! supported_tokens . has_value ( ) )
return WebIDL : : SimpleException { WebIDL : : SimpleExceptionType : : TypeError , MUST ( String : : formatted ( " Attribute {} does not define any supported tokens " , m_associated_attribute ) ) } ;
// AD-HOC: Other browsers return false for rel attributes on non-link elements for all attribute values we currently support.
if ( m_associated_attribute = = HTML : : AttributeNames : : rel & & ! is < HTML : : HTMLLinkElement > ( * m_associated_element ) )
return false ;
2021-10-18 18:53:40 +03:00
// 2. Let lowercase token be a copy of token, in ASCII lowercase.
2024-05-16 20:07:46 +03:00
auto lowercase_token = token . to_lowercase_string ( ) ;
2021-10-18 18:53:40 +03:00
// 3. If lowercase token is present in supported tokens, return true.
2024-05-16 20:07:46 +03:00
if ( supported_tokens - > contains_slow ( lowercase_token ) )
return true ;
2021-10-18 18:53:40 +03:00
// 4. Return false.
2024-05-16 20:07:46 +03:00
return false ;
2021-10-18 18:53:40 +03:00
}
// https://dom.spec.whatwg.org/#dom-domtokenlist-value
2023-08-12 12:30:21 +03:00
String DOMTokenList : : value ( ) const
2021-10-18 18:53:40 +03:00
{
StringBuilder builder ;
builder . join ( ' ' , m_token_set ) ;
2023-08-12 12:30:21 +03:00
return MUST ( builder . to_string ( ) ) ;
2021-10-18 18:53:40 +03:00
}
// https://dom.spec.whatwg.org/#ref-for-concept-element-attributes-set-value%E2%91%A2
2023-08-12 12:30:21 +03:00
void DOMTokenList : : set_value ( String const & value )
2021-10-18 18:53:40 +03:00
{
2022-08-28 14:42:07 +03:00
JS : : GCPtr < DOM : : Element > associated_element = m_associated_element . ptr ( ) ;
2021-10-18 18:53:40 +03:00
if ( ! associated_element )
return ;
2023-11-05 02:57:54 +03:00
MUST ( associated_element - > set_attribute ( m_associated_attribute , value ) ) ;
2021-10-18 18:53:40 +03:00
}
2022-09-25 19:03:42 +03:00
WebIDL : : ExceptionOr < void > DOMTokenList : : validate_token ( StringView token ) const
2021-10-18 18:53:40 +03:00
{
if ( token . is_empty ( ) )
2023-09-06 07:03:01 +03:00
return WebIDL : : SyntaxError : : create ( realm ( ) , " Non-empty DOM tokens are not allowed " _fly_string ) ;
2022-10-01 20:14:32 +03:00
if ( any_of ( token , Infra : : is_ascii_whitespace ) )
2023-09-06 07:03:01 +03:00
return WebIDL : : InvalidCharacterError : : create ( realm ( ) , " DOM tokens containing ASCII whitespace are not allowed " _fly_string ) ;
2021-10-18 18:53:40 +03:00
return { } ;
}
// https://dom.spec.whatwg.org/#concept-dtl-update
void DOMTokenList : : run_update_steps ( )
{
2022-08-28 14:42:07 +03:00
JS : : GCPtr < DOM : : Element > associated_element = m_associated_element . ptr ( ) ;
2021-10-18 18:53:40 +03:00
if ( ! associated_element )
return ;
// 1. If the associated element does not have an associated attribute and token set is empty, then return.
2023-11-05 02:57:54 +03:00
if ( ! associated_element - > has_attribute ( m_associated_attribute ) & & m_token_set . is_empty ( ) )
2021-10-18 18:53:40 +03:00
return ;
// 2. Set an attribute value for the associated element using associated attribute’ s local name and the result of running the ordered set serializer for token set.
2023-11-05 02:57:54 +03:00
MUST ( associated_element - > set_attribute ( m_associated_attribute , value ( ) ) ) ;
2021-10-18 18:53:40 +03:00
}
2023-02-28 03:05:39 +03:00
WebIDL : : ExceptionOr < JS : : Value > DOMTokenList : : item_value ( size_t index ) const
2022-08-08 16:19:46 +03:00
{
2023-08-12 12:30:21 +03:00
auto string = item ( index ) ;
if ( ! string . has_value ( ) )
2022-08-08 16:19:46 +03:00
return JS : : js_undefined ( ) ;
2023-08-12 12:30:21 +03:00
return JS : : PrimitiveString : : create ( vm ( ) , string . release_value ( ) ) ;
2022-08-08 16:19:46 +03:00
}
2021-10-18 18:53:40 +03:00
}