FormAssociatedElement.cpp 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. /*
  2. * Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2024, Jelle Raaijmakers <jelle@gmta.nl>
  4. * Copyright (c) 2024, Tim Ledbetter <tim.ledbetter@ladybird.org>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <LibUnicode/CharacterTypes.h>
  9. #include <LibUnicode/Segmenter.h>
  10. #include <LibWeb/DOM/Document.h>
  11. #include <LibWeb/DOM/Event.h>
  12. #include <LibWeb/DOM/Position.h>
  13. #include <LibWeb/HTML/FormAssociatedElement.h>
  14. #include <LibWeb/HTML/HTMLButtonElement.h>
  15. #include <LibWeb/HTML/HTMLFieldSetElement.h>
  16. #include <LibWeb/HTML/HTMLFormElement.h>
  17. #include <LibWeb/HTML/HTMLInputElement.h>
  18. #include <LibWeb/HTML/HTMLLegendElement.h>
  19. #include <LibWeb/HTML/HTMLSelectElement.h>
  20. #include <LibWeb/HTML/HTMLTextAreaElement.h>
  21. #include <LibWeb/HTML/Parser/HTMLParser.h>
  22. #include <LibWeb/Painting/Paintable.h>
  23. namespace Web::HTML {
  24. static SelectionDirection string_to_selection_direction(Optional<String> value)
  25. {
  26. if (!value.has_value())
  27. return SelectionDirection::None;
  28. if (value.value() == "forward"sv)
  29. return SelectionDirection::Forward;
  30. if (value.value() == "backward"sv)
  31. return SelectionDirection::Backward;
  32. return SelectionDirection::None;
  33. }
  34. void FormAssociatedElement::set_form(HTMLFormElement* form)
  35. {
  36. if (m_form)
  37. m_form->remove_associated_element({}, form_associated_element_to_html_element());
  38. m_form = form;
  39. if (m_form)
  40. m_form->add_associated_element({}, form_associated_element_to_html_element());
  41. }
  42. bool FormAssociatedElement::enabled() const
  43. {
  44. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled
  45. auto const& html_element = form_associated_element_to_html_element();
  46. // A form control is disabled if any of the following conditions are met:
  47. // 1. The element is a button, input, select, textarea, or form-associated custom element, and the disabled attribute is specified on this element (regardless of its value).
  48. // FIXME: This doesn't check for form-associated custom elements.
  49. if ((is<HTMLButtonElement>(html_element) || is<HTMLInputElement>(html_element) || is<HTMLSelectElement>(html_element) || is<HTMLTextAreaElement>(html_element)) && html_element.has_attribute(HTML::AttributeNames::disabled))
  50. return false;
  51. // 2. The element is a descendant of a fieldset element whose disabled attribute is specified, and is not a descendant of that fieldset element's first legend element child, if any.
  52. for (auto* fieldset_ancestor = html_element.first_ancestor_of_type<HTMLFieldSetElement>(); fieldset_ancestor; fieldset_ancestor = fieldset_ancestor->first_ancestor_of_type<HTMLFieldSetElement>()) {
  53. if (fieldset_ancestor->has_attribute(HTML::AttributeNames::disabled)) {
  54. auto* first_legend_element_child = fieldset_ancestor->first_child_of_type<HTMLLegendElement>();
  55. if (!first_legend_element_child || !html_element.is_descendant_of(*first_legend_element_child))
  56. return false;
  57. }
  58. }
  59. return true;
  60. }
  61. void FormAssociatedElement::set_parser_inserted(Badge<HTMLParser>)
  62. {
  63. m_parser_inserted = true;
  64. }
  65. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:nodes-are-inserted
  66. void FormAssociatedElement::form_node_was_inserted()
  67. {
  68. // 1. If the form-associated element's parser inserted flag is set, then return.
  69. if (m_parser_inserted)
  70. return;
  71. // 2. Reset the form owner of the form-associated element.
  72. reset_form_owner();
  73. }
  74. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:nodes-are-removed
  75. void FormAssociatedElement::form_node_was_removed()
  76. {
  77. // 1. If the form-associated element has a form owner and the form-associated element and its form owner are no longer in the same tree, then reset the form owner of the form-associated element.
  78. if (m_form && &form_associated_element_to_html_element().root() != &m_form->root())
  79. reset_form_owner();
  80. }
  81. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-3
  82. void FormAssociatedElement::form_node_attribute_changed(FlyString const& name, Optional<String> const& value)
  83. {
  84. // When a listed form-associated element's form attribute is set, changed, or removed, then the user agent must
  85. // reset the form owner of that element.
  86. if (name == HTML::AttributeNames::form) {
  87. auto& html_element = form_associated_element_to_html_element();
  88. if (value.has_value())
  89. html_element.document().add_form_associated_element_with_form_attribute(*this);
  90. else
  91. html_element.document().remove_form_associated_element_with_form_attribute(*this);
  92. reset_form_owner();
  93. }
  94. }
  95. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-4
  96. void FormAssociatedElement::element_id_changed(Badge<DOM::Document>)
  97. {
  98. // When a listed form-associated element has a form attribute and the ID of any of the elements in the tree changes,
  99. // then the user agent must reset the form owner of that form-associated element.
  100. VERIFY(form_associated_element_to_html_element().has_attribute(HTML::AttributeNames::form));
  101. reset_form_owner();
  102. }
  103. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-5
  104. void FormAssociatedElement::element_with_id_was_added_or_removed(Badge<DOM::Document>)
  105. {
  106. // When a listed form-associated element has a form attribute and an element with an ID is inserted into or removed
  107. // from the Document, then the user agent must reset the form owner of that form-associated element.
  108. VERIFY(form_associated_element_to_html_element().has_attribute(HTML::AttributeNames::form));
  109. reset_form_owner();
  110. }
  111. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#reset-the-form-owner
  112. void FormAssociatedElement::reset_form_owner()
  113. {
  114. auto& html_element = form_associated_element_to_html_element();
  115. // 1. Unset element's parser inserted flag.
  116. m_parser_inserted = false;
  117. // 2. If all of the following conditions are true
  118. // - element's form owner is not null
  119. // - element is not listed or its form content attribute is not present
  120. // - element's form owner is its nearest form element ancestor after the change to the ancestor chain
  121. // then do nothing, and return.
  122. if (m_form
  123. && (!is_listed() || !html_element.has_attribute(HTML::AttributeNames::form))
  124. && html_element.first_ancestor_of_type<HTMLFormElement>() == m_form.ptr()) {
  125. return;
  126. }
  127. // 3. Set element's form owner to null.
  128. set_form(nullptr);
  129. // 4. If element is listed, has a form content attribute, and is connected, then:
  130. if (is_listed() && html_element.has_attribute(HTML::AttributeNames::form) && html_element.is_connected()) {
  131. // 1. If the first element in element's tree, in tree order, to have an ID that is identical to element's form content attribute's value, is a form element, then associate the element with that form element.
  132. auto form_value = html_element.attribute(HTML::AttributeNames::form);
  133. html_element.root().for_each_in_inclusive_subtree_of_type<HTMLFormElement>([this, &form_value](HTMLFormElement& form_element) {
  134. if (form_element.id() == form_value) {
  135. set_form(&form_element);
  136. return TraversalDecision::Break;
  137. }
  138. return TraversalDecision::Continue;
  139. });
  140. }
  141. // 5. Otherwise, if element has an ancestor form element, then associate element with the nearest such ancestor form element.
  142. else {
  143. auto* form_ancestor = html_element.first_ancestor_of_type<HTMLFormElement>();
  144. if (form_ancestor)
  145. set_form(form_ancestor);
  146. }
  147. }
  148. // https://w3c.github.io/webdriver/#dfn-clear-algorithm
  149. void FormAssociatedElement::clear_algorithm()
  150. {
  151. // When the clear algorithm is invoked for an element that does not define its own clear algorithm, its reset
  152. // algorithm must be invoked instead.
  153. reset_algorithm();
  154. }
  155. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-fs-formaction
  156. String FormAssociatedElement::form_action() const
  157. {
  158. // The formAction IDL attribute must reflect the formaction content attribute, except that on getting, when the content attribute is missing or its value is the empty string,
  159. // the element's node document's URL must be returned instead.
  160. auto& html_element = form_associated_element_to_html_element();
  161. auto form_action_attribute = html_element.attribute(HTML::AttributeNames::formaction);
  162. if (!form_action_attribute.has_value() || form_action_attribute.value().is_empty()) {
  163. return html_element.document().url_string();
  164. }
  165. auto document_base_url = html_element.document().base_url();
  166. return MUST(document_base_url.complete_url(form_action_attribute.value()).to_string());
  167. }
  168. WebIDL::ExceptionOr<void> FormAssociatedElement::set_form_action(String const& value)
  169. {
  170. auto& html_element = form_associated_element_to_html_element();
  171. return html_element.set_attribute(HTML::AttributeNames::formaction, value);
  172. }
  173. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
  174. void FormAssociatedTextControlElement::relevant_value_was_changed()
  175. {
  176. auto the_relevant_value = relevant_value();
  177. auto relevant_value_length = the_relevant_value.code_points().length();
  178. // 1. If the element has a selection:
  179. if (m_selection_start < m_selection_end) {
  180. // 1. If the start of the selection is now past the end of the relevant value, set it to
  181. // the end of the relevant value.
  182. if (m_selection_start > relevant_value_length)
  183. m_selection_start = relevant_value_length;
  184. // 2. If the end of the selection is now past the end of the relevant value, set it to the
  185. // end of the relevant value.
  186. if (m_selection_end > relevant_value_length)
  187. m_selection_end = relevant_value_length;
  188. // 3. If the user agent does not support empty selection, and both the start and end of the
  189. // selection are now pointing to the end of the relevant value, then instead set the
  190. // element's text entry cursor position to the end of the relevant value, removing any
  191. // selection.
  192. // NOTE: We support empty selections.
  193. return;
  194. }
  195. // 2. Otherwise, the element must have a text entry cursor position position. If it is now past
  196. // the end of the relevant value, set it to the end of the relevant value.
  197. if (m_selection_start > relevant_value_length)
  198. m_selection_start = relevant_value_length;
  199. }
  200. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-select
  201. WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::select()
  202. {
  203. // 1. If this element is an input element, and either select() does not apply to this element
  204. // or the corresponding control has no selectable text, return.
  205. auto& html_element = form_associated_element_to_html_element();
  206. if (is<HTMLInputElement>(html_element)) {
  207. auto& input_element = static_cast<HTMLInputElement&>(html_element);
  208. if (!input_element.select_applies() || !input_element.has_selectable_text())
  209. return {};
  210. }
  211. // 2. Set the selection range with 0 and infinity.
  212. set_the_selection_range(0, NumericLimits<WebIDL::UnsignedLong>::max());
  213. return {};
  214. }
  215. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionstart
  216. Optional<WebIDL::UnsignedLong> FormAssociatedTextControlElement::selection_start() const
  217. {
  218. // 1. If this element is an input element, and selectionStart does not apply to this element, return null.
  219. auto const& html_element = form_associated_element_to_html_element();
  220. if (is<HTMLInputElement>(html_element)) {
  221. auto const& input_element = static_cast<HTMLInputElement const&>(html_element);
  222. if (!input_element.selection_or_range_applies())
  223. return {};
  224. }
  225. // 2. If there is no selection, return the code unit offset within the relevant value to the character that
  226. // immediately follows the text entry cursor.
  227. if (m_selection_start == m_selection_end) {
  228. return m_selection_start;
  229. }
  230. // 3. Return the code unit offset within the relevant value to the character that immediately follows the start of
  231. // the selection.
  232. return m_selection_start < m_selection_end ? m_selection_start : m_selection_end;
  233. }
  234. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionstart-2
  235. WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_selection_start(Optional<WebIDL::UnsignedLong> const& value)
  236. {
  237. // 1. If this element is an input element, and selectionStart does not apply to this element,
  238. // throw an "InvalidStateError" DOMException.
  239. auto& html_element = form_associated_element_to_html_element();
  240. if (is<HTMLInputElement>(html_element)) {
  241. auto& input_element = static_cast<HTMLInputElement&>(html_element);
  242. if (!input_element.selection_or_range_applies())
  243. return WebIDL::InvalidStateError::create(html_element.realm(), "setSelectionStart does not apply to this input type"_string);
  244. }
  245. // 2. Let end be the value of this element's selectionEnd attribute.
  246. auto end = m_selection_end;
  247. // 3. If end is less than the given value, set end to the given value.
  248. if (value.has_value() && end < value.value())
  249. end = value.value();
  250. // 4. Set the selection range with the given value, end, and the value of this element's
  251. // selectionDirection attribute.
  252. set_the_selection_range(value, end, selection_direction_state());
  253. return {};
  254. }
  255. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectionend
  256. Optional<WebIDL::UnsignedLong> FormAssociatedTextControlElement::selection_end() const
  257. {
  258. // 1. If this element is an input element, and selectionEnd does not apply to this element, return
  259. // null.
  260. auto const& html_element = form_associated_element_to_html_element();
  261. if (is<HTMLInputElement>(html_element)) {
  262. auto const& input_element = static_cast<HTMLInputElement const&>(html_element);
  263. if (!input_element.selection_or_range_applies())
  264. return {};
  265. }
  266. // 2. If there is no selection, return the code unit offset within the relevant value to the
  267. // character that immediately follows the text entry cursor.
  268. if (m_selection_start == m_selection_end) {
  269. return m_selection_start;
  270. }
  271. // 3. Return the code unit offset within the relevant value to the character that immediately
  272. // follows the end of the selection.
  273. return m_selection_start < m_selection_end ? m_selection_end : m_selection_start;
  274. }
  275. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:dom-textarea/input-selectionend-3
  276. WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_selection_end(Optional<WebIDL::UnsignedLong> const& value)
  277. {
  278. // 1. If this element is an input element, and selectionEnd does not apply to this element,
  279. // throw an "InvalidStateError" DOMException.
  280. auto& html_element = form_associated_element_to_html_element();
  281. if (is<HTMLInputElement>(html_element)) {
  282. auto& input_element = static_cast<HTMLInputElement&>(html_element);
  283. if (!input_element.selection_or_range_applies())
  284. return WebIDL::InvalidStateError::create(html_element.realm(), "setSelectionEnd does not apply to this input type"_string);
  285. }
  286. // 2. Set the selection range with the value of this element's selectionStart attribute, the
  287. // given value, and the value of this element's selectionDirection attribute.
  288. set_the_selection_range(m_selection_start, value, selection_direction_state());
  289. return {};
  290. }
  291. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#selection-direction
  292. Optional<String> FormAssociatedTextControlElement::selection_direction() const
  293. {
  294. // 1. If this element is an input element, and selectionDirection does not apply to this
  295. // element, return null.
  296. auto const& html_element = form_associated_element_to_html_element();
  297. if (is<HTMLInputElement>(html_element)) {
  298. auto const& input_element = static_cast<HTMLInputElement const&>(html_element);
  299. if (!input_element.selection_or_range_applies())
  300. return {};
  301. }
  302. // 2. Return this element's selection direction.
  303. switch (m_selection_direction) {
  304. case SelectionDirection::Forward:
  305. return "forward"_string;
  306. case SelectionDirection::Backward:
  307. return "backward"_string;
  308. case SelectionDirection::None:
  309. return "none"_string;
  310. default:
  311. VERIFY_NOT_REACHED();
  312. }
  313. }
  314. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-direction
  315. void FormAssociatedTextControlElement::set_selection_direction(Optional<String> direction)
  316. {
  317. // To set the selection direction of an element to a given direction, update the element's
  318. // selection direction to the given direction, unless the direction is "none" and the
  319. // platform does not support that direction; in that case, update the element's selection
  320. // direction to "forward".
  321. m_selection_direction = string_to_selection_direction(direction);
  322. }
  323. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-selectiondirection
  324. WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_selection_direction_binding(Optional<String> direction)
  325. {
  326. // 1. If this element is an input element, and selectionDirection does not apply to this element,
  327. // throw an "InvalidStateError" DOMException.
  328. auto const& html_element = form_associated_element_to_html_element();
  329. if (is<HTMLInputElement>(html_element)) {
  330. auto const& input_element = static_cast<HTMLInputElement const&>(html_element);
  331. if (!input_element.selection_direction_applies())
  332. return WebIDL::InvalidStateError::create(input_element.realm(), "selectionDirection does not apply to element"_string);
  333. }
  334. set_the_selection_range(m_selection_start, m_selection_end, string_to_selection_direction(direction));
  335. return {};
  336. }
  337. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext
  338. WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text(String const& replacement)
  339. {
  340. return set_range_text(replacement, m_selection_start, m_selection_end);
  341. }
  342. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setrangetext
  343. WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_range_text(String const& replacement, WebIDL::UnsignedLong start, WebIDL::UnsignedLong end, Bindings::SelectionMode selection_mode)
  344. {
  345. // 1. If this element is an input element, and setRangeText() does not apply to this element,
  346. // throw an "InvalidStateError" DOMException.
  347. auto& html_element = form_associated_element_to_html_element();
  348. if (is<HTMLInputElement>(html_element) && !static_cast<HTMLInputElement&>(html_element).selection_or_range_applies())
  349. return WebIDL::InvalidStateError::create(html_element.realm(), "setRangeText does not apply to this input type"_string);
  350. // 2. Set this element's dirty value flag to true.
  351. set_dirty_value_flag(true);
  352. // 3. If the method has only one argument, then let start and end have the values of the selectionStart attribute and the selectionEnd attribute respectively.
  353. // Otherwise, let start, end have the values of the second and third arguments respectively.
  354. // NOTE: This is handled by the caller.
  355. // 4. If start is greater than end, then throw an "IndexSizeError" DOMException.
  356. if (start > end)
  357. return WebIDL::IndexSizeError::create(html_element.realm(), "The start argument must be less than or equal to the end argument"_string);
  358. // 5. If start is greater than the length of the relevant value of the text control, then set it to the length of the relevant value of the text control.
  359. auto the_relevant_value = relevant_value();
  360. auto relevant_value_length = the_relevant_value.code_points().length();
  361. if (start > relevant_value_length)
  362. start = relevant_value_length;
  363. // 6. If end is greater than the length of the relevant value of the text control, then set it to the length of the relevant value of the text control.
  364. if (end > relevant_value_length)
  365. end = relevant_value_length;
  366. // 7. Let selection start be the current value of the selectionStart attribute.
  367. auto selection_start = m_selection_start;
  368. // 8. Let selection end be the current value of the selectionEnd attribute.
  369. auto selection_end = m_selection_end;
  370. // 9. If start is less than end, delete the sequence of code units within the element's relevant value starting with
  371. // the code unit at the startth position and ending with the code unit at the (end-1)th position.
  372. if (start < end) {
  373. StringBuilder builder;
  374. auto before_removal_point_view = the_relevant_value.code_points().unicode_substring_view(0, start);
  375. builder.append(before_removal_point_view.as_string());
  376. auto after_removal_point_view = the_relevant_value.code_points().unicode_substring_view(end);
  377. builder.append(after_removal_point_view.as_string());
  378. the_relevant_value = MUST(builder.to_string());
  379. }
  380. // 10. Insert the value of the first argument into the text of the relevant value of the text control, immediately before the startth code unit.
  381. StringBuilder builder;
  382. auto before_insertion_point_view = the_relevant_value.code_points().unicode_substring_view(0, start);
  383. builder.append(before_insertion_point_view.as_string());
  384. builder.append(replacement);
  385. auto after_insertion_point_view = the_relevant_value.code_points().unicode_substring_view(start);
  386. builder.append(after_insertion_point_view.as_string());
  387. the_relevant_value = MUST(builder.to_string());
  388. TRY(set_relevant_value(the_relevant_value));
  389. // 11. Let new length be the length of the value of the first argument.
  390. i64 new_length = replacement.code_points().length();
  391. // 12. Let new end be the sum of start and new length.
  392. auto new_end = start + new_length;
  393. // 13. Run the appropriate set of substeps from the following list:
  394. switch (selection_mode) {
  395. // If the fourth argument's value is "select"
  396. case Bindings::SelectionMode::Select:
  397. // Let selection start be start.
  398. selection_start = start;
  399. // Let selection end be new end.
  400. selection_end = new_end;
  401. break;
  402. // If the fourth argument's value is "start"
  403. case Bindings::SelectionMode::Start:
  404. // Let selection start and selection end be start.
  405. selection_start = start;
  406. selection_end = start;
  407. break;
  408. // If the fourth argument's value is "end"
  409. case Bindings::SelectionMode::End:
  410. selection_start = new_end;
  411. selection_end = new_end;
  412. break;
  413. // If the fourth argument's value is "preserve"
  414. case Bindings::SelectionMode::Preserve:
  415. // 1. Let old length be end minus start.
  416. auto old_length = end - start;
  417. // 2. Let delta be new length minus old length.
  418. auto delta = new_length - old_length;
  419. // 3. If selection start is greater than end, then increment it by delta.
  420. // (If delta is negative, i.e. the new text is shorter than the old text, then this will decrease the value of selection start.)
  421. // Otherwise: if selection start is greater than start, then set it to start.
  422. // (This snaps the start of the selection to the start of the new text if it was in the middle of the text that it replaced.)
  423. if (selection_start > end)
  424. selection_start += delta;
  425. else if (selection_start > start)
  426. selection_start = start;
  427. // 4. If selection end is greater than end, then increment it by delta in the same way.
  428. // Otherwise: if selection end is greater than start, then set it to new end.
  429. // (This snaps the end of the selection to the end of the new text if it was in the middle of the text that it replaced.)
  430. if (selection_end > end)
  431. selection_end += delta;
  432. else if (selection_end > start)
  433. selection_end = new_end;
  434. break;
  435. }
  436. // 14. Set the selection range with selection start and selection end.
  437. set_the_selection_range(selection_start, selection_end);
  438. return {};
  439. }
  440. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setselectionrange
  441. WebIDL::ExceptionOr<void> FormAssociatedTextControlElement::set_selection_range(Optional<WebIDL::UnsignedLong> start, Optional<WebIDL::UnsignedLong> end, Optional<String> direction)
  442. {
  443. // 1. If this element is an input element, and setSelectionRange() does not apply to this
  444. // element, throw an "InvalidStateError" DOMException.
  445. auto& html_element = form_associated_element_to_html_element();
  446. if (is<HTMLInputElement>(html_element) && !static_cast<HTMLInputElement&>(html_element).selection_or_range_applies())
  447. return WebIDL::InvalidStateError::create(html_element.realm(), "setSelectionRange does not apply to this input type"_string);
  448. // 2. Set the selection range with start, end, and direction.
  449. set_the_selection_range(start, end, string_to_selection_direction(direction));
  450. return {};
  451. }
  452. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-range
  453. void FormAssociatedTextControlElement::set_the_selection_range(Optional<WebIDL::UnsignedLong> start, Optional<WebIDL::UnsignedLong> end, SelectionDirection direction, SelectionSource source)
  454. {
  455. // 1. If start is null, let start be zero.
  456. start = start.value_or(0);
  457. // 2. If end is null, let end be zero.
  458. end = end.value_or(0);
  459. // 3. Set the selection of the text control to the sequence of code units within the relevant
  460. // value starting with the code unit at the startth position (in logical order) and ending
  461. // with the code unit at the (end-1)th position. Arguments greater than the length of the
  462. // relevant value of the text control (including the special value infinity) must be treated
  463. // as pointing at the end of the text control.
  464. auto the_relevant_value = relevant_value();
  465. auto relevant_value_length = the_relevant_value.code_points().length();
  466. auto new_selection_start = AK::min(start.value(), relevant_value_length);
  467. auto new_selection_end = AK::min(end.value(), relevant_value_length);
  468. // If end is less than or equal to start then the start of the selection and the end of the
  469. // selection must both be placed immediately before the character with offset end. In UAs
  470. // where there is no concept of an empty selection, this must set the cursor to be just
  471. // before the character with offset end.
  472. new_selection_start = AK::min(new_selection_start, new_selection_end);
  473. bool was_modified = m_selection_start != new_selection_start || m_selection_end != new_selection_end;
  474. m_selection_start = new_selection_start;
  475. m_selection_end = new_selection_end;
  476. // 4. If direction is not identical to either "backward" or "forward", or if the direction
  477. // argument was not given, set direction to "none".
  478. // NOTE: This is handled by the argument's default value and ::string_to_selection_direction().
  479. // 5. Set the selection direction of the text control to direction.
  480. was_modified |= m_selection_direction != direction;
  481. m_selection_direction = direction;
  482. // 6. If the previous steps caused the selection of the text control to be modified (in either
  483. // extent or direction), then queue an element task on the user interaction task source
  484. // given the element to fire an event named select at the element, with the bubbles attribute
  485. // initialized to true.
  486. if (was_modified) {
  487. auto& html_element = form_associated_element_to_html_element();
  488. // AD-HOC: We don't fire the event if the user moves the cursor without selecting any text.
  489. // This is not in the spec but matches how other browsers behave.
  490. if (source == SelectionSource::DOM || m_selection_start != m_selection_end) {
  491. html_element.queue_an_element_task(Task::Source::UserInteraction, [&html_element] {
  492. auto select_event = DOM::Event::create(html_element.realm(), EventNames::select, { .bubbles = true });
  493. static_cast<DOM::EventTarget*>(&html_element)->dispatch_event(select_event);
  494. });
  495. }
  496. // AD-HOC: Notify the element that the selection was changed, so it can perform
  497. // element-specific updates.
  498. selection_was_changed(m_selection_start, m_selection_end);
  499. }
  500. }
  501. void FormAssociatedTextControlElement::handle_insert(String const& data)
  502. {
  503. auto text_node = form_associated_element_to_text_node();
  504. if (!text_node || !text_node->is_editable())
  505. return;
  506. String data_for_insertion = data;
  507. if (auto max_length = text_node->max_length(); max_length.has_value()) {
  508. auto remaining_length = *max_length - text_node->data().code_points().length();
  509. if (remaining_length < data.code_points().length()) {
  510. data_for_insertion = MUST(data.substring_from_byte_offset(0, remaining_length));
  511. }
  512. }
  513. auto selection_start = this->selection_start();
  514. auto selection_end = this->selection_end();
  515. if (!selection_start.has_value() || !selection_end.has_value()) {
  516. return;
  517. }
  518. MUST(set_range_text(data_for_insertion, selection_start.value(), selection_end.value(), Bindings::SelectionMode::End));
  519. text_node->invalidate_style(DOM::StyleInvalidationReason::EditingInsertion);
  520. text_node->editable_text_node_owner()->did_edit_text_node();
  521. }
  522. void FormAssociatedTextControlElement::handle_delete(DeleteDirection direction)
  523. {
  524. auto text_node = form_associated_element_to_text_node();
  525. if (!text_node || !text_node->is_editable())
  526. return;
  527. auto selection_start = this->selection_start();
  528. auto selection_end = this->selection_end();
  529. if (!selection_start.has_value() || !selection_end.has_value()) {
  530. return;
  531. }
  532. if (selection_start == selection_end) {
  533. if (direction == DeleteDirection::Backward) {
  534. if (selection_start.value() > 0) {
  535. MUST(set_range_text(MUST(String::from_utf8(""sv)), selection_start.value() - 1, selection_end.value(), Bindings::SelectionMode::End));
  536. }
  537. } else {
  538. if (selection_start.value() < text_node->data().code_points().length()) {
  539. MUST(set_range_text(MUST(String::from_utf8(""sv)), selection_start.value(), selection_end.value() + 1, Bindings::SelectionMode::End));
  540. }
  541. }
  542. return;
  543. }
  544. MUST(set_range_text(MUST(String::from_utf8(""sv)), selection_start.value(), selection_end.value(), Bindings::SelectionMode::End));
  545. }
  546. void FormAssociatedTextControlElement::handle_return_key()
  547. {
  548. auto& html_element = form_associated_element_to_html_element();
  549. if (is<HTMLInputElement>(html_element)) {
  550. auto& input_element = static_cast<HTMLInputElement&>(html_element);
  551. if (auto* form = input_element.form()) {
  552. form->implicitly_submit_form().release_value_but_fixme_should_propagate_errors();
  553. return;
  554. }
  555. input_element.commit_pending_changes();
  556. }
  557. }
  558. void FormAssociatedTextControlElement::collapse_selection_to_offset(size_t position)
  559. {
  560. m_selection_start = position;
  561. m_selection_end = position;
  562. }
  563. void FormAssociatedTextControlElement::selection_was_changed()
  564. {
  565. auto text_node = form_associated_element_to_text_node();
  566. if (!text_node)
  567. return;
  568. auto* text_paintable = text_node->paintable();
  569. if (!text_paintable)
  570. return;
  571. if (m_selection_start == m_selection_end) {
  572. text_paintable->set_selected(false);
  573. text_paintable->set_selection_state(Painting::Paintable::SelectionState::None);
  574. text_node->document().reset_cursor_blink_cycle();
  575. } else {
  576. text_paintable->set_selected(true);
  577. text_paintable->set_selection_state(Painting::Paintable::SelectionState::StartAndEnd);
  578. }
  579. text_paintable->set_needs_display();
  580. }
  581. void FormAssociatedTextControlElement::select_all()
  582. {
  583. auto text_node = form_associated_element_to_text_node();
  584. if (!text_node)
  585. return;
  586. set_the_selection_range(0, text_node->length());
  587. selection_was_changed();
  588. }
  589. void FormAssociatedTextControlElement::set_selection_anchor(JS::NonnullGCPtr<DOM::Node> anchor_node, size_t anchor_offset)
  590. {
  591. auto text_node = form_associated_element_to_text_node();
  592. if (!text_node || anchor_node != text_node)
  593. return;
  594. collapse_selection_to_offset(anchor_offset);
  595. selection_was_changed();
  596. }
  597. void FormAssociatedTextControlElement::set_selection_focus(JS::NonnullGCPtr<DOM::Node> focus_node, size_t focus_offset)
  598. {
  599. auto text_node = form_associated_element_to_text_node();
  600. if (!text_node || focus_node != text_node)
  601. return;
  602. m_selection_end = focus_offset;
  603. selection_was_changed();
  604. }
  605. void FormAssociatedTextControlElement::move_cursor_to_start(CollapseSelection collapse)
  606. {
  607. auto text_node = form_associated_element_to_text_node();
  608. if (!text_node)
  609. return;
  610. if (collapse == CollapseSelection::Yes) {
  611. collapse_selection_to_offset(0);
  612. } else {
  613. m_selection_end = 0;
  614. }
  615. selection_was_changed();
  616. }
  617. void FormAssociatedTextControlElement::move_cursor_to_end(CollapseSelection collapse)
  618. {
  619. auto text_node = form_associated_element_to_text_node();
  620. if (!text_node)
  621. return;
  622. if (collapse == CollapseSelection::Yes) {
  623. collapse_selection_to_offset(text_node->length());
  624. } else {
  625. m_selection_end = text_node->length();
  626. }
  627. selection_was_changed();
  628. }
  629. void FormAssociatedTextControlElement::increment_cursor_position_offset(CollapseSelection collapse)
  630. {
  631. auto const text_node = form_associated_element_to_text_node();
  632. if (!text_node)
  633. return;
  634. if (auto offset = text_node->grapheme_segmenter().next_boundary(m_selection_end); offset.has_value()) {
  635. if (collapse == CollapseSelection::Yes) {
  636. collapse_selection_to_offset(*offset);
  637. } else {
  638. m_selection_end = *offset;
  639. }
  640. }
  641. selection_was_changed();
  642. }
  643. void FormAssociatedTextControlElement::decrement_cursor_position_offset(CollapseSelection collapse)
  644. {
  645. auto const text_node = form_associated_element_to_text_node();
  646. if (!text_node)
  647. return;
  648. if (auto offset = text_node->grapheme_segmenter().previous_boundary(m_selection_end); offset.has_value()) {
  649. if (collapse == CollapseSelection::Yes) {
  650. collapse_selection_to_offset(*offset);
  651. } else {
  652. m_selection_end = *offset;
  653. }
  654. }
  655. selection_was_changed();
  656. }
  657. static bool should_continue_beyond_word(Utf8View const& word)
  658. {
  659. for (auto code_point : word) {
  660. if (!Unicode::code_point_has_punctuation_general_category(code_point) && !Unicode::code_point_has_separator_general_category(code_point))
  661. return false;
  662. }
  663. return true;
  664. }
  665. void FormAssociatedTextControlElement::increment_cursor_position_to_next_word(CollapseSelection collapse)
  666. {
  667. auto const text_node = form_associated_element_to_text_node();
  668. if (!text_node)
  669. return;
  670. while (true) {
  671. if (auto offset = text_node->word_segmenter().next_boundary(m_selection_end); offset.has_value()) {
  672. auto word = text_node->data().code_points().substring_view(m_selection_end, *offset - m_selection_end);
  673. if (collapse == CollapseSelection::Yes) {
  674. collapse_selection_to_offset(*offset);
  675. } else {
  676. m_selection_end = *offset;
  677. }
  678. if (should_continue_beyond_word(word))
  679. continue;
  680. }
  681. break;
  682. }
  683. selection_was_changed();
  684. }
  685. void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_word(CollapseSelection collapse)
  686. {
  687. auto const text_node = form_associated_element_to_text_node();
  688. if (!text_node)
  689. return;
  690. while (true) {
  691. if (auto offset = text_node->word_segmenter().previous_boundary(m_selection_end); offset.has_value()) {
  692. auto word = text_node->data().code_points().substring_view(m_selection_end, m_selection_end - *offset);
  693. if (collapse == CollapseSelection::Yes) {
  694. collapse_selection_to_offset(*offset);
  695. } else {
  696. m_selection_end = *offset;
  697. }
  698. if (should_continue_beyond_word(word))
  699. continue;
  700. }
  701. break;
  702. }
  703. selection_was_changed();
  704. }
  705. JS::GCPtr<DOM::Position> FormAssociatedTextControlElement::cursor_position() const
  706. {
  707. auto const node = form_associated_element_to_text_node();
  708. if (!node)
  709. return nullptr;
  710. if (m_selection_start == m_selection_end)
  711. return DOM::Position::create(node->realm(), const_cast<DOM::Text&>(*node), m_selection_start);
  712. return nullptr;
  713. }
  714. }