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