HTMLOptionsCollection.cpp 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. * Copyright (c) 2022, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/Bindings/HTMLOptionsCollectionPrototype.h>
  7. #include <LibWeb/Bindings/Intrinsics.h>
  8. #include <LibWeb/DOM/ElementFactory.h>
  9. #include <LibWeb/HTML/HTMLOptGroupElement.h>
  10. #include <LibWeb/HTML/HTMLOptionElement.h>
  11. #include <LibWeb/HTML/HTMLOptionsCollection.h>
  12. #include <LibWeb/HTML/HTMLSelectElement.h>
  13. #include <LibWeb/Namespace.h>
  14. #include <LibWeb/WebIDL/DOMException.h>
  15. namespace Web::HTML {
  16. GC_DEFINE_ALLOCATOR(HTMLOptionsCollection);
  17. GC::Ref<HTMLOptionsCollection> HTMLOptionsCollection::create(DOM::ParentNode& root, Function<bool(DOM::Element const&)> filter)
  18. {
  19. return root.realm().create<HTMLOptionsCollection>(root, move(filter));
  20. }
  21. HTMLOptionsCollection::HTMLOptionsCollection(DOM::ParentNode& root, Function<bool(DOM::Element const&)> filter)
  22. : DOM::HTMLCollection(root, Scope::Descendants, move(filter))
  23. {
  24. m_legacy_platform_object_flags->has_indexed_property_setter = true;
  25. m_legacy_platform_object_flags->indexed_property_setter_has_identifier = true;
  26. }
  27. HTMLOptionsCollection::~HTMLOptionsCollection() = default;
  28. void HTMLOptionsCollection::initialize(JS::Realm& realm)
  29. {
  30. Base::initialize(realm);
  31. WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLOptionsCollection);
  32. }
  33. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmloptionscollection-length
  34. WebIDL::ExceptionOr<void> HTMLOptionsCollection::set_length(WebIDL::UnsignedLong value)
  35. {
  36. // 1. Let current be the number of nodes represented by the collection.
  37. auto current = static_cast<WebIDL::UnsignedLong>(length());
  38. // 2. If the given value is greater than current, then:
  39. if (value > current) {
  40. // 2.1. If the given value is greater than 100,000, then return.
  41. if (value > 100'000)
  42. return {};
  43. // 2.2. Let n be value − current.
  44. auto n = value - current;
  45. // 2.3. Append n new option elements with no attributes and no child nodes to the select element on which this is rooted.
  46. // Mutation events must be fired as if a DocumentFragment containing the new option elements had been inserted.
  47. auto root_element = root();
  48. for (WebIDL::UnsignedLong i = 0; i < n; i++)
  49. TRY(root_element->append_child(TRY(DOM::create_element(root_element->document(), HTML::TagNames::option, Namespace::HTML))));
  50. }
  51. // 3. If the given value is less than current, then:
  52. if (value < current) {
  53. // 3.1. Let n be current − value.
  54. auto n = current - value;
  55. // 3.2. Remove the last n nodes in the collection from their parent nodes.
  56. for (auto i = 0u; i < n; i++)
  57. item(length() - 1)->remove();
  58. }
  59. return {};
  60. }
  61. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmloptionscollection-setter
  62. WebIDL::ExceptionOr<void> HTMLOptionsCollection::set_value_of_indexed_property(u32 index, JS::Value unconverted_option)
  63. {
  64. // The spec doesn't seem to require this, but it's consistent with length handling and other browsers
  65. if (index >= 100'000) {
  66. return {};
  67. }
  68. // 1. If value is null, invoke the steps for the remove method with index as the argument, and return.
  69. if (unconverted_option.is_null()) {
  70. remove(static_cast<WebIDL::Long>(index));
  71. return {};
  72. }
  73. if (!unconverted_option.is_object() || !is<HTMLOptionElement>(unconverted_option.as_object())) {
  74. return WebIDL::TypeMismatchError::create(realm(), "The value provided is not an HTMLOptionElement"_string);
  75. }
  76. auto& option = static_cast<HTMLOptionElement&>(unconverted_option.as_object());
  77. // 2. Let length be the number of nodes represented by the collection.
  78. auto length = this->length();
  79. auto root_element = root();
  80. if (index >= length) {
  81. // 3. Let n be index minus length.
  82. auto n = index - length;
  83. // 4. If n is greater than zero, then append a DocumentFragment consisting of n-1 new option elements with no attributes and no child nodes to the select element on which the HTMLOptionsCollection is rooted.
  84. if (n > 0) {
  85. for (WebIDL::UnsignedLong i = 0; i < n - 1; i++) {
  86. TRY(root_element->append_child(TRY(DOM::create_element(root_element->document(), HTML::TagNames::option, Namespace::HTML))));
  87. }
  88. }
  89. // 5. If n is greater than or equal to zero, append value to the select element.
  90. TRY(root_element->append_child(option));
  91. } else {
  92. // 5 (cont). Otherwise, replace the indexth element in the collection by value.
  93. TRY(root_element->replace_child(option, *item(index)));
  94. }
  95. return {};
  96. }
  97. // https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#dom-htmloptionscollection-add
  98. WebIDL::ExceptionOr<void> HTMLOptionsCollection::add(HTMLOptionOrOptGroupElement element, Optional<HTMLElementOrElementIndex> before)
  99. {
  100. auto resolved_element = element.visit(
  101. [](auto& e) -> GC::Root<HTMLElement> {
  102. return GC::make_root(static_cast<HTML::HTMLElement&>(*e));
  103. });
  104. GC::Ptr<DOM::Node> before_element;
  105. if (before.has_value() && before->has<GC::Root<HTMLElement>>())
  106. before_element = before->get<GC::Root<HTMLElement>>().ptr();
  107. // 1. If element is an ancestor of the select element on which the HTMLOptionsCollection is rooted, then throw a "HierarchyRequestError" DOMException.
  108. if (resolved_element->is_ancestor_of(root()))
  109. return WebIDL::HierarchyRequestError::create(realm(), "The provided element is an ancestor of the root select element."_string);
  110. // 2. If before is an element, but that element isn't a descendant of the select element on which the HTMLOptionsCollection is rooted, then throw a "NotFoundError" DOMException.
  111. if (before_element && !before_element->is_descendant_of(root()))
  112. return WebIDL::NotFoundError::create(realm(), "The 'before' element is not a descendant of the root select element."_string);
  113. // 3. If element and before are the same element, then return.
  114. if (before_element && (resolved_element.ptr() == before_element.ptr()))
  115. return {};
  116. // 4. If before is a node, then let reference be that node. Otherwise, if before is an integer, and there is a beforeth node in the collection, let reference be that node. Otherwise, let reference be null.
  117. GC::Ptr<DOM::Node> reference;
  118. if (before_element)
  119. reference = move(before_element);
  120. else if (before.has_value() && before->has<i32>())
  121. reference = item(before->get<i32>());
  122. // 5. If reference is not null, let parent be the parent node of reference. Otherwise, let parent be the select element on which the HTMLOptionsCollection is rooted.
  123. DOM::Node* parent = reference ? reference->parent() : root().ptr();
  124. // 6. Pre-insert element into parent node before reference.
  125. (void)TRY(parent->pre_insert(*resolved_element, reference));
  126. return {};
  127. }
  128. // https://html.spec.whatwg.org/#dom-htmloptionscollection-remove
  129. void HTMLOptionsCollection::remove(WebIDL::Long index)
  130. {
  131. // 1. If the number of nodes represented by the collection is zero, return.
  132. if (length() == 0)
  133. return;
  134. // 2. If index is not a number greater than or equal to 0 and less than the number of nodes represented by the collection, return.
  135. if (index < 0 || static_cast<WebIDL::UnsignedLong>(index) >= length())
  136. return;
  137. // 3. Let element be the indexth element in the collection.
  138. auto* element = this->item(index);
  139. // 4. Remove element from its parent node.
  140. element->remove();
  141. }
  142. // https://html.spec.whatwg.org/#dom-htmloptionscollection-selectedindex
  143. WebIDL::Long HTMLOptionsCollection::selected_index() const
  144. {
  145. // The selectedIndex IDL attribute must act like the identically named attribute
  146. // on the select element on which the HTMLOptionsCollection is rooted.
  147. return verify_cast<HTMLSelectElement>(*root()).selected_index();
  148. }
  149. void HTMLOptionsCollection::set_selected_index(WebIDL::Long index)
  150. {
  151. verify_cast<HTMLSelectElement>(*root()).set_selected_index(index);
  152. }
  153. }