HTMLInputElement.cpp 119 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670
  1. /*
  2. * Copyright (c) 2018-2023, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2022, Adam Hodgen <ant1441@gmail.com>
  4. * Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org>
  5. * Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
  6. * Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
  7. * Copyright (c) 2024, Jelle Raaijmakers <jelle@ladybird.org>
  8. * Copyright (c) 2024, Fernando Kiotheka <fer@k6a.dev>
  9. *
  10. * SPDX-License-Identifier: BSD-2-Clause
  11. */
  12. #include <LibJS/Runtime/Date.h>
  13. #include <LibJS/Runtime/NativeFunction.h>
  14. #include <LibWeb/Bindings/HTMLInputElementPrototype.h>
  15. #include <LibWeb/Bindings/PrincipalHostDefined.h>
  16. #include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
  17. #include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
  18. #include <LibWeb/CSS/StyleValues/LengthStyleValue.h>
  19. #include <LibWeb/DOM/Document.h>
  20. #include <LibWeb/DOM/ElementFactory.h>
  21. #include <LibWeb/DOM/Event.h>
  22. #include <LibWeb/DOM/IDLEventListener.h>
  23. #include <LibWeb/DOM/ShadowRoot.h>
  24. #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
  25. #include <LibWeb/HTML/BrowsingContext.h>
  26. #include <LibWeb/HTML/Dates.h>
  27. #include <LibWeb/HTML/DecodedImageData.h>
  28. #include <LibWeb/HTML/EventNames.h>
  29. #include <LibWeb/HTML/HTMLDivElement.h>
  30. #include <LibWeb/HTML/HTMLFormElement.h>
  31. #include <LibWeb/HTML/HTMLInputElement.h>
  32. #include <LibWeb/HTML/Numbers.h>
  33. #include <LibWeb/HTML/Parser/HTMLParser.h>
  34. #include <LibWeb/HTML/Scripting/Environments.h>
  35. #include <LibWeb/HTML/SelectedFile.h>
  36. #include <LibWeb/HTML/SharedResourceRequest.h>
  37. #include <LibWeb/HTML/ValidityState.h>
  38. #include <LibWeb/HTML/Window.h>
  39. #include <LibWeb/Infra/CharacterTypes.h>
  40. #include <LibWeb/Infra/Strings.h>
  41. #include <LibWeb/Layout/BlockContainer.h>
  42. #include <LibWeb/Layout/CheckBox.h>
  43. #include <LibWeb/Layout/ImageBox.h>
  44. #include <LibWeb/Layout/RadioButton.h>
  45. #include <LibWeb/MimeSniff/MimeType.h>
  46. #include <LibWeb/MimeSniff/Resource.h>
  47. #include <LibWeb/Namespace.h>
  48. #include <LibWeb/Page/Page.h>
  49. #include <LibWeb/Painting/PaintableBox.h>
  50. #include <LibWeb/Selection/Selection.h>
  51. #include <LibWeb/UIEvents/EventNames.h>
  52. #include <LibWeb/UIEvents/MouseEvent.h>
  53. #include <LibWeb/WebIDL/DOMException.h>
  54. #include <LibWeb/WebIDL/ExceptionOr.h>
  55. namespace Web::HTML {
  56. GC_DEFINE_ALLOCATOR(HTMLInputElement);
  57. HTMLInputElement::HTMLInputElement(DOM::Document& document, DOM::QualifiedName qualified_name)
  58. : HTMLElement(document, move(qualified_name))
  59. {
  60. }
  61. HTMLInputElement::~HTMLInputElement() = default;
  62. void HTMLInputElement::initialize(JS::Realm& realm)
  63. {
  64. Base::initialize(realm);
  65. WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLInputElement);
  66. }
  67. void HTMLInputElement::visit_edges(Cell::Visitor& visitor)
  68. {
  69. Base::visit_edges(visitor);
  70. visitor.visit(m_inner_text_element);
  71. visitor.visit(m_text_node);
  72. visitor.visit(m_placeholder_element);
  73. visitor.visit(m_placeholder_text_node);
  74. visitor.visit(m_color_well_element);
  75. visitor.visit(m_file_button);
  76. visitor.visit(m_file_label);
  77. visitor.visit(m_legacy_pre_activation_behavior_checked_element_in_group);
  78. visitor.visit(m_selected_files);
  79. visitor.visit(m_slider_runnable_track);
  80. visitor.visit(m_slider_progress_element);
  81. visitor.visit(m_slider_thumb);
  82. visitor.visit(m_resource_request);
  83. }
  84. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-validity
  85. GC::Ref<ValidityState const> HTMLInputElement::validity() const
  86. {
  87. auto& realm = this->realm();
  88. dbgln("FIXME: Implement validity attribute getter");
  89. return realm.create<ValidityState>(realm);
  90. }
  91. GC::Ptr<Layout::Node> HTMLInputElement::create_layout_node(CSS::StyleProperties style)
  92. {
  93. if (type_state() == TypeAttributeState::Hidden)
  94. return nullptr;
  95. // NOTE: Image inputs are `appearance: none` per the default UA style,
  96. // but we still need to create an ImageBox for them, or no image will get loaded.
  97. if (type_state() == TypeAttributeState::ImageButton) {
  98. return heap().allocate<Layout::ImageBox>(document(), *this, move(style), *this);
  99. }
  100. // https://drafts.csswg.org/css-ui/#appearance-switching
  101. // This specification introduces the appearance property to provide some control over this behavior.
  102. // In particular, using appearance: none allows authors to suppress the native appearance of widgets,
  103. // giving them a primitive appearance where CSS can be used to restyle them.
  104. if (style.appearance() == CSS::Appearance::None) {
  105. return Element::create_layout_node_for_display_type(document(), style.display(), style, this);
  106. }
  107. if (type_state() == TypeAttributeState::SubmitButton || type_state() == TypeAttributeState::Button || type_state() == TypeAttributeState::ResetButton)
  108. return heap().allocate<Layout::BlockContainer>(document(), this, move(style));
  109. if (type_state() == TypeAttributeState::Checkbox)
  110. return heap().allocate<Layout::CheckBox>(document(), *this, move(style));
  111. if (type_state() == TypeAttributeState::RadioButton)
  112. return heap().allocate<Layout::RadioButton>(document(), *this, move(style));
  113. return Element::create_layout_node_for_display_type(document(), style.display(), style, this);
  114. }
  115. void HTMLInputElement::adjust_computed_style(CSS::StyleProperties& style)
  116. {
  117. if (type_state() == TypeAttributeState::Hidden || type_state() == TypeAttributeState::SubmitButton || type_state() == TypeAttributeState::Button || type_state() == TypeAttributeState::ResetButton || type_state() == TypeAttributeState::ImageButton || type_state() == TypeAttributeState::Checkbox || type_state() == TypeAttributeState::RadioButton)
  118. return;
  119. // https://drafts.csswg.org/css-display-3/#unbox
  120. if (style.display().is_contents())
  121. style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::None)));
  122. // AD-HOC: We rewrite `display: inline` to `display: inline-block`.
  123. // This is required for the internal shadow tree to work correctly in layout.
  124. if (style.display().is_inline_outside() && style.display().is_flow_inside())
  125. style.set_property(CSS::PropertyID::Display, CSS::DisplayStyleValue::create(CSS::Display::from_short(CSS::Display::Short::InlineBlock)));
  126. if (type_state() != TypeAttributeState::FileUpload) {
  127. if (style.property(CSS::PropertyID::Width).has_auto())
  128. style.set_property(CSS::PropertyID::Width, CSS::LengthStyleValue::create(CSS::Length(size(), CSS::Length::Type::Ch)));
  129. }
  130. // NOTE: The following line-height check is done for web compatability and usability reasons.
  131. // FIXME: The "normal" line-height value should be calculated but assume 1.0 for now.
  132. double normal_line_height = 1.0;
  133. double current_line_height = style.line_height().to_double();
  134. if (is_single_line() && current_line_height < normal_line_height)
  135. style.set_property(CSS::PropertyID::LineHeight, CSS::CSSKeywordValue::create(CSS::Keyword::Normal));
  136. }
  137. void HTMLInputElement::set_checked(bool checked, ChangeSource change_source)
  138. {
  139. if (m_checked == checked)
  140. return;
  141. // The dirty checkedness flag must be initially set to false when the element is created,
  142. // and must be set to true whenever the user interacts with the control in a way that changes the checkedness.
  143. if (change_source == ChangeSource::User)
  144. m_dirty_checkedness = true;
  145. m_checked = checked;
  146. invalidate_style(DOM::StyleInvalidationReason::HTMLInputElementSetChecked);
  147. if (auto* paintable = this->paintable())
  148. paintable->set_needs_display();
  149. }
  150. void HTMLInputElement::set_checked_binding(bool checked)
  151. {
  152. if (type_state() == TypeAttributeState::RadioButton) {
  153. if (checked)
  154. set_checked_within_group();
  155. else
  156. set_checked(false, ChangeSource::Programmatic);
  157. } else {
  158. set_checked(checked, ChangeSource::Programmatic);
  159. }
  160. }
  161. // https://html.spec.whatwg.org/multipage/input.html#dom-input-indeterminate
  162. void HTMLInputElement::set_indeterminate(bool value)
  163. {
  164. // On setting, it must be set to the new value. It has no effect except for changing the appearance of checkbox controls.
  165. m_indeterminate = value;
  166. }
  167. // https://html.spec.whatwg.org/multipage/input.html#dom-input-files
  168. GC::Ptr<FileAPI::FileList> HTMLInputElement::files()
  169. {
  170. // On getting, if the IDL attribute applies, it must return a FileList object that represents the current selected files.
  171. // The same object must be returned until the list of selected files changes.
  172. // If the IDL attribute does not apply, then it must instead return null.
  173. if (m_type != TypeAttributeState::FileUpload)
  174. return nullptr;
  175. if (!m_selected_files)
  176. m_selected_files = FileAPI::FileList::create(realm());
  177. return m_selected_files;
  178. }
  179. // https://html.spec.whatwg.org/multipage/input.html#dom-input-files
  180. void HTMLInputElement::set_files(GC::Ptr<FileAPI::FileList> files)
  181. {
  182. // 1. If the IDL attribute does not apply or the given value is null, then return.
  183. if (m_type != TypeAttributeState::FileUpload || files == nullptr)
  184. return;
  185. // 2. Replace the element's selected files with the given value.
  186. m_selected_files = files;
  187. }
  188. // https://html.spec.whatwg.org/multipage/input.html#attr-input-accept
  189. FileFilter HTMLInputElement::parse_accept_attribute() const
  190. {
  191. FileFilter filter;
  192. // If specified, the attribute must consist of a set of comma-separated tokens, each of which must be an ASCII
  193. // case-insensitive match for one of the following:
  194. auto accept = get_attribute_value(HTML::AttributeNames::accept);
  195. accept.bytes_as_string_view().for_each_split_view(',', SplitBehavior::Nothing, [&](StringView value) {
  196. // The string "audio/*"
  197. // Indicates that sound files are accepted.
  198. if (value.equals_ignoring_ascii_case("audio/*"sv))
  199. filter.add_filter(FileFilter::FileType::Audio);
  200. // The string "video/*"
  201. // Indicates that video files are accepted.
  202. if (value.equals_ignoring_ascii_case("video/*"sv))
  203. filter.add_filter(FileFilter::FileType::Video);
  204. // The string "image/*"
  205. // Indicates that image files are accepted.
  206. if (value.equals_ignoring_ascii_case("image/*"sv))
  207. filter.add_filter(FileFilter::FileType::Image);
  208. // A valid MIME type string with no parameters
  209. // Indicates that files of the specified type are accepted.
  210. else if (auto mime_type = MimeSniff::MimeType::parse(value); mime_type.has_value() && mime_type->parameters().is_empty())
  211. filter.add_filter(FileFilter::MimeType { mime_type->essence() });
  212. // A string whose first character is a U+002E FULL STOP character (.)
  213. // Indicates that files with the specified file extension are accepted.
  214. else if (value.starts_with('.'))
  215. filter.add_filter(FileFilter::Extension { MUST(String::from_utf8(value.substring_view(1))) });
  216. });
  217. return filter;
  218. }
  219. // https://html.spec.whatwg.org/multipage/input.html#update-the-file-selection
  220. void HTMLInputElement::update_the_file_selection(GC::Ref<FileAPI::FileList> files)
  221. {
  222. // 1. Queue an element task on the user interaction task source given element and the following steps:
  223. queue_an_element_task(Task::Source::UserInteraction, [this, files] {
  224. // 1. Update element's selected files so that it represents the user's selection.
  225. this->set_files(files.ptr());
  226. // 2. Fire an event named input at the input element, with the bubbles and composed attributes initialized to true.
  227. auto input_event = DOM::Event::create(this->realm(), EventNames::input, { .bubbles = true, .composed = true });
  228. this->dispatch_event(input_event);
  229. // 3. Fire an event named change at the input element, with the bubbles attribute initialized to true.
  230. auto change_event = DOM::Event::create(this->realm(), EventNames::change, { .bubbles = true });
  231. this->dispatch_event(change_event);
  232. });
  233. }
  234. // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
  235. static void show_the_picker_if_applicable(HTMLInputElement& element)
  236. {
  237. // To show the picker, if applicable for an input element element:
  238. // 1. If element's relevant global object does not have transient activation, then return.
  239. auto& global_object = relevant_global_object(element);
  240. if (!is<HTML::Window>(global_object))
  241. return;
  242. auto& relevant_global_object = static_cast<HTML::Window&>(global_object);
  243. if (!relevant_global_object.has_transient_activation())
  244. return;
  245. // 2. If element is not mutable, then return.
  246. if (!element.is_mutable())
  247. return;
  248. // 3. Consume user activation given element's relevant global object.
  249. relevant_global_object.consume_user_activation();
  250. // 4. If element's type attribute is in the File Upload state, then run these steps in parallel:
  251. if (element.type_state() == HTMLInputElement::TypeAttributeState::FileUpload) {
  252. // NOTE: These steps cannot be fully implemented here, and must be done in the PageClient when the response comes back from the PageHost
  253. // 1. Optionally, wait until any prior execution of this algorithm has terminated.
  254. // 2. Display a prompt to the user requesting that the user specify some files.
  255. // If the multiple attribute is not set on element, there must be no more than one file selected; otherwise, any number may be selected.
  256. // 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.
  257. // 3. Wait for the user to have made their selection.
  258. // 4. If the user dismissed the prompt without changing their selection,
  259. // then queue an element task on the user interaction task source given element to fire an event named cancel at element,
  260. // with the bubbles attribute initialized to true.
  261. // 5. Otherwise, update the file selection for element.
  262. auto accepted_file_types = element.parse_accept_attribute();
  263. auto allow_multiple_files = element.has_attribute(HTML::AttributeNames::multiple) ? AllowMultipleFiles::Yes : AllowMultipleFiles::No;
  264. auto weak_element = element.make_weak_ptr<HTMLInputElement>();
  265. element.document().browsing_context()->top_level_browsing_context()->page().did_request_file_picker(weak_element, move(accepted_file_types), allow_multiple_files);
  266. return;
  267. }
  268. // 5. Otherwise, the user agent should show any relevant user interface for selecting a value for element,
  269. // 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.)
  270. // If such a user interface is shown, it must respect the requirements stated in the relevant parts of the specification for how element
  271. // behaves given its type attribute state. (For example, various sections describe restrictions on the resulting value string.)
  272. // This step can have side effects, such as closing other pickers that were previously shown by this algorithm.
  273. // (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.)
  274. if (element.type_state() == HTMLInputElement::TypeAttributeState::Color) {
  275. auto weak_element = element.make_weak_ptr<HTMLInputElement>();
  276. 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)));
  277. }
  278. }
  279. // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
  280. WebIDL::ExceptionOr<void> HTMLInputElement::show_picker()
  281. {
  282. // The showPicker() method steps are:
  283. // 1. If this is not mutable, then throw an "InvalidStateError" DOMException.
  284. if (!is_mutable())
  285. return WebIDL::InvalidStateError::create(realm(), "Element is not mutable"_string);
  286. // 2. If this's relevant settings object's origin is not same origin with this's relevant settings object's top-level origin,
  287. // and this's type attribute is not in the File Upload state or Color state, then throw a "SecurityError" DOMException.
  288. // NOTE: File and Color inputs are exempted from this check for historical reason: their input activation behavior also shows their pickers,
  289. // and has never been guarded by an origin check.
  290. if (!relevant_settings_object(*this).origin().is_same_origin(relevant_settings_object(*this).top_level_origin)
  291. && m_type != TypeAttributeState::FileUpload && m_type != TypeAttributeState::Color) {
  292. return WebIDL::SecurityError::create(realm(), "Cross origin pickers are not allowed"_string);
  293. }
  294. // 3. If this's relevant global object does not have transient activation, then throw a "NotAllowedError" DOMException.
  295. // FIXME: The global object we get here should probably not need casted to Window to check for transient activation
  296. auto& global_object = relevant_global_object(*this);
  297. if (!is<HTML::Window>(global_object) || !static_cast<HTML::Window&>(global_object).has_transient_activation()) {
  298. return WebIDL::NotAllowedError::create(realm(), "Too long since user activation to show picker"_string);
  299. }
  300. // 4. Show the picker, if applicable, for this.
  301. show_the_picker_if_applicable(*this);
  302. return {};
  303. }
  304. // https://html.spec.whatwg.org/multipage/input.html#input-activation-behavior
  305. WebIDL::ExceptionOr<void> HTMLInputElement::run_input_activation_behavior(DOM::Event const& event)
  306. {
  307. if (type_state() == TypeAttributeState::Checkbox || type_state() == TypeAttributeState::RadioButton) {
  308. // 1. If the element is not connected, then return.
  309. if (!is_connected())
  310. return {};
  311. // 2. Fire an event named input at the element with the bubbles and composed attributes initialized to true.
  312. auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
  313. input_event->set_bubbles(true);
  314. input_event->set_composed(true);
  315. dispatch_event(input_event);
  316. // 3. Fire an event named change at the element with the bubbles attribute initialized to true.
  317. auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
  318. change_event->set_bubbles(true);
  319. dispatch_event(*change_event);
  320. } else if (type_state() == TypeAttributeState::SubmitButton) {
  321. GC::Ptr<HTMLFormElement> form;
  322. // 1. If the element does not have a form owner, then return.
  323. if (!(form = this->form()))
  324. return {};
  325. // 2. If the element's node document is not fully active, then return.
  326. if (!document().is_fully_active())
  327. return {};
  328. // 3. Submit the element's form owner from the element with userInvolvement set to event's user navigation involvement.
  329. TRY(form->submit_form(*this, { .user_involvement = user_navigation_involvement(event) }));
  330. } else if (type_state() == TypeAttributeState::FileUpload || type_state() == TypeAttributeState::Color) {
  331. show_the_picker_if_applicable(*this);
  332. }
  333. // https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):input-activation-behavior
  334. else if (type_state() == TypeAttributeState::ImageButton) {
  335. // 1. If the element does not have a form owner, then return.
  336. auto* form = this->form();
  337. if (!form)
  338. return {};
  339. // 2. If the element's node document is not fully active, then return.
  340. if (!document().is_fully_active())
  341. return {};
  342. // 3. If the user activated the control while explicitly selecting a coordinate, then set the element's selected
  343. // coordinate to that coordinate.
  344. if (event.is_trusted() && is<UIEvents::MouseEvent>(event)) {
  345. auto const& mouse_event = static_cast<UIEvents::MouseEvent const&>(event);
  346. CSSPixels x { mouse_event.offset_x() };
  347. CSSPixels y { mouse_event.offset_y() };
  348. m_selected_coordinate = { x.to_int(), y.to_int() };
  349. }
  350. // 4. Submit the element's form owner from the element with userInvolvement set to event's user navigation involvement.
  351. TRY(form->submit_form(*this, { .user_involvement = user_navigation_involvement(event) }));
  352. }
  353. // https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type=reset)
  354. else if (type_state() == TypeAttributeState::ResetButton) {
  355. // 1. If the element does not have a form owner, then return.
  356. auto* form = this->form();
  357. if (!form)
  358. return {};
  359. // 2. If the element's node document is not fully active, then return.
  360. if (!document().is_fully_active())
  361. return {};
  362. // 3. Reset the form owner from the element.
  363. form->reset_form();
  364. }
  365. return {};
  366. }
  367. void HTMLInputElement::did_edit_text_node()
  368. {
  369. // 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.
  370. auto old_value = move(m_value);
  371. m_value = value_sanitization_algorithm(m_text_node->data());
  372. m_dirty_value = true;
  373. m_has_uncommitted_changes = true;
  374. if (m_value != old_value)
  375. relevant_value_was_changed();
  376. update_placeholder_visibility();
  377. user_interaction_did_change_input_value();
  378. }
  379. void HTMLInputElement::did_pick_color(Optional<Color> picked_color, ColorPickerUpdateState state)
  380. {
  381. if (type_state() == TypeAttributeState::Color && picked_color.has_value()) {
  382. // then when the user changes the element's value
  383. m_value = value_sanitization_algorithm(picked_color.value().to_string_without_alpha());
  384. m_dirty_value = true;
  385. update_color_well_element();
  386. // the user agent must queue an element task on the user interaction task source
  387. user_interaction_did_change_input_value();
  388. // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events
  389. // [...] any time the user commits the change, the user agent must queue an element task on the user interaction task source
  390. if (state == ColorPickerUpdateState::Closed) {
  391. queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
  392. // given the input element
  393. // FIXME: to set its user interacted to true
  394. // and fire an event named change at the input element, with the bubbles attribute initialized to true.
  395. auto change_event = DOM::Event::create(realm(), HTML::EventNames::change);
  396. change_event->set_bubbles(true);
  397. dispatch_event(*change_event);
  398. });
  399. }
  400. }
  401. }
  402. void HTMLInputElement::did_select_files(Span<SelectedFile> selected_files, MultipleHandling multiple_handling)
  403. {
  404. // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
  405. // 4. If the user dismissed the prompt without changing their selection, then queue an element task on the user
  406. // interaction task source given element to fire an event named cancel at element, with the bubbles attribute
  407. // initialized to true.
  408. if (selected_files.is_empty()) {
  409. queue_an_element_task(HTML::Task::Source::UserInteraction, [this]() {
  410. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::cancel, { .bubbles = true }));
  411. });
  412. return;
  413. }
  414. auto files = FileAPI::FileList::create(realm());
  415. for (auto& selected_file : selected_files) {
  416. auto contents = selected_file.take_contents();
  417. auto mime_type = MimeSniff::Resource::sniff(contents);
  418. auto blob = FileAPI::Blob::create(realm(), move(contents), mime_type.essence());
  419. // FIXME: The FileAPI should use ByteString for file names.
  420. auto file_name = MUST(String::from_byte_string(selected_file.name()));
  421. // FIXME: Fill in other fields (e.g. last_modified).
  422. FileAPI::FilePropertyBag options {};
  423. options.type = mime_type.essence();
  424. auto file = MUST(FileAPI::File::create(realm(), { GC::make_root(blob) }, file_name, move(options)));
  425. files->add_file(file);
  426. }
  427. // https://html.spec.whatwg.org/multipage/input.html#update-the-file-selection
  428. // 1. Queue an element task on the user interaction task source given element and the following steps:
  429. queue_an_element_task(HTML::Task::Source::UserInteraction, [this, files, multiple_handling]() mutable {
  430. auto multiple = has_attribute(HTML::AttributeNames::multiple);
  431. // 1. Update element's selected files so that it represents the user's selection.
  432. if (m_selected_files && multiple && multiple_handling == MultipleHandling::Append) {
  433. for (size_t i = 0; i < files->length(); ++i)
  434. m_selected_files->add_file(*files->item(i));
  435. } else {
  436. m_selected_files = files;
  437. }
  438. update_file_input_shadow_tree();
  439. // 2. Fire an event named input at the input element, with the bubbles and composed attributes initialized to true.
  440. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::input, { .bubbles = true, .composed = true }));
  441. // 3. Fire an event named change at the input element, with the bubbles attribute initialized to true.
  442. dispatch_event(DOM::Event::create(realm(), HTML::EventNames::change, { .bubbles = true }));
  443. });
  444. }
  445. String HTMLInputElement::value() const
  446. {
  447. switch (value_attribute_mode()) {
  448. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
  449. case ValueAttributeMode::Value:
  450. // Return the current value of the element.
  451. return m_value;
  452. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
  453. case ValueAttributeMode::Default:
  454. // On getting, if the element has a value content attribute, return that attribute's value; otherwise, return
  455. // the empty string.
  456. return get_attribute_value(AttributeNames::value);
  457. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
  458. case ValueAttributeMode::DefaultOn:
  459. // On getting, if the element has a value content attribute, return that attribute's value; otherwise, return
  460. // the string "on".
  461. return get_attribute(AttributeNames::value).value_or("on"_string);
  462. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
  463. case ValueAttributeMode::Filename:
  464. // On getting, return the string "C:\fakepath\" followed by the name of the first file in the list of selected
  465. // files, if any, or the empty string if the list is empty.
  466. if (m_selected_files && m_selected_files->item(0))
  467. return MUST(String::formatted("C:\\fakepath\\{}", m_selected_files->item(0)->name()));
  468. return String {};
  469. }
  470. VERIFY_NOT_REACHED();
  471. }
  472. WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
  473. {
  474. auto& realm = this->realm();
  475. switch (value_attribute_mode()) {
  476. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
  477. case ValueAttributeMode::Value: {
  478. // 1. Let oldValue be the element's value.
  479. auto old_value = move(m_value);
  480. // 2. Set the element's value to the new value.
  481. // NOTE: For the TextNode this is done as part of step 4 below.
  482. // 3. Set the element's dirty value flag to true.
  483. m_dirty_value = true;
  484. // 4. Invoke the value sanitization algorithm, if the element's type attribute's current state defines one.
  485. m_value = value_sanitization_algorithm(value);
  486. // 5. If the element's value (after applying the value sanitization algorithm) is different from oldValue,
  487. // and the element has a text entry cursor position, move the text entry cursor position to the end of the
  488. // text control, unselecting any selected text and resetting the selection direction to "none".
  489. if (m_value != old_value) {
  490. relevant_value_was_changed();
  491. if (m_text_node) {
  492. m_text_node->set_data(m_value);
  493. update_placeholder_visibility();
  494. set_the_selection_range(m_text_node->length(), m_text_node->length());
  495. }
  496. update_shadow_tree();
  497. }
  498. break;
  499. }
  500. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
  501. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
  502. case ValueAttributeMode::Default:
  503. case ValueAttributeMode::DefaultOn:
  504. // On setting, set the value of the element's value content attribute to the new value.
  505. TRY(set_attribute(HTML::AttributeNames::value, value));
  506. break;
  507. // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
  508. case ValueAttributeMode::Filename:
  509. // On setting, if the new value is the empty string, empty the list of selected files; otherwise, throw an "InvalidStateError" DOMException.
  510. if (!value.is_empty())
  511. return WebIDL::InvalidStateError::create(realm, "Setting value of input type file to non-empty string"_string);
  512. m_selected_files = nullptr;
  513. break;
  514. }
  515. return {};
  516. }
  517. void HTMLInputElement::commit_pending_changes()
  518. {
  519. // The change event fires when the value is committed, if that makes sense for the control,
  520. // or else when the control loses focus
  521. switch (type_state()) {
  522. case TypeAttributeState::Email:
  523. case TypeAttributeState::Password:
  524. case TypeAttributeState::Search:
  525. case TypeAttributeState::Telephone:
  526. case TypeAttributeState::Text:
  527. case TypeAttributeState::URL:
  528. case TypeAttributeState::Checkbox:
  529. case TypeAttributeState::RadioButton:
  530. if (!m_has_uncommitted_changes)
  531. return;
  532. break;
  533. default:
  534. break;
  535. }
  536. m_has_uncommitted_changes = false;
  537. auto change_event = DOM::Event::create(realm(), HTML::EventNames::change, { .bubbles = true });
  538. dispatch_event(change_event);
  539. }
  540. void HTMLInputElement::update_placeholder_visibility()
  541. {
  542. if (!m_placeholder_element)
  543. return;
  544. if (this->placeholder_value().has_value()) {
  545. MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"sv));
  546. } else {
  547. MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"sv));
  548. }
  549. }
  550. void HTMLInputElement::update_text_input_shadow_tree()
  551. {
  552. if (m_text_node) {
  553. m_text_node->set_data(m_value);
  554. update_placeholder_visibility();
  555. }
  556. }
  557. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-readonly-3
  558. static bool is_allowed_to_be_readonly(HTML::HTMLInputElement::TypeAttributeState state)
  559. {
  560. switch (state) {
  561. case HTML::HTMLInputElement::TypeAttributeState::Text:
  562. case HTML::HTMLInputElement::TypeAttributeState::Search:
  563. case HTML::HTMLInputElement::TypeAttributeState::Telephone:
  564. case HTML::HTMLInputElement::TypeAttributeState::URL:
  565. case HTML::HTMLInputElement::TypeAttributeState::Email:
  566. case HTML::HTMLInputElement::TypeAttributeState::Password:
  567. case HTML::HTMLInputElement::TypeAttributeState::Date:
  568. case HTML::HTMLInputElement::TypeAttributeState::Month:
  569. case HTML::HTMLInputElement::TypeAttributeState::Week:
  570. case HTML::HTMLInputElement::TypeAttributeState::Time:
  571. case HTML::HTMLInputElement::TypeAttributeState::LocalDateAndTime:
  572. case HTML::HTMLInputElement::TypeAttributeState::Number:
  573. return true;
  574. default:
  575. return false;
  576. }
  577. }
  578. // https://html.spec.whatwg.org/multipage/input.html#attr-input-maxlength
  579. void HTMLInputElement::handle_maxlength_attribute()
  580. {
  581. if (m_text_node) {
  582. auto max_length = this->max_length();
  583. if (max_length >= 0) {
  584. m_text_node->set_max_length(max_length);
  585. } else {
  586. m_text_node->set_max_length({});
  587. }
  588. }
  589. }
  590. // https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly
  591. void HTMLInputElement::handle_readonly_attribute(Optional<String> const& maybe_value)
  592. {
  593. // 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.
  594. set_is_mutable(!maybe_value.has_value() || !is_allowed_to_be_readonly(m_type));
  595. }
  596. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-placeholder-3
  597. static bool is_allowed_to_have_placeholder(HTML::HTMLInputElement::TypeAttributeState state)
  598. {
  599. switch (state) {
  600. case HTML::HTMLInputElement::TypeAttributeState::Text:
  601. case HTML::HTMLInputElement::TypeAttributeState::Search:
  602. case HTML::HTMLInputElement::TypeAttributeState::URL:
  603. case HTML::HTMLInputElement::TypeAttributeState::Telephone:
  604. case HTML::HTMLInputElement::TypeAttributeState::Email:
  605. case HTML::HTMLInputElement::TypeAttributeState::Password:
  606. case HTML::HTMLInputElement::TypeAttributeState::Number:
  607. return true;
  608. default:
  609. return false;
  610. }
  611. }
  612. // https://html.spec.whatwg.org/multipage/input.html#attr-input-placeholder
  613. String HTMLInputElement::placeholder() const
  614. {
  615. auto maybe_placeholder = get_attribute(HTML::AttributeNames::placeholder);
  616. if (!maybe_placeholder.has_value())
  617. return String {};
  618. auto placeholder = *maybe_placeholder;
  619. // The attribute, if specified, must have a value that contains no U+000A LINE FEED (LF) or U+000D CARRIAGE RETURN (CR) characters.
  620. StringBuilder builder;
  621. for (auto c : placeholder.bytes_as_string_view()) {
  622. if (c != '\r' && c != '\n')
  623. builder.append(c);
  624. }
  625. return MUST(builder.to_string());
  626. }
  627. // https://html.spec.whatwg.org/multipage/input.html#attr-input-placeholder
  628. Optional<String> HTMLInputElement::placeholder_value() const
  629. {
  630. if (!m_text_node || !m_text_node->data().is_empty())
  631. return {};
  632. if (!is_allowed_to_have_placeholder(type_state()))
  633. return {};
  634. if (!has_attribute(HTML::AttributeNames::placeholder))
  635. return {};
  636. return placeholder();
  637. }
  638. void HTMLInputElement::create_shadow_tree_if_needed()
  639. {
  640. if (shadow_root())
  641. return;
  642. switch (type_state()) {
  643. case TypeAttributeState::Hidden:
  644. case TypeAttributeState::RadioButton:
  645. case TypeAttributeState::Checkbox:
  646. break;
  647. case TypeAttributeState::Button:
  648. case TypeAttributeState::SubmitButton:
  649. case TypeAttributeState::ResetButton:
  650. create_button_input_shadow_tree();
  651. break;
  652. case TypeAttributeState::ImageButton:
  653. break;
  654. case TypeAttributeState::Color:
  655. create_color_input_shadow_tree();
  656. break;
  657. case TypeAttributeState::FileUpload:
  658. create_file_input_shadow_tree();
  659. break;
  660. case TypeAttributeState::Range:
  661. create_range_input_shadow_tree();
  662. break;
  663. // FIXME: This could be better factored. Everything except the above types becomes a text input.
  664. default:
  665. create_text_input_shadow_tree();
  666. break;
  667. }
  668. }
  669. void HTMLInputElement::update_shadow_tree()
  670. {
  671. switch (type_state()) {
  672. case TypeAttributeState::Color:
  673. update_color_well_element();
  674. break;
  675. case TypeAttributeState::FileUpload:
  676. update_file_input_shadow_tree();
  677. break;
  678. case TypeAttributeState::Range:
  679. update_slider_shadow_tree_elements();
  680. break;
  681. default:
  682. update_text_input_shadow_tree();
  683. break;
  684. }
  685. }
  686. void HTMLInputElement::create_button_input_shadow_tree()
  687. {
  688. auto shadow_root = realm().create<DOM::ShadowRoot>(document(), *this, Bindings::ShadowRootMode::Closed);
  689. set_shadow_root(shadow_root);
  690. auto text_container = MUST(DOM::create_element(document(), HTML::TagNames::span, Namespace::HTML));
  691. MUST(text_container->set_attribute(HTML::AttributeNames::style, "display: inline-block; pointer-events: none;"_string));
  692. m_text_node = realm().create<DOM::Text>(document(), value());
  693. MUST(text_container->append_child(*m_text_node));
  694. MUST(shadow_root->append_child(*text_container));
  695. }
  696. void HTMLInputElement::create_text_input_shadow_tree()
  697. {
  698. auto shadow_root = realm().create<DOM::ShadowRoot>(document(), *this, Bindings::ShadowRootMode::Closed);
  699. set_shadow_root(shadow_root);
  700. auto initial_value = m_value;
  701. auto element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  702. MUST(element->set_attribute(HTML::AttributeNames::style, R"~~~(
  703. display: flex;
  704. height: 100%;
  705. align-items: center;
  706. white-space: pre;
  707. border: none;
  708. padding: 1px 2px;
  709. )~~~"_string));
  710. MUST(shadow_root->append_child(element));
  711. m_placeholder_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  712. m_placeholder_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Placeholder);
  713. // https://www.w3.org/TR/css-ui-4/#input-rules
  714. MUST(m_placeholder_element->set_attribute(HTML::AttributeNames::style, R"~~~(
  715. width: 100%;
  716. align-items: center;
  717. text-overflow: clip;
  718. white-space: nowrap;
  719. )~~~"_string));
  720. MUST(element->append_child(*m_placeholder_element));
  721. m_placeholder_text_node = realm().create<DOM::Text>(document(), String {});
  722. m_placeholder_text_node->set_data(placeholder());
  723. MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
  724. // https://www.w3.org/TR/css-ui-4/#input-rules
  725. m_inner_text_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  726. MUST(m_inner_text_element->set_attribute(HTML::AttributeNames::style, R"~~~(
  727. width: 100%;
  728. height: 1lh;
  729. align-items: center;
  730. text-overflow: clip;
  731. white-space: nowrap;
  732. )~~~"_string));
  733. MUST(element->append_child(*m_inner_text_element));
  734. m_text_node = realm().create<DOM::Text>(document(), move(initial_value));
  735. handle_readonly_attribute(attribute(HTML::AttributeNames::readonly));
  736. if (type_state() == TypeAttributeState::Password)
  737. m_text_node->set_is_password_input({}, true);
  738. handle_maxlength_attribute();
  739. MUST(m_inner_text_element->append_child(*m_text_node));
  740. update_placeholder_visibility();
  741. if (type_state() == TypeAttributeState::Number) {
  742. // Up button
  743. auto up_button = MUST(DOM::create_element(document(), HTML::TagNames::button, Namespace::HTML));
  744. // FIXME: This cursor property doesn't work
  745. MUST(up_button->set_attribute(HTML::AttributeNames::style, R"~~~(
  746. padding: 0;
  747. cursor: default;
  748. )~~~"_string));
  749. MUST(up_button->set_inner_html("<svg style=\"width: 1em; height: 1em;\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M7.41,15.41L12,10.83L16.59,15.41L18,14L12,8L6,14L7.41,15.41Z\" /></svg>"sv));
  750. MUST(element->append_child(up_button));
  751. auto mouseup_callback_function = JS::NativeFunction::create(
  752. realm(), [this](JS::VM&) {
  753. commit_pending_changes();
  754. return JS::js_undefined();
  755. },
  756. 0, "", &realm());
  757. auto mouseup_callback = realm().heap().allocate<WebIDL::CallbackType>(*mouseup_callback_function, realm());
  758. DOM::AddEventListenerOptions mouseup_listener_options;
  759. mouseup_listener_options.once = true;
  760. auto up_callback_function = JS::NativeFunction::create(
  761. realm(), [this](JS::VM&) {
  762. if (is_mutable()) {
  763. MUST(step_up());
  764. user_interaction_did_change_input_value();
  765. }
  766. return JS::js_undefined();
  767. },
  768. 0, "", &realm());
  769. auto step_up_callback = realm().heap().allocate<WebIDL::CallbackType>(*up_callback_function, realm());
  770. up_button->add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), step_up_callback));
  771. up_button->add_event_listener_without_options(UIEvents::EventNames::mouseup, DOM::IDLEventListener::create(realm(), mouseup_callback));
  772. // Down button
  773. auto down_button = MUST(DOM::create_element(document(), HTML::TagNames::button, Namespace::HTML));
  774. MUST(down_button->set_attribute(HTML::AttributeNames::style, R"~~~(
  775. padding: 0;
  776. cursor: default;
  777. )~~~"_string));
  778. MUST(down_button->set_inner_html("<svg style=\"width: 1em; height: 1em;\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path fill=\"currentColor\" d=\"M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z\" /></svg>"sv));
  779. MUST(element->append_child(down_button));
  780. auto down_callback_function = JS::NativeFunction::create(
  781. realm(), [this](JS::VM&) {
  782. if (is_mutable()) {
  783. MUST(step_down());
  784. user_interaction_did_change_input_value();
  785. }
  786. return JS::js_undefined();
  787. },
  788. 0, "", &realm());
  789. auto step_down_callback = realm().heap().allocate<WebIDL::CallbackType>(*down_callback_function, realm());
  790. down_button->add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), step_down_callback));
  791. down_button->add_event_listener_without_options(UIEvents::EventNames::mouseup, DOM::IDLEventListener::create(realm(), mouseup_callback));
  792. }
  793. }
  794. void HTMLInputElement::create_color_input_shadow_tree()
  795. {
  796. auto shadow_root = realm().create<DOM::ShadowRoot>(document(), *this, Bindings::ShadowRootMode::Closed);
  797. auto color = value_sanitization_algorithm(m_value);
  798. auto border = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  799. MUST(border->set_attribute(HTML::AttributeNames::style, R"~~~(
  800. width: fit-content;
  801. height: fit-content;
  802. padding: 4px;
  803. border: 1px solid ButtonBorder;
  804. background-color: ButtonFace;
  805. )~~~"_string));
  806. m_color_well_element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  807. MUST(m_color_well_element->set_attribute(HTML::AttributeNames::style, R"~~~(
  808. width: 32px;
  809. height: 16px;
  810. border: 1px solid ButtonBorder;
  811. box-sizing: border-box;
  812. )~~~"_string));
  813. MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, color));
  814. MUST(border->append_child(*m_color_well_element));
  815. MUST(shadow_root->append_child(border));
  816. set_shadow_root(shadow_root);
  817. }
  818. void HTMLInputElement::update_color_well_element()
  819. {
  820. if (!m_color_well_element)
  821. return;
  822. MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value));
  823. }
  824. void HTMLInputElement::create_file_input_shadow_tree()
  825. {
  826. auto& realm = this->realm();
  827. auto shadow_root = realm.create<DOM::ShadowRoot>(document(), *this, Bindings::ShadowRootMode::Closed);
  828. m_file_button = DOM::create_element(document(), HTML::TagNames::button, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  829. m_file_button->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::FileSelectorButton);
  830. m_file_label = DOM::create_element(document(), HTML::TagNames::label, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
  831. MUST(m_file_label->set_attribute(HTML::AttributeNames::style, "padding-left: 4px;"_string));
  832. auto on_button_click = [this](JS::VM&) {
  833. show_the_picker_if_applicable(*this);
  834. return JS::js_undefined();
  835. };
  836. auto on_button_click_function = JS::NativeFunction::create(realm, move(on_button_click), 0, "", &realm);
  837. auto on_button_click_callback = realm.heap().allocate<WebIDL::CallbackType>(on_button_click_function, realm);
  838. m_file_button->add_event_listener_without_options(UIEvents::EventNames::click, DOM::IDLEventListener::create(realm, on_button_click_callback));
  839. update_file_input_shadow_tree();
  840. MUST(shadow_root->append_child(*m_file_button));
  841. MUST(shadow_root->append_child(*m_file_label));
  842. set_shadow_root(shadow_root);
  843. }
  844. void HTMLInputElement::update_file_input_shadow_tree()
  845. {
  846. if (!m_file_button || !m_file_label)
  847. return;
  848. auto files_label = has_attribute(HTML::AttributeNames::multiple) ? "files"sv : "file"sv;
  849. m_file_button->set_text_content(MUST(String::formatted("Select {}...", files_label)));
  850. if (m_selected_files && m_selected_files->length() > 0) {
  851. if (m_selected_files->length() == 1)
  852. m_file_label->set_text_content(m_selected_files->item(0)->name());
  853. else
  854. m_file_label->set_text_content(MUST(String::formatted("{} files selected.", m_selected_files->length())));
  855. } else {
  856. m_file_label->set_text_content(MUST(String::formatted("No {} selected.", files_label)));
  857. }
  858. document().invalidate_layout_tree();
  859. }
  860. void HTMLInputElement::create_range_input_shadow_tree()
  861. {
  862. auto shadow_root = realm().create<DOM::ShadowRoot>(document(), *this, Bindings::ShadowRootMode::Closed);
  863. set_shadow_root(shadow_root);
  864. m_slider_runnable_track = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  865. m_slider_runnable_track->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::SliderRunnableTrack);
  866. MUST(shadow_root->append_child(*m_slider_runnable_track));
  867. m_slider_progress_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  868. MUST(m_slider_progress_element->set_attribute(HTML::AttributeNames::style, R"~~~(
  869. display: block;
  870. position: absolute;
  871. height: 100%;
  872. )~~~"_string));
  873. MUST(m_slider_runnable_track->append_child(*m_slider_progress_element));
  874. m_slider_thumb = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
  875. m_slider_thumb->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::SliderThumb);
  876. MUST(m_slider_runnable_track->append_child(*m_slider_thumb));
  877. update_slider_shadow_tree_elements();
  878. auto keydown_callback_function = JS::NativeFunction::create(
  879. realm(), [this](JS::VM& vm) {
  880. auto key = MUST(vm.argument(0).get(vm, "key")).as_string().utf8_string();
  881. if (key == "ArrowLeft" || key == "ArrowDown")
  882. MUST(step_down());
  883. if (key == "PageDown")
  884. MUST(step_down(10));
  885. if (key == "ArrowRight" || key == "ArrowUp")
  886. MUST(step_up());
  887. if (key == "PageUp")
  888. MUST(step_up(10));
  889. user_interaction_did_change_input_value();
  890. return JS::js_undefined();
  891. },
  892. 0, "", &realm());
  893. auto keydown_callback = realm().heap().allocate<WebIDL::CallbackType>(*keydown_callback_function, realm());
  894. add_event_listener_without_options(UIEvents::EventNames::keydown, DOM::IDLEventListener::create(realm(), keydown_callback));
  895. auto wheel_callback_function = JS::NativeFunction::create(
  896. realm(), [this](JS::VM& vm) {
  897. auto delta_y = MUST(vm.argument(0).get(vm, "deltaY")).as_i32();
  898. if (delta_y > 0) {
  899. MUST(step_down());
  900. } else {
  901. MUST(step_up());
  902. }
  903. user_interaction_did_change_input_value();
  904. return JS::js_undefined();
  905. },
  906. 0, "", &realm());
  907. auto wheel_callback = realm().heap().allocate<WebIDL::CallbackType>(*wheel_callback_function, realm());
  908. add_event_listener_without_options(UIEvents::EventNames::wheel, DOM::IDLEventListener::create(realm(), wheel_callback));
  909. auto update_slider_by_mouse = [this](JS::VM& vm) {
  910. auto client_x = MUST(vm.argument(0).get(vm, "clientX")).as_double();
  911. auto rect = get_bounding_client_rect();
  912. double minimum = *min();
  913. double maximum = *max();
  914. // FIXME: Snap new value to input steps
  915. MUST(set_value_as_number(clamp(round(((client_x - rect->left()) / rect->width()) * (maximum - minimum) + minimum), minimum, maximum)));
  916. user_interaction_did_change_input_value();
  917. };
  918. auto mousedown_callback_function = JS::NativeFunction::create(
  919. realm(), [this, update_slider_by_mouse](JS::VM& vm) {
  920. update_slider_by_mouse(vm);
  921. auto mousemove_callback_function = JS::NativeFunction::create(
  922. realm(), [update_slider_by_mouse](JS::VM& vm) {
  923. update_slider_by_mouse(vm);
  924. return JS::js_undefined();
  925. },
  926. 0, "", &realm());
  927. auto mousemove_callback = realm().heap().allocate<WebIDL::CallbackType>(*mousemove_callback_function, realm());
  928. auto mousemove_listener = DOM::IDLEventListener::create(realm(), mousemove_callback);
  929. auto& window = static_cast<HTML::Window&>(relevant_global_object(*this));
  930. window.add_event_listener_without_options(UIEvents::EventNames::mousemove, mousemove_listener);
  931. auto mouseup_callback_function = JS::NativeFunction::create(
  932. realm(), [this, mousemove_listener](JS::VM&) {
  933. auto& window = static_cast<HTML::Window&>(relevant_global_object(*this));
  934. window.remove_event_listener_without_options(UIEvents::EventNames::mousemove, mousemove_listener);
  935. return JS::js_undefined();
  936. },
  937. 0, "", &realm());
  938. auto mouseup_callback = realm().heap().allocate<WebIDL::CallbackType>(*mouseup_callback_function, realm());
  939. DOM::AddEventListenerOptions mouseup_listener_options;
  940. mouseup_listener_options.once = true;
  941. window.add_event_listener(UIEvents::EventNames::mouseup, DOM::IDLEventListener::create(realm(), mouseup_callback), mouseup_listener_options);
  942. return JS::js_undefined();
  943. },
  944. 0, "", &realm());
  945. auto mousedown_callback = realm().heap().allocate<WebIDL::CallbackType>(*mousedown_callback_function, realm());
  946. add_event_listener_without_options(UIEvents::EventNames::mousedown, DOM::IDLEventListener::create(realm(), mousedown_callback));
  947. }
  948. void HTMLInputElement::computed_css_values_changed()
  949. {
  950. auto appearance = computed_css_values()->appearance();
  951. if (!appearance.has_value() || *appearance == CSS::Appearance::None)
  952. return;
  953. auto palette = document().page().palette();
  954. auto accent_color = palette.color(ColorRole::Accent).to_string();
  955. auto const& accent_color_property = computed_css_values()->property(CSS::PropertyID::AccentColor);
  956. if (accent_color_property.has_color())
  957. accent_color = accent_color_property.to_string(CSS::CSSStyleValue::SerializationMode::Normal);
  958. if (m_slider_progress_element)
  959. MUST(m_slider_progress_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, accent_color));
  960. if (m_slider_thumb)
  961. MUST(m_slider_thumb->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, accent_color));
  962. }
  963. void HTMLInputElement::user_interaction_did_change_input_value()
  964. {
  965. // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events
  966. // For input elements without a defined input activation behavior, but to which these events apply,
  967. // and for which the user interface involves both interactive manipulation and an explicit commit action,
  968. // then when the user changes the element's value, the user agent must queue an element task on the user interaction task source
  969. // given the input element to fire an event named input at the input element, with the bubbles and composed attributes initialized to true,
  970. // and any time the user commits the change, the user agent must queue an element task on the user interaction task source given the input
  971. // element to set its user validity to true and fire an event named change at the input element, with the bubbles attribute initialized to true.
  972. queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
  973. auto input_event = DOM::Event::create(realm(), HTML::EventNames::input);
  974. input_event->set_bubbles(true);
  975. input_event->set_composed(true);
  976. dispatch_event(*input_event);
  977. });
  978. }
  979. void HTMLInputElement::update_slider_shadow_tree_elements()
  980. {
  981. double value = convert_string_to_number(value_sanitization_algorithm(m_value)).value_or(0);
  982. double minimum = *min();
  983. double maximum = *max();
  984. double position = (value - minimum) / (maximum - minimum) * 100;
  985. if (m_slider_progress_element)
  986. MUST(m_slider_progress_element->style_for_bindings()->set_property(CSS::PropertyID::Width, MUST(String::formatted("{}%", position))));
  987. if (m_slider_thumb)
  988. MUST(m_slider_thumb->style_for_bindings()->set_property(CSS::PropertyID::MarginLeft, MUST(String::formatted("{}%", position))));
  989. }
  990. void HTMLInputElement::did_receive_focus()
  991. {
  992. if (!m_text_node)
  993. return;
  994. m_text_node->invalidate_style(DOM::StyleInvalidationReason::DidReceiveFocus);
  995. if (m_placeholder_text_node)
  996. m_placeholder_text_node->invalidate_style(DOM::StyleInvalidationReason::DidReceiveFocus);
  997. }
  998. void HTMLInputElement::did_lose_focus()
  999. {
  1000. if (m_text_node) {
  1001. m_text_node->invalidate_style(DOM::StyleInvalidationReason::DidLoseFocus);
  1002. }
  1003. if (m_placeholder_text_node)
  1004. m_placeholder_text_node->invalidate_style(DOM::StyleInvalidationReason::DidLoseFocus);
  1005. commit_pending_changes();
  1006. }
  1007. void HTMLInputElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& value)
  1008. {
  1009. if (name == HTML::AttributeNames::checked) {
  1010. if (!value.has_value()) {
  1011. // When the checked content attribute is removed, if the control does not have dirty checkedness,
  1012. // the user agent must set the checkedness of the element to false.
  1013. if (!m_dirty_checkedness)
  1014. set_checked(false, ChangeSource::Programmatic);
  1015. } else {
  1016. // When the checked content attribute is added, if the control does not have dirty checkedness,
  1017. // the user agent must set the checkedness of the element to true
  1018. if (!m_dirty_checkedness)
  1019. set_checked(true, ChangeSource::Programmatic);
  1020. }
  1021. } else if (name == HTML::AttributeNames::type) {
  1022. auto new_type_attribute_state = parse_type_attribute(value.value_or(String {}));
  1023. type_attribute_changed(m_type, new_type_attribute_state);
  1024. // https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):the-input-element-4
  1025. // the input element's type attribute is changed back to the Image Button state, and the src attribute is present,
  1026. // and its value has changed since the last time the type attribute was in the Image Button state
  1027. if (type_state() == TypeAttributeState::ImageButton) {
  1028. if (auto src = attribute(AttributeNames::src); src.has_value() && src != m_last_src_value)
  1029. handle_src_attribute(*src).release_value_but_fixme_should_propagate_errors();
  1030. }
  1031. } else if (name == HTML::AttributeNames::value) {
  1032. if (!m_dirty_value) {
  1033. auto old_value = move(m_value);
  1034. if (!value.has_value()) {
  1035. m_value = String {};
  1036. } else {
  1037. m_value = value_sanitization_algorithm(*value);
  1038. }
  1039. if (m_value != old_value)
  1040. relevant_value_was_changed();
  1041. update_shadow_tree();
  1042. }
  1043. } else if (name == HTML::AttributeNames::placeholder) {
  1044. if (m_placeholder_text_node) {
  1045. m_placeholder_text_node->set_data(placeholder());
  1046. update_placeholder_visibility();
  1047. }
  1048. } else if (name == HTML::AttributeNames::readonly) {
  1049. handle_readonly_attribute(value);
  1050. } else if (name == HTML::AttributeNames::src) {
  1051. handle_src_attribute(value.value_or({})).release_value_but_fixme_should_propagate_errors();
  1052. } else if (name == HTML::AttributeNames::alt) {
  1053. if (layout_node() && type_state() == TypeAttributeState::ImageButton)
  1054. did_update_alt_text(verify_cast<Layout::ImageBox>(*layout_node()));
  1055. } else if (name == HTML::AttributeNames::maxlength) {
  1056. handle_maxlength_attribute();
  1057. } else if (name == HTML::AttributeNames::multiple) {
  1058. update_shadow_tree();
  1059. }
  1060. }
  1061. // https://html.spec.whatwg.org/multipage/input.html#input-type-change
  1062. void HTMLInputElement::type_attribute_changed(TypeAttributeState old_state, TypeAttributeState new_state)
  1063. {
  1064. auto new_value_attribute_mode = value_attribute_mode_for_type_state(new_state);
  1065. auto old_value_attribute_mode = value_attribute_mode_for_type_state(old_state);
  1066. // 1. If the previous state of the element's type attribute put the value IDL attribute in the value mode, and the element's
  1067. // value is not the empty string, and the new state of the element's type attribute puts the value IDL attribute in either
  1068. // the default mode or the default/on mode, then set the element's value content attribute to the element's value.
  1069. if (old_value_attribute_mode == ValueAttributeMode::Value && !m_value.is_empty() && (first_is_one_of(new_value_attribute_mode, ValueAttributeMode::Default, ValueAttributeMode::DefaultOn))) {
  1070. MUST(set_attribute(HTML::AttributeNames::value, m_value));
  1071. }
  1072. // 2. Otherwise, if the previous state of the element's type attribute put the value IDL attribute in any mode other
  1073. // than the value mode, and the new state of the element's type attribute puts the value IDL attribute in the value mode,
  1074. // then set the value of the element to the value of the value content attribute, if there is one, or the empty string
  1075. // otherwise, and then set the control's dirty value flag to false.
  1076. else if (old_value_attribute_mode != ValueAttributeMode::Value && new_value_attribute_mode == ValueAttributeMode::Value) {
  1077. m_value = attribute(HTML::AttributeNames::value).value_or({});
  1078. m_dirty_value = false;
  1079. }
  1080. // 3. Otherwise, if the previous state of the element's type attribute put the value IDL attribute in any mode other
  1081. // than the filename mode, and the new state of the element's type attribute puts the value IDL attribute in the filename mode,
  1082. // then set the value of the element to the empty string.
  1083. else if (old_value_attribute_mode != ValueAttributeMode::Filename && new_value_attribute_mode == ValueAttributeMode::Filename) {
  1084. m_value = String {};
  1085. }
  1086. // 4. Update the element's rendering and behavior to the new state's.
  1087. m_type = new_state;
  1088. set_shadow_root(nullptr);
  1089. create_shadow_tree_if_needed();
  1090. // FIXME: 5. Signal a type change for the element. (The Radio Button state uses this, in particular.)
  1091. // 6. Invoke the value sanitization algorithm, if one is defined for the type attribute's new state.
  1092. m_value = value_sanitization_algorithm(m_value);
  1093. // 7. Let previouslySelectable be true if setRangeText() previously applied to the element, and false otherwise.
  1094. auto previously_selectable = selection_or_range_applies_for_type_state(old_state);
  1095. // 8. Let nowSelectable be true if setRangeText() now applies to the element, and false otherwise.
  1096. auto now_selectable = selection_or_range_applies_for_type_state(new_state);
  1097. // 9. If previouslySelectable is false and nowSelectable is true, set the element's text entry cursor position to the
  1098. // beginning of the text control, and set its selection direction to "none".
  1099. if (!previously_selectable && now_selectable) {
  1100. set_selection_direction(OptionalNone {});
  1101. }
  1102. }
  1103. // https://html.spec.whatwg.org/multipage/input.html#attr-input-src
  1104. WebIDL::ExceptionOr<void> HTMLInputElement::handle_src_attribute(String const& value)
  1105. {
  1106. auto& realm = this->realm();
  1107. auto& vm = realm.vm();
  1108. if (type_state() != TypeAttributeState::ImageButton)
  1109. return {};
  1110. m_last_src_value = value;
  1111. // 1. Let url be the result of encoding-parsing a URL given the src attribute's value, relative to the element's
  1112. // node document.
  1113. auto url = document().encoding_parse_url(value);
  1114. // 2. If url is failure, then return.
  1115. if (!url.is_valid())
  1116. return {};
  1117. // 3. Let request be a new request whose URL is url, client is the element's node document's relevant settings
  1118. // object, destination is "image", initiator type is "input", credentials mode is "include", and whose
  1119. // use-URL-credentials flag is set.
  1120. auto request = Fetch::Infrastructure::Request::create(vm);
  1121. request->set_url(move(url));
  1122. request->set_client(&document().relevant_settings_object());
  1123. request->set_destination(Fetch::Infrastructure::Request::Destination::Image);
  1124. request->set_initiator_type(Fetch::Infrastructure::Request::InitiatorType::Input);
  1125. request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include);
  1126. request->set_use_url_credentials(true);
  1127. // 4. Fetch request, with processResponseEndOfBody set to the following step given response response:
  1128. m_resource_request = SharedResourceRequest::get_or_create(realm, document().page(), request->url());
  1129. m_resource_request->add_callbacks(
  1130. [this, &realm]() {
  1131. // 1. If the download was successful and the image is available, queue an element task on the user interaction
  1132. // task source given the input element to fire an event named load at the input element.
  1133. queue_an_element_task(HTML::Task::Source::UserInteraction, [this, &realm]() {
  1134. dispatch_event(DOM::Event::create(realm, HTML::EventNames::load));
  1135. });
  1136. m_load_event_delayer.clear();
  1137. document().invalidate_layout_tree();
  1138. },
  1139. [this, &realm]() {
  1140. // 2. Otherwise, if the fetching process fails without a response from the remote server, or completes but the
  1141. // image is not a valid or supported image, then queue an element task on the user interaction task source
  1142. // given the input element to fire an event named error on the input element.
  1143. queue_an_element_task(HTML::Task::Source::UserInteraction, [this, &realm]() {
  1144. dispatch_event(DOM::Event::create(realm, HTML::EventNames::error));
  1145. });
  1146. m_load_event_delayer.clear();
  1147. });
  1148. if (m_resource_request->needs_fetching()) {
  1149. m_resource_request->fetch_resource(realm, request);
  1150. }
  1151. // Fetching the image must delay the load event of the element's node document until the task that is queued by the
  1152. // networking task source once the resource has been fetched (defined below) has been run.
  1153. m_load_event_delayer.emplace(document());
  1154. return {};
  1155. }
  1156. HTMLInputElement::TypeAttributeState HTMLInputElement::parse_type_attribute(StringView type)
  1157. {
  1158. #define __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE(keyword, state) \
  1159. if (type.equals_ignoring_ascii_case(keyword##sv)) \
  1160. return HTMLInputElement::TypeAttributeState::state;
  1161. ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTES
  1162. #undef __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE
  1163. // The missing value default and the invalid value default are the Text state.
  1164. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:missing-value-default
  1165. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:invalid-value-default
  1166. return HTMLInputElement::TypeAttributeState::Text;
  1167. }
  1168. StringView HTMLInputElement::type() const
  1169. {
  1170. // FIXME: This should probably be `Reflect` in the IDL.
  1171. switch (m_type) {
  1172. #define __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE(keyword, state) \
  1173. case TypeAttributeState::state: \
  1174. return keyword##sv;
  1175. ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTES
  1176. #undef __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE
  1177. }
  1178. VERIFY_NOT_REACHED();
  1179. }
  1180. WebIDL::ExceptionOr<void> HTMLInputElement::set_type(String const& type)
  1181. {
  1182. return set_attribute(HTML::AttributeNames::type, type);
  1183. }
  1184. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-simple-colour
  1185. static bool is_valid_simple_color(StringView value)
  1186. {
  1187. // if it is exactly seven characters long,
  1188. if (value.length() != 7)
  1189. return false;
  1190. // and the first character is a U+0023 NUMBER SIGN character (#),
  1191. if (!value.starts_with('#'))
  1192. return false;
  1193. // and the remaining six characters are all ASCII hex digits
  1194. for (size_t i = 1; i < value.length(); i++)
  1195. if (!is_ascii_hex_digit(value[i]))
  1196. return false;
  1197. return true;
  1198. }
  1199. // https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm
  1200. String HTMLInputElement::value_sanitization_algorithm(String const& value) const
  1201. {
  1202. if (type_state() == HTMLInputElement::TypeAttributeState::Text || type_state() == HTMLInputElement::TypeAttributeState::Search || type_state() == HTMLInputElement::TypeAttributeState::Telephone || type_state() == HTMLInputElement::TypeAttributeState::Password) {
  1203. // Strip newlines from the value.
  1204. if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) {
  1205. StringBuilder builder;
  1206. for (auto c : value.bytes_as_string_view()) {
  1207. if (c != '\r' && c != '\n')
  1208. builder.append(c);
  1209. }
  1210. return MUST(builder.to_string());
  1211. }
  1212. } else if (type_state() == HTMLInputElement::TypeAttributeState::URL) {
  1213. // Strip newlines from the value, then strip leading and trailing ASCII whitespace from the value.
  1214. if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) {
  1215. StringBuilder builder;
  1216. for (auto c : value.bytes_as_string_view()) {
  1217. if (c != '\r' && c != '\n')
  1218. builder.append(c);
  1219. }
  1220. return MUST(String::from_utf8(builder.string_view().trim(Infra::ASCII_WHITESPACE)));
  1221. }
  1222. return MUST(value.trim(Infra::ASCII_WHITESPACE));
  1223. } else if (type_state() == HTMLInputElement::TypeAttributeState::Email) {
  1224. // https://html.spec.whatwg.org/multipage/input.html#email-state-(type=email):value-sanitization-algorithm
  1225. // FIXME: handle the `multiple` attribute
  1226. // Strip newlines from the value, then strip leading and trailing ASCII whitespace from the value.
  1227. if (value.bytes_as_string_view().contains('\r') || value.bytes_as_string_view().contains('\n')) {
  1228. StringBuilder builder;
  1229. for (auto c : value.bytes_as_string_view()) {
  1230. if (c != '\r' && c != '\n')
  1231. builder.append(c);
  1232. }
  1233. return MUST(String::from_utf8(builder.string_view().trim(Infra::ASCII_WHITESPACE)));
  1234. }
  1235. return MUST(value.trim(Infra::ASCII_WHITESPACE));
  1236. } else if (type_state() == HTMLInputElement::TypeAttributeState::Number) {
  1237. // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):value-sanitization-algorithm
  1238. // If the value of the element is not a valid floating-point number, then set it
  1239. // to the empty string instead.
  1240. if (!is_valid_floating_point_number(value))
  1241. return String {};
  1242. auto maybe_value = parse_floating_point_number(value);
  1243. // AD-HOC: The spec doesn’t require these checks — but other engines do them, and
  1244. // there’s a WPT case which tests that the value is less than Number.MAX_VALUE.
  1245. if (!maybe_value.has_value() || !isfinite(maybe_value.value()))
  1246. return String {};
  1247. } else if (type_state() == HTMLInputElement::TypeAttributeState::Date) {
  1248. // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):value-sanitization-algorithm
  1249. if (!is_valid_date_string(value))
  1250. return String {};
  1251. } else if (type_state() == HTMLInputElement::TypeAttributeState::Month) {
  1252. // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):value-sanitization-algorithm
  1253. if (!is_valid_month_string(value))
  1254. return String {};
  1255. } else if (type_state() == HTMLInputElement::TypeAttributeState::Week) {
  1256. // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm
  1257. if (!is_valid_week_string(value))
  1258. return String {};
  1259. } else if (type_state() == HTMLInputElement::TypeAttributeState::Time) {
  1260. // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):value-sanitization-algorithm
  1261. if (!is_valid_time_string(value))
  1262. return String {};
  1263. } else if (type_state() == HTMLInputElement::TypeAttributeState::LocalDateAndTime) {
  1264. // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):value-sanitization-algorithm
  1265. if (is_valid_local_date_and_time_string(value))
  1266. return normalize_local_date_and_time_string(value);
  1267. return String {};
  1268. } else if (type_state() == HTMLInputElement::TypeAttributeState::Range) {
  1269. // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):value-sanitization-algorithm
  1270. // 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.
  1271. auto maybe_value = parse_floating_point_number(value);
  1272. if (!is_valid_floating_point_number(value) ||
  1273. // AD-HOC: The spec doesn’t require these checks — but other engines do them.
  1274. !maybe_value.has_value() || !isfinite(maybe_value.value())) {
  1275. // 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.
  1276. auto minimum = *min();
  1277. auto maximum = *max();
  1278. if (maximum < minimum)
  1279. return JS::number_to_string(minimum);
  1280. return JS::number_to_string(minimum + (maximum - minimum) / 2);
  1281. }
  1282. } else if (type_state() == HTMLInputElement::TypeAttributeState::Color) {
  1283. // https://html.spec.whatwg.org/multipage/input.html#color-state-(type=color):value-sanitization-algorithm
  1284. // If the value of the element is a valid simple color, then set it to the value of the element converted to ASCII lowercase;
  1285. if (is_valid_simple_color(value))
  1286. return value.to_ascii_lowercase();
  1287. // otherwise, set it to the string "#000000".
  1288. return "#000000"_string;
  1289. }
  1290. return value;
  1291. }
  1292. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-form-reset-control
  1293. void HTMLInputElement::reset_algorithm()
  1294. {
  1295. // The reset algorithm for input elements is to set the dirty value flag and dirty checkedness flag back to false,
  1296. m_dirty_value = false;
  1297. m_dirty_checkedness = false;
  1298. // set the value of the element to the value of the value content attribute, if there is one, or the empty string otherwise,
  1299. auto old_value = move(m_value);
  1300. m_value = get_attribute_value(AttributeNames::value);
  1301. // set the checkedness of the element to true if the element has a checked content attribute and false if it does not,
  1302. m_checked = has_attribute(AttributeNames::checked);
  1303. // empty the list of selected files,
  1304. if (m_selected_files)
  1305. m_selected_files = FileAPI::FileList::create(realm());
  1306. // and then invoke the value sanitization algorithm, if the type attribute's current state defines one.
  1307. m_value = value_sanitization_algorithm(m_value);
  1308. if (m_value != old_value)
  1309. relevant_value_was_changed();
  1310. if (m_text_node) {
  1311. m_text_node->set_data(m_value);
  1312. update_placeholder_visibility();
  1313. }
  1314. update_shadow_tree();
  1315. }
  1316. // https://w3c.github.io/webdriver/#dfn-clear-algorithm
  1317. void HTMLInputElement::clear_algorithm()
  1318. {
  1319. // The clear algorithm for input elements is to set the dirty value flag and dirty checkedness flag back to false,
  1320. m_dirty_value = false;
  1321. m_dirty_checkedness = false;
  1322. // set the value of the element to an empty string,
  1323. auto old_value = move(m_value);
  1324. m_value = String {};
  1325. // set the checkedness of the element to true if the element has a checked content attribute and false if it does not,
  1326. m_checked = has_attribute(AttributeNames::checked);
  1327. // empty the list of selected files,
  1328. if (m_selected_files)
  1329. m_selected_files = FileAPI::FileList::create(realm());
  1330. // and then invoke the value sanitization algorithm iff the type attribute's current state defines one.
  1331. m_value = value_sanitization_algorithm(m_value);
  1332. // Unlike their associated reset algorithms, changes made to form controls as part of these algorithms do count as
  1333. // changes caused by the user (and thus, e.g. do cause input events to fire).
  1334. user_interaction_did_change_input_value();
  1335. if (m_value != old_value)
  1336. relevant_value_was_changed();
  1337. if (m_text_node) {
  1338. m_text_node->set_data(m_value);
  1339. update_placeholder_visibility();
  1340. }
  1341. update_shadow_tree();
  1342. }
  1343. void HTMLInputElement::form_associated_element_was_inserted()
  1344. {
  1345. create_shadow_tree_if_needed();
  1346. }
  1347. void HTMLInputElement::form_associated_element_was_removed(DOM::Node*)
  1348. {
  1349. set_shadow_root(nullptr);
  1350. }
  1351. void HTMLInputElement::apply_presentational_hints(CSS::StyleProperties& style) const
  1352. {
  1353. if (type_state() != TypeAttributeState::ImageButton)
  1354. return;
  1355. for_each_attribute([&](auto& name, auto& value) {
  1356. if (name == HTML::AttributeNames::align) {
  1357. if (value.equals_ignoring_ascii_case("center"sv))
  1358. style.set_property(CSS::PropertyID::TextAlign, CSS::CSSKeywordValue::create(CSS::Keyword::Center));
  1359. else if (value.equals_ignoring_ascii_case("middle"sv))
  1360. style.set_property(CSS::PropertyID::TextAlign, CSS::CSSKeywordValue::create(CSS::Keyword::Middle));
  1361. } else if (name == HTML::AttributeNames::border) {
  1362. if (auto parsed_value = parse_non_negative_integer(value); parsed_value.has_value()) {
  1363. auto width_style_value = CSS::LengthStyleValue::create(CSS::Length::make_px(*parsed_value));
  1364. style.set_property(CSS::PropertyID::BorderTopWidth, width_style_value);
  1365. style.set_property(CSS::PropertyID::BorderRightWidth, width_style_value);
  1366. style.set_property(CSS::PropertyID::BorderBottomWidth, width_style_value);
  1367. style.set_property(CSS::PropertyID::BorderLeftWidth, width_style_value);
  1368. auto border_style_value = CSS::CSSKeywordValue::create(CSS::Keyword::Solid);
  1369. style.set_property(CSS::PropertyID::BorderTopStyle, border_style_value);
  1370. style.set_property(CSS::PropertyID::BorderRightStyle, border_style_value);
  1371. style.set_property(CSS::PropertyID::BorderBottomStyle, border_style_value);
  1372. style.set_property(CSS::PropertyID::BorderLeftStyle, border_style_value);
  1373. }
  1374. } else if (name == HTML::AttributeNames::height) {
  1375. if (auto parsed_value = parse_dimension_value(value)) {
  1376. style.set_property(CSS::PropertyID::Height, *parsed_value);
  1377. }
  1378. }
  1379. // https://html.spec.whatwg.org/multipage/rendering.html#attributes-for-embedded-content-and-images:maps-to-the-dimension-property
  1380. else if (name == HTML::AttributeNames::hspace) {
  1381. if (auto parsed_value = parse_dimension_value(value)) {
  1382. style.set_property(CSS::PropertyID::MarginLeft, *parsed_value);
  1383. style.set_property(CSS::PropertyID::MarginRight, *parsed_value);
  1384. }
  1385. } else if (name == HTML::AttributeNames::vspace) {
  1386. if (auto parsed_value = parse_dimension_value(value)) {
  1387. style.set_property(CSS::PropertyID::MarginTop, *parsed_value);
  1388. style.set_property(CSS::PropertyID::MarginBottom, *parsed_value);
  1389. }
  1390. } else if (name == HTML::AttributeNames::width) {
  1391. if (auto parsed_value = parse_dimension_value(value)) {
  1392. style.set_property(CSS::PropertyID::Width, *parsed_value);
  1393. }
  1394. }
  1395. });
  1396. }
  1397. // https://html.spec.whatwg.org/multipage/input.html#the-input-element%3Aconcept-node-clone-ext
  1398. WebIDL::ExceptionOr<void> HTMLInputElement::cloned(DOM::Node& copy, bool)
  1399. {
  1400. // The cloning steps for input elements must propagate the value, dirty value flag, checkedness, and dirty checkedness flag from the node being cloned to the copy.
  1401. auto& input_clone = verify_cast<HTMLInputElement>(copy);
  1402. input_clone.m_value = m_value;
  1403. input_clone.m_dirty_value = m_dirty_value;
  1404. input_clone.m_checked = m_checked;
  1405. input_clone.m_dirty_checkedness = m_dirty_checkedness;
  1406. return {};
  1407. }
  1408. // https://html.spec.whatwg.org/multipage/input.html#radio-button-group
  1409. static bool is_in_same_radio_button_group(HTML::HTMLInputElement const& a, HTML::HTMLInputElement const& b)
  1410. {
  1411. auto non_empty_equals = [](auto const& value_a, auto const& value_b) {
  1412. return !value_a.is_empty() && value_a == value_b;
  1413. };
  1414. // The radio button group that contains an input element a also contains all the
  1415. // other input elements b that fulfill all of the following conditions:
  1416. return (
  1417. // - Both a and b are in the same tree.
  1418. &a.root() == &b.root()
  1419. // - The input element b's type attribute is in the Radio Button state.
  1420. && a.type_state() == b.type_state()
  1421. && b.type_state() == HTMLInputElement::TypeAttributeState::RadioButton
  1422. // - Either a and b have the same form owner, or they both have no form owner.
  1423. && a.form() == b.form()
  1424. // - They both have a name attribute, their name attributes are not empty, and the
  1425. // value of a's name attribute equals the value of b's name attribute.
  1426. && a.name().has_value()
  1427. && b.name().has_value()
  1428. && non_empty_equals(a.name().value(), b.name().value()));
  1429. }
  1430. // https://html.spec.whatwg.org/multipage/input.html#radio-button-state-(type=radio)
  1431. void HTMLInputElement::set_checked_within_group()
  1432. {
  1433. if (checked())
  1434. return;
  1435. set_checked(true, ChangeSource::User);
  1436. // No point iterating the tree if we have an empty name.
  1437. if (!name().has_value() || name()->is_empty())
  1438. return;
  1439. root().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
  1440. if (element.checked() && &element != this && is_in_same_radio_button_group(*this, element))
  1441. element.set_checked(false, ChangeSource::User);
  1442. return TraversalDecision::Continue;
  1443. });
  1444. }
  1445. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-pre-activation-behavior
  1446. void HTMLInputElement::legacy_pre_activation_behavior()
  1447. {
  1448. m_before_legacy_pre_activation_behavior_checked = checked();
  1449. m_before_legacy_pre_activation_behavior_indeterminate = indeterminate();
  1450. // 1. If this element's type attribute is in the Checkbox state, then set
  1451. // this element's checkedness to its opposite value (i.e. true if it is
  1452. // false, false if it is true) and set this element's indeterminate IDL
  1453. // attribute to false.
  1454. if (type_state() == TypeAttributeState::Checkbox) {
  1455. set_checked(!checked(), ChangeSource::User);
  1456. set_indeterminate(false);
  1457. }
  1458. // 2. If this element's type attribute is in the Radio Button state, then
  1459. // get a reference to the element in this element's radio button group that
  1460. // has its checkedness set to true, if any, and then set this element's
  1461. // checkedness to true.
  1462. if (type_state() == TypeAttributeState::RadioButton) {
  1463. root().for_each_in_inclusive_subtree_of_type<HTML::HTMLInputElement>([&](auto& element) {
  1464. if (element.checked() && is_in_same_radio_button_group(*this, element)) {
  1465. m_legacy_pre_activation_behavior_checked_element_in_group = &element;
  1466. return TraversalDecision::Break;
  1467. }
  1468. return TraversalDecision::Continue;
  1469. });
  1470. set_checked_within_group();
  1471. }
  1472. }
  1473. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-canceled-activation-behavior
  1474. void HTMLInputElement::legacy_cancelled_activation_behavior()
  1475. {
  1476. // 1. If the element's type attribute is in the Checkbox state, then set the
  1477. // element's checkedness and the element's indeterminate IDL attribute back
  1478. // to the values they had before the legacy-pre-activation behavior was run.
  1479. if (type_state() == TypeAttributeState::Checkbox) {
  1480. set_checked(m_before_legacy_pre_activation_behavior_checked, ChangeSource::Programmatic);
  1481. set_indeterminate(m_before_legacy_pre_activation_behavior_indeterminate);
  1482. }
  1483. // 2. If this element 's type attribute is in the Radio Button state, then
  1484. // if the element to which a reference was obtained in the
  1485. // legacy-pre-activation behavior, if any, is still in what is now this
  1486. // element' s radio button group, if it still has one, and if so, setting
  1487. // that element 's checkedness to true; or else, if there was no such
  1488. // element, or that element is no longer in this element' s radio button
  1489. // group, or if this element no longer has a radio button group, setting
  1490. // this element's checkedness to false.
  1491. if (type_state() == TypeAttributeState::RadioButton) {
  1492. bool did_reselect_previous_element = false;
  1493. if (m_legacy_pre_activation_behavior_checked_element_in_group) {
  1494. auto& element_in_group = *m_legacy_pre_activation_behavior_checked_element_in_group;
  1495. if (is_in_same_radio_button_group(*this, element_in_group)) {
  1496. element_in_group.set_checked_within_group();
  1497. did_reselect_previous_element = true;
  1498. }
  1499. m_legacy_pre_activation_behavior_checked_element_in_group = nullptr;
  1500. }
  1501. if (!did_reselect_previous_element)
  1502. set_checked(false, ChangeSource::User);
  1503. }
  1504. }
  1505. void HTMLInputElement::legacy_cancelled_activation_behavior_was_not_called()
  1506. {
  1507. m_legacy_pre_activation_behavior_checked_element_in_group = nullptr;
  1508. }
  1509. GC::Ptr<DecodedImageData> HTMLInputElement::image_data() const
  1510. {
  1511. if (m_resource_request)
  1512. return m_resource_request->image_data();
  1513. return nullptr;
  1514. }
  1515. bool HTMLInputElement::is_image_available() const
  1516. {
  1517. return image_data() != nullptr;
  1518. }
  1519. Optional<CSSPixels> HTMLInputElement::intrinsic_width() const
  1520. {
  1521. if (auto image_data = this->image_data())
  1522. return image_data->intrinsic_width();
  1523. return {};
  1524. }
  1525. Optional<CSSPixels> HTMLInputElement::intrinsic_height() const
  1526. {
  1527. if (auto image_data = this->image_data())
  1528. return image_data->intrinsic_height();
  1529. return {};
  1530. }
  1531. Optional<CSSPixelFraction> HTMLInputElement::intrinsic_aspect_ratio() const
  1532. {
  1533. if (auto image_data = this->image_data())
  1534. return image_data->intrinsic_aspect_ratio();
  1535. return {};
  1536. }
  1537. RefPtr<Gfx::ImmutableBitmap> HTMLInputElement::current_image_bitmap(Gfx::IntSize size) const
  1538. {
  1539. if (auto image_data = this->image_data())
  1540. return image_data->bitmap(0, size);
  1541. return nullptr;
  1542. }
  1543. void HTMLInputElement::set_visible_in_viewport(bool)
  1544. {
  1545. // FIXME: Loosen grip on image data when it's not visible, e.g via volatile memory.
  1546. }
  1547. // https://html.spec.whatwg.org/multipage/interaction.html#dom-tabindex
  1548. i32 HTMLInputElement::default_tab_index_value() const
  1549. {
  1550. // See the base function for the spec comments.
  1551. return 0;
  1552. }
  1553. // https://html.spec.whatwg.org/multipage/input.html#dom-input-maxlength
  1554. WebIDL::Long HTMLInputElement::max_length() const
  1555. {
  1556. // The maxLength IDL attribute must reflect the maxlength content attribute, limited to only non-negative numbers.
  1557. if (auto maxlength_string = get_attribute(HTML::AttributeNames::maxlength); maxlength_string.has_value()) {
  1558. if (auto maxlength = parse_non_negative_integer(*maxlength_string); maxlength.has_value() && *maxlength <= 2147483647)
  1559. return *maxlength;
  1560. }
  1561. return -1;
  1562. }
  1563. WebIDL::ExceptionOr<void> HTMLInputElement::set_max_length(WebIDL::Long value)
  1564. {
  1565. // The maxLength IDL attribute must reflect the maxlength content attribute, limited to only non-negative numbers.
  1566. return set_attribute(HTML::AttributeNames::maxlength, TRY(convert_non_negative_integer_to_string(realm(), value)));
  1567. }
  1568. // https://html.spec.whatwg.org/multipage/input.html#dom-input-minlength
  1569. WebIDL::Long HTMLInputElement::min_length() const
  1570. {
  1571. // The minLength IDL attribute must reflect the minlength content attribute, limited to only non-negative numbers.
  1572. if (auto minlength_string = get_attribute(HTML::AttributeNames::minlength); minlength_string.has_value()) {
  1573. if (auto minlength = parse_non_negative_integer(*minlength_string); minlength.has_value() && *minlength <= 2147483647)
  1574. return *minlength;
  1575. }
  1576. return -1;
  1577. }
  1578. WebIDL::ExceptionOr<void> HTMLInputElement::set_min_length(WebIDL::Long value)
  1579. {
  1580. // The minLength IDL attribute must reflect the minlength content attribute, limited to only non-negative numbers.
  1581. return set_attribute(HTML::AttributeNames::minlength, TRY(convert_non_negative_integer_to_string(realm(), value)));
  1582. }
  1583. // https://html.spec.whatwg.org/multipage/input.html#the-size-attribute
  1584. WebIDL::UnsignedLong HTMLInputElement::size() const
  1585. {
  1586. // The size attribute, if specified, must have a value that is a valid non-negative integer greater than zero.
  1587. // The size IDL attribute is limited to only positive numbers and has a default value of 20.
  1588. if (auto size_string = get_attribute(HTML::AttributeNames::size); size_string.has_value()) {
  1589. if (auto size = parse_non_negative_integer(*size_string); size.has_value() && *size != 0 && *size <= 2147483647)
  1590. return *size;
  1591. }
  1592. return 20;
  1593. }
  1594. WebIDL::ExceptionOr<void> HTMLInputElement::set_size(WebIDL::UnsignedLong value)
  1595. {
  1596. if (value == 0)
  1597. return WebIDL::IndexSizeError::create(realm(), "Size must be greater than zero"_string);
  1598. if (value > 2147483647)
  1599. value = 20;
  1600. return set_attribute(HTML::AttributeNames::size, String::number(value));
  1601. }
  1602. // https://html.spec.whatwg.org/multipage/input.html#dom-input-height
  1603. WebIDL::UnsignedLong HTMLInputElement::height() const
  1604. {
  1605. const_cast<DOM::Document&>(document()).update_layout();
  1606. // When the input element's type attribute is not in the Image Button state, then no image is available.
  1607. if (type_state() != TypeAttributeState::ImageButton)
  1608. return 0;
  1609. // Return the rendered height of the image, in CSS pixels, if the image is being rendered.
  1610. if (auto* paintable_box = this->paintable_box())
  1611. return paintable_box->content_height().to_int();
  1612. // On setting [the width or height IDL attribute], they must act as if they reflected the respective content attributes of the same name.
  1613. if (auto height_string = get_attribute(HTML::AttributeNames::height); height_string.has_value()) {
  1614. if (auto height = parse_non_negative_integer(*height_string); height.has_value() && *height <= 2147483647)
  1615. return *height;
  1616. }
  1617. // ...or else the natural height and height of the image, in CSS pixels, if an image is available but not being rendered
  1618. if (auto bitmap = current_image_bitmap())
  1619. return bitmap->height();
  1620. // ...or else 0, if the image is not available or does not have intrinsic dimensions.
  1621. return 0;
  1622. }
  1623. WebIDL::ExceptionOr<void> HTMLInputElement::set_height(WebIDL::UnsignedLong value)
  1624. {
  1625. if (value > 2147483647)
  1626. value = 0;
  1627. return set_attribute(HTML::AttributeNames::height, String::number(value));
  1628. }
  1629. // https://html.spec.whatwg.org/multipage/input.html#dom-input-width
  1630. WebIDL::UnsignedLong HTMLInputElement::width() const
  1631. {
  1632. const_cast<DOM::Document&>(document()).update_layout();
  1633. // When the input element's type attribute is not in the Image Button state, then no image is available.
  1634. if (type_state() != TypeAttributeState::ImageButton)
  1635. return 0;
  1636. // Return the rendered width of the image, in CSS pixels, if the image is being rendered.
  1637. if (auto* paintable_box = this->paintable_box())
  1638. return paintable_box->content_width().to_int();
  1639. // On setting [the width or height IDL attribute], they must act as if they reflected the respective content attributes of the same name.
  1640. if (auto width_string = get_attribute(HTML::AttributeNames::width); width_string.has_value()) {
  1641. if (auto width = parse_non_negative_integer(*width_string); width.has_value() && *width <= 2147483647)
  1642. return *width;
  1643. }
  1644. // ...or else the natural width and height of the image, in CSS pixels, if an image is available but not being rendered
  1645. if (auto bitmap = current_image_bitmap())
  1646. return bitmap->width();
  1647. // ...or else 0, if the image is not available or does not have intrinsic dimensions.
  1648. return 0;
  1649. }
  1650. WebIDL::ExceptionOr<void> HTMLInputElement::set_width(WebIDL::UnsignedLong value)
  1651. {
  1652. if (value > 2147483647)
  1653. value = 0;
  1654. return set_attribute(HTML::AttributeNames::width, String::number(value));
  1655. }
  1656. // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number
  1657. Optional<double> HTMLInputElement::convert_string_to_number(StringView input) const
  1658. {
  1659. // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):concept-input-value-string-number
  1660. if (type_state() == TypeAttributeState::Number)
  1661. return parse_floating_point_number(input);
  1662. // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-value-string-number
  1663. if (type_state() == TypeAttributeState::Range)
  1664. return parse_floating_point_number(input);
  1665. dbgln("HTMLInputElement::convert_string_to_number() not implemented for input type {}", type());
  1666. return {};
  1667. }
  1668. // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number
  1669. String HTMLInputElement::convert_number_to_string(double input) const
  1670. {
  1671. // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):concept-input-value-number-string
  1672. if (type_state() == TypeAttributeState::Number)
  1673. return String::number(input);
  1674. // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-value-number-string
  1675. if (type_state() == TypeAttributeState::Range)
  1676. return String::number(input);
  1677. dbgln("HTMLInputElement::convert_number_to_string() not implemented for input type {}", type());
  1678. return {};
  1679. }
  1680. // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-date
  1681. WebIDL::ExceptionOr<GC::Ptr<JS::Date>> HTMLInputElement::convert_string_to_date(StringView input) const
  1682. {
  1683. // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-string-date
  1684. if (type_state() == TypeAttributeState::Date) {
  1685. // If parsing a date from input results in an error, then return an error;
  1686. auto maybe_date = parse_date_string(realm(), input);
  1687. if (maybe_date.is_exception())
  1688. return maybe_date.exception();
  1689. // otherwise, return a new Date object representing midnight UTC on the morning of the parsed date.
  1690. return maybe_date.value();
  1691. }
  1692. // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):concept-input-value-string-date
  1693. if (type_state() == TypeAttributeState::Time) {
  1694. // If parsing a time from input results in an error, then return an error;
  1695. auto maybe_time = parse_time_string(realm(), input);
  1696. if (maybe_time.is_exception())
  1697. return maybe_time.exception();
  1698. // otherwise, return a new Date object representing the parsed time in UTC on 1970-01-01.
  1699. return maybe_time.value();
  1700. }
  1701. dbgln("HTMLInputElement::convert_string_to_date() not implemented for input type {}", type());
  1702. return nullptr;
  1703. }
  1704. // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-date-string
  1705. String HTMLInputElement::covert_date_to_string(GC::Ref<JS::Date> input) const
  1706. {
  1707. // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-date-string
  1708. if (type_state() == TypeAttributeState::Date) {
  1709. // Return a valid date string that represents the date current at the time represented by input in the UTC time zone.
  1710. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string
  1711. return MUST(String::formatted("{:04d}-{:02d}-{:02d}", JS::year_from_time(input->date_value()), JS::month_from_time(input->date_value()) + 1, JS::date_from_time(input->date_value())));
  1712. }
  1713. // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):concept-input-value-string-date
  1714. if (type_state() == TypeAttributeState::Time) {
  1715. // Return a valid time string that represents the UTC time component that is represented by input.
  1716. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-time-string
  1717. auto seconds = JS::sec_from_time(input->date_value());
  1718. auto milliseconds = JS::ms_from_time(input->date_value());
  1719. if (seconds > 0) {
  1720. if (milliseconds > 0)
  1721. return MUST(String::formatted("{:02d}:{:02d}:{:02d}.{:3d}", JS::hour_from_time(input->date_value()), JS::min_from_time(input->date_value()), seconds, milliseconds));
  1722. return MUST(String::formatted("{:02d}:{:02d}:{:02d}", JS::hour_from_time(input->date_value()), JS::min_from_time(input->date_value()), seconds));
  1723. }
  1724. return MUST(String::formatted("{:02d}:{:02d}", JS::hour_from_time(input->date_value()), JS::min_from_time(input->date_value())));
  1725. }
  1726. dbgln("HTMLInputElement::covert_date_to_string() not implemented for input type {}", type());
  1727. return {};
  1728. }
  1729. // https://html.spec.whatwg.org/multipage/input.html#attr-input-min
  1730. Optional<double> HTMLInputElement::min() const
  1731. {
  1732. // If the element has a min attribute, and the result of applying the algorithm to convert a string to a number to
  1733. // the value of the min attribute is a number, then that number is the element's minimum; otherwise, if the type
  1734. // attribute's current state defines a default minimum, then that is the minimum; otherwise, the element has no minimum.
  1735. if (auto min_string = get_attribute(HTML::AttributeNames::min); min_string.has_value()) {
  1736. if (auto min = convert_string_to_number(*min_string); min.has_value())
  1737. return *min;
  1738. }
  1739. // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-min-default
  1740. if (type_state() == TypeAttributeState::Range)
  1741. return 0;
  1742. return {};
  1743. }
  1744. // https://html.spec.whatwg.org/multipage/input.html#attr-input-max
  1745. Optional<double> HTMLInputElement::max() const
  1746. {
  1747. // If the element has a max attribute, and the result of applying the algorithm to convert a string to a number to the
  1748. // value of the max attribute is a number, then that number is the element's maximum; otherwise, if the type attribute's
  1749. // current state defines a default maximum, then that is the maximum; otherwise, the element has no maximum.
  1750. if (auto max_string = get_attribute(HTML::AttributeNames::max); max_string.has_value()) {
  1751. if (auto max = convert_string_to_number(*max_string); max.has_value())
  1752. return *max;
  1753. }
  1754. // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-max-default
  1755. if (type_state() == TypeAttributeState::Range)
  1756. return 100;
  1757. return {};
  1758. }
  1759. // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-default
  1760. double HTMLInputElement::default_step() const
  1761. {
  1762. // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):concept-input-step-default
  1763. if (type_state() == TypeAttributeState::Number)
  1764. return 1;
  1765. // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-step-default
  1766. if (type_state() == TypeAttributeState::Range)
  1767. return 1;
  1768. dbgln("HTMLInputElement::default_step() not implemented for input type {}", type());
  1769. return 0;
  1770. }
  1771. // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-scale
  1772. double HTMLInputElement::step_scale_factor() const
  1773. {
  1774. // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):concept-input-step-scale
  1775. if (type_state() == TypeAttributeState::Number)
  1776. return 1;
  1777. // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-step-scale
  1778. if (type_state() == TypeAttributeState::Range)
  1779. return 1;
  1780. dbgln("HTMLInputElement::step_scale_factor() not implemented for input type {}", type());
  1781. return 0;
  1782. }
  1783. // https://html.spec.whatwg.org/multipage/input.html#concept-input-step
  1784. Optional<double> HTMLInputElement::allowed_value_step() const
  1785. {
  1786. // 1. If the attribute does not apply, then there is no allowed value step.
  1787. if (!step_applies())
  1788. return {};
  1789. // 2. Otherwise, if the attribute is absent, then the allowed value step is the default step multiplied by the step scale factor.
  1790. auto maybe_step_string = get_attribute(AttributeNames::step);
  1791. if (!maybe_step_string.has_value())
  1792. return default_step() * step_scale_factor();
  1793. auto step_string = *maybe_step_string;
  1794. // 3. Otherwise, if the attribute's value is an ASCII case-insensitive match for the string "any", then there is no allowed value step.
  1795. if (Infra::is_ascii_case_insensitive_match(step_string, "any"_string))
  1796. return {};
  1797. // 4. Otherwise, if the rules for parsing floating-point number values, when they are applied to the attribute's value, return an error,
  1798. // zero, or a number less than zero, then the allowed value step is the default step multiplied by the step scale factor.
  1799. auto maybe_step = parse_floating_point_number(step_string);
  1800. if (!maybe_step.has_value() || *maybe_step == 0 || *maybe_step < 0)
  1801. return default_step() * step_scale_factor();
  1802. // 5. Otherwise, the allowed value step is the number returned by the rules for parsing floating-point number values when they are applied
  1803. // to the attribute's value, multiplied by the step scale factor.
  1804. return *maybe_step * step_scale_factor();
  1805. }
  1806. // https://html.spec.whatwg.org/multipage/input.html#concept-input-min-zero
  1807. double HTMLInputElement::step_base() const
  1808. {
  1809. // 1. If the element has a min content attribute, and the result of applying the algorithm to convert a string to a number to the value of
  1810. // the min content attribute is not an error, then return that result.
  1811. if (auto min = this->min(); min.has_value())
  1812. return *min;
  1813. // 2. If the element has a value content attribute, and the result of applying the algorithm to convert a string to a number to the value of
  1814. // the value content attribute is not an error, then return that result.
  1815. if (auto value = convert_string_to_number(this->value()); value.has_value())
  1816. return *value;
  1817. // 3. If a default step base is defined for this element given its type attribute's state, then return it.
  1818. if (type_state() == TypeAttributeState::Week) {
  1819. // The default step base is −259,200,000 (the start of week 1970-W01).
  1820. return -259'200'000;
  1821. }
  1822. // 4. Return zero.
  1823. return 0;
  1824. }
  1825. // https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasdate
  1826. JS::Object* HTMLInputElement::value_as_date() const
  1827. {
  1828. // On getting, if the valueAsDate attribute does not apply, as defined for the input element's type attribute's current state, then return null.
  1829. if (!value_as_date_applies())
  1830. return nullptr;
  1831. // Otherwise, run the algorithm to convert a string to a Date object defined for that state to the element's value;
  1832. // if the algorithm returned a Date object, then return it, otherwise, return null.
  1833. auto maybe_date = convert_string_to_date(value());
  1834. if (!maybe_date.is_exception())
  1835. return maybe_date.value().ptr();
  1836. return nullptr;
  1837. }
  1838. // https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasdate
  1839. WebIDL::ExceptionOr<void> HTMLInputElement::set_value_as_date(Optional<GC::Root<JS::Object>> const& value)
  1840. {
  1841. // On setting, if the valueAsDate attribute does not apply, as defined for the input element's type attribute's current state, then throw an "InvalidStateError" DOMException;
  1842. if (!value_as_date_applies())
  1843. return WebIDL::InvalidStateError::create(realm(), "valueAsDate: Invalid input type used"_string);
  1844. // otherwise, if the new value is not null and not a Date object throw a TypeError exception;
  1845. if (value.has_value() && !is<JS::Date>(**value))
  1846. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "valueAsDate: input is not a Date"sv };
  1847. // otherwise if the new value is null or a Date object representing the NaN time value, then set the value of the element to the empty string;
  1848. if (!value.has_value()) {
  1849. TRY(set_value(String {}));
  1850. return {};
  1851. }
  1852. auto& date = static_cast<JS::Date&>(**value);
  1853. if (!isfinite(date.date_value())) {
  1854. TRY(set_value(String {}));
  1855. return {};
  1856. }
  1857. // otherwise, run the algorithm to convert a Date object to a string, as defined for that state, on the new value, and set the value of the element to the resulting string.
  1858. TRY(set_value(covert_date_to_string(date)));
  1859. return {};
  1860. }
  1861. // https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasnumber
  1862. double HTMLInputElement::value_as_number() const
  1863. {
  1864. // 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.
  1865. if (!value_as_number_applies())
  1866. return NAN;
  1867. // Otherwise, run the algorithm to convert a string to a number defined for that state to the element's value;
  1868. // if the algorithm returned a number, then return it, otherwise, return a Not-a-Number (NaN) value.
  1869. return convert_string_to_number(value()).value_or(NAN);
  1870. }
  1871. // https://html.spec.whatwg.org/multipage/input.html#dom-input-valueasnumber
  1872. WebIDL::ExceptionOr<void> HTMLInputElement::set_value_as_number(double value)
  1873. {
  1874. // On setting, if the new value is infinite, then throw a TypeError exception.
  1875. if (!isfinite(value))
  1876. return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "valueAsNumber: Value is infinite"sv };
  1877. // 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.
  1878. if (!value_as_number_applies())
  1879. return WebIDL::InvalidStateError::create(realm(), "valueAsNumber: Invalid input type used"_string);
  1880. // Otherwise, if the new value is a Not-a-Number (NaN) value, then set the value of the element to the empty string.
  1881. if (value == NAN) {
  1882. TRY(set_value(String {}));
  1883. return {};
  1884. }
  1885. // 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.
  1886. TRY(set_value(convert_number_to_string(value)));
  1887. return {};
  1888. }
  1889. // https://html.spec.whatwg.org/multipage/input.html#dom-input-stepup
  1890. WebIDL::ExceptionOr<void> HTMLInputElement::step_up(WebIDL::Long n)
  1891. {
  1892. return step_up_or_down(false, n);
  1893. }
  1894. // https://html.spec.whatwg.org/multipage/input.html#dom-input-stepdown
  1895. WebIDL::ExceptionOr<void> HTMLInputElement::step_down(WebIDL::Long n)
  1896. {
  1897. return step_up_or_down(true, n);
  1898. }
  1899. // https://html.spec.whatwg.org/multipage/input.html#dom-input-stepup
  1900. WebIDL::ExceptionOr<void> HTMLInputElement::step_up_or_down(bool is_down, WebIDL::Long n)
  1901. {
  1902. // 1. If the stepDown() and stepUp() methods do not apply, as defined for the input element's type attribute's current state, then throw an "InvalidStateError" DOMException.
  1903. if (!step_up_or_down_applies())
  1904. return WebIDL::InvalidStateError::create(realm(), MUST(String::formatted("{}: Invalid input type used", is_down ? "stepDown()" : "stepUp()")));
  1905. // 2. If the element has no allowed value step, then throw an "InvalidStateError" DOMException.
  1906. auto maybe_allowed_value_step = allowed_value_step();
  1907. if (!maybe_allowed_value_step.has_value())
  1908. return WebIDL::InvalidStateError::create(realm(), "element has no allowed value step"_string);
  1909. double allowed_value_step = *maybe_allowed_value_step;
  1910. // 3. If the element has a minimum and a maximum and the minimum is greater than the maximum, then return.
  1911. auto maybe_minimum = min();
  1912. auto maybe_maximum = max();
  1913. if (maybe_minimum.has_value() && maybe_maximum.has_value() && *maybe_minimum > *maybe_maximum)
  1914. return {};
  1915. // FIXME: 4. If the element has a minimum and a maximum and there is no value greater than or equal to the element's minimum and less than
  1916. // or equal to the element's maximum that, when subtracted from the step base, is an integral multiple of the allowed value step, then return.
  1917. // 5. If applying the algorithm to convert a string to a number to the string given by the element's value does not result in an error,
  1918. // then let value be the result of that algorithm. Otherwise, let value be zero.
  1919. double value = convert_string_to_number(this->value()).value_or(0);
  1920. // 6. Let valueBeforeStepping be value.
  1921. double value_before_stepping = value;
  1922. // 7. If value subtracted from the step base is not an integral multiple of the allowed value step, then set value to the nearest value that,
  1923. // when subtracted from the step base, is an integral multiple of the allowed value step, and that is less than value if the method invoked was the stepDown() method, and more than value otherwise.
  1924. if (fmod(step_base() - value, allowed_value_step) != 0) {
  1925. double diff = step_base() - value;
  1926. if (is_down) {
  1927. value = diff - fmod(diff, allowed_value_step);
  1928. } else {
  1929. value = diff + fmod(diff, allowed_value_step);
  1930. }
  1931. } else {
  1932. // 1. Let n be the argument.
  1933. // 2. Let delta be the allowed value step multiplied by n.
  1934. double delta = allowed_value_step * n;
  1935. // 3. If the method invoked was the stepDown() method, negate delta.
  1936. if (is_down)
  1937. delta = -delta;
  1938. // 4. Let value be the result of adding delta to value.
  1939. value += delta;
  1940. }
  1941. // 8. If the element has a minimum, and value is less than that minimum, then set value to the smallest value that,
  1942. // when subtracted from the step base, is an integral multiple of the allowed value step, and that is more than or equal to minimum.
  1943. if (maybe_minimum.has_value() && value < *maybe_minimum) {
  1944. value = AK::max(value, *maybe_minimum);
  1945. }
  1946. // 9. If the element has a maximum, and value is greater than that maximum, then set value to the largest value that,
  1947. // when subtracted from the step base, is an integral multiple of the allowed value step, and that is less than or equal to maximum.
  1948. if (maybe_maximum.has_value() && value > *maybe_maximum) {
  1949. value = AK::min(value, *maybe_maximum);
  1950. }
  1951. // 10. If either the method invoked was the stepDown() method and value is greater than valueBeforeStepping,
  1952. // or the method invoked was the stepUp() method and value is less than valueBeforeStepping, then return.
  1953. if (is_down) {
  1954. if (value > value_before_stepping)
  1955. return {};
  1956. } else {
  1957. if (value < value_before_stepping)
  1958. return {};
  1959. }
  1960. // 11. Let value as string be the result of running the algorithm to convert a number to a string,
  1961. // as defined for the input element's type attribute's current state, on value.
  1962. auto value_as_string = convert_number_to_string(value);
  1963. // 12. Set the value of the element to value as string.
  1964. TRY(set_value(value_as_string));
  1965. return {};
  1966. }
  1967. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-checkvalidity
  1968. WebIDL::ExceptionOr<bool> HTMLInputElement::check_validity()
  1969. {
  1970. dbgln("(STUBBED) HTMLInputElement::check_validity(). Called on: {}", debug_description());
  1971. return true;
  1972. }
  1973. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-reportvalidity
  1974. WebIDL::ExceptionOr<bool> HTMLInputElement::report_validity()
  1975. {
  1976. dbgln("(STUBBED) HTMLInputElement::report_validity(). Called on: {}", debug_description());
  1977. return true;
  1978. }
  1979. // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-setcustomvalidity
  1980. void HTMLInputElement::set_custom_validity(String const& error)
  1981. {
  1982. dbgln("(STUBBED) HTMLInputElement::set_custom_validity(error={}). Called on: {}", error, debug_description());
  1983. return;
  1984. }
  1985. Optional<ARIA::Role> HTMLInputElement::default_role() const
  1986. {
  1987. // https://www.w3.org/TR/html-aria/#el-input-button
  1988. if (type_state() == TypeAttributeState::Button)
  1989. return ARIA::Role::button;
  1990. // https://www.w3.org/TR/html-aria/#el-input-checkbox
  1991. if (type_state() == TypeAttributeState::Checkbox) {
  1992. // https://github.com/w3c/html-aam/issues/496
  1993. if (has_attribute("switch"_string))
  1994. return ARIA::Role::switch_;
  1995. return ARIA::Role::checkbox;
  1996. }
  1997. // https://www.w3.org/TR/html-aria/#el-input-email
  1998. if (type_state() == TypeAttributeState::Email && !has_attribute(AttributeNames::list))
  1999. return ARIA::Role::textbox;
  2000. // https://www.w3.org/TR/html-aria/#el-input-image
  2001. if (type_state() == TypeAttributeState::ImageButton)
  2002. return ARIA::Role::button;
  2003. // https://www.w3.org/TR/html-aria/#el-input-number
  2004. if (type_state() == TypeAttributeState::Number)
  2005. return ARIA::Role::spinbutton;
  2006. // https://www.w3.org/TR/html-aria/#el-input-radio
  2007. if (type_state() == TypeAttributeState::RadioButton)
  2008. return ARIA::Role::radio;
  2009. // https://www.w3.org/TR/html-aria/#el-input-range
  2010. if (type_state() == TypeAttributeState::Range)
  2011. return ARIA::Role::slider;
  2012. // https://www.w3.org/TR/html-aria/#el-input-reset
  2013. if (type_state() == TypeAttributeState::ResetButton)
  2014. return ARIA::Role::button;
  2015. // https://www.w3.org/TR/html-aria/#el-input-text-list
  2016. if ((type_state() == TypeAttributeState::Text
  2017. || type_state() == TypeAttributeState::Search
  2018. || type_state() == TypeAttributeState::Telephone
  2019. || type_state() == TypeAttributeState::URL
  2020. || type_state() == TypeAttributeState::Email)
  2021. && has_attribute(AttributeNames::list))
  2022. return ARIA::Role::combobox;
  2023. // https://www.w3.org/TR/html-aria/#el-input-search
  2024. if (type_state() == TypeAttributeState::Search && !has_attribute(AttributeNames::list))
  2025. return ARIA::Role::searchbox;
  2026. // https://www.w3.org/TR/html-aria/#el-input-submit
  2027. if (type_state() == TypeAttributeState::SubmitButton)
  2028. return ARIA::Role::button;
  2029. // https://www.w3.org/TR/html-aria/#el-input-tel
  2030. if (type_state() == TypeAttributeState::Telephone)
  2031. return ARIA::Role::textbox;
  2032. // https://www.w3.org/TR/html-aria/#el-input-text
  2033. if (type_state() == TypeAttributeState::Text && !has_attribute(AttributeNames::list))
  2034. return ARIA::Role::textbox;
  2035. // https://www.w3.org/TR/html-aria/#el-input-url
  2036. if (type_state() == TypeAttributeState::URL && !has_attribute(AttributeNames::list))
  2037. return ARIA::Role::textbox;
  2038. // https://www.w3.org/TR/html-aria/#el-input-color
  2039. // https://www.w3.org/TR/html-aria/#el-input-date
  2040. // https://www.w3.org/TR/html-aria/#el-input-datetime-local
  2041. // https://www.w3.org/TR/html-aria/#el-input-file
  2042. // https://www.w3.org/TR/html-aria/#el-input-hidden
  2043. // https://www.w3.org/TR/html-aria/#el-input-month
  2044. // https://www.w3.org/TR/html-aria/#el-input-password
  2045. // https://www.w3.org/TR/html-aria/#el-input-time
  2046. // https://www.w3.org/TR/html-aria/#el-input-week
  2047. return {};
  2048. }
  2049. bool HTMLInputElement::is_button() const
  2050. {
  2051. // https://html.spec.whatwg.org/multipage/input.html#submit-button-state-(type=submit):concept-button
  2052. // https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):concept-button
  2053. // https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type=reset):concept-button
  2054. // https://html.spec.whatwg.org/multipage/input.html#button-state-(type=button):concept-button
  2055. return type_state() == TypeAttributeState::SubmitButton
  2056. || type_state() == TypeAttributeState::ImageButton
  2057. || type_state() == TypeAttributeState::ResetButton
  2058. || type_state() == TypeAttributeState::Button;
  2059. }
  2060. bool HTMLInputElement::is_submit_button() const
  2061. {
  2062. // https://html.spec.whatwg.org/multipage/input.html#submit-button-state-(type=submit):concept-submit-button
  2063. // https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):concept-submit-button
  2064. return type_state() == TypeAttributeState::SubmitButton
  2065. || type_state() == TypeAttributeState::ImageButton;
  2066. }
  2067. // https://html.spec.whatwg.org/multipage/input.html#text-(type=text)-state-and-search-state-(type=search)
  2068. // https://html.spec.whatwg.org/multipage/input.html#password-state-(type=password)
  2069. // "one line plain text edit control"
  2070. bool HTMLInputElement::is_single_line() const
  2071. {
  2072. // NOTE: For web compatibility reasons, we consider other types
  2073. // in addition to Text, Search, and Password as single line inputs.
  2074. return type_state() == TypeAttributeState::Text
  2075. || type_state() == TypeAttributeState::Search
  2076. || type_state() == TypeAttributeState::Password
  2077. || type_state() == TypeAttributeState::Email
  2078. || type_state() == TypeAttributeState::Telephone
  2079. || type_state() == TypeAttributeState::Number;
  2080. }
  2081. bool HTMLInputElement::has_activation_behavior() const
  2082. {
  2083. return true;
  2084. }
  2085. void HTMLInputElement::activation_behavior(DOM::Event const& event)
  2086. {
  2087. // The activation behavior for input elements are these steps:
  2088. // FIXME: 1. If this element is not mutable and is not in the Checkbox state and is not in the Radio state, then return.
  2089. // 2. Run this element's input activation behavior, if any, and do nothing otherwise.
  2090. run_input_activation_behavior(event).release_value_but_fixme_should_propagate_errors();
  2091. }
  2092. bool HTMLInputElement::has_input_activation_behavior() const
  2093. {
  2094. switch (type_state()) {
  2095. case TypeAttributeState::Checkbox:
  2096. case TypeAttributeState::Color:
  2097. case TypeAttributeState::FileUpload:
  2098. case TypeAttributeState::ImageButton:
  2099. case TypeAttributeState::RadioButton:
  2100. case TypeAttributeState::ResetButton:
  2101. case TypeAttributeState::SubmitButton:
  2102. return true;
  2103. default:
  2104. return false;
  2105. }
  2106. }
  2107. // https://html.spec.whatwg.org/multipage/input.html#do-not-apply
  2108. bool HTMLInputElement::select_applies() const
  2109. {
  2110. switch (type_state()) {
  2111. case TypeAttributeState::Button:
  2112. case TypeAttributeState::Checkbox:
  2113. case TypeAttributeState::Hidden:
  2114. case TypeAttributeState::ImageButton:
  2115. case TypeAttributeState::RadioButton:
  2116. case TypeAttributeState::Range:
  2117. case TypeAttributeState::ResetButton:
  2118. case TypeAttributeState::SubmitButton:
  2119. return false;
  2120. default:
  2121. return true;
  2122. }
  2123. }
  2124. // https://html.spec.whatwg.org/multipage/input.html#do-not-apply
  2125. bool HTMLInputElement::selection_or_range_applies() const
  2126. {
  2127. return selection_or_range_applies_for_type_state(type_state());
  2128. }
  2129. // https://html.spec.whatwg.org/multipage/input.html#do-not-apply
  2130. bool HTMLInputElement::selection_direction_applies() const
  2131. {
  2132. switch (type_state()) {
  2133. case TypeAttributeState::Text:
  2134. case TypeAttributeState::Search:
  2135. case TypeAttributeState::Telephone:
  2136. case TypeAttributeState::URL:
  2137. case TypeAttributeState::Password:
  2138. return true;
  2139. default:
  2140. return false;
  2141. }
  2142. }
  2143. bool HTMLInputElement::has_selectable_text() const
  2144. {
  2145. // Potential FIXME: Date, Month, Week, Time and LocalDateAndTime are rendered as a basic text input for now,
  2146. // thus they have selectable text, this need to change when we will have a visual date/time selector.
  2147. switch (type_state()) {
  2148. case TypeAttributeState::Text:
  2149. case TypeAttributeState::Search:
  2150. case TypeAttributeState::Telephone:
  2151. case TypeAttributeState::URL:
  2152. case TypeAttributeState::Password:
  2153. case TypeAttributeState::Date:
  2154. case TypeAttributeState::Month:
  2155. case TypeAttributeState::Week:
  2156. case TypeAttributeState::Time:
  2157. case TypeAttributeState::LocalDateAndTime:
  2158. case TypeAttributeState::Number:
  2159. return true;
  2160. default:
  2161. return false;
  2162. }
  2163. }
  2164. bool HTMLInputElement::selection_or_range_applies_for_type_state(TypeAttributeState type_state)
  2165. {
  2166. switch (type_state) {
  2167. case TypeAttributeState::Text:
  2168. case TypeAttributeState::Search:
  2169. case TypeAttributeState::Telephone:
  2170. case TypeAttributeState::URL:
  2171. case TypeAttributeState::Password:
  2172. return true;
  2173. default:
  2174. return false;
  2175. }
  2176. }
  2177. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:event-change-2
  2178. bool HTMLInputElement::change_event_applies() const
  2179. {
  2180. switch (type_state()) {
  2181. case TypeAttributeState::Checkbox:
  2182. case TypeAttributeState::Color:
  2183. case TypeAttributeState::Date:
  2184. case TypeAttributeState::Email:
  2185. case TypeAttributeState::FileUpload:
  2186. case TypeAttributeState::LocalDateAndTime:
  2187. case TypeAttributeState::Month:
  2188. case TypeAttributeState::Number:
  2189. case TypeAttributeState::Password:
  2190. case TypeAttributeState::RadioButton:
  2191. case TypeAttributeState::Range:
  2192. case TypeAttributeState::Search:
  2193. case TypeAttributeState::Telephone:
  2194. case TypeAttributeState::Text:
  2195. case TypeAttributeState::Time:
  2196. case TypeAttributeState::URL:
  2197. case TypeAttributeState::Week:
  2198. return true;
  2199. default:
  2200. return false;
  2201. }
  2202. }
  2203. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:dom-input-valueasdate-3
  2204. bool HTMLInputElement::value_as_date_applies() const
  2205. {
  2206. switch (type_state()) {
  2207. case TypeAttributeState::Date:
  2208. case TypeAttributeState::Month:
  2209. case TypeAttributeState::Week:
  2210. case TypeAttributeState::Time:
  2211. return true;
  2212. default:
  2213. return false;
  2214. }
  2215. }
  2216. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:dom-input-valueasnumber-3
  2217. bool HTMLInputElement::value_as_number_applies() const
  2218. {
  2219. switch (type_state()) {
  2220. case TypeAttributeState::Date:
  2221. case TypeAttributeState::Month:
  2222. case TypeAttributeState::Week:
  2223. case TypeAttributeState::Time:
  2224. case TypeAttributeState::LocalDateAndTime:
  2225. case TypeAttributeState::Number:
  2226. case TypeAttributeState::Range:
  2227. return true;
  2228. default:
  2229. return false;
  2230. }
  2231. }
  2232. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-step-3
  2233. bool HTMLInputElement::step_applies() const
  2234. {
  2235. return value_as_number_applies();
  2236. }
  2237. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:dom-input-stepup-3
  2238. bool HTMLInputElement::step_up_or_down_applies() const
  2239. {
  2240. return value_as_number_applies();
  2241. }
  2242. // https://html.spec.whatwg.org/multipage/input.html#the-input-element:dom-input-value-2
  2243. HTMLInputElement::ValueAttributeMode HTMLInputElement::value_attribute_mode_for_type_state(TypeAttributeState type_state)
  2244. {
  2245. switch (type_state) {
  2246. case TypeAttributeState::Text:
  2247. case TypeAttributeState::Search:
  2248. case TypeAttributeState::Telephone:
  2249. case TypeAttributeState::URL:
  2250. case TypeAttributeState::Email:
  2251. case TypeAttributeState::Password:
  2252. case TypeAttributeState::Date:
  2253. case TypeAttributeState::Month:
  2254. case TypeAttributeState::Week:
  2255. case TypeAttributeState::Time:
  2256. case TypeAttributeState::LocalDateAndTime:
  2257. case TypeAttributeState::Number:
  2258. case TypeAttributeState::Range:
  2259. case TypeAttributeState::Color:
  2260. return ValueAttributeMode::Value;
  2261. case TypeAttributeState::Hidden:
  2262. case TypeAttributeState::SubmitButton:
  2263. case TypeAttributeState::ImageButton:
  2264. case TypeAttributeState::ResetButton:
  2265. case TypeAttributeState::Button:
  2266. return ValueAttributeMode::Default;
  2267. case TypeAttributeState::Checkbox:
  2268. case TypeAttributeState::RadioButton:
  2269. return ValueAttributeMode::DefaultOn;
  2270. case TypeAttributeState::FileUpload:
  2271. return ValueAttributeMode::Filename;
  2272. }
  2273. VERIFY_NOT_REACHED();
  2274. }
  2275. HTMLInputElement::ValueAttributeMode HTMLInputElement::value_attribute_mode() const
  2276. {
  2277. return value_attribute_mode_for_type_state(type_state());
  2278. }
  2279. bool HTMLInputElement::is_focusable() const
  2280. {
  2281. return m_type != TypeAttributeState::Hidden && enabled();
  2282. }
  2283. }