Coverage Report

Created: 2025-03-04 07:22

/src/serenity/Userland/Libraries/LibWeb/HTML/HTMLMeterElement.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2020, the SerenityOS developers.
3
 * Copyright (c) 2023, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibWeb/Bindings/HTMLMeterElementPrototype.h>
9
#include <LibWeb/DOM/Document.h>
10
#include <LibWeb/DOM/ElementFactory.h>
11
#include <LibWeb/DOM/ShadowRoot.h>
12
#include <LibWeb/HTML/HTMLMeterElement.h>
13
#include <LibWeb/HTML/Numbers.h>
14
#include <LibWeb/Namespace.h>
15
16
namespace Web::HTML {
17
18
JS_DEFINE_ALLOCATOR(HTMLMeterElement);
19
20
HTMLMeterElement::HTMLMeterElement(DOM::Document& document, DOM::QualifiedName qualified_name)
21
0
    : HTMLElement(document, move(qualified_name))
22
0
{
23
0
}
24
25
0
HTMLMeterElement::~HTMLMeterElement() = default;
26
27
void HTMLMeterElement::initialize(JS::Realm& realm)
28
0
{
29
0
    Base::initialize(realm);
30
0
    WEB_SET_PROTOTYPE_FOR_INTERFACE(HTMLMeterElement);
31
0
}
32
33
void HTMLMeterElement::visit_edges(Cell::Visitor& visitor)
34
0
{
35
0
    Base::visit_edges(visitor);
36
0
    visitor.visit(m_meter_value_element);
37
0
}
38
39
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-meter-actual
40
double HTMLMeterElement::value() const
41
0
{
42
    // If the value attribute is specified and a value could be parsed out of it, then that value is the candidate actual value. Otherwise, the candidate actual value is zero.
43
0
    double candidate_value = 0.0;
44
0
    if (auto value_string = get_attribute(HTML::AttributeNames::value); value_string.has_value()) {
45
0
        if (auto value = parse_floating_point_number(*value_string); value.has_value())
46
0
            candidate_value = *value;
47
0
    }
48
49
    // If the candidate actual value is less than the minimum value, then the actual value is the minimum value.
50
    // Otherwise, if the candidate actual value is greater than the maximum value, then the actual value is the maximum value.
51
    // Otherwise, the actual value is the candidate actual value.
52
0
    return clamp(candidate_value, min(), max());
53
0
}
54
55
WebIDL::ExceptionOr<void> HTMLMeterElement::set_value(double value)
56
0
{
57
0
    TRY(set_attribute(HTML::AttributeNames::value, String::number(value)));
58
0
    update_meter_value_element();
59
0
    return {};
60
0
}
61
62
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-meter-minimum
63
double HTMLMeterElement::min() const
64
0
{
65
    // If the min attribute is specified and a value could be parsed out of it, then the minimum value is that value. Otherwise, the minimum value is zero.
66
0
    if (auto min_string = get_attribute(HTML::AttributeNames::min); min_string.has_value()) {
67
0
        if (auto min = parse_floating_point_number(*min_string); min.has_value())
68
0
            return *min;
69
0
    }
70
0
    return 0;
71
0
}
72
73
WebIDL::ExceptionOr<void> HTMLMeterElement::set_min(double value)
74
0
{
75
0
    TRY(set_attribute(HTML::AttributeNames::min, String::number(value)));
76
0
    update_meter_value_element();
77
0
    return {};
78
0
}
79
80
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-meter-maximum
81
double HTMLMeterElement::max() const
82
0
{
83
    // If the max attribute is specified and a value could be parsed out of it, then the candidate maximum value is that value. Otherwise, the candidate maximum value is 1.0.
84
0
    double candidate_max = 1.0;
85
0
    if (auto max_string = get_attribute(HTML::AttributeNames::max); max_string.has_value()) {
86
0
        if (auto max = parse_floating_point_number(*max_string); max.has_value())
87
0
            candidate_max = *max;
88
0
    }
89
90
    // If the candidate maximum value is greater than or equal to the minimum value, then the maximum value is the candidate maximum value. Otherwise, the maximum value is the same as the minimum value.
91
0
    return AK::max(candidate_max, min());
92
0
}
93
94
WebIDL::ExceptionOr<void> HTMLMeterElement::set_max(double value)
95
0
{
96
0
    TRY(set_attribute(HTML::AttributeNames::max, String::number(value)));
97
0
    update_meter_value_element();
98
0
    return {};
99
0
}
100
101
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-meter-low
102
double HTMLMeterElement::low() const
103
0
{
104
    // If the low attribute is specified and a value could be parsed out of it, then the candidate low boundary is that value. Otherwise, the candidate low boundary is the same as the minimum value.
105
0
    double candidate_low = min();
106
0
    if (auto low_string = get_attribute(HTML::AttributeNames::low); low_string.has_value()) {
107
0
        if (auto low = parse_floating_point_number(*low_string); low.has_value())
108
0
            candidate_low = *low;
109
0
    }
110
111
    // If the candidate low boundary is less than the minimum value, then the low boundary is the minimum value.
112
    // Otherwise, if the candidate low boundary is greater than the maximum value, then the low boundary is the maximum value.
113
    // Otherwise, the low boundary is the candidate low boundary.
114
0
    return clamp(candidate_low, min(), max());
115
0
}
116
117
WebIDL::ExceptionOr<void> HTMLMeterElement::set_low(double value)
118
0
{
119
0
    TRY(set_attribute(HTML::AttributeNames::low, String::number(value)));
120
0
    update_meter_value_element();
121
0
    return {};
122
0
}
123
124
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-meter-high
125
double HTMLMeterElement::high() const
126
0
{
127
    // If the high attribute is specified and a value could be parsed out of it, then the candidate high boundary is that value. Otherwise, the candidate high boundary is the same as the maximum value.
128
0
    double candidate_high = max();
129
0
    if (auto high_string = get_attribute(HTML::AttributeNames::high); high_string.has_value()) {
130
0
        if (auto high = parse_floating_point_number(*high_string); high.has_value())
131
0
            candidate_high = *high;
132
0
    }
133
134
    // If the candidate high boundary is less than the low boundary, then the high boundary is the low boundary.
135
    // Otherwise, if the candidate high boundary is greater than the maximum value, then the high boundary is the maximum value.
136
    // Otherwise, the high boundary is the candidate high boundary.
137
0
    return clamp(candidate_high, low(), max());
138
0
}
139
140
WebIDL::ExceptionOr<void> HTMLMeterElement::set_high(double value)
141
0
{
142
0
    TRY(set_attribute(HTML::AttributeNames::high, String::number(value)));
143
0
    update_meter_value_element();
144
0
    return {};
145
0
}
146
147
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-meter-optimum
148
double HTMLMeterElement::optimum() const
149
0
{
150
    // If the optimum attribute is specified and a value could be parsed out of it, then the candidate optimum point is that value. Otherwise, the candidate optimum point is the midpoint between the minimum value and the maximum value.
151
0
    double candidate_optimum = (max() + min()) / 2;
152
0
    if (auto optimum_string = get_attribute(HTML::AttributeNames::optimum); optimum_string.has_value()) {
153
0
        if (auto optimum = parse_floating_point_number(*optimum_string); optimum.has_value())
154
0
            candidate_optimum = *optimum;
155
0
    }
156
157
    // If the candidate optimum point is less than the minimum value, then the optimum point is the minimum value.
158
    // Otherwise, if the candidate optimum point is greater than the maximum value, then the optimum point is the maximum value.
159
    // Otherwise, the optimum point is the candidate optimum point.
160
0
    return clamp(candidate_optimum, min(), max());
161
0
}
162
163
WebIDL::ExceptionOr<void> HTMLMeterElement::set_optimum(double value)
164
0
{
165
0
    TRY(set_attribute(HTML::AttributeNames::optimum, String::number(value)));
166
0
    update_meter_value_element();
167
0
    return {};
168
0
}
169
170
void HTMLMeterElement::inserted()
171
0
{
172
0
    create_shadow_tree_if_needed();
173
0
}
174
175
void HTMLMeterElement::removed_from(DOM::Node*)
176
0
{
177
0
    set_shadow_root(nullptr);
178
0
}
179
180
void HTMLMeterElement::create_shadow_tree_if_needed()
181
0
{
182
0
    if (shadow_root())
183
0
        return;
184
185
0
    auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
186
0
    set_shadow_root(shadow_root);
187
188
0
    auto meter_bar_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
189
0
    meter_bar_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterBar);
190
0
    MUST(shadow_root->append_child(*meter_bar_element));
191
192
0
    m_meter_value_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
193
0
    MUST(meter_bar_element->append_child(*m_meter_value_element));
194
0
    update_meter_value_element();
195
0
}
196
197
void HTMLMeterElement::update_meter_value_element()
198
0
{
199
0
    if (!m_meter_value_element)
200
0
        return;
201
202
    // UA requirements for regions of the gauge:
203
0
    double value = this->value();
204
0
    double min = this->min();
205
0
    double max = this->max();
206
0
    double low = this->low();
207
0
    double high = this->high();
208
0
    double optimum = this->optimum();
209
210
    // If the optimum point is equal to the low boundary or the high boundary, or anywhere in between them, then the region between the low and high boundaries of the gauge must be treated as the optimum region, and the low and high parts, if any, must be treated as suboptimal.
211
0
    if (optimum >= low && optimum <= high) {
212
0
        if (value >= low && value <= high)
213
0
            m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterOptimumValue);
214
0
        else
215
0
            m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterSuboptimumValue);
