HTMLInputElement.cpp 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391
  1. /*
  2. * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, Adam Hodgen <ant1441@gmail.com>
  4. * Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
  5. * Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
  6. *
  7. * SPDX-License-Identifier: BSD-2-Clause
  8. */
  9. #include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
  10. #include <LibWeb/CSS/StyleValues/IdentifierStyleValue.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/HTML/BrowsingContext.h>
  16. #include <LibWeb/HTML/EventNames.h>
  17. #include <LibWeb/HTML/HTMLDivElement.h>
  18. #include <LibWeb/HTML/HTMLFormElement.h>
  19. #include <LibWeb/HTML/HTMLInputElement.h>
  20. #include <LibWeb/HTML/Numbers.h>
  21. #include <LibWeb/HTML/Scripting/Environments.h>
  22. #include <LibWeb/HTML/Window.h>
  23. #include <LibWeb/Infra/CharacterTypes.h>
  24. #include <LibWeb/Infra/Strings.h>
  25. #include <LibWeb/Layout/BlockContainer.h>
  26. #include <LibWeb/Layout/ButtonBox.h>
  27. #include <LibWeb/Layout/CheckBox.h>
  28. #include <LibWeb/Layout/RadioButton.h>
  29. #include <LibWeb/Namespace.h>
  30. #include <LibWeb/Page/Page.h>
  31. #include <LibWeb/WebIDL/DOMException.h>
  32. #include <LibWeb/WebIDL/ExceptionOr.h>
  33. namespace Web::HTML {
  34. JS_DEFINE_ALLOCATOR(HTMLInputElement);
  35. HTMLInputElement::HTMLInputElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  36. : HTMLElement(document, move(qualified_name))
  37. {
  38. }
  39. HTMLInputElement::~HTMLInputElement() = default;
  40. void HTMLInputElement::initialize(JS::Realm& realm)
  41. {
  42. Base::initialize(realm);
  43. set_prototype(&Bindings::ensure_web_prototype<Bindings::HTMLInputElementPrototype>(realm, "HTMLInputElement"_fly_string));
  44. }
  45. void HTMLInputElement::visit_edges(Cell::Visitor& visitor)
  46. {
  47. Base::visit_edges(visitor);
  48. visitor.visit(m_inner_text_element);
  49. visitor.visit(m_text_node);
  50. visitor.visit(m_placeholder_element);
  51. visitor.visit(m_placeholder_text_node);
  52. visitor.visit(m_color_well_element);
  53. visitor.visit(m_legacy_pre_activation_behavior_checked_element_in_group);
  54. visitor.visit(m_selected_files);
  55. }
  56. JS::GCPtr<Layout::Node> HTMLInputElement::create_layout_node(NonnullRefPtr<CSS::StyleProperties> style)
  57. {
  58. if (type_state() == TypeAttributeState::Hidden)
  59. return nullptr;
  60. if (type_state() == TypeAttributeState::SubmitButton || type_state() == TypeAttributeState::Button || type_state() == TypeAttributeState::ResetButton || type_state() == TypeAttributeState::FileUpload)
  61. return heap().allocate_without_realm<Layout::ButtonBox>(document(), *this, move(style));
  62. if (type_state() == TypeAttributeState::Checkbox)
  63. return heap().allocate_without_realm<Layout::CheckBox>(document(), *this, move(style));
  64. if (type_state() == TypeAttributeState::RadioButton)
  65. return heap().allocate_without_realm<Layout::RadioButton>(document(), *this, move(style));
  66. // AD-HOC: We rewrite `display: inline` to `display: inline-block`.
  67. // This is required for the internal shadow tree to work correctly in layout.
  68. if (style->display().is_inline_outside() && style->display().is_flow_inside())
  69. style->set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::InlineBlock)));
  70. return Element::create_layout_node_for_display_type(document(), style->display(), style, this);
  71. }
  72. void HTMLInputElement::set_checked(bool checked, ChangeSource change_source)
  73. {
  74. if (m_checked == checked)
  75. return;
  76. // The dirty checkedness flag must be initially set to false when the element is created,
  77. // and must be set to true whenever the user interacts with the control in a way that changes the checkedness.
  78. if (change_source == ChangeSource::User)
  79. m_dirty_checkedness = true;
  80. m_checked = checked;
  81. // This element's :checked pseudo-class could be used in a sibling's sibling-selector,
  82. // so we need to invalidate the style of all siblings.
  83. if (parent()) {
  84. parent()->for_each_child([&](auto& child) {
  85. child.invalidate_style();
  86. });
  87. }
  88. }
  89. void HTMLInputElement::set_checked_binding(bool checked)
  90. {
  91. if (type_state() == TypeAttributeState::RadioButton) {
  92. if (checked)
  93. set_checked_within_group();
  94. else
  95. set_checked(false, ChangeSource::Programmatic);
  96. } else {
  97. set_checked(checked, ChangeSource::Programmatic);
  98. }
  99. }
  100. // https://html.spec.whatwg.org/multipage/input.html#dom-input-indeterminate
  101. void HTMLInputElement::set_indeterminate(bool value)
  102. {
  103. // On setting, it must be set to the new value. It has no effect except for changing the appearance of checkbox controls.
  104. m_indeterminate = value;
  105. }
  106. // https://html.spec.whatwg.org/multipage/input.html#dom-input-files
  107. JS::GCPtr<FileAPI::FileList> HTMLInputElement::files()
  108. {
  109. // On getting, if the IDL attribute applies, it must return a FileList object that represents the current selected files.
  110. // The same object must be returned until the list of selected files changes.
  111. // If the IDL attribute does not apply, then it must instead return null.
  112. if (m_type != TypeAttributeState::FileUpload)
  113. return nullptr;
  114. if (!m_selected_files)
  115. m_selected_files = FileAPI::FileList::create(realm(), {});
  116. return m_selected_files;
  117. }
  118. // https://html.spec.whatwg.org/multipage/input.html#dom-input-files
  119. void HTMLInputElement::set_files(JS::GCPtr<FileAPI::FileList> files)
  120. {
  121. // 1. If the IDL attribute does not apply or the given value is null, then return.
  122. if (m_type != TypeAttributeState::FileUpload || files == nullptr)
  123. return;
  124. // 2. Replace the element's selected files with the given value.
  125. m_selected_files = files;
  126. }
  127. // https://html.spec.whatwg.org/multipage/input.html#update-the-file-selection
  128. void HTMLInputElement::update_the_file_selection(JS::NonnullGCPtr<FileAPI::FileList> files)
  129. {
  130. // 1. Queue an element task on the user interaction task source given element and the following steps:
  131. queue_an_element_task(Task::Source::UserInteraction, [this, files] {
  132. // 1. Update element's selected files so that it represents the user's selection.
  133. this->set_files(files.ptr());
  134. // 2. Fire an event named input at the input element, with the bubbles and composed attributes initialized to true.
  135. auto input_event = DOM::Event::create(this->realm(), EventNames::input, { .bubbles = true, .composed = true });
  136. this->dispatch_event(input_event);
  137. // 3. Fire an event named change at the input element, with the bubbles attribute initialized to true.
  138. auto change_event = DOM::Event::create(this->realm(), EventNames::change, { .bubbles = true });
  139. this->dispatch_event(change_event);
  140. });
  141. }
  142. // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
  143. static void show_the_picker_if_applicable(HTMLInputElement& element)
  144. {
  145. // To show the picker, if applicable for an input element element:
  146. // 1. If element's relevant global object does not have transient activation, then return.
  147. auto& global_object = relevant_global_object(element);
  148. if (!is<HTML::Window>(global_object) || !static_cast<HTML::Window&>(global_object).has_transient_activation())
  149. return;
  150. // 2. If element is not mutable, then return.
  151. if (!element.is_mutable())
  152. return;
  153. // 3. If element's type attribute is in the File Upload state, then run these steps in parallel:
  154. if (element.type_state() == HTMLInputElement::TypeAttributeState::FileUpload) {
  155. // NOTE: These steps cannot be fully implemented here, and must be done in the PageClient when the response comes back from the PageHost
  156. // 1. Optionally, wait until any prior execution of this algorithm has terminated.
  157. // 2. Display a prompt to the user requesting that the user specify some files.
  158. // If the multiple attribute is not set on element, there must be no more than one file selected; otherwise, any number may be selected.
  159. // Files can be from the filesystem or created on the fly, e.g., a picture taken from a camera connected to the user's device.
  160. // 3. Wait for the user to have made their selection.
  161. // 4. If the user dismissed the prompt without changing their selection,
  162. // then queue an element task on the user interaction task source given element to fire an event named cancel at element,
  163. // with the bubbles attribute initialized to true.
  164. // 5. Otherwise, update the file selection for element.
  165. bool const multiple = element.has_attribute(HTML::AttributeNames::multiple);
  166. auto weak_element = element.make_weak_ptr<DOM::EventTarget>();
  167. // FIXME: Pass along accept attribute information https://html.spec.whatwg.org/multipage/input.html#attr-input-accept
  168. // The accept attribute may be specified to provide user agents with a hint of what file types will be accepted.
  169. element.document().browsing_context()->top_level_browsing_context()->page()->client().page_did_request_file_picker(weak_element, multiple);
  170. return;
  171. }
  172. // 4. Otherwise, the user agent should show any relevant user interface for selecting a value for element,
  173. // in the way it normally would when the user interacts with the control. (If no such UI applies to element, then this step does nothing.)
  174. // If such a user interface is shown, it must respect the requirements stated in the relevant parts of the specification for how element
  175. // behaves given its type attribute state. (For example, various sections describe restrictions on the resulting value string.)
  176. // This step can have side effects, such as closing other pickers that were previously shown by this algorithm.
  177. // (If this closes a file selection picker, then per the above that will lead to firing either input and change events, or a cancel event.)
  178. if (element.type_state() == HTMLInputElement::TypeAttributeState::Color) {
  179. auto weak_element = element.make_weak_ptr<HTMLInputElement>();
  180. element.document().browsing_context()->top_level_browsing_context()->page()->did_request_color_picker(weak_element, Color::from_string(element.value()).value_or(Color(0, 0, 0)));
  181. }
  182. }
  183. // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
  184. WebIDL::ExceptionOr<void> HTMLInputElement::show_picker()
  185. {
  186. // The showPicker() method steps are:
  187. // 1. If this is not mutable, then throw an "InvalidStateError" DOMException.
  188. if (!m_is_mutable)
  189. return WebIDL::InvalidStateError::create(realm(), "Element is not mutable"_fly_string);
  190. // 2. If this's relevant settings object's origin is not same origin with this's relevant settings object's top-level origin,
  191. // and this's type attribute is not in the File Upload state or Color state, then throw a "SecurityError" DOMException.
  192. // NOTE: File and Color inputs are exempted from this check for historical reason: their input activation behavior also shows their pickers,
  193. // and has never been guarded by an origin check.
  194. if (!relevant_settings_object(*this).origin().is_same_origin(relevant_settings_object(*this).top_level_origin)
  195. && m_type != TypeAttributeState::FileUpload && m_type != TypeAttributeState::Color) {
  196. return WebIDL::SecurityError::create(realm(), "Cross origin pickers are not allowed"_fly_string);
  197. }
  198. // 3. If this's relevant global object does not have transient activation, then throw a "NotAllowedError" DOMException.
  199. // FIXME: The global object we get here should probably not need casted to Window to check for transient activation
  200. auto& global_object = relevant_global_object(*this);
  201. if (!is<HTML::Window>(global_object) || !static_cast<HTML::Window&>(global_object).has_transient_activation()) {
  202. return WebIDL::NotAllowedError::create(realm(), "Too long since user activation to show picker"_fly_string);
  203. }
  204. // 4. Show the picker, if applicable, for this.
  205. show_the_picker_if_applicable(*this);
  206. return {};
  207. }
  208. // https://html.spec.whatwg.org/multipage/input.html#input-activation-behavior
  209. WebIDL::ExceptionOr<void> HTMLInputElement::run_input_activation_behavior()
  210. {
  211. if (type_state() == TypeAttributeState::Checkbox || type_state() == TypeAttributeState::RadioButton) {
  212. // 1. If the element is not connected, then return.
  213. if (!is_connected())
  214. return {};
  215. // 2. Fire an event named input at the element with the bubbles and composed attributes initialized to true.
  216. auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
  217. input_event->set_bubbles(true);
  218. input_event->set_composed(true);
  219. dispatch_event(input_event);
  220. // 3. Fire an event named change at the element with the bubbles attribute initialized to true.
  221. auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
  222. change_event->set_bubbles(true);
  223. dispatch_event(*change_event);
  224. } else if (type_state() == TypeAttributeState::SubmitButton) {
  225. JS::GCPtr<HTMLFormElement> form;
  226. // 1. If the element does not have a form owner, then return.
  227. if (!(form = this->form()))
  228. return {};
  229. // 2. If the element's node document is not fully active, then return.
  230. if (!document().is_fully_active())
  231. return {};
  232. // 3. Submit the form owner from the element.
  233. TRY(form->submit_form(*this));
  234. } else if (type_state() == TypeAttributeState::FileUpload || type_state() == TypeAttributeState::Color) {
  235. show_the_picker_if_applicable(*this);
  236. }
  237. return {};
  238. }
  239. void HTMLInputElement::did_edit_text_node(Badge<BrowsingContext>)
  240. {
  241. // An input element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the value.
  242. m_value = value_sanitization_algorithm(m_text_node->data());
  243. m_dirty_value = true;
  244. m_has_uncommitted_changes = true;
  245. update_placeholder_visibility();
  246. // NOTE: This is a bit ad-hoc, but basically implements part of "4.10.5.5 Common event behaviors"
  247. // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events
  248. queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
  249. auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
  250. input_event->set_bubbles(true);
  251. input_event->set_composed(true);
  252. dispatch_event(*input_event);
  253. });
  254. }
  255. void HTMLInputElement::did_pick_color(Optional<Color> picked_color)
  256. {
  257. // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events
  258. // For input elements without a defined input activation behavior, but to which these events apply
  259. // and for which the user interface involves both interactive manipulation and an explicit commit action
  260. if (type_state() == TypeAttributeState::Color && picked_color.has_value()) {
  261. // then when the user changes the element's value
  262. m_value = value_sanitization_algorithm(picked_color.value().to_string_without_alpha());
  263. m_dirty_value = true;
  264. if (m_color_well_element)
  265. MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value));
  266. // the user agent must queue an element task on the user interaction task source
  267. queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
  268. // given the input element to fire an event named input at the input element, with the bubbles and composed attributes initialized to true
  269. auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
  270. input_event->set_bubbles(true);
  271. input_event->set_composed(true);
  272. dispatch_event(*input_event);
  273. });
  274. // and any time the user commits the change, the user agent must queue an element task on the user interaction task source
  275. queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
  276. // given the input element
  277. // FIXME: to set its user interacted to true
  278. // and fire an event named change at the input element, with the bubbles attribute initialized to true.
  279. auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
  280. change_event->set_bubbles(true);
  281. dispatch_event(*change_event);
  282. });
  283. }
  284. }
  285. String HTMLInputElement::value() const
  286. {
  287. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
  288. if (type_state() == TypeAttributeState::FileUpload) {
  289. // NOTE: This "fakepath" requirement is a sad accident of history. See the example in the File Upload state section for more information.
  290. // NOTE: Since path components are not permitted in filenames in the list of selected files, the "\fakepath\" cannot be mistaken for a path component.
  291. // On getting, return the string "C:\fakepath\" followed by the name of the first file in the list of selected files, if any, or the empty string if the list is empty.
  292. if (m_selected_files && m_selected_files->item(0))
  293. return MUST(String::formatted("C:\\fakepath\\{}", m_selected_files->item(0)->name()));
  294. return String {};
  295. }
  296. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
  297. if (type_state() == TypeAttributeState::Checkbox || type_state() == TypeAttributeState::RadioButton) {
  298. // On getting, if the element has a value content attribute, return that attribute's value; otherwise, return the string "on".
  299. return get_attribute(AttributeNames::value).value_or("on"_string);
  300. }
  301. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
  302. if (type_state() == TypeAttributeState::Hidden
  303. || type_state() == TypeAttributeState::SubmitButton
  304. || type_state() == TypeAttributeState::ImageButton
  305. || type_state() == TypeAttributeState::ResetButton
  306. || type_state() == TypeAttributeState::Button) {
  307. // On getting, if the element has a value content attribute, return that attribute's value; otherwise, return the empty string.
  308. return get_attribute(AttributeNames::value).value_or(String {});
  309. }
  310. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
  311. // Return the current value of the element.
  312. return m_value;
  313. }
  314. WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
  315. {
  316. auto& realm = this->realm();
  317. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
  318. if (type_state() == TypeAttributeState::FileUpload) {
  319. // On setting, if the new value is the empty string, empty the list of selected files; otherwise, throw an "InvalidStateError" DOMException.
  320. if (!value.is_empty())
  321. return WebIDL::InvalidStateError::create(realm, "Setting value of input type file to non-empty string"_fly_string);
  322. m_selected_files = nullptr;
  323. return {};
  324. }
  325. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
  326. // 1. Let oldValue be the element's value.
  327. auto old_value = move(m_value);
  328. // 2. Set the element's value to the new value.
  329. // NOTE: For the TextNode this is done as part of step 4 below.
  330. if (type_state() == TypeAttributeState::Color && m_color_well_element)
  331. MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value));
  332. // 3. Set the element's dirty value flag to true.
  333. m_dirty_value = true;
  334. // 4. Invoke the value sanitization algorithm, if the element's type attribute's current state defines one.
  335. m_value = value_sanitization_algorithm(value);
  336. // 5. If the element's value (after applying the value sanitization algorithm) is different from oldValue,
  337. // and the element has a text entry cursor position, move the text entry cursor position to the end of the
  338. // text control, unselecting any selected text and resetting the selection direction to "none".
  339. if (m_text_node && (m_value != old_value)) {
  340. m_text_node->set_data(m_value);
  341. update_placeholder_visibility();
  342. if (auto* browsing_context = document().browsing_context())
  343. browsing_context->set_cursor_position(DOM::Position::create(realm, *m_text_node, m_text_node->data().bytes().size()));
  344. }
  345. return {};
  346. }
  347. void HTMLInputElement::commit_pending_changes()
  348. {
  349. // The change event fires when the value is committed, if that makes sense for the control,
  350. // or else when the control loses focus
  351. switch (type_state()) {
  352. case TypeAttributeState::Email:
  353. case TypeAttributeState::Password:
  354. case TypeAttributeState::Search:
  355. case TypeAttributeState::Telephone:
  356. case TypeAttributeState::Text:
  357. case TypeAttributeState::URL:
  358. if (!m_has_uncommitted_changes)
  359. return;
  360. break;
  361. default:
  362. break;
  363. }
  364. m_has_uncommitted_changes = false;
  365. auto change_event = DOM::Event::create(realm(), HTML::EventNames::change, { .bubbles = true });
  366. dispatch_event(change_event);
  367. }
  368. void HTMLInputElement::update_placeholder_visibility()
  369. {
  370. if (!m_placeholder_element)
  371. return;
  372. auto placeholder_text = this->placeholder_value();
  373. if (placeholder_text.has_value()) {
  374. MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"sv));
  375. } else {
  376. MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"sv));
  377. }
  378. }
  379. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-readonly-3
  380. static bool is_allowed_to_be_readonly(HTML::HTMLInputElement::TypeAttributeState state)
  381. {
  382. switch (state) {
  383. case HTML::HTMLInputElement::TypeAttributeState::Text:
  384. case HTML::HTMLInputElement::TypeAttributeState::Search:
  385. case HTML::HTMLInputElement::TypeAttributeState::Telephone:
  386. case HTML::HTMLInputElement::TypeAttributeState::URL:
  387. case HTML::HTMLInputElement::TypeAttributeState::Email:
  388. case HTML::HTMLInputElement::TypeAttributeState::Password:
  389. case HTML::HTMLInputElement::TypeAttributeState::Date:
  390. case HTML::HTMLInputElement::TypeAttributeState::Month:
  391. case HTML::HTMLInputElement::TypeAttributeState::Week:
  392. case HTML::HTMLInputElement::TypeAttributeState::Time:
  393. case HTML::HTMLInputElement::TypeAttributeState::LocalDateAndTime:
  394. case HTML::HTMLInputElement::TypeAttributeState::Number:
  395. return true;
  396. default:
  397. return false;
  398. }
  399. }
  400. // https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly
  401. void HTMLInputElement::handle_readonly_attribute(Optional<String> const& maybe_value)
  402. {
  403. // 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.
  404. m_is_mutable = !maybe_value.has_value() || !is_allowed_to_be_readonly(m_type);
  405. if (m_text_node)
  406. m_text_node->set_always_editable(m_is_mutable);
  407. }
  408. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-placeholder-3
  409. static bool is_allowed_to_have_placeholder(HTML::HTMLInputElement::TypeAttributeState state)
  410. {
  411. switch (state) {
  412. case HTML::HTMLInputElement::TypeAttributeState::Text:
  413. case HTML::HTMLInputElement::TypeAttributeState::Search:
  414. case HTML::HTMLInputElement::TypeAttributeState::URL:
  415. case HTML::HTMLInputElement::TypeAttributeState::Telephone:
  416. case HTML::HTMLInputElement::TypeAttributeState::Email:
  417. case HTML::HTMLInputElement::TypeAttributeState::Password:
  418. case HTML::HTMLInputElement::TypeAttributeState::Number:
  419. return true;
  420. default:
  421. return false;
  422. }
  423. }
  424. // https://html.spec.whatwg.org/multipage/input.html#attr-input-placeholder
  425. Optional<DeprecatedString> HTMLInputElement::placeholder_value() const
  426. {
  427. if (!m_text_node || !m_text_node->data().is_empty())
  428. return {};
  429. if (!is_allowed_to_have_placeholder(type_state()))
  430. return {};
  431. if (!has_attribute(HTML::AttributeNames::placeholder))
  432. return {};
  433. auto placeholder = deprecated_attribute(HTML::AttributeNames::placeholder);
  434. if (placeholder.contains('\r') || placeholder.contains('\n')) {
  435. StringBuilder builder;
  436. for (auto ch : placeholder) {
  437. if (ch != '\r' && ch != '\n')
  438. builder.append(ch);
  439. }
  440. placeholder = builder.to_deprecated_string();
  441. }
  442. return placeholder;
  443. }
  444. class PlaceholderElement final : public HTMLDivElement {
  445. JS_CELL(PlaceholderElement, HTMLDivElement);
  446. JS_DECLARE_ALLOCATOR(PlaceholderElement);
  447. public:
  448. PlaceholderElement(DOM::Document& document)
  449. : HTMLDivElement(document, DOM::QualifiedName { HTML::TagNames::div, ""_fly_string, Namespace::HTML })
  450. {
  451. }
  452. virtual Optional<CSS::Selector::PseudoElement> pseudo_element() const override { return CSS::Selector::PseudoElement::Placeholder; }
  453. };
  454. JS_DEFINE_ALLOCATOR(PlaceholderElement);
  455. void HTMLInputElement::create_shadow_tree_if_needed()
  456. {
  457. if (shadow_root_internal())
  458. return;
  459. switch (type_state()) {
  460. case TypeAttributeState::Hidden:
  461. case TypeAttributeState::RadioButton:
  462. case TypeAttributeState::Checkbox:
  463. case TypeAttributeState::Button:
  464. case TypeAttributeState::SubmitButton:
  465. case TypeAttributeState::ResetButton:
  466. case TypeAttributeState::ImageButton:
  467. break;
  468. case TypeAttributeState::Color:
  469. create_color_input_shadow_tree();
  470. break;
  471. // FIXME: This could be better factored. Everything except the above types becomes a text input.
  472. default:
  473. create_text_input_shadow_tree();
  474. break;
  475. }
  476. }
  477. void HTMLInputElement::create_text_input_shadow_tree()
  478. {
  479. auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
  480. auto initial_value = m_value;
  481. auto element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  482. MUST(element->set_attribute(HTML::AttributeNames::style, R"~~~(
  483. display: flex;
  484. height: 100%;
  485. align-items: center;
  486. white-space: pre;
  487. border: none;
  488. padding: 1px 2px;
  489. )~~~"_string));
  490. m_placeholder_element = heap().allocate<PlaceholderElement>(realm(), document());
  491. MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Height, "1lh"sv));
  492. m_placeholder_text_node = heap().allocate<DOM::Text>(realm(), document(), initial_value);
  493. m_placeholder_text_node->set_data(attribute(HTML::AttributeNames::placeholder).value_or(String {}));
  494. m_placeholder_text_node->set_editable_text_node_owner(Badge<HTMLInputElement> {}, *this);
  495. MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
  496. MUST(element->append_child(*m_placeholder_element));
  497. m_inner_text_element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  498. MUST(m_inner_text_element->style_for_bindings()->set_property(CSS::PropertyID::Height, "1lh"sv));
  499. m_text_node = heap().allocate<DOM::Text>(realm(), document(), move(initial_value));
  500. if (m_type == TypeAttributeState::FileUpload) {
  501. // NOTE: file upload state is mutable, but we don't allow the text node to be modifed
  502. m_text_node->set_always_editable(false);
  503. } else {
  504. handle_readonly_attribute(attribute(HTML::AttributeNames::readonly));
  505. }
  506. m_text_node->set_editable_text_node_owner(Badge<HTMLInputElement> {}, *this);
  507. if (m_type == TypeAttributeState::Password)
  508. m_text_node->set_is_password_input({}, true);
  509. MUST(m_inner_text_element->append_child(*m_text_node));
  510. MUST(element->append_child(*m_inner_text_element));
  511. MUST(shadow_root->append_child(element));
  512. set_shadow_root(shadow_root);
  513. }
  514. void HTMLInputElement::create_color_input_shadow_tree()
  515. {
  516. auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
  517. auto color = value_sanitization_algorithm(m_value);
  518. auto border = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  519. MUST(border->set_attribute(HTML::AttributeNames::style, R"~~~(
  520. width: fit-content;
  521. height: fit-content;
  522. padding: 4px;
  523. border: 1px solid ButtonBorder;
  524. background-color: ButtonFace;
  525. )~~~"_string));
  526. m_color_well_element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  527. MUST(m_color_well_element->set_attribute(HTML::AttributeNames::style, R"~~~(
  528. width: 24px;
  529. height: 24px;
  530. border: 1px solid ButtonBorder;
  531. box-sizing: border-box;
  532. )~~~"_string));
  533. MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, color));
  534. MUST(border->append_child(*m_color_well_element));
  535. MUST(shadow_root->append_child(border));
  536. set_shadow_root(shadow_root);
  537. }
  538. void HTMLInputElement::did_receive_focus()
  539. {
  540. auto* browsing_context = document().browsing_context();
  541. if (!browsing_context)
  542. return;
  543. if (!m_text_node)
  544. return;
  545. browsing_context->set_cursor_position(DOM::Position::create(realm(), *m_text_node, 0));
  546. }
  547. void HTMLInputElement::did_lose_focus()
  548. {
  549. commit_pending_changes();
  550. }
  551. void HTMLInputElement::attribute_changed(FlyString const& name, Optional<String> const& value)
  552. {
  553. HTMLElement::attribute_changed(name, value);
  554. if (name == HTML::AttributeNames::checked) {
  555. if (!value.has_value()) {
  556. // When the checked content attribute is removed, if the control does not have dirty checkedness,
  557. // the user agent must set the checkedness of the element to false.
  558. if (!m_dirty_checkedness)
  559. set_checked(false, ChangeSource::Programmatic);
  560. } else {
  561. // When the checked content attribute is added, if the control does not have dirty checkedness,
  562. // the user agent must set the checkedness of the element to true
  563. if (!m_dirty_checkedness)
  564. set_checked(true, ChangeSource::Programmatic);
  565. }
  566. } else if (name == HTML::AttributeNames::type) {
  567. m_type = parse_type_attribute(value.value_or(String {}));
  568. } else if (name == HTML::AttributeNames::value) {
  569. if (!value.has_value()) {
  570. if (!m_dirty_value) {
  571. m_value = String {};
  572. update_placeholder_visibility();
  573. if (type_state() == TypeAttributeState::Color && m_color_well_element)
  574. MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value));
  575. }
  576. } else {
  577. if (!m_dirty_value) {
  578. m_value = value_sanitization_algorithm(*value);
  579. update_placeholder_visibility();
  580. if (type_state() == TypeAttributeState::Color && m_color_well_element)
  581. MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value));
  582. }
  583. }
  584. } else if (name == HTML::AttributeNames::placeholder) {
  585. if (m_placeholder_text_node)
  586. m_placeholder_text_node->set_data(value.value_or(String {}));
  587. } else if (name == HTML::AttributeNames::readonly) {
  588. handle_readonly_attribute(value);
  589. }
  590. }
  591. HTMLInputElement::TypeAttributeState HTMLInputElement::parse_type_attribute(StringView type)
  592. {
  593. #define __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE(keyword, state) \
  594. if (type.equals_ignoring_ascii_case(#keyword##sv)) \
  595. return HTMLInputElement::TypeAttributeState::state;
  596. ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTES
  597. #undef __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE
  598. // The missing value default and the invalid value default are the Text state.
  599. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:missing-value-default
  600. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:invalid-value-default
  601. return HTMLInputElement::TypeAttributeState::Text;
  602. }
  603. StringView HTMLInputElement::type() const
  604. {
  605. // FIXME: This should probably be `Reflect` in the IDL.
  606. switch (m_type) {
  607. #define __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE(keyword, state) \
  608. case TypeAttributeState::state: \
  609. return #keyword##sv;
  610. ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTES
  611. #undef __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE
  612. }
  613. VERIFY_NOT_REACHED();
  614. }
  615. WebIDL::ExceptionOr<void> HTMLInputElement::set_type(String const& type)
  616. {
  617. return set_attribute(HTML::AttributeNames::type, type);
  618. }
  619. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-simple-colour
  620. static bool is_valid_simple_color(StringView value)
  621. {
  622. // if it is exactly seven characters long,
  623. if (value.length() != 7)
  624. return false;
  625. // and the first character is a U+0023 NUMBER SIGN character (#),
  626. if (!value.starts_with('#'))
  627. return false;
  628. // and the remaining six characters are all ASCII hex digits
  629. for (size_t i = 1; i < value.length(); i++)
  630. if (!is_ascii_hex_digit(value[i]))
  631. return false;
  632. return true;
  633. }
  634. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-time-string
  635. static bool is_valid_time_string(StringView value)
  636. {
  637. // A string is a valid time string representing an hour hour, a minute minute, and a second second if it consists of the following components in the given order:
  638. // 1. Two ASCII digits, representing hour, in the range 0 ≤ hour ≤ 23
  639. // 2. A U+003A COLON character (:)
  640. // 3. Two ASCII digits, representing minute, in the range 0 ≤ minute ≤ 59
  641. // 4. If second is nonzero, or optionally if second is zero:
  642. // 1. A U+003A COLON character (:)
  643. // 2. Two ASCII digits, representing the integer part of second, in the range 0 ≤ s ≤ 59
  644. // 3. If second is not an integer, or optionally if second is an integer:
  645. // 1. A U+002E FULL STOP character (.)
  646. // 2. One, two, or three ASCII digits, representing the fractional part of second
  647. auto parts = value.split_view(':');
  648. if (parts.size() != 2 || parts.size() != 3)
  649. return false;
  650. if (parts[0].length() != 2)
  651. return false;
  652. auto hour = (parse_ascii_digit(parts[0][0]) * 10) + parse_ascii_digit(parts[0][1]);
  653. if (hour > 23)
  654. return false;
  655. if (parts[1].length() != 2)
  656. return false;
  657. auto minute = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]);
  658. if (minute > 59)
  659. return false;
  660. if (parts.size() == 2)
  661. return true;
  662. if (parts[2].length() < 2)
  663. return false;
  664. auto second = (parse_ascii_digit(parts[2][0]) * 10) + parse_ascii_digit(parts[2][1]);
  665. if (second > 59)
  666. return false;
  667. if (parts[2].length() == 2)
  668. return true;
  669. auto second_parts = parts[2].split_view('.');
  670. if (second_parts.size() != 2)
  671. return false;
  672. if (second_parts[1].length() < 1 || second_parts[1].length() > 3)
  673. return false;
  674. for (auto digit : second_parts[1])
  675. if (!is_ascii_digit(digit))
  676. return false;
  677. return true;
  678. }
  679. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#week-number-of-the-last-day
  680. static u32 week_number_of_the_last_day(u64)
  681. {
  682. // FIXME: sometimes return 53 (!)
  683. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#weeks
  684. return 52;
  685. }
  686. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-week-string
  687. static bool is_valid_week_string(StringView value)
  688. {
  689. // A string is a valid week string representing a week-year year and week week if it consists of the following components in the given order:
  690. // 1. Four or more ASCII digits, representing year, where year > 0
  691. // 2. A U+002D HYPHEN-MINUS character (-)
  692. // 3. A U+0057 LATIN CAPITAL LETTER W character (W)
  693. // 4. Two ASCII digits, representing the week week, in the range 1 ≤ week ≤ maxweek, where maxweek is the week number of the last day of week-year year
  694. auto parts = value.split_view('-');
  695. if (parts.size() != 2)
  696. return false;
  697. if (parts[0].length() < 4)
  698. return false;
  699. for (auto digit : parts[0])
  700. if (!is_ascii_digit(digit))
  701. return false;
  702. if (parts[1].length() != 3)
  703. return false;
  704. if (!parts[1].starts_with('W'))
  705. return false;
  706. if (!is_ascii_digit(parts[1][1]))
  707. return false;
  708. if (!is_ascii_digit(parts[1][2]))
  709. return false;
  710. u64 year = 0;
  711. for (auto d : parts[0]) {
  712. year *= 10;
  713. year += parse_ascii_digit(d);
  714. }
  715. auto week = (parse_ascii_digit(parts[1][1]) * 10) + parse_ascii_digit(parts[1][2]);
  716. return week >= 1 && week <= week_number_of_the_last_day(year);
  717. }
  718. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-month-string
  719. static bool is_valid_month_string(StringView value)
  720. {
  721. // A string is a valid month string representing a year year and month month if it consists of the following components in the given order:
  722. // 1. Four or more ASCII digits, representing year, where year > 0
  723. // 2. A U+002D HYPHEN-MINUS character (-)
  724. // 3. Two ASCII digits, representing the month month, in the range 1 ≤ month ≤ 12
  725. auto parts = value.split_view('-');
  726. if (parts.size() != 2)
  727. return false;
  728. if (parts[0].length() < 4)
  729. return false;
  730. for (auto digit : parts[0])
  731. if (!is_ascii_digit(digit))
  732. return false;
  733. if (parts[1].length() != 2)
  734. return false;
  735. if (!is_ascii_digit(parts[1][0]))
  736. return false;
  737. if (!is_ascii_digit(parts[1][1]))
  738. return false;
  739. auto month = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]);
  740. return month >= 1 && month <= 12;
  741. }
  742. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string
  743. static bool is_valid_date_string(StringView value)
  744. {
  745. // A string is a valid date string representing a year year, month month, and day day if it consists of the following components in the given order:
  746. // 1. A valid month string, representing year and month
  747. // 2. A U+002D HYPHEN-MINUS character (-)
  748. // 3. Two ASCII digits, representing day, in the range 1 ≤ day ≤ maxday where maxday is the number of days in the month month and year year
  749. auto parts = value.split_view('-');
  750. if (parts.size() != 3)
  751. return false;
  752. if (!is_valid_month_string(DeprecatedString::formatted("{}-{}", parts[0], parts[1])))
  753. return false;
  754. if (parts[2].length() != 2)
  755. return false;
  756. i64 year = 0;
  757. for (auto d : parts[0]) {
  758. year *= 10;
  759. year += parse_ascii_digit(d);
  760. }
  761. auto month = (parse_ascii_digit(parts[1][0]) * 10) + parse_ascii_digit(parts[1][1]);
  762. i64 day = (parse_ascii_digit(parts[2][0]) * 10) + parse_ascii_digit(parts[2][1]);
  763. return day >= 1 && day <= AK::days_in_month(year, month);
  764. }
  765. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-local-date-and-time-string
  766. static bool is_valid_local_date_and_time_string(StringView value)
  767. {
  768. auto parts_split_by_T = value.split_view('T');
  769. if (parts_split_by_T.size() == 2)
  770. return is_valid_date_string(parts_split_by_T[0]) && is_valid_time_string(parts_split_by_T[1]);
  771. auto parts_split_by_space = value.split_view(' ');
  772. if (parts_split_by_space.size() == 2)
  773. return is_valid_date_string(parts_split_by_space[0]) && is_valid_time_string(parts_split_by_space[1]);
  774. return false;
  775. }
  776. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-normalised-local-date-and-time-string
  777. static String normalize_local_date_and_time_string(String const& value)
  778. {
  779. VERIFY(value.count(" "sv) == 1);
  780. return MUST(value.replace(" "sv, "T"sv, ReplaceMode::FirstOnly));
  781. }
  782. // https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm
  783. String HTMLInputElement::value_sanitization_algorithm(String const& value) const
  784. {
  785. if (type_state() == HTMLInputElement::TypeAttributeState::Text || type_state() == HTMLInputElement::TypeAttributeState::Search || type_state() == HTMLInputElement::TypeAttributeState::Telephone || type_state() == HTMLInputElement::TypeAttributeState::Password) {
  786. // Strip newlines from the value.
  787. if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) {
  788. StringBuilder builder;
  789. for (auto c : value.bytes_as_string_view()) {
  790. if (c != '\r' && c != '\n')
  791. builder.append(c);
  792. }
  793. return MUST(builder.to_string());
  794. }
  795. } else if (type_state() == HTMLInputElement::TypeAttributeState::URL) {
  796. // Strip newlines from the value, then strip leading and trailing ASCII whitespace from the value.
  797. if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) {
  798. StringBuilder builder;
  799. for (auto c : value.bytes_as_string_view()) {
  800. if (c != '\r' && c != '\n')
  801. builder.append(c);
  802. }
  803. return MUST(String::from_utf8(builder.string_view().trim(Infra::ASCII_WHITESPACE)));
  804. }
  805. } else if (type_state() == HTMLInputElement::TypeAttributeState::Email) {
  806. // https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email):value-sanitization-algorithm
  807. // FIXME: handle the `multiple` attribute
  808. // Strip newlines from the value, then strip leading and trailing ASCII whitespace from the value.
  809. if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) {
  810. StringBuilder builder;
  811. for (auto c : value.bytes_as_string_view()) {
  812. if (c != '\r' && c != '\n')
  813. builder.append(c);
  814. }
  815. return MUST(String::from_utf8(builder.string_view().trim(Infra::ASCII_WHITESPACE)));
  816. }
  817. } else if (type_state() == HTMLInputElement::TypeAttributeState::Number) {
  818. // If the value of the element is not a valid floating-point number, then set it to the empty string instead.
  819. auto maybe_value = parse_floating_point_number(value);
  820. if (!maybe_value.has_value() || !isfinite(maybe_value.value()))
  821. return String {};
  822. } else if (type_state() == HTMLInputElement::TypeAttributeState::Date) {
  823. // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):value-sanitization-algorithm
  824. if (!is_valid_date_string(value))
  825. return String {};
  826. } else if (type_state() == HTMLInputElement::TypeAttributeState::Month) {
  827. // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):value-sanitization-algorithm
  828. if (!is_valid_month_string(value))
  829. return String {};
  830. } else if (type_state() == HTMLInputElement::TypeAttributeState::Week) {
  831. // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm
  832. if (!is_valid_week_string(value))
  833. return String {};
  834. } else if (type_state() == HTMLInputElement::TypeAttributeState::Time) {
  835. // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):value-sanitization-algorithm
  836. if (!is_valid_time_string(value))
  837. return String {};
  838. } else if (type_state() == HTMLInputElement::TypeAttributeState::LocalDateAndTime) {
  839. // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):value-sanitization-algorithm
  840. if (is_valid_local_date_and_time_string(value))
  841. return normalize_local_date_and_time_string(value);
  842. return String {};
  843. } else if (type_state() == HTMLInputElement::TypeAttributeState::Range) {
  844. // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):value-sanitization-algorithm
  845. // If the value of the element is not a valid floating-point number, then set it to the best representation, as a floating-point number, of the default value.
  846. auto maybe_value = parse_floating_point_number(value);
  847. if (!maybe_value.has_value() || !isfinite(maybe_value.value())) {
  848. // The default value is the minimum plus half the difference between the minimum and the maximum, unless the maximum is less than the minimum, in which case the default value is the minimum.
  849. auto min = parse_floating_point_number(get_attribute(HTML::AttributeNames::min).value_or("0"_string)).value_or(0);
  850. auto max = parse_floating_point_number(get_attribute(HTML::AttributeNames::max).value_or("1"_string)).value_or(1);
  851. if (max > min)
  852. return JS::number_to_string(min);
  853. return JS::number_to_string(min + (max - min) / 2);
  854. }
  855. } else if (type_state() == HTMLInputElement::TypeAttributeState::Color) {
  856. // https://html.spec.whatwg.org/multipage/input.html#color-state-(type=color):value-sanitization-algorithm
  857. // If the value of the element is a valid simple color, then set it to the value of the element converted to ASCII lowercase;
  858. if (is_valid_simple_color(value))
  859. return MUST(Infra::to_ascii_lowercase(value));
  860. // otherwise, set it to the string "#000000".
  861. return "#000000"_string;
  862. }
  863. return value;
  864. }
  865. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-form-reset-control
  866. void HTMLInputElement::reset_algorithm()
  867. {
  868. // The reset algorithm for input elements is to set the dirty value flag and dirty checkedness flag back to false,
  869. m_dirty_value = false;
  870. m_dirty_checkedness = false;
  871. // set the value of the element to the value of the value content attribute, if there is one, or the empty string otherwise,
  872. m_value = get_attribute(AttributeNames::value).value_or(String {});
  873. // set the checkedness of the element to true if the element has a checked content attribute and false if it does not,
  874. m_checked = has_attribute(AttributeNames::checked);
  875. // empty the list of selected files,
  876. m_selected_files = FileAPI::FileList::create(realm(), {});
  877. // and then invoke the value sanitization algorithm, if the type attribute's current state defines one.
  878. m_value = value_sanitization_algorithm(m_value);
  879. if (m_text_node) {
  880. m_text_node->set_data(m_value);
  881. update_placeholder_visibility();
  882. }
  883. if (type_state() == TypeAttributeState::Color && m_color_well_element)
  884. MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value));
  885. }
  886. void HTMLInputElement::form_associated_element_was_inserted()
  887. {
  888. create_shadow_tree_if_needed();
  889. }
  890. void HTMLInputElement::form_associated_element_was_removed(DOM::Node*)
  891. {
  892. set_shadow_root(nullptr);
  893. }
  894. // https://html.spec.whatwg.org/multipage/input.html#radio-button-group
  895. static bool is_in_same_radio_button_group(HTML::HTMLInputElement const& a, HTML::HTMLInputElement const& b)
  896. {
  897. auto non_empty_equals = [](auto const& value_a, auto const& value_b) {
  898. return !value_a.is_empty() && value_a == value_b;
  899. };
  900. // The radio button group that contains an input element a also contains all the
  901. // other input elements b that fulfill all of the following conditions:
  902. return (
  903. // - Both a and b are in the same tree.
  904. // - The input element b's type attribute is in the Radio Button state.
  905. a.type_state() == b.type_state()
  906. && b.type_state() == HTMLInputElement::TypeAttributeState::RadioButton
  907. // - Either a and b have the same form owner, or they both have no form owner.
  908. && a.form() == b.form()
  909. // - They both have a name attribute, their name attributes are not empty, and the
  910. // value of a's name attribute equals the value of b's name attribute.
  911. && a.has_attribute(HTML::AttributeNames::name)
  912. && b.has_attribute(HTML::AttributeNames::name)
  913. && non_empty_equals(a.name(), b.name()));
  914. }
  915. // https://html.spec.whatwg.org/multipage/input.html#radio-button-state-(type=radio)
  916. void HTMLInputElement::set_checked_within_group()
  917. {
  918. if (checked())
  919. return;
  920. set_checked(true, ChangeSource::User);
  921. // No point iterating the tree if we have an empty name.
  922. auto name = this->name();
  923. if (name.is_empty())
  924. return;
  925. document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
  926. if (element.checked() && &element != this && is_in_same_radio_button_group(*this, element))
  927. element.set_checked(false, ChangeSource::User);
  928. return IterationDecision::Continue;
  929. });
  930. }
  931. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-pre-activation-behavior
  932. void HTMLInputElement::legacy_pre_activation_behavior()
  933. {
  934. m_before_legacy_pre_activation_behavior_checked = checked();
  935. m_before_legacy_pre_activation_behavior_indeterminate = indeterminate();
  936. // 1. If this element's type attribute is in the Checkbox state, then set
  937. // this element's checkedness to its opposite value (i.e. true if it is
  938. // false, false if it is true) and set this element's indeterminate IDL
  939. // attribute to false.
  940. if (type_state() == TypeAttributeState::Checkbox) {
  941. set_checked(!checked(), ChangeSource::User);
  942. set_indeterminate(false);
  943. }
  944. // 2. If this element's type attribute is in the Radio Button state, then
  945. // get a reference to the element in this element's radio button group that
  946. // has its checkedness set to true, if any, and then set this element's
  947. // checkedness to true.
  948. if (type_state() == TypeAttributeState::RadioButton) {
  949. DeprecatedString name = this->name();
  950. document().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
  951. if (element.checked() && is_in_same_radio_button_group(*this, element)) {
  952. m_legacy_pre_activation_behavior_checked_element_in_group = &element;
  953. return IterationDecision::Break;
  954. }
  955. return IterationDecision::Continue;
  956. });
  957. set_checked_within_group();
  958. }
  959. }
  960. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-canceled-activation-behavior
  961. void HTMLInputElement::legacy_cancelled_activation_behavior()
  962. {
  963. // 1. If the element's type attribute is in the Checkbox state, then set the
  964. // element's checkedness and the element's indeterminate IDL attribute back
  965. // to the values they had before the legacy-pre-activation behavior was run.
  966. if (type_state() == TypeAttributeState::Checkbox) {
  967. set_checked(m_before_legacy_pre_activation_behavior_checked, ChangeSource::Programmatic);
  968. set_indeterminate(m_before_legacy_pre_activation_behavior_indeterminate);
  969. }
  970. // 2. If this element 's type attribute is in the Radio Button state, then
  971. // if the element to which a reference was obtained in the
  972. // legacy-pre-activation behavior, if any, is still in what is now this
  973. // element' s radio button group, if it still has one, and if so, setting
  974. // that element 's checkedness to true; or else, if there was no such
  975. // element, or that element is no longer in this element' s radio button
  976. // group, or if this element no longer has a radio button group, setting
  977. // this element's checkedness to false.
  978. if (type_state() == TypeAttributeState::RadioButton) {
  979. bool did_reselect_previous_element = false;
  980. if (m_legacy_pre_activation_behavior_checked_element_in_group) {
  981. auto& element_in_group = *m_legacy_pre_activation_behavior_checked_element_in_group;
  982. if (is_in_same_radio_button_group(*this, element_in_group)) {
  983. element_in_group.set_checked_within_group();
  984. did_reselect_previous_element = true;
  985. }
  986. m_legacy_pre_activation_behavior_checked_element_in_group = nullptr;
  987. }
  988. if (!did_reselect_previous_element)
  989. set_checked(false, ChangeSource::User);
  990. }
  991. }
  992. void HTMLInputElement::legacy_cancelled_activation_behavior_was_not_called()
  993. {
  994. m_legacy_pre_activation_behavior_checked_element_in_group = nullptr;
  995. }
  996. // https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex
  997. i32 HTMLInputElement::default_tab_index_value() const
  998. {
  999. // See the base function for the spec comments.
  1000. return 0;
  1001. }
  1002. // https://html.spec.whatwg.org/multipage/input.html#the-size-attribute
  1003. unsigned HTMLInputElement::size() const
  1004. {
  1005. // The size IDL attribute is limited to only positive numbers and has a default value of 20.
  1006. auto maybe_size_string = get_attribute(HTML::AttributeNames::size);
  1007. if (maybe_size_string.has_value()) {
  1008. auto maybe_size = parse_non_negative_integer(maybe_size_string.value());
  1009. if (maybe_size.has_value())
  1010. return maybe_size.value();
  1011. }
  1012. return 20;
  1013. }
  1014. WebIDL::ExceptionOr<void> HTMLInputElement::set_size(unsigned value)
  1015. {
  1016. return set_attribute(HTML::AttributeNames::size, MUST(String::number(value)));
  1017. }
  1018. // https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasnumber
  1019. WebIDL::ExceptionOr<double> HTMLInputElement::value_as_number() const
  1020. {
  1021. // On getting, if the valueAsNumber attribute does not apply, as defined for the input element's type attribute's current state, then return a Not-a-Number (NaN) value.
  1022. if (type_state() != TypeAttributeState::Date || type_state() != TypeAttributeState::Month || type_state() != TypeAttributeState::Week || type_state() != TypeAttributeState::Time || type_state() != TypeAttributeState::LocalDateAndTime || type_state() != TypeAttributeState::Number || type_state() != TypeAttributeState::Range)
  1023. return NAN;
  1024. // Otherwise, run the algorithm to convert a string to a number defined for that state to the element's value; if the algorithm returned a number, then return it, otherwise, return a Not-a-Number (NaN) value.
  1025. return value().bytes_as_string_view().to_double().value_or(NAN);
  1026. }
  1027. // https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasnumber
  1028. WebIDL::ExceptionOr<void> HTMLInputElement::set_value_as_number(double value)
  1029. {
  1030. // On setting, if the new value is infinite, then throw a TypeError exception.
  1031. if (!isfinite(value))
  1032. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "valueAsNumber: Value is infinite"sv };
  1033. // Otherwise, if the valueAsNumber attribute does not apply, as defined for the input element's type attribute's current state, then throw an "InvalidStateError" DOMException.
  1034. if (type_state() != TypeAttributeState::Date || type_state() != TypeAttributeState::Month || type_state() != TypeAttributeState::Week || type_state() != TypeAttributeState::Time || type_state() != TypeAttributeState::LocalDateAndTime || type_state() != TypeAttributeState::Number || type_state() != TypeAttributeState::Range)
  1035. return WebIDL::InvalidStateError::create(realm(), "valueAsNumber: Invalid input type used"_fly_string);
  1036. // Otherwise, if the new value is a Not-a-Number (NaN) value, then set the value of the element to the empty string.
  1037. if (value == NAN) {
  1038. m_value = String {};
  1039. return {};
  1040. }
  1041. // Otherwise, run the algorithm to convert a number to a string, as defined for that state, on the new value, and set the value of the element to the resulting string.
  1042. m_value = MUST(String::number(value));
  1043. return {};
  1044. }
  1045. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity
  1046. WebIDL::ExceptionOr<bool> HTMLInputElement::check_validity()
  1047. {
  1048. dbgln("(STUBBED) HTMLInputElement::check_validity(). Called on: {}", debug_description());
  1049. return true;
  1050. }
  1051. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-reportvalidity
  1052. WebIDL::ExceptionOr<bool> HTMLInputElement::report_validity()
  1053. {
  1054. dbgln("(STUBBED) HTMLInputElement::report_validity(). Called on: {}", debug_description());
  1055. return true;
  1056. }
  1057. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-setcustomvalidity
  1058. void HTMLInputElement::set_custom_validity(String const& error)
  1059. {
  1060. dbgln("(STUBBED) HTMLInputElement::set_custom_validity(error={}). Called on: {}", error, debug_description());
  1061. return;
  1062. }
  1063. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-select
  1064. WebIDL::ExceptionOr<void> HTMLInputElement::select()
  1065. {
  1066. dbgln("(STUBBED) HTMLInputElement::select(). Called on: {}", debug_description());
  1067. return {};
  1068. }
  1069. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-textarea/input-setselectionrange
  1070. WebIDL::ExceptionOr<void> HTMLInputElement::set_selection_range(u32 start, u32 end, Optional<String> const& direction)
  1071. {
  1072. dbgln("(STUBBED) HTMLInputElement::set_selection_range(start={}, end={}, direction='{}'). Called on: {}", start, end, direction, debug_description());
  1073. return {};
  1074. }
  1075. Optional<ARIA::Role> HTMLInputElement::default_role() const
  1076. {
  1077. // https://www.w3.org/TR/html-aria/#el-input-button
  1078. if (type_state() == TypeAttributeState::Button)
  1079. return ARIA::Role::button;
  1080. // https://www.w3.org/TR/html-aria/#el-input-checkbox
  1081. if (type_state() == TypeAttributeState::Checkbox)
  1082. return ARIA::Role::checkbox;
  1083. // https://www.w3.org/TR/html-aria/#el-input-email
  1084. if (type_state() == TypeAttributeState::Email && !has_attribute(AttributeNames::list))
  1085. return ARIA::Role::textbox;
  1086. // https://www.w3.org/TR/html-aria/#el-input-image
  1087. if (type_state() == TypeAttributeState::ImageButton)
  1088. return ARIA::Role::button;
  1089. // https://www.w3.org/TR/html-aria/#el-input-number
  1090. if (type_state() == TypeAttributeState::Number)
  1091. return ARIA::Role::spinbutton;
  1092. // https://www.w3.org/TR/html-aria/#el-input-radio
  1093. if (type_state() == TypeAttributeState::RadioButton)
  1094. return ARIA::Role::radio;
  1095. // https://www.w3.org/TR/html-aria/#el-input-range
  1096. if (type_state() == TypeAttributeState::Range)
  1097. return ARIA::Role::slider;
  1098. // https://www.w3.org/TR/html-aria/#el-input-reset
  1099. if (type_state() == TypeAttributeState::ResetButton)
  1100. return ARIA::Role::button;
  1101. // https://www.w3.org/TR/html-aria/#el-input-text-list
  1102. if ((type_state() == TypeAttributeState::Text
  1103. || type_state() == TypeAttributeState::Search
  1104. || type_state() == TypeAttributeState::Telephone
  1105. || type_state() == TypeAttributeState::URL
  1106. || type_state() == TypeAttributeState::Email)
  1107. && has_attribute(AttributeNames::list))
  1108. return ARIA::Role::combobox;
  1109. // https://www.w3.org/TR/html-aria/#el-input-search
  1110. if (type_state() == TypeAttributeState::Search && !has_attribute(AttributeNames::list))
  1111. return ARIA::Role::textbox;
  1112. // https://www.w3.org/TR/html-aria/#el-input-submit
  1113. if (type_state() == TypeAttributeState::SubmitButton)
  1114. return ARIA::Role::button;
  1115. // https://www.w3.org/TR/html-aria/#el-input-tel
  1116. if (type_state() == TypeAttributeState::Telephone)
  1117. return ARIA::Role::textbox;
  1118. // https://www.w3.org/TR/html-aria/#el-input-text
  1119. if (type_state() == TypeAttributeState::Text && !has_attribute(AttributeNames::list))
  1120. return ARIA::Role::textbox;
  1121. // https://www.w3.org/TR/html-aria/#el-input-url
  1122. if (type_state() == TypeAttributeState::URL && !has_attribute(AttributeNames::list))
  1123. return ARIA::Role::textbox;
  1124. // https://www.w3.org/TR/html-aria/#el-input-color
  1125. // https://www.w3.org/TR/html-aria/#el-input-date
  1126. // https://www.w3.org/TR/html-aria/#el-input-datetime-local
  1127. // https://www.w3.org/TR/html-aria/#el-input-file
  1128. // https://www.w3.org/TR/html-aria/#el-input-hidden
  1129. // https://www.w3.org/TR/html-aria/#el-input-month
  1130. // https://www.w3.org/TR/html-aria/#el-input-password
  1131. // https://www.w3.org/TR/html-aria/#el-input-time
  1132. // https://www.w3.org/TR/html-aria/#el-input-week
  1133. return {};
  1134. }
  1135. bool HTMLInputElement::is_button() const
  1136. {
  1137. // https://html.spec.whatwg.org/multipage/input.html#submit-button-state-(type=submit):concept-button
  1138. // https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):concept-button
  1139. // https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type=reset):concept-button
  1140. // https://html.spec.whatwg.org/multipage/input.html#button-state-(type=button):concept-button
  1141. return type_state() == TypeAttributeState::SubmitButton
  1142. || type_state() == TypeAttributeState::ImageButton
  1143. || type_state() == TypeAttributeState::ResetButton
  1144. || type_state() == TypeAttributeState::Button;
  1145. }
  1146. bool HTMLInputElement::is_submit_button() const
  1147. {
  1148. // https://html.spec.whatwg.org/multipage/input.html#submit-button-state-(type=submit):concept-submit-button
  1149. // https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):concept-submit-button
  1150. return type_state() == TypeAttributeState::SubmitButton
  1151. || type_state() == TypeAttributeState::ImageButton;
  1152. }
  1153. bool HTMLInputElement::has_activation_behavior() const
  1154. {
  1155. return true;
  1156. }
  1157. void HTMLInputElement::activation_behavior(DOM::Event const&)
  1158. {
  1159. // The activation behavior for input elements are these steps:
  1160. // FIXME: 1. If this element is not mutable and is not in the Checkbox state and is not in the Radio state, then return.
  1161. // 2. Run this element's input activation behavior, if any, and do nothing otherwise.
  1162. run_input_activation_behavior().release_value_but_fixme_should_propagate_errors();
  1163. }
  1164. bool HTMLInputElement::has_input_activation_behavior() const
  1165. {
  1166. switch (type_state()) {
  1167. case TypeAttributeState::Checkbox:
  1168. case TypeAttributeState::Color:
  1169. case TypeAttributeState::FileUpload:
  1170. case TypeAttributeState::ImageButton:
  1171. case TypeAttributeState::RadioButton:
  1172. case TypeAttributeState::ResetButton:
  1173. case TypeAttributeState::SubmitButton:
  1174. return true;
  1175. default:
  1176. return false;
  1177. }
  1178. }
  1179. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:event-change-2
  1180. bool HTMLInputElement::change_event_applies() const
  1181. {
  1182. switch (type_state()) {
  1183. case TypeAttributeState::Checkbox:
  1184. case TypeAttributeState::Color:
  1185. case TypeAttributeState::Date:
  1186. case TypeAttributeState::Email:
  1187. case TypeAttributeState::FileUpload:
  1188. case TypeAttributeState::LocalDateAndTime:
  1189. case TypeAttributeState::Month:
  1190. case TypeAttributeState::Number:
  1191. case TypeAttributeState::Password:
  1192. case TypeAttributeState::RadioButton:
  1193. case TypeAttributeState::Range:
  1194. case TypeAttributeState::Search:
  1195. case TypeAttributeState::Telephone:
  1196. case TypeAttributeState::Text:
  1197. case TypeAttributeState::Time:
  1198. case TypeAttributeState::URL:
  1199. case TypeAttributeState::Week:
  1200. return true;
  1201. default:
  1202. return false;
  1203. }
  1204. }
  1205. }