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::context::JSContext;
11use js::rust::HandleObject;
12use stylo_dom::ElementState;
13
14use crate::dom::attr::Attr;
15use crate::dom::bindings::cell::DomRefCell;
16use crate::dom::bindings::codegen::Bindings::HTMLMeterElementBinding::HTMLMeterElementMethods;
17use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
18use crate::dom::bindings::inheritance::Castable;
19use crate::dom::bindings::num::Finite;
20use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
21use crate::dom::bindings::str::DOMString;
22use crate::dom::document::Document;
23use crate::dom::element::{AttributeMutation, Element};
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<Element>,
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    pub(crate) fn new(
59        cx: &mut js::context::JSContext,
60        local_name: LocalName,
61        prefix: Option<Prefix>,
62        document: &Document,
63        proto: Option<HandleObject>,
64    ) -> DomRoot<HTMLMeterElement> {
65        Node::reflect_node_with_proto(
66            cx,
67            Box::new(HTMLMeterElement::new_inherited(
68                local_name, prefix, document,
69            )),
70            document,
71            proto,
72        )
73    }
74
75    fn create_shadow_tree(&self, cx: &mut JSContext) {
76        let document = self.owner_document();
77        let root = self.upcast::<Element>().attach_ua_shadow_root(cx, true);
78
79        let meter_value = Element::create(
80            cx,
81            QualName::new(None, ns!(html), local_name!("div")),
82            None,
83            &document,
84            crate::dom::element::ElementCreator::ScriptCreated,
85            crate::dom::element::CustomElementCreationMode::Asynchronous,
86            None,
87        );
88        root.upcast::<Node>()
89            .AppendChild(cx, meter_value.upcast::<Node>())
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, cx: &mut JSContext) -> Ref<'_, ShadowTree> {
100        if !self.upcast::<Element>().is_shadow_host() {
101            self.create_shadow_tree(cx);
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, cx: &mut JSContext) {
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(cx);
159        let position = (value - min) / (max - min) * 100.0;
160        let style = format!("width: {position}%");
161        shadow_tree.meter_value.set_string_attribute(
162            &local_name!("style"),
163            style.into(),
164            CanGc::from_cx(cx),
165        );
166    }
167}
168
169impl HTMLMeterElementMethods<crate::DomTypeHolder> for HTMLMeterElement {
170    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
171    make_labels_getter!(Labels, labels_node_list);
172
173    /// <https://html.spec.whatwg.org/multipage/#concept-meter-actual>
174    fn Value(&self) -> Finite<f64> {
175        let min = *self.Min();
176        let max = *self.Max();
177
178        Finite::wrap(
179            self.upcast::<Element>()
180                .get_string_attribute(&local_name!("value"))
181                .parse_floating_point_number()
182                .map_or(0.0, |candidate_actual_value| {
183                    candidate_actual_value.clamp(min, max)
184                }),
185        )
186    }
187
188    /// <https://html.spec.whatwg.org/multipage/#dom-meter-value>
189    fn SetValue(&self, value: Finite<f64>, can_gc: CanGc) {
190        let mut string_value = DOMString::from((*value).to_string());
191        string_value.set_best_representation_of_the_floating_point_number();
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((*value).to_string());
209        string_value.set_best_representation_of_the_floating_point_number();
210        self.upcast::<Element>()
211            .set_string_attribute(&local_name!("min"), string_value, can_gc);
212    }
213
214    /// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
215    fn Max(&self) -> Finite<f64> {
216        Finite::wrap(
217            self.upcast::<Element>()
218                .get_string_attribute(&local_name!("max"))
219                .parse_floating_point_number()
220                .unwrap_or(1.0)
221                .max(*self.Min()),
222        )
223    }
224
225    /// <https://html.spec.whatwg.org/multipage/#concept-meter-maximum>
226    fn SetMax(&self, value: Finite<f64>, can_gc: CanGc) {
227        let mut string_value = DOMString::from((*value).to_string());
228        string_value.set_best_representation_of_the_floating_point_number();
229        self.upcast::<Element>()
230            .set_string_attribute(&local_name!("max"), string_value, can_gc);
231    }
232
233    /// <https://html.spec.whatwg.org/multipage/#concept-meter-low>
234    fn Low(&self) -> Finite<f64> {
235        let min = *self.Min();
236        let max = *self.Max();
237
238        Finite::wrap(
239            self.upcast::<Element>()
240                .get_string_attribute(&local_name!("low"))
241                .parse_floating_point_number()
242                .map_or(min, |candidate_low_boundary| {
243                    candidate_low_boundary.clamp(min, max)
244                }),
245        )
246    }
247
248    /// <https://html.spec.whatwg.org/multipage/#dom-meter-low>
249    fn SetLow(&self, value: Finite<f64>, can_gc: CanGc) {
250        let mut string_value = DOMString::from((*value).to_string());
251        string_value.set_best_representation_of_the_floating_point_number();
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((*value).to_string());
278        string_value.set_best_representation_of_the_floating_point_number();
279        self.upcast::<Element>()
280            .set_string_attribute(&local_name!("high"), string_value, can_gc);
281    }
282
283    /// <https://html.spec.whatwg.org/multipage/#concept-meter-optimum>
284    fn Optimum(&self) -> Finite<f64> {
285        let max = *self.Max();
286        let min = *self.Min();
287
288        Finite::wrap(
289            self.upcast::<Element>()
290                .get_string_attribute(&local_name!("optimum"))
291                .parse_floating_point_number()
292                .map_or(max.add(min).div(2.0), |candidate_optimum_point| {
293                    candidate_optimum_point.clamp(min, max)
294                }),
295        )
296    }
297
298    /// <https://html.spec.whatwg.org/multipage/#dom-meter-optimum>
299    fn SetOptimum(&self, value: Finite<f64>, can_gc: CanGc) {
300        let mut string_value = DOMString::from((*value).to_string());
301        string_value.set_best_representation_of_the_floating_point_number();
302        self.upcast::<Element>().set_string_attribute(
303            &local_name!("optimum"),
304            string_value,
305            can_gc,
306        );
307    }
308}
309
310impl VirtualMethods for HTMLMeterElement {
311    fn super_type(&self) -> Option<&dyn VirtualMethods> {
312        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
313    }
314
315    fn attribute_mutated(
316        &self,
317        cx: &mut js::context::JSContext,
318        attr: &Attr,
319        mutation: AttributeMutation,
320    ) {
321        self.super_type()
322            .unwrap()
323            .attribute_mutated(cx, attr, mutation);
324
325        let is_important_attribute = matches!(
326            attr.local_name(),
327            &local_name!("high") |
328                &local_name!("low") |
329                &local_name!("min") |
330                &local_name!("max") |
331                &local_name!("optimum") |
332                &local_name!("value")
333        );
334        if is_important_attribute {
335            self.update_state(cx);
336        }
337    }
338
339    fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
340        self.super_type().unwrap().children_changed(cx, mutation);
341
342        self.update_state(cx);
343    }
344
345    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
346        self.super_type().unwrap().bind_to_tree(cx, context);
347
348        self.update_state(cx);
349    }
350}