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
55 .shadow_tree
56 .safe_borrow_mut(cx.no_gc())
57 .insert(ShadowTree {
58 meter_value: container.as_traced(),
59 });
60 }
61
62 fn borrow_for_shadow_tree(&self) -> Ref<'_, Option<ShadowTree>> {
63 self.shadow_tree.borrow()
64 }
65}
66
67impl HTMLMeterElement {
69 fn new_inherited(
70 local_name: LocalName,
71 prefix: Option<Prefix>,
72 document: &Document,
73 ) -> HTMLMeterElement {
74 HTMLMeterElement {
75 htmlelement: HTMLElement::new_inherited(local_name, prefix, document),
76 labels_node_list: MutNullableDom::new(None),
77 shadow_tree: Default::default(),
78 }
79 }
80
81 pub(crate) fn new(
82 cx: &mut js::context::JSContext,
83 local_name: LocalName,
84 prefix: Option<Prefix>,
85 document: &Document,
86 proto: Option<HandleObject>,
87 ) -> DomRoot<HTMLMeterElement> {
88 Node::reflect_node_with_proto(
89 cx,
90 Box::new(HTMLMeterElement::new_inherited(
91 local_name, prefix, document,
92 )),
93 document,
94 proto,
95 )
96 }
97
98 fn update_state(&self, cx: &mut JSContext) {
99 let value = *self.Value();
100 let low = *self.Low();
101 let high = *self.High();
102 let min = *self.Min();
103 let max = *self.Max();
104 let optimum = *self.Optimum();
105
106 let element_state = if optimum < low {
111 if value < low {
112 ElementState::OPTIMUM
113 } else if value <= high {
114 ElementState::SUB_OPTIMUM
115 } else {
116 ElementState::SUB_SUB_OPTIMUM
117 }
118 }
119 else if optimum > high {
124 if value > high {
125 ElementState::OPTIMUM
126 } else if value >= low {
127 ElementState::SUB_OPTIMUM
128 } else {
129 ElementState::SUB_SUB_OPTIMUM
130 }
131 }
132 else if (low..=high).contains(&value) {
136 ElementState::OPTIMUM
137 } else {
138 ElementState::SUB_OPTIMUM
139 };
140
141 self.upcast::<Element>()
143 .set_state(cx.no_gc(), ElementState::METER_OPTIMUM_STATES, false);
144 self.upcast::<Element>()
145 .set_state(cx.no_gc(), element_state, true);
146
147 let shadow_tree = self.shadow_tree(cx);
149 let position = (value - min) / (max - min) * 100.0;
150 let style = format!("width: {position}%");
151 shadow_tree
152 .meter_value
153 .set_string_attribute(cx, &local_name!("style"), style.into());
154 }
155}
156
157impl HTMLMeterElementMethods<crate::DomTypeHolder> for HTMLMeterElement {
158 make_labels_getter!(Labels, labels_node_list);
160
161 fn Value(&self) -> Finite<f64> {
163 let min = *self.Min();
164 let max = *self.Max();
165
166 Finite::wrap(
167 self.upcast::<Element>()
168 .get_string_attribute(&local_name!("value"))
169 .parse_floating_point_number()
170 .map_or(0.0, |candidate_actual_value| {
171 candidate_actual_value.clamp(min, max)
172 }),
173 )
174 }
175
176 fn SetValue(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
178 let mut string_value = DOMString::from((*value).to_string());
179 string_value.set_best_representation_of_the_floating_point_number();
180 self.upcast::<Element>()
181 .set_string_attribute(cx, &local_name!("value"), string_value);
182 }
183
184 fn Min(&self) -> Finite<f64> {
186 Finite::wrap(
187 self.upcast::<Element>()
188 .get_string_attribute(&local_name!("min"))
189 .parse_floating_point_number()
190 .unwrap_or(0.0),
191 )
192 }
193
194 fn SetMin(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
196 let mut string_value = DOMString::from((*value).to_string());
197 string_value.set_best_representation_of_the_floating_point_number();
198 self.upcast::<Element>()
199 .set_string_attribute(cx, &local_name!("min"), string_value);
200 }
201
202 fn Max(&self) -> Finite<f64> {
204 Finite::wrap(
205 self.upcast::<Element>()
206 .get_string_attribute(&local_name!("max"))
207 .parse_floating_point_number()
208 .unwrap_or(1.0)
209 .max(*self.Min()),
210 )
211 }
212
213 fn SetMax(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
215 let mut string_value = DOMString::from((*value).to_string());
216 string_value.set_best_representation_of_the_floating_point_number();
217 self.upcast::<Element>()
218 .set_string_attribute(cx, &local_name!("max"), string_value);
219 }
220
221 fn Low(&self) -> Finite<f64> {
223 let min = *self.Min();
224 let max = *self.Max();
225
226 Finite::wrap(
227 self.upcast::<Element>()
228 .get_string_attribute(&local_name!("low"))
229 .parse_floating_point_number()
230 .map_or(min, |candidate_low_boundary| {
231 candidate_low_boundary.clamp(min, max)
232 }),
233 )
234 }
235
236 fn SetLow(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
238 let mut string_value = DOMString::from((*value).to_string());
239 string_value.set_best_representation_of_the_floating_point_number();
240 self.upcast::<Element>()
241 .set_string_attribute(cx, &local_name!("low"), string_value);
242 }
243
244 fn High(&self) -> Finite<f64> {
246 let max: f64 = *self.Max();
247 let low: f64 = *self.Low();
248
249 Finite::wrap(
250 self.upcast::<Element>()
251 .get_string_attribute(&local_name!("high"))
252 .parse_floating_point_number()
253 .map_or(max, |candidate_high_boundary| {
254 if candidate_high_boundary < low {
255 return low;
256 }
257
258 candidate_high_boundary.clamp(*self.Min(), max)
259 }),
260 )
261 }
262
263 fn SetHigh(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
265 let mut string_value = DOMString::from((*value).to_string());
266 string_value.set_best_representation_of_the_floating_point_number();
267 self.upcast::<Element>()
268 .set_string_attribute(cx, &local_name!("high"), string_value);
269 }
270
271 fn Optimum(&self) -> Finite<f64> {
273 let max = *self.Max();
274 let min = *self.Min();
275
276 Finite::wrap(
277 self.upcast::<Element>()
278 .get_string_attribute(&local_name!("optimum"))
279 .parse_floating_point_number()
280 .map_or(max.add(min).div(2.0), |candidate_optimum_point| {
281 candidate_optimum_point.clamp(min, max)
282 }),
283 )
284 }
285
286 fn SetOptimum(&self, cx: &mut js::context::JSContext, value: Finite<f64>) {
288 let mut string_value = DOMString::from((*value).to_string());
289 string_value.set_best_representation_of_the_floating_point_number();
290 self.upcast::<Element>()
291 .set_string_attribute(cx, &local_name!("optimum"), string_value);
292 }
293}
294
295impl VirtualMethods for HTMLMeterElement {
296 fn super_type(&self) -> Option<&dyn VirtualMethods> {
297 Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
298 }
299
300 fn attribute_mutated(
301 &self,
302 cx: &mut js::context::JSContext,
303 attr: AttrRef<'_>,
304 mutation: AttributeMutation,
305 ) {
306 self.super_type()
307 .unwrap()
308 .attribute_mutated(cx, attr, mutation);
309
310 let is_important_attribute = matches!(
311 attr.local_name(),
312 &local_name!("high") |
313 &local_name!("low") |
314 &local_name!("min") |
315 &local_name!("max") |
316 &local_name!("optimum") |
317 &local_name!("value")
318 );
319 if is_important_attribute {
320 self.update_state(cx);
321 }
322 }
323
324 fn children_changed(&self, cx: &mut JSContext, mutation: &ChildrenMutation) {
325 self.super_type().unwrap().children_changed(cx, mutation);
326
327 self.update_state(cx);
328 }
329
330 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
331 self.super_type().unwrap().bind_to_tree(cx, context);
332
333 self.update_state(cx);
334 }
335}