Skip to main content

script/dom/html/form_controls/
htmlprogresselement.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;
6
7use dom_struct::dom_struct;
8use html5ever::{LocalName, Prefix, local_name};
9use js::context::JSContext;
10use js::rust::HandleObject;
11use script_bindings::cell::DomRefCell;
12use style::selector_parser::PseudoElement;
13use stylo_dom::ElementState;
14
15use crate::dom::bindings::codegen::Bindings::HTMLProgressElementBinding::HTMLProgressElementMethods;
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, 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 HTMLProgressElement {
33    htmlelement: HTMLElement,
34    labels_node_list: MutNullableDom<NodeList>,
35    shadow_tree: DomRefCell<Option<ShadowTree>>,
36}
37
38/// Holds handles to all slots in the UA shadow tree
39#[derive(Clone, JSTraceable, MallocSizeOf)]
40#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
41struct ShadowTree {
42    progress_bar: Dom<Element>,
43}
44
45impl UAShadowRoot<ShadowTree> for HTMLProgressElement {
46    fn store_for_shadow_tree(&self, cx: &mut JSContext, shadow_root: DomRoot<ShadowRoot>) {
47        let progress_bar = self.create_element_in_ua_shadowroot(cx, local_name!("div"));
48        shadow_root
49            .upcast::<Node>()
50            .AppendChild(cx, progress_bar.upcast::<Node>())
51            .unwrap();
52        progress_bar
53            .upcast::<Node>()
54            .set_implemented_pseudo_element(PseudoElement::MozProgressBar);
55
56        let _ = self
57            .shadow_tree
58            .safe_borrow_mut(cx.no_gc())
59            .insert(ShadowTree {
60                progress_bar: progress_bar.as_traced(),
61            });
62    }
63
64    fn borrow_for_shadow_tree(&self) -> Ref<'_, Option<ShadowTree>> {
65        self.shadow_tree.borrow()
66    }
67}
68
69impl HTMLProgressElement {
70    fn new_inherited(
71        local_name: LocalName,
72        prefix: Option<Prefix>,
73        document: &Document,
74    ) -> HTMLProgressElement {
75        HTMLProgressElement {
76            htmlelement: HTMLElement::new_inherited_with_state(
77                ElementState::INDETERMINATE,
78                local_name,
79                prefix,
80                document,
81            ),
82            labels_node_list: MutNullableDom::new(None),
83            shadow_tree: Default::default(),
84        }
85    }
86
87    pub(crate) fn new(
88        cx: &mut js::context::JSContext,
89        local_name: LocalName,
90        prefix: Option<Prefix>,
91        document: &Document,
92        proto: Option<HandleObject>,
93    ) -> DomRoot<HTMLProgressElement> {
94        Node::reflect_node_with_proto(
95            cx,
96            Box::new(HTMLProgressElement::new_inherited(
97                local_name, prefix, document,
98            )),
99            document,
100            proto,
101        )
102    }
103
104    /// Update the visual width of bar
105    fn update_state(&self, cx: &mut JSContext) {
106        let shadow_tree = self.shadow_tree(cx);
107        let position = (*self.Value() / *self.Max()) * 100.0;
108        let style = format!("width: {}%", position);
109
110        shadow_tree
111            .progress_bar
112            .set_string_attribute(cx, &local_name!("style"), style.into());
113    }
114}
115
116impl HTMLProgressElementMethods<crate::DomTypeHolder> for HTMLProgressElement {
117    // https://html.spec.whatwg.org/multipage/#dom-lfe-labels
118    make_labels_getter!(Labels, labels_node_list);
119
120    /// <https://html.spec.whatwg.org/multipage/#dom-progress-value>
121    fn Value(&self) -> Finite<f64> {
122        // In case of missing `value`, parse error, or negative `value`, `value` should be
123        // interpreted as 0.  As `get_string_attribute` returns an empty string in case the
124        // attribute is missing, this case is handeled as the default of the `map_or` function.
125        //
126        // It is safe to wrap the number coming from `parse_floating_point_number` as it will
127        // return Err on inf and nan
128        self.upcast::<Element>()
129            .get_string_attribute(&local_name!("value"))
130            .parse_floating_point_number()
131            .map_or(Finite::wrap(0.0), |v| {
132                if v < 0.0 {
133                    Finite::wrap(0.0)
134                } else {
135                    Finite::wrap(v.min(*self.Max()))
136                }
137            })
138    }
139
140    /// <https://html.spec.whatwg.org/multipage/#dom-progress-value>
141    fn SetValue(&self, cx: &mut JSContext, new_val: Finite<f64>) {
142        if *new_val >= 0.0 {
143            let mut string_value = DOMString::from((*new_val).to_string());
144            string_value.set_best_representation_of_the_floating_point_number();
145            self.upcast::<Element>()
146                .set_string_attribute(cx, &local_name!("value"), string_value);
147        }
148    }
149
150    /// <https://html.spec.whatwg.org/multipage/#dom-progress-max>
151    fn Max(&self) -> Finite<f64> {
152        // In case of missing `max`, parse error, or negative `max`, `max` should be interpreted as
153        // 1.0. As `get_string_attribute` returns an empty string in case the attribute is missing,
154        // these cases are handeled by `map_or`
155        self.upcast::<Element>()
156            .get_string_attribute(&local_name!("max"))
157            .parse_floating_point_number()
158            .map_or(Finite::wrap(1.0), |m| {
159                if m <= 0.0 {
160                    Finite::wrap(1.0)
161                } else {
162                    Finite::wrap(m)
163                }
164            })
165    }
166
167    /// <https://html.spec.whatwg.org/multipage/#dom-progress-max>
168    fn SetMax(&self, cx: &mut JSContext, new_val: Finite<f64>) {
169        if *new_val > 0.0 {
170            let mut string_value = DOMString::from((*new_val).to_string());
171            string_value.set_best_representation_of_the_floating_point_number();
172            self.upcast::<Element>()
173                .set_string_attribute(cx, &local_name!("max"), string_value);
174        }
175    }
176
177    /// <https://html.spec.whatwg.org/multipage/#dom-progress-position>
178    fn Position(&self) -> Finite<f64> {
179        let value = self
180            .upcast::<Element>()
181            .get_string_attribute(&local_name!("value"));
182        if value.is_empty() {
183            Finite::wrap(-1.0)
184        } else {
185            let value = self.Value();
186            let max = self.Max();
187            // An unsafe Finite constructor might be nice here, as it's unlikely for the
188            // compiler to infer the following guarantees. It is probably premature
189            // optimization though.
190            //
191            // Safety: `ret` have to be a finite, defined number. This is the case since both
192            // value and max is finite, max > 0, and a value >> max cannot exist, as
193            // Self::Value(&self) enforces value <= max.
194            Finite::wrap(*value / *max)
195        }
196    }
197}
198
199impl VirtualMethods for HTMLProgressElement {
200    fn super_type(&self) -> Option<&dyn VirtualMethods> {
201        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
202    }
203
204    fn attribute_mutated(
205        &self,
206        cx: &mut js::context::JSContext,
207        attr: AttrRef<'_>,
208        mutation: AttributeMutation,
209    ) {
210        self.super_type()
211            .unwrap()
212            .attribute_mutated(cx, attr, mutation);
213
214        let is_important_attribute = matches!(
215            attr.local_name(),
216            &local_name!("value") | &local_name!("max")
217        );
218        if is_important_attribute {
219            self.update_state(cx);
220        }
221
222        if matches!(attr.local_name(), &local_name!("value")) {
223            self.upcast::<Element>().set_state(
224                cx.no_gc(),
225                ElementState::INDETERMINATE,
226                matches!(mutation, AttributeMutation::Removed),
227            );
228        }
229    }
230
231    fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
232        self.super_type().unwrap().bind_to_tree(cx, context);
233
234        self.update_state(cx);
235    }
236}