HTMLAllCollection.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /*
  2. * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibJS/Runtime/PropertyKey.h>
  7. #include <LibWeb/Bindings/Intrinsics.h>
  8. #include <LibWeb/DOM/Document.h>
  9. #include <LibWeb/DOM/Element.h>
  10. #include <LibWeb/DOM/ParentNode.h>
  11. #include <LibWeb/Forward.h>
  12. #include <LibWeb/HTML/HTMLAllCollection.h>
  13. #include <LibWeb/HTML/HTMLButtonElement.h>
  14. #include <LibWeb/HTML/HTMLEmbedElement.h>
  15. #include <LibWeb/HTML/HTMLFormElement.h>
  16. #include <LibWeb/HTML/HTMLFrameElement.h>
  17. #include <LibWeb/HTML/HTMLFrameSetElement.h>
  18. #include <LibWeb/HTML/HTMLIFrameElement.h>
  19. #include <LibWeb/HTML/HTMLImageElement.h>
  20. #include <LibWeb/HTML/HTMLInputElement.h>
  21. #include <LibWeb/HTML/HTMLLinkElement.h>
  22. #include <LibWeb/HTML/HTMLMapElement.h>
  23. #include <LibWeb/HTML/HTMLMetaElement.h>
  24. #include <LibWeb/HTML/HTMLObjectElement.h>
  25. #include <LibWeb/HTML/HTMLSelectElement.h>
  26. #include <LibWeb/HTML/HTMLTextAreaElement.h>
  27. #include <LibWeb/Namespace.h>
  28. namespace Web::HTML {
  29. JS_DEFINE_ALLOCATOR(HTMLAllCollection);
  30. JS::NonnullGCPtr<HTMLAllCollection> HTMLAllCollection::create(DOM::ParentNode& root, Scope scope, Function<bool(DOM::Element const&)> filter)
  31. {
  32. return root.heap().allocate<HTMLAllCollection>(root.realm(), root, scope, move(filter));
  33. }
  34. HTMLAllCollection::HTMLAllCollection(DOM::ParentNode& root, Scope scope, Function<bool(DOM::Element const&)> filter)
  35. : PlatformObject(root.realm())
  36. , m_root(root)
  37. , m_filter(move(filter))
  38. , m_scope(scope)
  39. {
  40. m_legacy_platform_object_flags = LegacyPlatformObjectFlags {
  41. .supports_indexed_properties = true,
  42. .supports_named_properties = true,
  43. .has_legacy_unenumerable_named_properties_interface_extended_attribute = true,
  44. };
  45. }
  46. HTMLAllCollection::~HTMLAllCollection() = default;
  47. void HTMLAllCollection::initialize(JS::Realm& realm)
  48. {
  49. Base::initialize(realm);
  50. WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLAllCollection);
  51. }
  52. void HTMLAllCollection::visit_edges(Cell::Visitor& visitor)
  53. {
  54. Base::visit_edges(visitor);
  55. visitor.visit(m_root);
  56. }
  57. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#all-named-elements
  58. static bool is_all_named_element(DOM::Element const& element)
  59. {
  60. // The following elements are "all"-named elements: a, button, embed, form, frame, frameset, iframe, img, input, map, meta, object, select, and textarea
  61. return is<HTML::HTMLLinkElement>(element)
  62. || is<HTML::HTMLButtonElement>(element)
  63. || is<HTML::HTMLEmbedElement>(element)
  64. || is<HTML::HTMLFormElement>(element)
  65. || is<HTML::HTMLFrameElement>(element)
  66. || is<HTML::HTMLFrameSetElement>(element)
  67. || is<HTML::HTMLIFrameElement>(element)
  68. || is<HTML::HTMLImageElement>(element)
  69. || is<HTML::HTMLInputElement>(element)
  70. || is<HTML::HTMLMapElement>(element)
  71. || is<HTML::HTMLMetaElement>(element)
  72. || is<HTML::HTMLObjectElement>(element)
  73. || is<HTML::HTMLSelectElement>(element)
  74. || is<HTML::HTMLTextAreaElement>(element);
  75. }
  76. JS::MarkedVector<JS::NonnullGCPtr<DOM::Element>> HTMLAllCollection::collect_matching_elements() const
  77. {
  78. JS::MarkedVector<JS::NonnullGCPtr<DOM::Element>> elements(m_root->heap());
  79. if (m_scope == Scope::Descendants) {
  80. m_root->for_each_in_subtree_of_type<DOM::Element>([&](auto& element) {
  81. if (m_filter(element))
  82. elements.append(element);
  83. return IterationDecision::Continue;
  84. });
  85. } else {
  86. m_root->for_each_child_of_type<DOM::Element>([&](auto& element) {
  87. if (m_filter(element))
  88. elements.append(element);
  89. return IterationDecision::Continue;
  90. });
  91. }
  92. return elements;
  93. }
  94. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmlallcollection-length
  95. size_t HTMLAllCollection::length() const
  96. {
  97. // The length getter steps are to return the number of nodes represented by the collection.
  98. return collect_matching_elements().size();
  99. }
  100. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmlallcollection-item
  101. Variant<JS::NonnullGCPtr<DOM::HTMLCollection>, JS::NonnullGCPtr<DOM::Element>, Empty> HTMLAllCollection::item(Optional<FlyString> const& name_or_index) const
  102. {
  103. // 1. If nameOrIndex was not provided, return null.
  104. if (!name_or_index.has_value())
  105. return Empty {};
  106. // 2. Return the result of getting the "all"-indexed or named element(s) from this, given nameOrIndex.
  107. return get_the_all_indexed_or_named_elements(name_or_index.value());
  108. }
  109. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmlallcollection-nameditem
  110. Variant<JS::NonnullGCPtr<DOM::HTMLCollection>, JS::NonnullGCPtr<DOM::Element>, Empty> HTMLAllCollection::named_item(FlyString const& name) const
  111. {
  112. // The namedItem(name) method steps are to return the result of getting the "all"-named element(s) from this given name.
  113. return get_the_all_named_elements(name);
  114. }
  115. // https://dom.spec.whatwg.org/#ref-for-dfn-supported-property-names
  116. Vector<FlyString> HTMLAllCollection::supported_property_names() const
  117. {
  118. // The supported property names consist of the non-empty values of all the id attributes of all the
  119. // elements represented by the collection, and the non-empty values of all the name attributes of
  120. // all the "all"-named elements represented by the collection, in tree order, ignoring later duplicates,
  121. // with the id of an element preceding its name if it contributes both, they differ from each other, and
  122. // neither is the duplicate of an earlier entry.
  123. Vector<FlyString> result;
  124. auto elements = collect_matching_elements();
  125. for (auto const& element : elements) {
  126. if (auto const& id = element->id(); id.has_value() && !id->is_empty()) {
  127. if (!result.contains_slow(id.value()))
  128. result.append(id.value());
  129. }
  130. if (is_all_named_element(*element) && element->name().has_value() && !element->name()->is_empty()) {
  131. auto name = element->name().value();
  132. if (!name.is_empty() && !result.contains_slow(name))
  133. result.append(move(name));
  134. }
  135. }
  136. // 3. Return result.
  137. return result;
  138. }
  139. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#concept-get-all-named
  140. Variant<JS::NonnullGCPtr<DOM::HTMLCollection>, JS::NonnullGCPtr<DOM::Element>, Empty> HTMLAllCollection::get_the_all_named_elements(FlyString const& name) const
  141. {
  142. // 1. If name is the empty string, return null.
  143. if (name.is_empty())
  144. return Empty {};
  145. // 2. Let subCollection be an HTMLCollection object rooted at the same Document as collection, whose filter matches only elements that are either:
  146. auto sub_collection = DOM::HTMLCollection::create(m_root, DOM::HTMLCollection::Scope::Descendants, [name](DOM::Element const& element) {
  147. // * "all"-named elements with a name attribute equal to name, or,
  148. if (is_all_named_element(element) && element.name() == name)
  149. return true;
  150. // * elements with an ID equal to name.
  151. return element.id() == name;
  152. });
  153. // 3. If there is exactly one element in subCollection, then return that element.
  154. auto matching_elements = sub_collection->collect_matching_elements();
  155. if (matching_elements.size() == 1)
  156. return matching_elements.first();
  157. // 4. Otherwise, if subCollection is empty, return null.
  158. if (matching_elements.is_empty())
  159. return Empty {};
  160. // 5. Otherwise, return subCollection.
  161. return sub_collection;
  162. }
  163. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#concept-get-all-indexed
  164. JS::GCPtr<DOM::Element> HTMLAllCollection::get_the_all_indexed_element(u32 index) const
  165. {
  166. // To get the "all"-indexed element from an HTMLAllCollection collection given an index index, return the indexth
  167. // element in collection, or null if there is no such indexth element.
  168. auto elements = collect_matching_elements();
  169. if (index >= elements.size())
  170. return nullptr;
  171. return elements[index];
  172. }
  173. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#concept-get-all-indexed-or-named
  174. Variant<JS::NonnullGCPtr<DOM::HTMLCollection>, JS::NonnullGCPtr<DOM::Element>, Empty> HTMLAllCollection::get_the_all_indexed_or_named_elements(JS::PropertyKey const& name_or_index) const
  175. {
  176. // 1. If nameOrIndex, converted to a JavaScript String value, is an array index property name, return the result of getting the "all"-indexed element from
  177. // collection given the number represented by nameOrIndex.
  178. if (name_or_index.is_number()) {
  179. auto maybe_element = get_the_all_indexed_element(name_or_index.as_number());
  180. if (!maybe_element)
  181. return Empty {};
  182. return JS::NonnullGCPtr<DOM::Element> { *maybe_element };
  183. }
  184. // 2. Return the result of getting the "all"-named element(s) from collection given nameOrIndex.
  185. return get_the_all_named_elements(MUST(FlyString::from_deprecated_fly_string(name_or_index.as_string())));
  186. }
  187. bool HTMLAllCollection::is_supported_property_index(u32 index) const
  188. {
  189. return index < collect_matching_elements().size();
  190. }
  191. WebIDL::ExceptionOr<JS::Value> HTMLAllCollection::item_value(size_t index) const
  192. {
  193. return get_the_all_indexed_element(index);
  194. }
  195. WebIDL::ExceptionOr<JS::Value> HTMLAllCollection::named_item_value(FlyString const& name) const
  196. {
  197. return named_item(name).visit(
  198. [](Empty) -> JS::Value { return JS::js_undefined(); },
  199. [](auto const& value) -> JS::Value { return value; });
  200. }
  201. }