script/dom/html/form_controls/
htmlmeterelement.rs1use std::cell::Ref;
6use std::ops::{Add, Div};
7
8use dom_struct::dom_struct;
9use html5ever::{LocalName, Prefix, local_name};
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::virtualmethods::VirtualMethods;
26use crate::dom::node::{BindContext, ChildrenMutation, Node};
27use crate::dom::nodelist::NodeList;
28use crate::dom::shadowroot::ShadowRoot;
29use crate::dom::shadowroot::ua_shadowroot::{CreateUAShadowRootForSelf, UAShadowRoot};
30
31#[dom_struct]
32pub(crate) struct HTMLMeterElement {
33 htmlelement: HTMLElement,
34 labels_node_list: MutNullableDom<NodeList>,
35 shadow_tree: DomRefCell<Option<ShadowTree>>,
36}
37
38#[derive(Clone, JSTraceable, MallocSizeOf)]
40#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
41struct ShadowTree {
42 meter_value: Dom<Element>,
43}
44
45impl UAShadowRoot<ShadowTree> for HTMLMeterElement {
46 fn store_for_shadow_tree(&self, cx: &mut JSContext, shadow_root: DomRoot<ShadowRoot>) {
47 let container = self.create_element_in_ua_shadowroot(cx, local_name!("div"));
48
49 shadow_root
50 .upcast::<Node>()
51 .AppendChild(cx, container.upcast::<Node>())
52 .expect("Must always be able to append to shadow root");
53
54 let _ = self.shadow_tree.borrow_mut().insert(ShadowTree {
55 meter_value: container.as_traced(),
56 });
57 }
58
59 fn borrow_for_shadow_tree(&self) -> Ref<'_, Option<ShadowTree>> {
60 self.shadow_tree.borrow()
61 }
62}
63
64impl HTMLMeterElement {
66 fn new_inherited(
67 local_name: LocalName,
68 prefix: Option<Prefix>,
69 document: &Document,
70 ) -> HTMLMeterElement {
71 HTMLMeterElement {
72 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
73 labels_node_list: MutNullableDom::new(None),
74 shadow_tree: Default::default(),
75 }
76 }
77
78 pub(crate) fn new(
79 cx: &mut js::context::JSContext,
80 local_name: LocalName,
81 prefix: Option<Prefix>,
82 document: &Document,
83 proto: Option<HandleObject>,
84 ) -> DomRoot<HTMLMeterElement> {
85 Node::reflect_node_with_proto(
86 cx,
87 Box::new(HTMLMeterElement::new_inherited(
88 local_name, prefix, document,
89 )),
90 document,
91 proto,
92 )
93 }
94
95 fn update_state(&self, cx: &mut JSContext) {
96 let value = *self.Value();
97 let low = *self.Low();
98 let high = *self.High();
99 let min = *self.Min();
100 let max = *self.Max();
101 let optimum = *self.Optimum();
102
103 let element_state = if optimum < low {
108 if value < low {
109 ElementState::OPTIMUM
110 } else if value <= high {
111 ElementState::SUB_OPTIMUM
112 } else {
113 ElementState::SUB_SUB_OPTIMUM
114 }
115 }
116 else if optimum > high {
121 if value > high {
122 ElementState::OPTIMUM
123 } else if value >= low {
124 ElementState::SUB_OPTIMUM
125 } else {
126 ElementState::SUB_SUB_OPTIMUM
127 }
128 }
129 else if (low..=high).contains(&value) {
133 ElementState::OPTIMUM
134 } else {
135 ElementState::SUB_OPTIMUM
136 };
137
138 self.upcast::<Element>()
140 .set_state(ElementState::METER_OPTIMUM_STATES, false);
141 self.upcast::<Element>().set_state(element_state, true);
142
143 let shadow_tree = self.shadow_tree(cx);
145 let position = (value - min) / (max - min) * 100.0;
146 let style = format!("width: {position}%");
147 shadow_tree
148 .meter_value
149 .set_string_attribute(cx, &local_name!("style"), style.into());
150 }
151}
152
153impl HTMLMeterElementMethods<crate::DomTypeHolder> for HTMLMeterElement {
154 make_labels_getter!(Labels, labels_node_list);
156
157 fn Value(&self) -> Finite<f64> {
159 let min = *self.Min();
160 let max = *self.Max();
161
162 Finite::wrap(
163 self.upcast::<Element>()
164 .get_string_attribute(&local_name!("value"))
165 .parse_floating_point_number()
166 .map_or(0.0, |candidate_actual_value| {
167 candidate_actual_value.clamp(min, max)
168 }),
169 )
170 }
171
172 fn SetValue(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
174 let mut string_value = DOMString::from((*value).to_string());
175 string_value.set_best_representation_of_the_floating_point_number();
176 self.upcast::<Element>()
177 .set_string_attribute(cx, &local_name!("value"), string_value);
178 }
179
180 fn Min(&self) -> Finite<f64> {
182 Finite::wrap(
183 self.upcast::<Element>()
184 .get_string_attribute(&local_name!("min"))
185 .parse_floating_point_number()
186 .unwrap_or(0.0),
187 )
188 }
189
190 fn SetMin(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
192 let mut string_value = DOMString::from((*value).to_string());
193 string_value.set_best_representation_of_the_floating_point_number();
194 self.upcast::<Element>()
195 .set_string_attribute(cx, &local_name!("min"), string_value);
196 }
197
198 fn Max(&self) -> Finite<f64> {
200 Finite::wrap(
201 self.upcast::<Element>()
202 .get_string_attribute(&local_name!("max"))
203 .parse_floating_point_number()
204 .unwrap_or(1.0)
205 .max(*self.Min()),
206 )
207 }
208
209 fn SetMax(&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>()
214 .set_string_attribute(cx, &local_name!("max"), string_value);
215 }
216
217 fn Low(&self) -> Finite<f64> {
219 let min = *self.Min();
220 let max = *self.Max();
221
222 Finite::wrap(
223 self.upcast::<Element>()
224 .get_string_attribute(&local_name!("low"))
225 .parse_floating_point_number()
226 .map_or(min, |candidate_low_boundary| {
227 candidate_low_boundary.clamp(min, max)
228 }),
229 )
230 }
231
232 fn SetLow(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
234 let mut string_value = DOMString::from((*value).to_string());
235 string_value.set_best_representation_of_the_floating_point_number();
236 self.upcast::<Element>()
237 .set_string_attribute(cx, &local_name!("low"), string_value);
238 }
239
240 fn High(&self) -> Finite<f64> {
242 let max: f64 = *self.Max();
243 let low: f64 = *self.Low();
244
245 Finite::wrap(
246 self.upcast::<Element>()
247 .get_string_attribute(&local_name!("high"))
248 .parse_floating_point_number()
249 .map_or(max, |candidate_high_boundary| {
250 if candidate_high_boundary < low {
251 return low;
252 }
253
254 candidate_high_boundary.clamp(*self.Min(), max)
255 }),
256 )
257 }
258
259 fn SetHigh(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
261 let mut string_value = DOMString::from((*value).to_string());
262 string_value.set_best_representation_of_the_floating_point_number();
263 self.upcast::<Element>()
264 .set_string_attribute(cx, &local_name!("high"), string_value);
265 }
266
267 fn Optimum(&self) -> Finite<f64> {
269 let max = *self.Max();
270 let min = *self.Min();
271
272 Finite::wrap(
273 self.upcast::<Element>()
274 .get_string_attribute(&local_name!("optimum"))
275 .parse_floating_point_number()
276 .map_or(max.add(min).div(2.0), |candidate_optimum_point| {
277 candidate_optimum_point.clamp(min, max)
278 }),
279 )
280 }
281
282 fn SetOptimum(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
284 let mut string_value = DOMString::from((*value).to_string());
285 string_value.set_best_representation_of_the_floating_point_number();
286 self.upcast::<Element>()
287 .set_string_attribute(cx, &local_name!("optimum"), string_value);
288 }
289}
290
291impl VirtualMethods for HTMLMeterElement {
292 fn super_type(&self) -> Option<&dyn VirtualMethods> {
293 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
294 }
295
296 fn attribute_mutated(
297 &self,
298 cx: &mut js::context::JSContext,
299 attr: AttrRef<'_>,
300 mutation: AttributeMutation,
301 ) {
302 self.super_type()
303 .unwrap()
304 .attribute_mutated(cx, attr, mutation);
305
306 let is_important_attribute = matches!(
307 attr.local_name(),
308 &local_name!("high") |
309 &local_name!("low") |
310 &local_name!("min") |
311 &local_name!("max") |
312 &local_name!("optimum") |
313 &local_name!("value")
314 );
315 if is_important_attribute {
316 self.update_state(cx);
317 }
318 }
319
320 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
321 self.super_type().unwrap().children_changed(cx, mutation);
322
323 self.update_state(cx);
324 }
325
326 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
327 self.super_type().unwrap().bind_to_tree(cx, context);
328
329 self.update_state(cx);
330 }
331}