2021-04-22 16:11:20 -03:00
/*
2024-10-04 08:19:50 -03:00
* Copyright ( c ) 2021 - 2022 , Andreas Kling < andreas @ ladybird . org >
2021-09-26 11:14:37 -03:00
* Copyright ( c ) 2021 , Luke Wilde < lukew @ serenityos . org >
2021-04-22 16:11:20 -03:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2025-10-26 01:28:48 -03:00
# include <AK/InsertionSort.h>
2026-04-18 05:54:06 -03:00
# include <LibWeb/Bindings/HTMLCollection.h>
2022-09-25 19:15:49 -03:00
# include <LibWeb/Bindings/Intrinsics.h>
2024-03-19 11:41:02 -03:00
# include <LibWeb/DOM/Document.h>
2021-04-22 16:11:20 -03:00
# include <LibWeb/DOM/Element.h>
# include <LibWeb/DOM/HTMLCollection.h>
# include <LibWeb/DOM/ParentNode.h>
2021-09-26 11:14:37 -03:00
# include <LibWeb/Namespace.h>
2021-04-22 16:11:20 -03:00
namespace Web : : DOM {
2024-11-14 12:01:23 -03:00
GC_DEFINE_ALLOCATOR ( HTMLCollection ) ;
2023-11-19 15:47:52 -03:00
2025-10-26 01:28:48 -03:00
GC : : Ref < HTMLCollection > HTMLCollection : : create ( ParentNode & root , Scope scope , Function < bool ( Element const & ) > filter , Function < bool ( Element const & , Element const & ) > sort )
2022-09-01 15:50:16 -03:00
{
2025-10-26 01:28:48 -03:00
return root . realm ( ) . create < HTMLCollection > ( root , scope , move ( filter ) , move ( sort ) ) ;
2022-09-01 15:50:16 -03:00
}
2025-10-26 01:28:48 -03:00
HTMLCollection : : HTMLCollection ( ParentNode & root , Scope scope , Function < bool ( Element const & ) > filter , Function < bool ( Element const & , Element const & ) > sort )
2024-01-09 20:05:03 -03:00
: PlatformObject ( root . realm ( ) )
2026-04-30 09:34:41 -03:00
, GC : : WeakContainer ( heap ( ) )
2022-09-01 15:50:16 -03:00
, m_root ( root )
2021-04-22 16:11:20 -03:00
, m_filter ( move ( filter ) )
2025-10-26 01:28:48 -03:00
, m_sort ( move ( sort ) )
2023-05-23 06:25:07 -03:00
, m_scope ( scope )
2021-04-22 16:11:20 -03:00
{
2024-01-09 20:05:03 -03:00
m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
. supports_indexed_properties = true ,
. supports_named_properties = true ,
. has_legacy_unenumerable_named_properties_interface_extended_attribute = true ,
} ;
2021-04-22 16:11:20 -03:00
}
2022-03-14 16:21:51 -03:00
HTMLCollection : : ~ HTMLCollection ( ) = default ;
2021-04-22 16:11:20 -03:00
2023-08-07 03:41:28 -03:00
void HTMLCollection : : initialize ( JS : : Realm & realm )
2023-01-10 08:56:59 -03:00
{
2024-03-16 09:13:08 -03:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( HTMLCollection ) ;
2025-04-20 11:22:57 -03:00
Base : : initialize ( realm ) ;
2023-01-10 08:56:59 -03:00
}
2022-09-01 15:50:16 -03:00
void HTMLCollection : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
2023-11-19 00:18:00 -03:00
visitor . visit ( m_root ) ;
2024-07-25 02:49:41 -03:00
}
2026-05-14 09:10:19 -03:00
GC : : Cell const & HTMLCollection : : owner_cell ( Badge < GC : : Heap > ) const
{
return * this ;
}
2026-04-30 09:34:41 -03:00
void HTMLCollection : : remove_dead_cells ( Badge < GC : : Heap > )
{
2026-05-15 14:03:42 -03:00
m_cached_elements . remove_all_matching ( [ & ] ( GC : : RawPtr < Element > const & element ) {
auto * block = GC : : HeapBlock : : from_cell ( element ) ;
return ! heap ( ) . is_live_heap_block ( block ) | | element - > state ( ) ! = Cell : : State : : Live | | ! element - > is_marked ( ) ;
2026-04-30 09:34:41 -03:00
} ) ;
if ( m_cached_name_to_element_mappings ) {
2026-05-15 14:03:42 -03:00
m_cached_name_to_element_mappings - > remove_all_matching ( [ & ] ( FlyString const & , GC : : RawPtr < Element > const & element ) {
auto * block = GC : : HeapBlock : : from_cell ( element ) ;
return ! heap ( ) . is_live_heap_block ( block ) | | element - > state ( ) ! = Cell : : State : : Live | | ! element - > is_marked ( ) ;
2026-04-30 09:34:41 -03:00
} ) ;
}
}
2024-07-25 02:49:41 -03:00
void HTMLCollection : : update_name_to_element_mappings_if_needed ( ) const
{
update_cache_if_needed ( ) ;
if ( m_cached_name_to_element_mappings )
return ;
2026-04-30 09:34:41 -03:00
m_cached_name_to_element_mappings = make < OrderedHashMap < FlyString , GC : : RawPtr < Element > > > ( ) ;
2024-07-25 02:49:41 -03:00
for ( auto const & element : m_cached_elements ) {
// 1. If element has an ID which is not in result, append element’ s ID to result.
if ( auto const & id = element - > id ( ) ; id . has_value ( ) ) {
if ( ! id . value ( ) . is_empty ( ) & & ! m_cached_name_to_element_mappings - > contains ( id . value ( ) ) )
m_cached_name_to_element_mappings - > set ( id . value ( ) , element ) ;
}
// 2. If element is in the HTML namespace and has a name attribute whose value is neither the empty string nor is in result, append element’ s name attribute value to result.
if ( element - > namespace_uri ( ) = = Namespace : : HTML & & element - > name ( ) . has_value ( ) ) {
auto element_name = element - > name ( ) . value ( ) ;
if ( ! element_name . is_empty ( ) & & ! m_cached_name_to_element_mappings - > contains ( element_name ) )
m_cached_name_to_element_mappings - > set ( move ( element_name ) , element ) ;
}
}
2022-09-01 15:50:16 -03:00
}
2024-04-01 10:08:22 -03:00
void HTMLCollection : : update_cache_if_needed ( ) const
2021-04-22 16:11:20 -03:00
{
2024-04-01 10:08:22 -03:00
// Nothing to do, the DOM hasn't updated since we last built the cache.
if ( m_cached_dom_tree_version = = root ( ) - > document ( ) . dom_tree_version ( ) )
return ;
m_cached_elements . clear ( ) ;
2024-07-25 02:49:41 -03:00
m_cached_name_to_element_mappings = nullptr ;
2024-04-01 10:08:22 -03:00
if ( m_scope = = Scope : : Descendants ) {
m_root - > for_each_in_subtree_of_type < Element > ( [ & ] ( auto & element ) {
if ( m_filter ( element ) )
m_cached_elements . append ( element ) ;
2024-05-04 10:47:04 -03:00
return TraversalDecision : : Continue ;
2024-04-01 10:08:22 -03:00
} ) ;
} else {
m_root - > for_each_child_of_type < Element > ( [ & ] ( auto & element ) {
if ( m_filter ( element ) )
m_cached_elements . append ( element ) ;
return IterationDecision : : Continue ;
} ) ;
2023-05-23 06:25:07 -03:00
}
2025-10-26 01:28:48 -03:00
if ( m_sort ) {
insertion_sort ( m_cached_elements , [ this ] ( auto const & a , auto const & b ) {
2025-12-16 15:31:23 -03:00
return this - > m_sort ( * a , * b ) ;
2025-10-26 01:28:48 -03:00
} ) ;
}
2024-04-01 10:08:22 -03:00
m_cached_dom_tree_version = root ( ) - > document ( ) . dom_tree_version ( ) ;
}
2024-03-19 11:41:02 -03:00
2024-12-26 10:32:52 -03:00
GC : : RootVector < GC : : Ref < Element > > HTMLCollection : : collect_matching_elements ( ) const
2024-04-01 10:08:22 -03:00
{
update_cache_if_needed ( ) ;
2026-05-19 15:40:15 -03:00
GC : : RootVector < GC : : Ref < Element > > elements ;
2024-03-19 11:41:02 -03:00
for ( auto & element : m_cached_elements )
2025-12-16 15:31:23 -03:00
elements . append ( * element ) ;
2021-04-22 16:11:20 -03:00
return elements ;
}
2021-09-26 11:14:37 -03:00
// https://dom.spec.whatwg.org/#dom-htmlcollection-length
2023-08-18 22:41:57 -03:00
size_t HTMLCollection : : length ( ) const
2021-04-22 16:11:20 -03:00
{
2021-09-26 11:14:37 -03:00
// The length getter steps are to return the number of nodes represented by the collection.
2024-04-01 10:31:12 -03:00
update_cache_if_needed ( ) ;
return m_cached_elements . size ( ) ;
2021-04-22 16:11:20 -03:00
}
2021-09-26 11:14:37 -03:00
// https://dom.spec.whatwg.org/#dom-htmlcollection-item
Element * HTMLCollection : : item ( size_t index ) const
2021-04-22 16:11:20 -03:00
{
2021-09-26 11:14:37 -03:00
// The item(index) method steps are to return the indexth element in the collection. If there is no indexth element in the collection, then the method must return null.
2024-04-01 10:31:12 -03:00
update_cache_if_needed ( ) ;
if ( index > = m_cached_elements . size ( ) )
2021-04-22 16:11:20 -03:00
return nullptr ;
2024-04-01 10:31:12 -03:00
return m_cached_elements [ index ] ;
2021-04-22 16:11:20 -03:00
}
2021-09-26 11:14:37 -03:00
// https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key
2024-04-26 07:27:27 -03:00
Element * HTMLCollection : : named_item ( FlyString const & key ) const
2021-04-22 16:11:20 -03:00
{
2021-09-26 11:14:37 -03:00
// 1. If key is the empty string, return null.
2024-04-26 07:27:27 -03:00
if ( key . is_empty ( ) )
2021-04-22 16:11:20 -03:00
return nullptr ;
2024-04-01 10:31:12 -03:00
2024-07-25 02:49:41 -03:00
update_name_to_element_mappings_if_needed ( ) ;
if ( auto it = m_cached_name_to_element_mappings - > get ( key ) ; it . has_value ( ) )
return it . value ( ) ;
2021-04-22 16:11:20 -03:00
return nullptr ;
}
2024-07-25 02:49:41 -03:00
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
bool HTMLCollection : : is_supported_property_name ( FlyString const & name ) const
{
update_name_to_element_mappings_if_needed ( ) ;
return m_cached_name_to_element_mappings - > contains ( name ) ;
}
2021-09-26 11:14:37 -03:00
// https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
2023-12-24 16:59:00 -03:00
Vector < FlyString > HTMLCollection : : supported_property_names ( ) const
2021-09-26 11:14:37 -03:00
{
// 1. Let result be an empty list.
2023-12-24 16:59:00 -03:00
Vector < FlyString > result ;
2021-09-26 11:14:37 -03:00
// 2. For each element represented by the collection, in tree order:
2024-07-25 02:49:41 -03:00
update_name_to_element_mappings_if_needed ( ) ;
for ( auto const & it : * m_cached_name_to_element_mappings ) {
result . append ( it . key ) ;
2021-09-26 11:14:37 -03:00
}
// 3. Return result.
return result ;
}
2024-07-25 03:15:51 -03:00
Optional < JS : : Value > HTMLCollection : : item_value ( size_t index ) const
2022-09-01 15:50:16 -03:00
{
auto * element = item ( index ) ;
if ( ! element )
2024-07-25 03:15:51 -03:00
return { } ;
2024-04-01 10:23:31 -03:00
return element ;
2022-09-01 15:50:16 -03:00
}
2024-07-25 02:36:10 -03:00
JS : : Value HTMLCollection : : named_item_value ( FlyString const & name ) const
2022-09-01 15:50:16 -03:00
{
2024-04-01 10:23:31 -03:00
auto * element = named_item ( name ) ;
2022-09-01 15:50:16 -03:00
if ( ! element )
return JS : : js_undefined ( ) ;
2024-04-01 10:23:31 -03:00
return element ;
2022-09-01 15:50:16 -03:00
}
2025-05-13 08:06:33 -03:00
2021-04-22 16:11:20 -03:00
}