HTMLTextAreaElement.cpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibWeb/Bindings/Intrinsics.h>
  8. #include <LibWeb/CSS/StyleProperties.h>
  9. #include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
  10. #include <LibWeb/DOM/Document.h>
  11. #include <LibWeb/DOM/ElementFactory.h>
  12. #include <LibWeb/DOM/Event.h>
  13. #include <LibWeb/DOM/ShadowRoot.h>
  14. #include <LibWeb/DOM/Text.h>
  15. #include <LibWeb/HTML/HTMLTextAreaElement.h>
  16. #include <LibWeb/HTML/Numbers.h>
  17. #include <LibWeb/Namespace.h>
  18. namespace Web::HTML {
  19. HTMLTextAreaElement::HTMLTextAreaElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  20. : HTMLElement(document, move(qualified_name))
  21. {
  22. }
  23. HTMLTextAreaElement::~HTMLTextAreaElement() = default;
  24. JS::GCPtr<Layout::Node> HTMLTextAreaElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
  25. {
  26. // AD-HOC: We rewrite `display: inline` to `display: inline-block`.
  27. // This is required for the internal shadow tree to work correctly in layout.
  28. if (style->display().is_inline_outside() && style->display().is_flow_inside())
  29. style->set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::InlineBlock)));
  30. return Element::create_layout_node_for_display_type(document(), style->display(), style, this);
  31. }
  32. void HTMLTextAreaElement::initialize(JS::Realm& realm)
  33. {
  34. Base::initialize(realm);
  35. set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLTextAreaElementPrototype>(realm, "HTMLTextAreaElement"_fly_string));
  36. }
  37. void HTMLTextAreaElement::visit_edges(Cell::Visitor& visitor)
  38. {
  39. Base::visit_edges(visitor);
  40. visitor.visit(m_inner_text_element);
  41. visitor.visit(m_text_node);
  42. }
  43. void HTMLTextAreaElement::did_receive_focus()
  44. {
  45. auto* browsing_context = document().browsing_context();
  46. if (!browsing_context)
  47. return;
  48. if (!m_text_node)
  49. return;
  50. browsing_context->set_cursor_position(DOM::Position::create(realm(), *m_text_node, 0));
  51. }
  52. void HTMLTextAreaElement::did_lose_focus()
  53. {
  54. // The change event fires when the value is committed, if that makes sense for the control,
  55. // or else when the control loses focus
  56. queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
  57. auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
  58. change_event->set_bubbles(true);
  59. dispatch_event(change_event);
  60. });
  61. }
  62. // https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex
  63. i32 HTMLTextAreaElement::default_tab_index_value() const
  64. {
  65. // See the base function for the spec comments.
  66. return 0;
  67. }
  68. // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:concept-form-reset-control
  69. void HTMLTextAreaElement::reset_algorithm()
  70. {
  71. // The reset algorithm for textarea elements is to set the dirty value flag back to false,
  72. m_dirty = false;
  73. // and set the raw value of element to its child text content.
  74. m_raw_value = child_text_content();
  75. }
  76. void HTMLTextAreaElement::form_associated_element_was_inserted()
  77. {
  78. create_shadow_tree_if_needed();
  79. }
  80. void HTMLTextAreaElement::form_associated_element_was_removed(DOM::Node*)
  81. {
  82. set_shadow_root(nullptr);
  83. }
  84. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-cols
  85. unsigned HTMLTextAreaElement::cols() const
  86. {
  87. // The cols and rows attributes are limited to only positive numbers with fallback. The cols IDL attribute's default value is 20.
  88. auto maybe_cols_string = get_attribute(HTML::AttributeNames::cols);
  89. if (maybe_cols_string.has_value()) {
  90. auto maybe_cols = parse_non_negative_integer(maybe_cols_string.value());
  91. if (maybe_cols.has_value())
  92. return maybe_cols.value();
  93. }
  94. return 20;
  95. }
  96. WebIDL::ExceptionOr<void> HTMLTextAreaElement::set_cols(unsigned value)
  97. {
  98. return set_attribute(HTML::AttributeNames::cols, MUST(String::number(value)));
  99. }
  100. // https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-rows
  101. unsigned HTMLTextAreaElement::rows() const
  102. {
  103. // The cols and rows attributes are limited to only positive numbers with fallback. The rows IDL attribute's default value is 2.
  104. auto maybe_rows_string = get_attribute(HTML::AttributeNames::rows);
  105. if (maybe_rows_string.has_value()) {
  106. auto maybe_rows = parse_non_negative_integer(maybe_rows_string.value());
  107. if (maybe_rows.has_value())
  108. return maybe_rows.value();
  109. }
  110. return 2;
  111. }
  112. WebIDL::ExceptionOr<void> HTMLTextAreaElement::set_rows(unsigned value)
  113. {
  114. return set_attribute(HTML::AttributeNames::rows, MUST(String::number(value)));
  115. }
  116. void HTMLTextAreaElement::create_shadow_tree_if_needed()
  117. {
  118. if (shadow_root_internal())
  119. return;
  120. auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
  121. auto element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  122. m_inner_text_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  123. m_text_node = heap().allocate<DOM::Text>(realm(), document(), String {});
  124. m_text_node->set_always_editable(true);
  125. m_text_node->set_editable_text_node_owner(Badge<HTMLTextAreaElement> {}, *this);
  126. // NOTE: If `children_changed()` was called before now, `m_raw_value` will hold the text content.
  127. // Otherwise, it will get filled in whenever that does get called.
  128. m_text_node->set_text_content(m_raw_value);
  129. MUST(m_inner_text_element->append_child(*m_text_node));
  130. MUST(element->append_child(*m_inner_text_element));
  131. MUST(shadow_root->append_child(element));
  132. set_shadow_root(shadow_root);
  133. }
  134. // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:children-changed-steps
  135. void HTMLTextAreaElement::children_changed()
  136. {
  137. // The children changed steps for textarea elements must, if the element's dirty value flag is false,
  138. // set the element's raw value to its child text content.
  139. if (!m_dirty) {
  140. m_raw_value = child_text_content();
  141. if (m_text_node)
  142. m_text_node->set_text_content(m_raw_value);
  143. }
  144. }
  145. void HTMLTextAreaElement::did_edit_text_node(Badge<Web::HTML::BrowsingContext>)
  146. {
  147. // 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.
  148. m_dirty = true;
  149. }
  150. }