HTMLTextAreaElement.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
  4. * Copyright (c) 2024, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
  5. *
  6. * SPDX-License-Identifier: BSD-2-Clause
  7. */
  8. #include <LibWeb/Bindings/Intrinsics.h>
  9. #include <LibWeb/CSS/StyleProperties.h>
  10. #include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
  11. #include <LibWeb/DOM/Document.h>
  12. #include <LibWeb/DOM/ElementFactory.h>
  13. #include <LibWeb/DOM/Event.h>
  14. #include <LibWeb/DOM/ShadowRoot.h>
  15. #include <LibWeb/DOM/Text.h>
  16. #include <LibWeb/HTML/HTMLTextAreaElement.h>
  17. #include <LibWeb/HTML/Numbers.h>
  18. #include <LibWeb/Namespace.h>
  19. namespace Web::HTML {
  20. JS_DEFINE_ALLOCATOR(HTMLTextAreaElement);
  21. HTMLTextAreaElement::HTMLTextAreaElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  22. : HTMLElement(document, move(qualified_name))
  23. , m_input_event_timer(Web::Platform::Timer::create_single_shot(0, [this]() { queue_firing_input_event(); }))
  24. {
  25. }
  26. HTMLTextAreaElement::~HTMLTextAreaElement() = default;
  27. JS::GCPtr<Layout::Node> HTMLTextAreaElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
  28. {
  29. // AD-HOC: We rewrite `display: inline` to `display: inline-block`.
  30. // This is required for the internal shadow tree to work correctly in layout.
  31. if (style->display().is_inline_outside() && style->display().is_flow_inside())
  32. style->set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::InlineBlock)));
  33. return Element::create_layout_node_for_display_type(document(), style->display(), style, this);
  34. }
  35. void HTMLTextAreaElement::initialize(JS::Realm& realm)
  36. {
  37. Base::initialize(realm);
  38. set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLTextAreaElementPrototype>(realm, "HTMLTextAreaElement"_fly_string));
  39. }
  40. void HTMLTextAreaElement::visit_edges(Cell::Visitor& visitor)
  41. {
  42. Base::visit_edges(visitor);
  43. visitor.visit(m_placeholder_element);
  44. visitor.visit(m_placeholder_text_node);
  45. visitor.visit(m_inner_text_element);
  46. visitor.visit(m_text_node);
  47. }
  48. void HTMLTextAreaElement::did_receive_focus()
  49. {
  50. auto* browsing_context = document().browsing_context();
  51. if (!browsing_context)
  52. return;
  53. if (!m_text_node)
  54. return;
  55. browsing_context->set_cursor_position(DOM::Position::create(realm(), *m_text_node, 0));
  56. }
  57. void HTMLTextAreaElement::did_lose_focus()
  58. {
  59. // The change event fires when the value is committed, if that makes sense for the control,
  60. // or else when the control loses focus
  61. queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
  62. auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
  63. change_event->set_bubbles(true);
  64. dispatch_event(change_event);
  65. });
  66. }
  67. // https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex
  68. i32 HTMLTextAreaElement::default_tab_index_value() const
  69. {
  70. // See the base function for the spec comments.
  71. return 0;
  72. }
  73. // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-form-reset-control
  74. void HTMLTextAreaElement::reset_algorithm()
  75. {
  76. // The reset algorithm for textarea elements is to set the dirty value flag back to false,
  77. m_dirty_value = false;
  78. // and set the raw value of element to its child text content.
  79. m_raw_value = child_text_content();
  80. update_placeholder_visibility();
  81. }
  82. void HTMLTextAreaElement::form_associated_element_was_inserted()
  83. {
  84. create_shadow_tree_if_needed();
  85. }
  86. void HTMLTextAreaElement::form_associated_element_was_removed(DOM::Node*)
  87. {
  88. set_shadow_root(nullptr);
  89. }
  90. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-defaultvalue
  91. String HTMLTextAreaElement::default_value() const
  92. {
  93. // The defaultValue attribute's getter must return the element's child text content.
  94. return child_text_content();
  95. }
  96. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-defaultvalue
  97. void HTMLTextAreaElement::set_default_value(String const& default_value)
  98. {
  99. // The defaultValue attribute's setter must string replace all with the given value within this element.
  100. string_replace_all(default_value);
  101. }
  102. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-value
  103. String HTMLTextAreaElement::value() const
  104. {
  105. // The value IDL attribute must, on getting, return the element's API value.
  106. return m_raw_value;
  107. }
  108. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-output-value
  109. void HTMLTextAreaElement::set_value(String const& value)
  110. {
  111. // FIXME: 1. Let oldAPIValue be this element's API value.
  112. // 2. Set this element's raw value to the new value.
  113. m_raw_value = value;
  114. // 3. Set this element's dirty value flag to true.
  115. m_dirty_value = true;
  116. // FIXME: 4. If the new API value is different from oldAPIValue, then move the text entry cursor position to the end of the text control, unselecting any selected text and resetting the selection direction to "none".
  117. update_placeholder_visibility();
  118. }
  119. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-textlength
  120. u32 HTMLTextAreaElement::text_length() const
  121. {
  122. // The textLength IDL attribute must return the length of the element's API value.
  123. // FIXME: This is inefficient!
  124. auto utf16_data = MUST(AK::utf8_to_utf16(m_raw_value));
  125. return Utf16View { utf16_data }.length_in_code_units();
  126. }
  127. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity
  128. bool HTMLTextAreaElement::check_validity()
  129. {
  130. dbgln("(STUBBED) HTMLTextAreaElement::check_validity(). Called on: {}", debug_description());
  131. return true;
  132. }
  133. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-reportvalidity
  134. bool HTMLTextAreaElement::report_validity()
  135. {
  136. dbgln("(STUBBED) HTMLTextAreaElement::report_validity(). Called on: {}", debug_description());
  137. return true;
  138. }
  139. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-setcustomvalidity
  140. void HTMLTextAreaElement::set_custom_validity(String const& error)
  141. {
  142. dbgln("(STUBBED) HTMLTextAreaElement::set_custom_validity(\"{}\"). Called on: {}", error, debug_description());
  143. }
  144. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-maxlength
  145. WebIDL::Long HTMLTextAreaElement::max_length() const
  146. {
  147. // The maxLength IDL attribute must reflect the maxlength content attribute, limited to only non-negative numbers.
  148. if (auto maxlength_string = get_attribute(HTML::AttributeNames::maxlength); maxlength_string.has_value()) {
  149. if (auto maxlength = parse_non_negative_integer(*maxlength_string); maxlength.has_value())
  150. return *maxlength;
  151. }
  152. return -1;
  153. }
  154. WebIDL::ExceptionOr<void> HTMLTextAreaElement::set_max_length(WebIDL::Long value)
  155. {
  156. // The maxLength IDL attribute must reflect the maxlength content attribute, limited to only non-negative numbers.
  157. return set_attribute(HTML::AttributeNames::maxlength, TRY(convert_non_negative_integer_to_string(realm(), value)));
  158. }
  159. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-minlength
  160. WebIDL::Long HTMLTextAreaElement::min_length() const
  161. {
  162. // The minLength IDL attribute must reflect the minlength content attribute, limited to only non-negative numbers.
  163. if (auto minlength_string = get_attribute(HTML::AttributeNames::minlength); minlength_string.has_value()) {
  164. if (auto minlength = parse_non_negative_integer(*minlength_string); minlength.has_value())
  165. return *minlength;
  166. }
  167. return -1;
  168. }
  169. WebIDL::ExceptionOr<void> HTMLTextAreaElement::set_min_length(WebIDL::Long value)
  170. {
  171. // The minLength IDL attribute must reflect the minlength content attribute, limited to only non-negative numbers.
  172. return set_attribute(HTML::AttributeNames::minlength, TRY(convert_non_negative_integer_to_string(realm(), value)));
  173. }
  174. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-cols
  175. unsigned HTMLTextAreaElement::cols() const
  176. {
  177. // The cols and rows attributes are limited to only positive numbers with fallback. The cols IDL attribute's default value is 20.
  178. if (auto cols_string = get_attribute(HTML::AttributeNames::cols); cols_string.has_value()) {
  179. if (auto cols = parse_non_negative_integer(*cols_string); cols.has_value())
  180. return *cols;
  181. }
  182. return 20;
  183. }
  184. WebIDL::ExceptionOr<void> HTMLTextAreaElement::set_cols(unsigned cols)
  185. {
  186. return set_attribute(HTML::AttributeNames::cols, MUST(String::number(cols)));
  187. }
  188. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-rows
  189. unsigned HTMLTextAreaElement::rows() const
  190. {
  191. // The cols and rows attributes are limited to only positive numbers with fallback. The rows IDL attribute's default value is 2.
  192. if (auto rows_string = get_attribute(HTML::AttributeNames::rows); rows_string.has_value()) {
  193. if (auto rows = parse_non_negative_integer(*rows_string); rows.has_value())
  194. return *rows;
  195. }
  196. return 2;
  197. }
  198. WebIDL::ExceptionOr<void> HTMLTextAreaElement::set_rows(unsigned rows)
  199. {
  200. return set_attribute(HTML::AttributeNames::rows, MUST(String::number(rows)));
  201. }
  202. void HTMLTextAreaElement::create_shadow_tree_if_needed()
  203. {
  204. if (shadow_root_internal())
  205. return;
  206. auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
  207. set_shadow_root(shadow_root);
  208. auto element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  209. MUST(shadow_root->append_child(element));
  210. m_placeholder_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  211. m_placeholder_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Placeholder);
  212. MUST(element->append_child(*m_placeholder_element));
  213. m_placeholder_text_node = heap().allocate<DOM::Text>(realm(), document(), String {});
  214. m_placeholder_text_node->set_data(get_attribute_value(HTML::AttributeNames::placeholder));
  215. m_placeholder_text_node->set_editable_text_node_owner(Badge<HTMLTextAreaElement> {}, *this);
  216. MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
  217. m_inner_text_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  218. MUST(element->append_child(*m_inner_text_element));
  219. m_text_node = heap().allocate<DOM::Text>(realm(), document(), String {});
  220. handle_readonly_attribute(attribute(HTML::AttributeNames::readonly));
  221. m_text_node->set_editable_text_node_owner(Badge<HTMLTextAreaElement> {}, *this);
  222. // NOTE: If `children_changed()` was called before now, `m_raw_value` will hold the text content.
  223. // Otherwise, it will get filled in whenever that does get called.
  224. m_text_node->set_text_content(m_raw_value);
  225. handle_maxlength_attribute();
  226. MUST(m_inner_text_element->append_child(*m_text_node));
  227. update_placeholder_visibility();
  228. }
  229. // https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly
  230. void HTMLTextAreaElement::handle_readonly_attribute(Optional<String> const& maybe_value)
  231. {
  232. // The readonly attribute is a boolean attribute that controls whether or not the user can edit the form control. When specified, the element is not mutable.
  233. m_is_mutable = !maybe_value.has_value();
  234. if (m_text_node)
  235. m_text_node->set_always_editable(m_is_mutable);
  236. }
  237. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-maxlength
  238. void HTMLTextAreaElement::handle_maxlength_attribute()
  239. {
  240. if (m_text_node) {
  241. auto max_length = this->max_length();
  242. if (max_length >= 0) {
  243. m_text_node->set_max_length(max_length);
  244. } else {
  245. m_text_node->set_max_length({});
  246. }
  247. }
  248. }
  249. void HTMLTextAreaElement::update_placeholder_visibility()
  250. {
  251. if (!m_placeholder_element)
  252. return;
  253. if (!m_text_node)
  254. return;
  255. auto placeholder_text = get_attribute(AttributeNames::placeholder);
  256. if (placeholder_text.has_value() && m_text_node->data().is_empty()) {
  257. MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"sv));
  258. } else {
  259. MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"sv));
  260. }
  261. }
  262. // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:children-changed-steps
  263. void HTMLTextAreaElement::children_changed()
  264. {
  265. // The children changed steps for textarea elements must, if the element's dirty value flag is false,
  266. // set the element's raw value to its child text content.
  267. if (!m_dirty_value) {
  268. m_raw_value = child_text_content();
  269. if (m_text_node)
  270. m_text_node->set_text_content(m_raw_value);
  271. update_placeholder_visibility();
  272. }
  273. }
  274. void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& value)
  275. {
  276. if (name == HTML::AttributeNames::placeholder) {
  277. if (m_placeholder_text_node)
  278. m_placeholder_text_node->set_data(value.value_or(String {}));
  279. } else if (name == HTML::AttributeNames::readonly) {
  280. handle_readonly_attribute(value);
  281. } else if (name == HTML::AttributeNames::maxlength) {
  282. handle_maxlength_attribute();
  283. }
  284. }
  285. void HTMLTextAreaElement::did_edit_text_node(Badge<Web::HTML::BrowsingContext>)
  286. {
  287. VERIFY(m_text_node);
  288. m_raw_value = m_text_node->data();
  289. // Any time the user causes the element's raw value to change, the user agent must queue an element task on the user
  290. // interaction task source given the textarea element to fire an event named input at the textarea element, with the
  291. // bubbles and composed attributes initialized to true. User agents may wait for a suitable break in the user's
  292. // interaction before queuing the task; for example, a user agent could wait for the user to have not hit a key for
  293. // 100ms, so as to only fire the event when the user pauses, instead of continuously for each keystroke.
  294. m_input_event_timer->restart(100);
  295. // A textarea element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the raw value.
  296. m_dirty_value = true;
  297. update_placeholder_visibility();
  298. }
  299. void HTMLTextAreaElement::queue_firing_input_event()
  300. {
  301. queue_an_element_task(HTML::Task::Source::UserInteraction, [this]() {
  302. auto change_event = DOM::Event::create(realm(), HTML::EventNames::input, { .bubbles = true, .composed = true });
  303. dispatch_event(change_event);
  304. });
  305. }
  306. }