/src/serenity/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2022, Adam Hodgen <ant1441@gmail.com> |
4 | | * Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com> |
5 | | * Copyright (c) 2024, Jelle Raaijmakers <jelle@gmta.nl> |
6 | | * |
7 | | * SPDX-License-Identifier: BSD-2-Clause |
8 | | */ |
9 | | |
10 | | #pragma once |
11 | | |
12 | | #include <LibWeb/DOM/DocumentLoadEventDelayer.h> |
13 | | #include <LibWeb/DOM/Text.h> |
14 | | #include <LibWeb/FileAPI/FileList.h> |
15 | | #include <LibWeb/HTML/ColorPickerUpdateState.h> |
16 | | #include <LibWeb/HTML/FileFilter.h> |
17 | | #include <LibWeb/HTML/FormAssociatedElement.h> |
18 | | #include <LibWeb/HTML/HTMLElement.h> |
19 | | #include <LibWeb/Layout/ImageProvider.h> |
20 | | #include <LibWeb/WebIDL/DOMException.h> |
21 | | #include <LibWeb/WebIDL/Types.h> |
22 | | |
23 | | namespace Web::HTML { |
24 | | |
25 | | // https://html.spec.whatwg.org/multipage/input.html#attr-input-type |
26 | | #define ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTES \ |
27 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("hidden", Hidden) \ |
28 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("text", Text) \ |
29 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("search", Search) \ |
30 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("tel", Telephone) \ |
31 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("url", URL) \ |
32 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("email", Email) \ |
33 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("password", Password) \ |
34 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("date", Date) \ |
35 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("month", Month) \ |
36 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("week", Week) \ |
37 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("time", Time) \ |
38 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("datetime-local", LocalDateAndTime) \ |
39 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("number", Number) \ |
40 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("range", Range) \ |
41 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("color", Color) \ |
42 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("checkbox", Checkbox) \ |
43 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("radio", RadioButton) \ |
44 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("file", FileUpload) \ |
45 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("submit", SubmitButton) \ |
46 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("image", ImageButton) \ |
47 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("reset", ResetButton) \ |
48 | 0 | __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE("button", Button) |
49 | | |
50 | | class HTMLInputElement final |
51 | | : public HTMLElement |
52 | | , public FormAssociatedTextControlElement |
53 | | , public DOM::EditableTextNodeOwner |
54 | | , public Layout::ImageProvider { |
55 | | WEB_PLATFORM_OBJECT(HTMLInputElement, HTMLElement); |
56 | | JS_DECLARE_ALLOCATOR(HTMLInputElement); |
57 | | FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLInputElement) |
58 | | |
59 | | public: |
60 | | virtual ~HTMLInputElement() override; |
61 | | |
62 | | virtual JS::GCPtr<Layout::Node> create_layout_node(NonnullRefPtr<CSS::StyleProperties>) override; |
63 | | virtual void adjust_computed_style(CSS::StyleProperties&) override; |
64 | | |
65 | | enum class TypeAttributeState { |
66 | | #define __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE(_, state) state, |
67 | | ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTES |
68 | | #undef __ENUMERATE_HTML_INPUT_TYPE_ATTRIBUTE |
69 | | }; |
70 | | |
71 | | StringView type() const; |
72 | 0 | TypeAttributeState type_state() const { return m_type; } |
73 | | WebIDL::ExceptionOr<void> set_type(String const&); |
74 | | |
75 | 0 | String default_value() const { return get_attribute_value(HTML::AttributeNames::value); } |
76 | | |
77 | | virtual String value() const override; |
78 | | WebIDL::ExceptionOr<void> set_value(String const&); |
79 | | |
80 | | // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value |
81 | 0 | virtual String relevant_value() override { return value(); } |
82 | 0 | WebIDL::ExceptionOr<void> set_relevant_value(String const& value) override { return set_value(value); } |
83 | | |
84 | 0 | virtual void set_dirty_value_flag(bool flag) override { m_dirty_value = flag; } |
85 | | |
86 | | void commit_pending_changes(); |
87 | | |
88 | | String placeholder() const; |
89 | | Optional<String> placeholder_value() const; |
90 | | |
91 | 0 | bool checked() const { return m_checked; } |
92 | | enum class ChangeSource { |
93 | | Programmatic, |
94 | | User, |
95 | | }; |
96 | | void set_checked(bool, ChangeSource = ChangeSource::Programmatic); |
97 | | |
98 | 0 | bool checked_binding() const { return checked(); } |
99 | | void set_checked_binding(bool); |
100 | | |
101 | 0 | bool indeterminate() const { return m_indeterminate; } |
102 | | void set_indeterminate(bool); |
103 | | |
104 | 0 | bool is_mutable() const { return m_is_mutable; } |
105 | | |
106 | | void did_pick_color(Optional<Color> picked_color, ColorPickerUpdateState state); |
107 | | |
108 | | enum class MultipleHandling { |
109 | | Replace, |
110 | | Append, |
111 | | }; |
112 | | void did_select_files(Span<SelectedFile> selected_files, MultipleHandling = MultipleHandling::Replace); |
113 | | |
114 | | JS::GCPtr<FileAPI::FileList> files(); |
115 | | void set_files(JS::GCPtr<FileAPI::FileList>); |
116 | | |
117 | | FileFilter parse_accept_attribute() const; |
118 | | |
119 | | // NOTE: User interaction |
120 | | // https://html.spec.whatwg.org/multipage/input.html#update-the-file-selection |
121 | | void update_the_file_selection(JS::NonnullGCPtr<FileAPI::FileList>); |
122 | | |
123 | | WebIDL::Long max_length() const; |
124 | | WebIDL::ExceptionOr<void> set_max_length(WebIDL::Long); |
125 | | |
126 | | WebIDL::Long min_length() const; |
127 | | WebIDL::ExceptionOr<void> set_min_length(WebIDL::Long); |
128 | | |
129 | | unsigned size() const; |
130 | | WebIDL::ExceptionOr<void> set_size(unsigned value); |
131 | | |
132 | | struct SelectedCoordinate { |
133 | | int x { 0 }; |
134 | | int y { 0 }; |
135 | | }; |
136 | 0 | SelectedCoordinate selected_coordinate() const { return m_selected_coordinate; } |
137 | | |
138 | | JS::Object* value_as_date() const; |
139 | | WebIDL::ExceptionOr<void> set_value_as_date(Optional<JS::Handle<JS::Object>> const&); |
140 | | |
141 | | double value_as_number() const; |
142 | | WebIDL::ExceptionOr<void> set_value_as_number(double value); |
143 | | |
144 | | WebIDL::ExceptionOr<void> step_up(WebIDL::Long n = 1); |
145 | | WebIDL::ExceptionOr<void> step_down(WebIDL::Long n = 1); |
146 | | |
147 | | WebIDL::ExceptionOr<bool> check_validity(); |
148 | | WebIDL::ExceptionOr<bool> report_validity(); |
149 | | void set_custom_validity(String const&); |
150 | | |
151 | | WebIDL::ExceptionOr<void> show_picker(); |
152 | | |
153 | | // ^DOM::EditableTextNodeOwner |
154 | | virtual void did_edit_text_node(Badge<DOM::Document>) override; |
155 | | |
156 | | // ^EventTarget |
157 | | // https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute:the-input-element |
158 | 0 | virtual bool is_focusable() const override { return m_type != TypeAttributeState::Hidden; } |
159 | | |
160 | | // ^FormAssociatedElement |
161 | | // https://html.spec.whatwg.org/multipage/forms.html#category-listed |
162 | 0 | virtual bool is_listed() const override { return true; } |
163 | | |
164 | | // https://html.spec.whatwg.org/multipage/forms.html#category-submit |
165 | 0 | virtual bool is_submittable() const override { return true; } |
166 | | |
167 | | // https://html.spec.whatwg.org/multipage/forms.html#category-reset |
168 | 0 | virtual bool is_resettable() const override { return true; } |
169 | | |
170 | | // https://html.spec.whatwg.org/multipage/forms.html#category-autocapitalize |
171 | 0 | virtual bool is_auto_capitalize_inheriting() const override { return true; } |
172 | | |
173 | | // https://html.spec.whatwg.org/multipage/forms.html#concept-button |
174 | | virtual bool is_button() const override; |
175 | | |
176 | | // https://html.spec.whatwg.org/multipage/forms.html#concept-submit-button |
177 | | virtual bool is_submit_button() const override; |
178 | | |
179 | | bool is_single_line() const; |
180 | | |
181 | | virtual void reset_algorithm() override; |
182 | | virtual void clear_algorithm() override; |
183 | | |
184 | | virtual void form_associated_element_was_inserted() override; |
185 | | virtual void form_associated_element_was_removed(DOM::Node*) override; |
186 | | virtual void form_associated_element_attribute_changed(FlyString const&, Optional<String> const&) override; |
187 | | |
188 | | virtual WebIDL::ExceptionOr<void> cloned(Node&, bool) override; |
189 | | |
190 | | JS::NonnullGCPtr<ValidityState const> validity() const; |
191 | | |
192 | | // ^HTMLElement |
193 | | // https://html.spec.whatwg.org/multipage/forms.html#category-label |
194 | 0 | virtual bool is_labelable() const override { return type_state() != TypeAttributeState::Hidden; } |
195 | | |
196 | | virtual Optional<ARIA::Role> default_role() const override; |
197 | | |
198 | 0 | JS::GCPtr<Element> placeholder_element() { return m_placeholder_element; } |
199 | 0 | JS::GCPtr<Element const> placeholder_element() const { return m_placeholder_element; } |
200 | | |
201 | | virtual bool has_activation_behavior() const override; |
202 | | virtual void activation_behavior(DOM::Event const&) override; |
203 | | |
204 | | bool has_input_activation_behavior() const; |
205 | | bool change_event_applies() const; |
206 | | bool value_as_date_applies() const; |
207 | | bool value_as_number_applies() const; |
208 | | bool step_applies() const; |
209 | | bool step_up_or_down_applies() const; |
210 | | bool select_applies() const; |
211 | | bool selection_or_range_applies() const; |
212 | | bool selection_direction_applies() const; |
213 | | bool has_selectable_text() const; |
214 | | |
215 | | static bool selection_or_range_applies_for_type_state(TypeAttributeState); |
216 | | |
217 | 0 | Optional<String> selection_direction_binding() { return selection_direction(); } |
218 | | |
219 | | protected: |
220 | | void selection_was_changed(size_t selection_start, size_t selection_end) override; |
221 | | |
222 | | private: |
223 | | HTMLInputElement(DOM::Document&, DOM::QualifiedName); |
224 | | |
225 | | void type_attribute_changed(TypeAttributeState old_state, TypeAttributeState new_state); |
226 | | |
227 | | virtual void apply_presentational_hints(CSS::StyleProperties&) const override; |
228 | | |
229 | | // ^DOM::Node |
230 | 0 | virtual bool is_html_input_element() const final { return true; } |
231 | | |
232 | | // ^DOM::EventTarget |
233 | | virtual void did_lose_focus() override; |
234 | | virtual void did_receive_focus() override; |
235 | | virtual void legacy_pre_activation_behavior() override; |
236 | | virtual void legacy_cancelled_activation_behavior() override; |
237 | | virtual void legacy_cancelled_activation_behavior_was_not_called() override; |
238 | | |
239 | | // ^DOM::Element |
240 | | virtual i32 default_tab_index_value() const override; |
241 | | virtual void computed_css_values_changed() override; |
242 | | |
243 | | // https://html.spec.whatwg.org/multipage/input.html#image-button-state-(type=image):dimension-attributes |
244 | 0 | virtual bool supports_dimension_attributes() const override { return type_state() == TypeAttributeState::ImageButton; } |
245 | | |
246 | | // ^Layout::ImageProvider |
247 | | virtual bool is_image_available() const override; |
248 | | virtual Optional<CSSPixels> intrinsic_width() const override; |
249 | | virtual Optional<CSSPixels> intrinsic_height() const override; |
250 | | virtual Optional<CSSPixelFraction> intrinsic_aspect_ratio() const override; |
251 | | virtual RefPtr<Gfx::ImmutableBitmap> current_image_bitmap(Gfx::IntSize = {}) const override; |
252 | | virtual void set_visible_in_viewport(bool) override; |
253 | 0 | virtual JS::NonnullGCPtr<DOM::Element const> to_html_element() const override { return *this; } |
254 | | |
255 | | virtual void initialize(JS::Realm&) override; |
256 | | virtual void visit_edges(Cell::Visitor&) override; |
257 | | |
258 | | Optional<double> convert_string_to_number(StringView input) const; |
259 | | String convert_number_to_string(double input) const; |
260 | | |
261 | | WebIDL::ExceptionOr<JS::GCPtr<JS::Date>> convert_string_to_date(StringView input) const; |
262 | | String covert_date_to_string(JS::NonnullGCPtr<JS::Date> input) const; |
263 | | |
264 | | Optional<double> min() const; |
265 | | Optional<double> max() const; |
266 | | double default_step() const; |
267 | | double step_scale_factor() const; |
268 | | Optional<double> allowed_value_step() const; |
269 | | double step_base() const; |
270 | | WebIDL::ExceptionOr<void> step_up_or_down(bool is_down, WebIDL::Long n); |
271 | | |
272 | | static TypeAttributeState parse_type_attribute(StringView); |
273 | | void create_shadow_tree_if_needed(); |
274 | | void update_shadow_tree(); |
275 | | void create_button_input_shadow_tree(); |
276 | | void create_text_input_shadow_tree(); |
277 | | void create_color_input_shadow_tree(); |
278 | | void create_file_input_shadow_tree(); |
279 | | void create_range_input_shadow_tree(); |
280 | | WebIDL::ExceptionOr<void> run_input_activation_behavior(DOM::Event const&); |
281 | | void set_checked_within_group(); |
282 | | |
283 | | void handle_maxlength_attribute(); |
284 | | void handle_readonly_attribute(Optional<String> const& value); |
285 | | WebIDL::ExceptionOr<void> handle_src_attribute(String const& value); |
286 | | |
287 | | void user_interaction_did_change_input_value(); |
288 | | |
289 | | // https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm |
290 | | String value_sanitization_algorithm(String const&) const; |
291 | | |
292 | | enum class ValueAttributeMode { |
293 | | Value, |
294 | | Default, |
295 | | DefaultOn, |
296 | | Filename, |
297 | | }; |
298 | | static ValueAttributeMode value_attribute_mode_for_type_state(TypeAttributeState); |
299 | | ValueAttributeMode value_attribute_mode() const; |
300 | | |
301 | | void update_placeholder_visibility(); |
302 | | JS::GCPtr<DOM::Element> m_placeholder_element; |
303 | | JS::GCPtr<DOM::Text> m_placeholder_text_node; |
304 | | |
305 | | void update_text_input_shadow_tree(); |
306 | | JS::GCPtr<DOM::Element> m_inner_text_element; |
307 | | JS::GCPtr<DOM::Text> m_text_node; |
308 | | bool m_checked { false }; |
309 | | |
310 | | void update_color_well_element(); |
311 | | JS::GCPtr<DOM::Element> m_color_well_element; |
312 | | |
313 | | void update_file_input_shadow_tree(); |
314 | | JS::GCPtr<DOM::Element> m_file_button; |
315 | | JS::GCPtr<DOM::Element> m_file_label; |
316 | | |
317 | | void update_slider_shadow_tree_elements(); |
318 | | JS::GCPtr<DOM::Element> m_slider_runnable_track; |
319 | | JS::GCPtr<DOM::Element> m_slider_progress_element; |
320 | | JS::GCPtr<DOM::Element> m_slider_thumb; |
321 | | |
322 | | JS::GCPtr<DecodedImageData> image_data() const; |
323 | | JS::GCPtr<SharedResourceRequest> m_resource_request; |
324 | | SelectedCoordinate m_selected_coordinate; |
325 | | |
326 | | Optional<DOM::DocumentLoadEventDelayer> m_load_event_delayer; |
327 | | |
328 | | // https://html.spec.whatwg.org/multipage/input.html#dom-input-indeterminate |
329 | | bool m_indeterminate { false }; |
330 | | |
331 | | // https://html.spec.whatwg.org/multipage/input.html#concept-input-checked-dirty-flag |
332 | | bool m_dirty_checkedness { false }; |
333 | | |
334 | | // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-dirty |
335 | | bool m_dirty_value { false }; |
336 | | |
337 | | // https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-fe-mutable |
338 | | bool m_is_mutable { true }; |
339 | | |
340 | | // https://html.spec.whatwg.org/multipage/input.html#the-input-element:legacy-pre-activation-behavior |
341 | | bool m_before_legacy_pre_activation_behavior_checked { false }; |
342 | | bool m_before_legacy_pre_activation_behavior_indeterminate { false }; |
343 | | JS::GCPtr<HTMLInputElement> m_legacy_pre_activation_behavior_checked_element_in_group; |
344 | | |
345 | | // https://html.spec.whatwg.org/multipage/input.html#concept-input-type-file-selected |
346 | | JS::GCPtr<FileAPI::FileList> m_selected_files; |
347 | | |
348 | | TypeAttributeState m_type { TypeAttributeState::Text }; |
349 | | String m_value; |
350 | | |
351 | | String m_last_src_value; |
352 | | |
353 | | bool m_has_uncommitted_changes { false }; |
354 | | }; |
355 | | |
356 | | } |
357 | | |
358 | | namespace Web::DOM { |
359 | | template<> |
360 | 0 | inline bool Node::fast_is<HTML::HTMLInputElement>() const { return is_html_input_element(); } |
361 | | } |