1use 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#[derive(Clone, JSTraceable, MallocSizeOf)]
39#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
40struct ShadowTree {
41 meter_value: Dom<Element>,
42}
43
44impl 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 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 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 else if (low..=high).contains(&value) {
147 ElementState::OPTIMUM
148 } else {
149 ElementState::SUB_OPTIMUM
150 };
151
152 self.upcast::<Element>()
154 .set_state(ElementState::METER_OPTIMUM_STATES, false);
155 self.upcast::<Element>().set_state(element_state, true);
156
157 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 make_labels_getter!(Labels, labels_node_list);
172
173 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 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 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 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 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 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 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 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 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 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 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 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}