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