Coverage Report

Created: 2025-08-28 06:26

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