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