216
0
    }
217
    // Otherwise, if the optimum point is less than the low boundary, then the region between the minimum value and the low boundary must be treated as the optimum region, the region from the low boundary up to the high boundary must be treated as a suboptimal region, and the remaining region must be treated as an even less good region.
218
0
    else if (optimum < low) {
219
0
        if (value >= low && value <= high)
220
0
            m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterSuboptimumValue);
221
0
        else
222
0
            m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterEvenLessGoodValue);
223
0
    }
224
    // Finally, if the optimum point is higher than the high boundary, then the situation is reversed; the region between the high boundary and the maximum value must be treated as the optimum region, the region from the high boundary down to the low boundary must be treated as a suboptimal region, and the remaining region must be treated as an even less good region.
225
0
    else {
226
0
        if (value >= high && value <= max)
227
0
            m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterOptimumValue);
228
0
        else if (value >= low && value <= high)
229
0
            m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterSuboptimumValue);
230
0
        else
231
0
            m_meter_value_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::MeterEvenLessGoodValue);
232
0
    }
233
234
0
    double position = (value - min) / (max - min) * 100;
235
0
    MUST(m_meter_value_element->style_for_bindings()->set_property(CSS::PropertyID::Width, MUST(String::formatted("{}%", position))));
236
0
}
237
}