HTMLCollection.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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/Intrinsics.h>
  8. #include <LibWeb/DOM/Document.h>
  9. #include <LibWeb/DOM/Element.h>
  10. #include <LibWeb/DOM/HTMLCollection.h>
  11. #include <LibWeb/DOM/ParentNode.h>
  12. #include <LibWeb/Namespace.h>
  13. namespace Web::DOM {
  14. JS_DEFINE_ALLOCATOR(HTMLCollection);
  15. JS::NonnullGCPtr<HTMLCollection> HTMLCollection::create(ParentNode& root, Scope scope, Function<bool(Element const&)> filter)
  16. {
  17. return root.heap().allocate<HTMLCollection>(root.realm(), root, scope, move(filter));
  18. }
  19. HTMLCollection::HTMLCollection(ParentNode& root, Scope scope, Function<bool(Element const&)> filter)
  20. : PlatformObject(root.realm())
  21. , m_root(root)
  22. , m_filter(move(filter))
  23. , m_scope(scope)
  24. {
  25. m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
  26. .supports_indexed_properties = true,
  27. .supports_named_properties = true,
  28. .has_legacy_unenumerable_named_properties_interface_extended_attribute = true,
  29. };
  30. }
  31. HTMLCollection::~HTMLCollection() = default;
  32. void HTMLCollection::initialize(JS::Realm& realm)
  33. {
  34. Base::initialize(realm);
  35. WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLCollection);
  36. }
  37. void HTMLCollection::visit_edges(Cell::Visitor& visitor)
  38. {
  39. Base::visit_edges(visitor);
  40. visitor.visit(m_root);
  41. for (auto& element : m_cached_elements)
  42. visitor.visit(element);
  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 IterationDecision::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& name) const
  91. {
  92. // 1. If key is the empty string, return null.
  93. if (name.is_empty())
  94. return nullptr;
  95. update_cache_if_needed();
  96. auto const& elements = m_cached_elements;
  97. // 2. Return the first element in the collection for which at least one of the following is true:
  98. // - it has an ID which is key;
  99. if (auto it = elements.find_if([&](auto& entry) { return entry->id().has_value() && entry->id().value() == name; }); it != elements.end())
  100. return *it;
  101. // - it is in the HTML namespace and has a name attribute whose value is key;
  102. if (auto it = elements.find_if([&](auto& entry) { return entry->namespace_uri() == Namespace::HTML && entry->name() == name; }); it != elements.end())
  103. return *it;
  104. // or null if there is no such element.
  105. return nullptr;
  106. }
  107. // https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
  108. Vector<FlyString> HTMLCollection::supported_property_names() const
  109. {
  110. // 1. Let result be an empty list.
  111. Vector<FlyString> result;
  112. // 2. For each element represented by the collection, in tree order:
  113. update_cache_if_needed();
  114. for (auto const& element : m_cached_elements) {
  115. // 1. If element has an ID which is not in result, append element’s ID to result.
  116. if (auto const& id = element->id(); id.has_value()) {
  117. if (!result.contains_slow(id.value()))
  118. result.append(id.value());
  119. }
  120. // 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.
  121. if (element->namespace_uri() == Namespace::HTML && element->name().has_value()) {
  122. auto name = element->name().value();
  123. if (!name.is_empty() && !result.contains_slow(name))
  124. result.append(move(name));
  125. }
  126. }
  127. // 3. Return result.
  128. return result;
  129. }
  130. // https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-indices%E2%91%A1
  131. bool HTMLCollection::is_supported_property_index(u32 index) const
  132. {
  133. // 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.
  134. // If there are no such elements, then there are no supported property indices.
  135. return index < length();
  136. }
  137. WebIDL::ExceptionOr<JS::Value> HTMLCollection::item_value(size_t index) const
  138. {
  139. auto* element = item(index);
  140. if (!element)
  141. return JS::js_undefined();
  142. return element;
  143. }
  144. WebIDL::ExceptionOr<JS::Value> HTMLCollection::named_item_value(FlyString const& name) const
  145. {
  146. auto* element = named_item(name);
  147. if (!element)
  148. return JS::js_undefined();
  149. return element;
  150. }
  151. }