HTMLCollection.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. * Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibWeb/Bindings/HTMLCollectionPrototype.h>
  8. #include <LibWeb/Bindings/Intrinsics.h>
  9. #include <LibWeb/DOM/Document.h>
  10. #include <LibWeb/DOM/Element.h>
  11. #include <LibWeb/DOM/HTMLCollection.h>
  12. #include <LibWeb/DOM/ParentNode.h>
  13. #include <LibWeb/Namespace.h>
  14. namespace Web::DOM {
  15. JS_DEFINE_ALLOCATOR(HTMLCollection);
  16. JS::NonnullGCPtr<HTMLCollection> HTMLCollection::create(ParentNode& root, Scope scope, Function<bool(Element const&)> filter)
  17. {
  18. return root.heap().allocate<HTMLCollection>(root.realm(), root, scope, move(filter));
  19. }
  20. HTMLCollection::HTMLCollection(ParentNode& root, Scope scope, Function<bool(Element const&)> filter)
  21. : PlatformObject(root.realm())
  22. , m_root(root)
  23. , m_filter(move(filter))
  24. , m_scope(scope)
  25. {
  26. m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
  27. .supports_indexed_properties = true,
  28. .supports_named_properties = true,
  29. .has_legacy_unenumerable_named_properties_interface_extended_attribute = true,
  30. };
  31. }
  32. HTMLCollection::~HTMLCollection() = default;
  33. void HTMLCollection::initialize(JS::Realm& realm)
  34. {
  35. Base::initialize(realm);
  36. WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLCollection);
  37. }
  38. void HTMLCollection::visit_edges(Cell::Visitor& visitor)
  39. {
  40. Base::visit_edges(visitor);
  41. visitor.visit(m_root);
  42. visitor.visit(m_cached_elements);
  43. }
  44. void HTMLCollection::update_cache_if_needed() const
  45. {
  46. // Nothing to do, the DOM hasn't updated since we last built the cache.
  47. if (m_cached_dom_tree_version == root()->document().dom_tree_version())
  48. return;
  49. m_cached_elements.clear();
  50. if (m_scope == Scope::Descendants) {
  51. m_root->for_each_in_subtree_of_type<Element>([&](auto& element) {
  52. if (m_filter(element))
  53. m_cached_elements.append(element);
  54. return TraversalDecision::Continue;
  55. });
  56. } else {
  57. m_root->for_each_child_of_type<Element>([&](auto& element) {
  58. if (m_filter(element))
  59. m_cached_elements.append(element);
  60. return IterationDecision::Continue;
  61. });
  62. }
  63. m_cached_dom_tree_version = root()->document().dom_tree_version();
  64. }
  65. JS::MarkedVector<JS::NonnullGCPtr<Element>> HTMLCollection::collect_matching_elements() const
  66. {
  67. update_cache_if_needed();
  68. JS::MarkedVector<JS::NonnullGCPtr<Element>> elements(heap());
  69. for (auto& element : m_cached_elements)
  70. elements.append(element);
  71. return elements;
  72. }
  73. // https://dom.spec.whatwg.org/#dom-htmlcollection-length
  74. size_t HTMLCollection::length() const
  75. {
  76. // The length getter steps are to return the number of nodes represented by the collection.
  77. update_cache_if_needed();
  78. return m_cached_elements.size();
  79. }
  80. // https://dom.spec.whatwg.org/#dom-htmlcollection-item
  81. Element* HTMLCollection::item(size_t index) const
  82. {
  83. // 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.
  84. update_cache_if_needed();
  85. if (index >= m_cached_elements.size())
  86. return nullptr;
  87. return m_cached_elements[index];
  88. }
  89. // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key
  90. Element* HTMLCollection::named_item(FlyString const& key) const
  91. {
  92. // 1. If key is the empty string, return null.
  93. if (key.is_empty())
  94. return nullptr;
  95. update_cache_if_needed();
  96. // 2. Return the first element in the collection for which at least one of the following is true:
  97. for (auto const& element : m_cached_elements) {
  98. // - it has an ID which is key;
  99. if (element->id() == key)
  100. return element;
  101. // - it is in the HTML namespace and has a name attribute whose value is key;
  102. if (element->namespace_uri() == Namespace::HTML && element->name() == key)
  103. return element;
  104. }
  105. // or null if there is no such element.
  106. return nullptr;
  107. }
  108. // https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
  109. Vector<FlyString> HTMLCollection::supported_property_names() const
  110. {
  111. // 1. Let result be an empty list.
  112. Vector<FlyString> result;
  113. // 2. For each element represented by the collection, in tree order:
  114. update_cache_if_needed();
  115. for (auto const& element : m_cached_elements) {
  116. // 1. If element has an ID which is not in result, append element’s ID to result.
  117. if (auto const& id = element->id(); id.has_value()) {
  118. if (!result.contains_slow(id.value()))
  119. result.append(id.value());
  120. }
  121. // 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.
  122. if (element->namespace_uri() == Namespace::HTML && element->name().has_value()) {
  123. auto name = element->name().value();
  124. if (!name.is_empty() && !result.contains_slow(name))
  125. result.append(move(name));
  126. }
  127. }
  128. // 3. Return result.
  129. return result;
  130. }
  131. // https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-indices%E2%91%A1
  132. bool HTMLCollection::is_supported_property_index(u32 index) const
  133. {
  134. // The object’s supported property indices are the numbers in the range zero to one less than the number of elements represented by the collection.
  135. // If there are no such elements, then there are no supported property indices.
  136. return index < length();
  137. }
  138. WebIDL::ExceptionOr<JS::Value> HTMLCollection::item_value(size_t index) const
  139. {
  140. auto* element = item(index);
  141. if (!element)
  142. return JS::js_undefined();
  143. return element;
  144. }
  145. WebIDL::ExceptionOr<JS::Value> HTMLCollection::named_item_value(FlyString const& name) const
  146. {
  147. auto* element = named_item(name);
  148. if (!element)
  149. return JS::js_undefined();
  150. return element;
  151. }
  152. }