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_string((*value).to_string());
188
189        string_value.set_best_representation_of_the_floating_point_number();
190
191        self.upcast::<Element>()
192            .set_string_attribute(&local_name!("value"), string_value, can_gc);
193    }
194
195    /// <https://html.spec.whatwg.org/multipage/#concept-meter-minimum>
196    fn Min(&self) -> Finite<f64> {
197        Finite::wrap(
198            self.upcast::<Element>()
199                .get_string_attribute(&local_name!("min"))
200                .parse_floating_point_number()
201                .unwrap_or(0.0),
202        )
203    }
204
205    /// <https://html.spec.whatwg.org/multipage/#dom-meter-min>
206    fn SetMin(&self, value: Finite<f64>, can_gc: CanGc) {
207        let mut string_value = DOMString::from_string((*value).to_string());
208
209        string_value.set_best_representation_of_the_floating_point_number();
210
211        self.upcast::<Element>()
212            .set_string_attribute(&local_name!("min"), string_value, can_gc);
213    }
214
215    /// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
216    fn Max(&self) -> Finite<f64> {
217        Finite::wrap(
218            self.upcast::<Element>()
219                .get_string_attribute(&local_name!("max"))
220                .parse_floating_point_number()
221                .unwrap_or(1.0)
222                .max(*self.Min()),
223        )
224    }
225
226    /// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
227    fn SetMax(&self, value: Finite<f64>, can_gc: CanGc) {
228        let mut string_value = DOMString::from_string((*value).to_string());
229
230        string_value.set_best_representation_of_the_floating_point_number();
231
232        self.upcast::<Element>()
233            .set_string_attribute(&local_name!("max"), string_value, can_gc);
234    }
235
236    /// <https://html.spec.whatwg.org/multipage/#concept-meter-low>
237    fn Low(&self) -> Finite<f64> {
238        let min = *self.Min();
239        let max = *self.Max();
240
241        Finite::wrap(
242            self.upcast::<Element>()
243                .get_string_attribute(&local_name!("low"))
244                .parse_floating_point_number()
245                .map_or(min, |candidate_low_boundary| {
246                    candidate_low_boundary.clamp(min, max)
247                }),
248        )
249    }
250
251    /// <https://html.spec.whatwg.org/multipage/#dom-meter-low>
252    fn SetLow(&self, value: Finite<f64>, can_gc: CanGc) {
253        let mut string_value = DOMString::from_string((*value).to_string());
254
255        string_value.set_best_representation_of_the_floating_point_number();
256
257        self.upcast::<Element>()
258            .set_string_attribute(&local_name!("low"), string_value, can_gc);
259    }
260
261    /// <https://html.spec.whatwg.org/multipage/#concept-meter-high>
262    fn High(&self) -> Finite<f64> {
263        let max: f64 = *self.Max();
264        let low: f64 = *self.Low();
265
266        Finite::wrap(
267            self.upcast::<Element>()
268                .get_string_attribute(&local_name!("high"))
269                .parse_floating_point_number()
270                .map_or(max, |candidate_high_boundary| {
271                    if candidate_high_boundary < low {
272                        return low;
273                    }
274
275                    candidate_high_boundary.clamp(*self.Min(), max)
276                }),
277        )
278    }
279
280    /// <https://html.spec.whatwg.org/multipage/#dom-meter-high>
281    fn SetHigh(&self, value: Finite<f64>, can_gc: CanGc) {
282        let mut string_value = DOMString::from_string((*value).to_string());
283
284        string_value.set_best_representation_of_the_floating_point_number();
285
286        self.upcast::<Element>()
287            .set_string_attribute(&local_name!("high"), string_value, can_gc);
288    }
289
290    /// <https://html.spec.whatwg.org/multipage/#concept-meter-optimum>
291    fn Optimum(&self) -> Finite<f64> {
292        let max = *self.Max();
293        let min = *self.Min();
294
295        Finite::wrap(
296            self.upcast::<Element>()
297                .get_string_attribute(&local_name!("optimum"))
298                .parse_floating_point_number()
299                .map_or(max.add(min).div(2.0), |candidate_optimum_point| {
300                    candidate_optimum_point.clamp(min, max)
301                }),
302        )
303    }
304
305    /// <https://html.spec.whatwg.org/multipage/#dom-meter-optimum>
306    fn SetOptimum(&self, value: Finite<f64>, can_gc: CanGc) {
307        let mut string_value = DOMString::from_string((*value).to_string());
308
309        string_value.set_best_representation_of_the_floating_point_number();
310
311        self.upcast::<Element>().set_string_attribute(
312            &local_name!("optimum"),
313            string_value,
314            can_gc,
315        );
316    }
317}
318
319impl VirtualMethods for HTMLMeterElement {
320    fn super_type(&self) -> Option<&dyn VirtualMethods> {
321        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
322    }
323
324    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
325        self.super_type()
326            .unwrap()
327            .attribute_mutated(attr, mutation, can_gc);
328
329        let is_important_attribute = matches!(
330            attr.local_name(),
331            &local_name!("high") |
332                &local_name!("low") |
333                &local_name!("min") |
334                &local_name!("max") |
335                &local_name!("optimum") |
336                &local_name!("value")
337        );
338        if is_important_attribute {
339            self.update_state(can_gc);
340        }
341    }
342
343    fn children_changed(&self, mutation: &ChildrenMutation, can_gc: CanGc) {
344        self.super_type()
345            .unwrap()
346            .children_changed(mutation, can_gc);
347
348        self.update_state(can_gc);
349    }
350
351    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
352        self.super_type().unwrap().bind_to_tree(context, can_gc);
353
354        self.update_state(can_gc);
355    }
356}