HTMLCollection.cpp 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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. visitor.visit(m_cached_elements);
  42. }
  43. void HTMLCollection::update_cache_if_needed() const
  44. {
  45. // Nothing to do, the DOM hasn't updated since we last built the cache.
  46. if (m_cached_dom_tree_version == root()->document().dom_tree_version())
  47. return;
  48. m_cached_elements.clear();
  49. if (m_scope == Scope::Descendants) {
  50. m_root->for_each_in_subtree_of_type<Element>([&](auto& element) {
  51. if (m_filter(element))
  52. m_cached_elements.append(element);
  53. return IterationDecision::Continue;
  54. });
  55. } else {
  56. m_root->for_each_child_of_type<Element>([&](auto& element) {
  57. if (m_filter(element))
  58. m_cached_elements.append(element);
  59. return IterationDecision::Continue;
  60. });
  61. }
  62. m_cached_dom_tree_version = root()->document().dom_tree_version();
  63. }
  64. JS::MarkedVector<JS::NonnullGCPtr<Element>> HTMLCollection::collect_matching_elements() const
  65. {
  66. update_cache_if_needed();
  67. JS::MarkedVector<JS::NonnullGCPtr<Element>> elements(heap());
  68. for (auto& element : m_cached_elements)
  69. elements.append(element);
  70. return elements;
  71. }
  72. // https://dom.spec.whatwg.org/#dom-htmlcollection-length
  73. size_t HTMLCollection::length() const
  74. {
  75. // The length getter steps are to return the number of nodes represented by the collection.
  76. update_cache_if_needed();
  77. return m_cached_elements.size();
  78. }
  79. // https://dom.spec.whatwg.org/#dom-htmlcollection-item
  80. Element* HTMLCollection::item(size_t index) const
  81. {
  82. // 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.
  83. update_cache_if_needed();
  84. if (index >= m_cached_elements.size())
  85. return nullptr;
  86. return m_cached_elements[index];
  87. }
  88. // https://dom.spec.whatwg.org/#dom-htmlcollection-nameditem-key
  89. Element* HTMLCollection::named_item(FlyString const& name) const
  90. {
  91. // 1. If key is the empty string, return null.
  92. if (name.is_empty())
  93. return nullptr;
  94. update_cache_if_needed();
  95. auto const& elements = m_cached_elements;
  96. // 2. Return the first element in the collection for which at least one of the following is true:
  97. // - it has an ID which is key;
  98. if (auto it = elements.find_if([&](auto& entry) { return entry->id().has_value() && entry->id().value() == name; }); it != elements.end())
  99. return *it;
  100. // - it is in the HTML namespace and has a name attribute whose value is key;
  101. if (auto it = elements.find_if([&](auto& entry) { return entry->namespace_uri() == Namespace::HTML && entry->name() == name; }); it != elements.end())
  102. return *it;
  103. // or null if there is no such element.
  104. return nullptr;
  105. }
  106. // https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
  107. Vector<FlyString> HTMLCollection::supported_property_names() const
  108. {
  109. // 1. Let result be an empty list.
  110. Vector<FlyString> result;
  111. // 2. For each element represented by the collection, in tree order:
  112. update_cache_if_needed();
  113. for (auto const& element : m_cached_elements) {
  114. // 1. If element has an ID which is not in result, append element’s ID to result.
  115. if (auto const& id = element->id(); id.has_value()) {
  116. if (!result.contains_slow(id.value()))
  117. result.append(id.value());
  118. }
  119. // 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.
  120. if (element->namespace_uri() == Namespace::HTML && element->name().has_value()) {
  121. auto name = element->name().value();
  122. if (!name.is_empty() && !result.contains_slow(name))
  123. result.append(move(name));
  124. }
  125. }
  126. // 3. Return result.
  127. return result;
  128. }
  129. // https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-indices%E2%91%A1
  130. bool HTMLCollection::is_supported_property_index(u32 index) const
  131. {
  132. // 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.
  133. // If there are no such elements, then there are no supported property indices.
  134. return index < length();
  135. }
  136. WebIDL::ExceptionOr<JS::Value> HTMLCollection::item_value(size_t index) const
  137. {
  138. auto* element = item(index);
  139. if (!element)
  140. return JS::js_undefined();
  141. return element;
  142. }
  143. WebIDL::ExceptionOr<JS::Value> HTMLCollection::named_item_value(FlyString const& name) const
  144. {
  145. auto* element = named_item(name);
  146. if (!element)
  147. return JS::js_undefined();
  148. return element;
  149. }
  150. }