Skip to main content

script/dom/html/form_controls/
htmlmeterelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Ref;
6use std::ops::{Add, Div};
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name};
10use js::context::JSContext;
11use js::rust::HandleObject;
12use script_bindings::cell::DomRefCell;
13use stylo_dom::ElementState;
14
15use crate::dom::bindings::codegen::Bindings::HTMLMeterElementBinding::HTMLMeterElementMethods;
16use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
17use crate::dom::bindings::inheritance::Castable;
18use crate::dom::bindings::num::Finite;
19use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::document::Document;
22use crate::dom::element::attributes::storage::AttrRef;
23use crate::dom::element::{AttributeMutation, Element};
24use crate::dom::html::htmlelement::HTMLElement;
25use crate::dom::node::virtualmethods::VirtualMethods;
26use crate::dom::node::{BindContext, ChildrenMutation, Node};
27use crate::dom::nodelist::NodeList;
28use crate::dom::shadowroot::ShadowRoot;
29use crate::dom::shadowroot::ua_shadowroot::{CreateUAShadowRootForSelf, UAShadowRoot};
30
31#[dom_struct]
32pub(crate) struct HTMLMeterElement {
33    htmlelement: HTMLElement,
34    labels_node_list: MutNullableDom<NodeList>,
35    shadow_tree: DomRefCell<Option<ShadowTree>>,
36}
37
38/// Holds handles to all slots in the UA shadow tree
39#[derive(Clone, JSTraceable, MallocSizeOf)]
40#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
41struct ShadowTree {
42    meter_value: Dom<Element>,
43}
44
45impl UAShadowRoot<ShadowTree> for HTMLMeterElement {
46    fn store_for_shadow_tree(&self, cx: &mut JSContext, shadow_root: DomRoot<ShadowRoot>) {
47        let container = self.create_element_in_ua_shadowroot(cx, local_name!("div"));
48
49        shadow_root
50            .upcast::<Node>()
51            .AppendChild(cx, container.upcast::<Node>())
52            .expect("Must always be able to append to shadow root");
53
54        let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
55            meter_value: container.as_traced(),
56        });
57    }
58
59    fn borrow_for_shadow_tree(&self) -> Ref<'_, Option<ShadowTree>> {
60        self.shadow_tree.borrow()
61    }
62}
63
64/// <https://html.spec.whatwg.org/multipage/#the-meter-element>
65impl HTMLMeterElement {
66    fn new_inherited(
67        local_name: LocalName,
68        prefix: Option<Prefix>,
69        document: &Document,
70    ) -> HTMLMeterElement {
71        HTMLMeterElement {
72            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
73            labels_node_list: MutNullableDom::new(None),
74            shadow_tree: Default::default(),
75        }
76    }
77
78    pub(crate) fn new(
79        cx: &mut js::context::JSContext,
80        local_name: LocalName,
81        prefix: Option<Prefix>,
82        document: &Document,
83        proto: Option<HandleObject>,
84    ) -> DomRoot<HTMLMeterElement> {
85        Node::reflect_node_with_proto(
86            cx,
87            Box::new(HTMLMeterElement::new_inherited(
88                local_name, prefix, document,
89            )),
90            document,
91            proto,
92        )
93    }
94
95    fn update_state(&self, cx: &mut JSContext) {
96        let value = *self.Value();
97        let low = *self.Low();
98        let high = *self.High();
99        let min = *self.Min();
100        let max = *self.Max();
101        let optimum = *self.Optimum();
102
103        // If the optimum point is less than the low boundary, then the region between the minimum value and
104        // the low boundary must be treated as the optimum region, the region from the low boundary up to the
105        // high boundary must be treated as a suboptimal region, and the remaining region must be treated as
106        // an even less good region
107        let element_state = if optimum < low {
108            if value < low {
109                ElementState::OPTIMUM
110            } else if value <= high {
111                ElementState::SUB_OPTIMUM
112            } else {
113                ElementState::SUB_SUB_OPTIMUM
114            }
115        }
116        // If the optimum point is higher than the high boundary, then the situation is reversed; the region between
117        // the high boundary and the maximum value must be treated as the optimum region, the region from the high
118        // boundary down to the low boundary must be treated as a suboptimal region, and the remaining region must
119        // be treated as an even less good region.
120        else if optimum > high {
121            if value > high {
122                ElementState::OPTIMUM
123            } else if value >= low {
124                ElementState::SUB_OPTIMUM
125            } else {
126                ElementState::SUB_SUB_OPTIMUM
127            }
128        }
129        // If the optimum point is equal to the low boundary or the high boundary, or anywhere in between them,
130        // then the region between the low and high boundaries of the gauge must be treated as the optimum region,
131        // and the low and high parts, if any, must be treated as suboptimal.
132        else if (low..=high).contains(&value) {
133            ElementState::OPTIMUM
134        } else {
135            ElementState::SUB_OPTIMUM
136        };
137
138        // Set the correct pseudo class
139        self.upcast::<Element>()
140            .set_state(ElementState::METER_OPTIMUM_STATES, false);
141        self.upcast::<Element>().set_state(element_state, true);
142
143        // Update the visual width of the meter
144        let shadow_tree = self.shadow_tree(cx);
145        let position = (value - min) / (max - min) * 100.0;
146        let style = format!("width: {position}%");
147        shadow_tree
148            .meter_value
149            .set_string_attribute(cx, &local_name!("style"), style.into());
150    }
151}
152
153impl HTMLMeterElementMethods<crate::DomTypeHolder> for HTMLMeterElement {
154    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
155    make_labels_getter!(Labels, labels_node_list);
156
157    /// <https://html.spec.whatwg.org/multipage/#concept-meter-actual>
158    fn Value(&self) -> Finite<f64> {
159        let min = *self.Min();
160        let max = *self.Max();
161
162        Finite::wrap(
163            self.upcast::<Element>()
164                .get_string_attribute(&local_name!("value"))
165                .parse_floating_point_number()
166                .map_or(0.0, |candidate_actual_value| {
167                    candidate_actual_value.clamp(min, max)
168                }),
169        )
170    }
171
172    /// <https://html.spec.whatwg.org/multipage/#dom-meter-value>
173    fn SetValue(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
174        let mut string_value = DOMString::from((*value).to_string());
175        string_value.set_best_representation_of_the_floating_point_number();
176        self.upcast::<Element>()
177            .set_string_attribute(cx, &local_name!("value"), string_value);
178    }
179
180    /// <https://html.spec.whatwg.org/multipage/#concept-meter-minimum>
181    fn Min(&self) -> Finite<f64> {
182        Finite::wrap(
183            self.upcast::<Element>()
184                .get_string_attribute(&local_name!("min"))
185                .parse_floating_point_number()
186                .unwrap_or(0.0),
187        )
188    }
189
190    /// <https://html.spec.whatwg.org/multipage/#dom-meter-min>
191    fn SetMin(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
192        let mut string_value = DOMString::from((*value).to_string());
193        string_value.set_best_representation_of_the_floating_point_number();
194        self.upcast::<Element>()
195            .set_string_attribute(cx, &local_name!("min"), string_value);
196    }
197
198    /// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
199    fn Max(&self) -> Finite<f64> {
200        Finite::wrap(
201            self.upcast::<Element>()
202                .get_string_attribute(&local_name!("max"))
203                .parse_floating_point_number()
204                .unwrap_or(1.0)
205                .max(*self.Min()),
206        )
207    }
208
209    /// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
210    fn SetMax(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
211        let mut string_value = DOMString::from((*value).to_string());
212        string_value.set_best_representation_of_the_floating_point_number();
213        self.upcast::<Element>()
214            .set_string_attribute(cx, &local_name!("max"), string_value);
215    }
216
217    /// <https://html.spec.whatwg.org/multipage/#concept-meter-low>
218    fn Low(&self) -> Finite<f64> {
219        let min = *self.Min();
220        let max = *self.Max();
221
222        Finite::wrap(
223            self.upcast::<Element>()
224                .get_string_attribute(&local_name!("low"))
225                .parse_floating_point_number()
226                .map_or(min, |candidate_low_boundary| {
227                    candidate_low_boundary.clamp(min, max)
228                }),
229        )
230    }
231
232    /// <https://html.spec.whatwg.org/multipage/#dom-meter-low>
233    fn SetLow(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
234        let mut string_value = DOMString::from((*value).to_string());
235        string_value.set_best_representation_of_the_floating_point_number();
236        self.upcast::<Element>()
237            .set_string_attribute(cx, &local_name!("low"), string_value);
238    }
239
240    /// <https://html.spec.whatwg.org/multipage/#concept-meter-high>
241    fn High(&self) -> Finite<f64> {
242        let max: f64 = *self.Max();
243        let low: f64 = *self.Low();
244
245        Finite::wrap(
246            self.upcast::<Element>()
247                .get_string_attribute(&local_name!("high"))
248                .parse_floating_point_number()
249                .map_or(max, |candidate_high_boundary| {
250                    if candidate_high_boundary < low {
251                        return low;
252                    }
253
254                    candidate_high_boundary.clamp(*self.Min(), max)
255                }),
256        )
257    }
258
259    /// <https://html.spec.whatwg.org/multipage/#dom-meter-high>
260    fn SetHigh(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
261        let mut string_value = DOMString::from((*value).to_string());
262        string_value.set_best_representation_of_the_floating_point_number();
263        self.upcast::<Element>()
264            .set_string_attribute(cx, &local_name!("high"), string_value);
265    }
266
267    /// <https://html.spec.whatwg.org/multipage/#concept-meter-optimum>
268    fn Optimum(&self) -> Finite<f64> {
269        let max = *self.Max();
270        let min = *self.Min();
271
272        Finite::wrap(
273            self.upcast::<Element>()
274                .get_string_attribute(&local_name!("optimum"))
275                .parse_floating_point_number()
276                .map_or(max.add(min).div(2.0), |candidate_optimum_point| {
277                    candidate_optimum_point.clamp(min, max)
278                }),
279        )
280    }
281
282    /// <https://html.spec.whatwg.org/multipage/#dom-meter-optimum>
283    fn SetOptimum(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
284        let mut string_value = DOMString::from((*value).to_string());
285        string_value.set_best_representation_of_the_floating_point_number();
286        self.upcast::<Element>()
287            .set_string_attribute(cx, &local_name!("optimum"), string_value);
288    }
289}
290
291impl VirtualMethods for HTMLMeterElement {
292    fn super_type(&self) -> Option<&dyn VirtualMethods> {
293        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
294    }
295
296    fn attribute_mutated(
297        &self,
298        cx: &mut js::context::JSContext,
299        attr: AttrRef<'_>,
300        mutation: AttributeMutation,
301    ) {
302        self.super_type()
303            .unwrap()
304            .attribute_mutated(cx, attr, mutation);
305
306        let is_important_attribute = matches!(
307            attr.local_name(),
308            &local_name!("high") |
309                &local_name!("low") |
310                &local_name!("min") |
311                &local_name!("max") |
312                &local_name!("optimum") |
313                &local_name!("value")
314        );
315        if is_important_attribute {
316            self.update_state(cx);
317        }
318    }
319
320    fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
321        self.super_type().unwrap().children_changed(cx, mutation);
322
323        self.update_state(cx);
324    }
325
326    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
327        self.super_type().unwrap().bind_to_tree(cx, context);
328
329        self.update_state(cx);
330    }
331}