Skip to main content

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