HTMLAllCollection.cpp 9.3 KB

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