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
55            .shadow_tree
56            .safe_borrow_mut(cx.no_gc())
57            .insert(ShadowTree {
58                meter_value: container.as_traced(),
59            });
60    }
61
62    fn borrow_for_shadow_tree(&self) -> Ref<'_, Option<ShadowTree>> {
63        self.shadow_tree.borrow()
64    }
65}
66
67/// <https://html.spec.whatwg.org/multipage/#the-meter-element>
68impl HTMLMeterElement {
69    fn new_inherited(
70        local_name: LocalName,
71        prefix: Option<Prefix>,
72        document: &Document,
73    ) -> HTMLMeterElement {
74        HTMLMeterElement {
75            htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
76            labels_node_list: MutNullableDom::new(None),
77            shadow_tree: Default::default(),
78        }
79    }
80
81    pub(crate) fn new(
82        cx: &mut js::context::JSContext,
83        local_name: LocalName,
84        prefix: Option<Prefix>,
85        document: &Document,
86        proto: Option<HandleObject>,
87    ) -> DomRoot<HTMLMeterElement> {
88        Node::reflect_node_with_proto(
89            cx,
90            Box::new(HTMLMeterElement::new_inherited(
91                local_name, prefix, document,
92            )),
93            document,
94            proto,
95        )
96    }
97
98    fn update_state(&self, cx: &mut JSContext) {
99        let value = *self.Value();
100        let low = *self.Low();
101        let high = *self.High();
102        let min = *self.Min();
103        let max = *self.Max();
104        let optimum = *self.Optimum();
105
106        // If the optimum point is less than the low boundary, then the region between the minimum value and
107        // the low boundary must be treated as the optimum region, the region from the low boundary up to the
108        // high boundary must be treated as a suboptimal region, and the remaining region must be treated as
109        // an even less good region
110        let element_state = if optimum < low {
111            if value < low {
112                ElementState::OPTIMUM
113            } else if value <= high {
114                ElementState::SUB_OPTIMUM
115            } else {
116                ElementState::SUB_SUB_OPTIMUM
117            }
118        }
119        // If the optimum point is higher than the high boundary, then the situation is reversed; the region between
120        // the high boundary and the maximum value must be treated as the optimum region, the region from the high
121        // boundary down to the low boundary must be treated as a suboptimal region, and the remaining region must
122        // be treated as an even less good region.
123        else if optimum > high {
124            if value > high {
125                ElementState::OPTIMUM
126            } else if value >= low {
127                ElementState::SUB_OPTIMUM
128            } else {
129                ElementState::SUB_SUB_OPTIMUM
130            }
131        }
132        // If the optimum point is equal to the low boundary or the high boundary, or anywhere in between them,
133        // then the region between the low and high boundaries of the gauge must be treated as the optimum region,
134        // and the low and high parts, if any, must be treated as suboptimal.
135        else if (low..=high).contains(&value) {
136            ElementState::OPTIMUM
137        } else {
138            ElementState::SUB_OPTIMUM
139        };
140
141        // Set the correct pseudo class
142        self.upcast::<Element>()
143            .set_state(cx.no_gc(), ElementState::METER_OPTIMUM_STATES, false);
144        self.upcast::<Element>()
145            .set_state(cx.no_gc(), element_state, true);
146
147        // Update the visual width of the meter
148        let shadow_tree = self.shadow_tree(cx);
149        let position = (value - min) / (max - min) * 100.0;
150        let style = format!("width: {position}%");
151        shadow_tree
152            .meter_value
153            .set_string_attribute(cx, &local_name!("style"), style.into());
154    }
155}
156
157impl HTMLMeterElementMethods<crate::DomTypeHolder> for HTMLMeterElement {
158    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
159    make_labels_getter!(Labels, labels_node_list);
160
161    /// <https://html.spec.whatwg.org/multipage/#concept-meter-actual>
162    fn Value(&self) -> Finite<f64> {
163        let min = *self.Min();
164        let max = *self.Max();
165
166        Finite::wrap(
167            self.upcast::<Element>()
168                .get_string_attribute(&local_name!("value"))
169                .parse_floating_point_number()
170                .map_or(0.0, |candidate_actual_value| {
171                    candidate_actual_value.clamp(min, max)
172                }),
173        )
174    }
175
176    /// <https://html.spec.whatwg.org/multipage/#dom-meter-value>
177    fn SetValue(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
178        let mut string_value = DOMString::from((*value).to_string());
179        string_value.set_best_representation_of_the_floating_point_number();
180        self.upcast::<Element>()
181            .set_string_attribute(cx, &local_name!("value"), string_value);
182    }
183
184    /// <https://html.spec.whatwg.org/multipage/#concept-meter-minimum>
185    fn Min(&self) -> Finite<f64> {
186        Finite::wrap(
187            self.upcast::<Element>()
188                .get_string_attribute(&local_name!("min"))
189                .parse_floating_point_number()
190                .unwrap_or(0.0),
191        )
192    }
193
194    /// <https://html.spec.whatwg.org/multipage/#dom-meter-min>
195    fn SetMin(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
196        let mut string_value = DOMString::from((*value).to_string());
197        string_value.set_best_representation_of_the_floating_point_number();
198        self.upcast::<Element>()
199            .set_string_attribute(cx, &local_name!("min"), string_value);
200    }
201
202    /// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
203    fn Max(&self) -> Finite<f64> {
204        Finite::wrap(
205            self.upcast::<Element>()
206                .get_string_attribute(&local_name!("max"))
207                .parse_floating_point_number()
208                .unwrap_or(1.0)
209                .max(*self.Min()),
210        )
211    }
212
213    /// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
214    fn SetMax(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
215        let mut string_value = DOMString::from((*value).to_string());
216        string_value.set_best_representation_of_the_floating_point_number();
217        self.upcast::<Element>()
218            .set_string_attribute(cx, &local_name!("max"), string_value);
219    }
220
221    /// <https://html.spec.whatwg.org/multipage/#concept-meter-low>
222    fn Low(&self) -> Finite<f64> {
223        let min = *self.Min();
224        let max = *self.Max();
225
226        Finite::wrap(
227            self.upcast::<Element>()
228                .get_string_attribute(&local_name!("low"))
229                .parse_floating_point_number()
230                .map_or(min, |candidate_low_boundary| {
231                    candidate_low_boundary.clamp(min, max)
232                }),
233        )
234    }
235
236    /// <https://html.spec.whatwg.org/multipage/#dom-meter-low>
237    fn SetLow(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
238        let mut string_value = DOMString::from((*value).to_string());
239        string_value.set_best_representation_of_the_floating_point_number();
240        self.upcast::<Element>()
241            .set_string_attribute(cx, &local_name!("low"), string_value);
242    }
243
244    /// <https://html.spec.whatwg.org/multipage/#concept-meter-high>
245    fn High(&self) -> Finite<f64> {
246        let max: f64 = *self.Max();
247        let low: f64 = *self.Low();
248
249        Finite::wrap(
250            self.upcast::<Element>()
251                .get_string_attribute(&local_name!("high"))
252                .parse_floating_point_number()
253                .map_or(max, |candidate_high_boundary| {
254                    if candidate_high_boundary < low {
255                        return low;
256                    }
257
258                    candidate_high_boundary.clamp(*self.Min(), max)
259                }),
260        )
261    }
262
263    /// <https://html.spec.whatwg.org/multipage/#dom-meter-high>
264    fn SetHigh(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
265        let mut string_value = DOMString::from((*value).to_string());
266        string_value.set_best_representation_of_the_floating_point_number();
267        self.upcast::<Element>()
268            .set_string_attribute(cx, &local_name!("high"), string_value);
269    }
270
271    /// <https://html.spec.whatwg.org/multipage/#concept-meter-optimum>
272    fn Optimum(&self) -> Finite<f64> {
273        let max = *self.Max();
274        let min = *self.Min();
275
276        Finite::wrap(
277            self.upcast::<Element>()
278                .get_string_attribute(&local_name!("optimum"))
279                .parse_floating_point_number()
280                .map_or(max.add(min).div(2.0), |candidate_optimum_point| {
281                    candidate_optimum_point.clamp(min, max)
282                }),
283        )
284    }
285
286    /// <https://html.spec.whatwg.org/multipage/#dom-meter-optimum>
287    fn SetOptimum(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
288        let mut string_value = DOMString::from((*value).to_string());
289        string_value.set_best_representation_of_the_floating_point_number();
290        self.upcast::<Element>()
291            .set_string_attribute(cx, &local_name!("optimum"), string_value);
292    }
293}
294
295impl VirtualMethods for HTMLMeterElement {
296    fn super_type(&self) -> Option<&dyn VirtualMethods> {
297        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
298    }
299
300    fn attribute_mutated(
301        &self,
302        cx: &mut js::context::JSContext,
303        attr: AttrRef<'_>,
304        mutation: AttributeMutation,
305    ) {
306        self.super_type()
307            .unwrap()
308            .attribute_mutated(cx, attr, mutation);
309
310        let is_important_attribute = matches!(
311            attr.local_name(),
312            &local_name!("high") |
313                &local_name!("low") |
314                &local_name!("min") |
315                &local_name!("max") |
316                &local_name!("optimum") |
317                &local_name!("value")
318        );
319        if is_important_attribute {
320            self.update_state(cx);
321        }
322    }
323
324    fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
325        self.super_type().unwrap().children_changed(cx, mutation);
326
327        self.update_state(cx);
328    }
329
330    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
331        self.super_type().unwrap().bind_to_tree(cx, context);
332
333        self.update_state(cx);
334    }
335}