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